1
2
3
4
5
6
7
8 :(before "End initialize_transform_rewrite_literal_string_to_text()")
9 recipes_taking_literal_strings.insert("screen-should-contain");
10 recipes_taking_literal_strings.insert("screen-should-contain-in-color");
11
12 :(scenarios run_mu_scenario)
13 :(scenario screen_in_scenario)
14 scenario screen-in-scenario [
15 local-scope
16 assume-screen 5/width, 3/height
17 run [
18 ¦ a:char <- copy 97/a
19 ¦ screen:&:screen <- print screen:&:screen, a
20 ]
21 screen-should-contain [
22
23 ¦ .a .
24 ¦ . .
25 ¦ . .
26 ]
27 ]
28
29
30 :(scenario screen_in_scenario_unicode)
31
32 scenario screen-in-scenario-unicode [
33 local-scope
34 assume-screen 5/width, 3/height
35 run [
36 ¦ lambda:char <- copy 955/greek-small-lambda
37 ¦ screen:&:screen <- print screen:&:screen, lambda
38 ¦ a:char <- copy 97/a
39 ¦ screen:&:screen <- print screen:&:screen, a
40 ]
41 screen-should-contain [
42
43 ¦ .λa .
44 ¦ . .
45 ¦ . .
46 ]
47 ]
48
49
50 :(scenario screen_in_scenario_color)
51 scenario screen-in-scenario-color [
52 local-scope
53 assume-screen 5/width, 3/height
54 run [
55 ¦ lambda:char <- copy 955/greek-small-lambda
56 ¦ screen:&:screen <- print screen:&:screen, lambda, 1/red
57 ¦ a:char <- copy 97/a
58 ¦ screen:&:screen <- print screen:&:screen, a, 7/white
59 ]
60
61 screen-should-contain [
62
63 ¦ .λa .
64 ¦ . .
65 ¦ . .
66 ]
67
68
69 screen-should-contain-in-color 7/white, [
70
71 ¦ . a .
72 ¦ . .
73 ¦ . .
74 ]
75
76 screen-should-contain-in-color 1/red, [
77
78 ¦ .λ .
79 ¦ . .
80 ¦ . .
81 ]
82 ]
83
84
85 :(scenario screen_in_scenario_error)
86 % Scenario_testing_scenario = true;
87 % Hide_errors = true;
88 scenario screen-in-scenario-error [
89 local-scope
90 assume-screen 5/width, 3/height
91 run [
92 ¦ a:char <- copy 97/a
93 ¦ screen:&:screen <- print screen:&:screen, a
94 ]
95 screen-should-contain [
96
97 ¦ .b .
98 ¦ . .
99 ¦ . .
100 ]
101 ]
102 +error: expected screen location (0, 0) to contain 98 ('b') instead of 97 ('a')
103
104 :(scenario screen_in_scenario_color_error)
105 % Scenario_testing_scenario = true;
106 % Hide_errors = true;
107
108 scenario screen-in-scenario-color-error [
109 local-scope
110 assume-screen 5/width, 3/height
111 run [
112 ¦ a:char <- copy 97/a
113 ¦ screen:&:screen <- print screen:&:screen, a, 1/red
114 ]
115 screen-should-contain-in-color 2/green, [
116
117 ¦ .a .
118 ¦ . .
119 ¦ . .
120 ]
121 ]
122 +error: expected screen location (0, 0) to be in color 2 instead of 1
123
124 :(scenarios run)
125 :(scenario convert_names_does_not_fail_when_mixing_special_names_and_numeric_locations)
126 % Scenario_testing_scenario = true;
127 def main [
128 screen:num <- copy 1:num
129 ]
130 -error: mixing variable names and numeric addresses in main
131 $error: 0
132 :(scenarios run_mu_scenario)
133
134
135
136
137
138
139
140 :(before "End Globals")
141 extern const int Max_variables_in_scenarios = Reserved_for_tests-100;
142 int Next_predefined_global_for_scenarios = Max_variables_in_scenarios;
143 :(before "End Setup")
144 assert(Next_predefined_global_for_scenarios < Reserved_for_tests);
145
146 :(before "End Globals")
147
148 extern const int SCREEN = Next_predefined_global_for_scenarios++;
149
150
151
152 :(before "End Special Scenario Variable Names(r)")
153 Name[r]["screen"] = SCREEN;
154
155 :(before "End is_special_name Special-cases")
156 if (s == "screen") return true;
157
158 :(before "End Rewrite Instruction(curr, recipe result)")
159
160
161 if (curr.name == "assume-screen") {
162 curr.name = "new-fake-screen";
163 if (!curr.products.empty()) {
164 ¦ raise << result.name << ": 'assume-screen' has no products\n" << end();
165 }
166 else if (!starts_with(result.name, "scenario_")) {
167 ¦ raise << result.name << ": 'assume-screen' can't be called here, only in scenarios\n" << end();
168 }
169 else {
170 ¦ assert(curr.products.empty());
171 ¦ curr.products.push_back(reagent("screen:&:screen/raw"));
172 ¦ curr.products.at(0).set_value(SCREEN);
173 }
174 }
175
176 :(scenario assume_screen_shows_up_in_errors)
177 % Hide_errors = true;
178 scenario assume-screen-shows-up-in-errors [
179 assume-screen width, 5
180 ]
181 +error: scenario_assume-screen-shows-up-in-errors: missing type for 'width' in 'assume-screen width, 5'
182
183
184 :(before "End Primitive Recipe Declarations")
185 SCREEN_SHOULD_CONTAIN,
186 :(before "End Primitive Recipe Numbers")
187 put(Recipe_ordinal, "screen-should-contain", SCREEN_SHOULD_CONTAIN);
188 :(before "End Primitive Recipe Checks")
189 case SCREEN_SHOULD_CONTAIN: {
190 if (SIZE(inst.ingredients) != 1) {
191 ¦ raise << maybe(get(Recipe, r).name) << "'screen-should-contain' requires exactly one ingredient, but got '" << to_original_string(inst) << "'\n" << end();
192 ¦ break;
193 }
194 if (!is_literal_text(inst.ingredients.at(0))) {
195 ¦ raise << maybe(get(Recipe, r).name) << "first ingredient of 'screen-should-contain' should be a literal string, but got '" << inst.ingredients.at(0).original_string << "'\n" << end();
196 ¦ break;
197 }
198 break;
199 }
200 :(before "End Primitive Recipe Implementations")
201 case SCREEN_SHOULD_CONTAIN: {
202 if (!Passed) break;
203 assert(scalar(ingredients.at(0)));
204 check_screen(current_instruction().ingredients.at(0).name, -1);
205 break;
206 }
207
208 :(before "End Primitive Recipe Declarations")
209 SCREEN_SHOULD_CONTAIN_IN_COLOR,
210 :(before "End Primitive Recipe Numbers")
211 put(Recipe_ordinal, "screen-should-contain-in-color", SCREEN_SHOULD_CONTAIN_IN_COLOR);
212 :(before "End Primitive Recipe Checks")
213 case SCREEN_SHOULD_CONTAIN_IN_COLOR: {
214 if (SIZE(inst.ingredients) != 2) {
215 ¦ raise << maybe(get(Recipe, r).name) << "'screen-should-contain-in-color' requires exactly two ingredients, but got '" << to_original_string(inst) << "'\n" << end();
216 ¦ break;
217 }
218 if (!is_mu_number(inst.ingredients.at(0))) {
219 ¦ raise << maybe(get(Recipe, r).name) << "first ingredient of 'screen-should-contain-in-color' should be a number (color code), but got '" << inst.ingredients.at(0).original_string << "'\n" << end();
220 ¦ break;
221 }
222 if (!is_literal_text(inst.ingredients.at(1))) {
223 ¦ raise << maybe(get(Recipe, r).name) << "second ingredient of 'screen-should-contain-in-color' should be a literal string, but got '" << inst.ingredients.at(1).original_string << "'\n" << end();
224 ¦ break;
225 }
226 break;
227 }
228 :(before "End Primitive Recipe Implementations")
229 case SCREEN_SHOULD_CONTAIN_IN_COLOR: {
230 if (!Passed) break;
231 assert(scalar(ingredients.at(0)));
232 assert(scalar(ingredients.at(1)));
233 check_screen(current_instruction().ingredients.at(1).name, ingredients.at(0).at(0));
234 break;
235 }
236
237 :(before "End Types")
238
239 struct raw_string_stream {
240 int index;
241 const int max;
242 const char* buf;
243
244 raw_string_stream(const string&);
245 uint32_t get();
246 uint32_t peek();
247 bool at_end() const;
248 void skip_whitespace_and_comments();
249 };
250
251 :(code)
252 void check_screen(const string& expected_contents, const int color) {
253 int screen_location = get_or_insert(Memory, SCREEN)+1;
254 int data_offset = find_element_name(get(Type_ordinal, "screen"), "data", "");
255 assert(data_offset >= 0);
256 int screen_data_location = screen_location+data_offset;
257 int screen_data_start = get_or_insert(Memory, screen_data_location) + 1;
258 int width_offset = find_element_name(get(Type_ordinal, "screen"), "num-columns", "");
259 int screen_width = get_or_insert(Memory, screen_location+width_offset);
260 int height_offset = find_element_name(get(Type_ordinal, "screen"), "num-rows", "");
261 int screen_height = get_or_insert(Memory, screen_location+height_offset);
262 raw_string_stream cursor(expected_contents);
263
264 int addr = screen_data_start+1;
265 for (int row = 0; row < screen_height; ++row) {
266 ¦ cursor.skip_whitespace_and_comments();
267 ¦ if (cursor.at_end()) break;
268 ¦ if (cursor.get() != '.') {
269 ¦ ¦ raise << Current_scenario->name << ": each row of the expected screen should start with a '.'\n" << end();
270 ¦ ¦ if (!Scenario_testing_scenario) Passed = false;
271 ¦ ¦ return;
272 ¦ }
273 ¦ for (int column = 0; column < screen_width; ++column, addr+= 2) {
274 ¦ ¦ const int cell_color_offset = 1;
275 ¦ ¦ uint32_t curr = cursor.get();
276 ¦ ¦ if (get_or_insert(Memory, addr) == 0 && isspace(curr)) continue;
277 ¦ ¦ if (curr == ' ' && color != -1 && color != get_or_insert(Memory, addr+cell_color_offset)) {
278 ¦ ¦ ¦
279 ¦ ¦ ¦ continue;
280 ¦ ¦ }
281 ¦ ¦ if (get_or_insert(Memory, addr) != 0 && get_or_insert(Memory, addr) == curr) {
282 ¦ ¦ ¦ if (color == -1 || color == get_or_insert(Memory, addr+cell_color_offset)) continue;
283 ¦ ¦ ¦
284 ¦ ¦ ¦ if (Current_scenario && !Scenario_testing_scenario) {
285 ¦ ¦ ¦ ¦
286 ¦ ¦ ¦ ¦ raise << "\nF - " << Current_scenario->name << ": expected screen location (" << row << ", " << column << ", address " << addr << ", value " << no_scientific(get_or_insert(Memory, addr)) << ") to be in color " << color << " instead of " << no_scientific(get_or_insert(Memory, addr+cell_color_offset)) << "\n" << end();
287 ¦ ¦ ¦ ¦ dump_screen();
288 ¦ ¦ ¦ }
289 ¦ ¦ ¦ else {
290 ¦ ¦ ¦ ¦
291 ¦ ¦ ¦ ¦ raise << "expected screen location (" << row << ", " << column << ") to be in color " << color << " instead of " << no_scientific(get_or_insert(Memory, addr+cell_color_offset)) << '\n' << end();
292 ¦ ¦ ¦ }
293 ¦ ¦ ¦ if (!Scenario_testing_scenario) Passed = false;
294 ¦ ¦ ¦ return;
295 ¦ ¦ }
296
297 ¦ ¦
298 ¦ ¦
299 ¦ ¦ char expected_pretty[10] = {0};
300 ¦ ¦ if (curr < 256 && !iscntrl(curr)) {
301 ¦ ¦ ¦
302 ¦ ¦ ¦ expected_pretty[0] = ' ', expected_pretty[1] = '(', expected_pretty[2] = '\'', expected_pretty[3] = static_cast<unsigned char>(curr), expected_pretty[4] = '\'', expected_pretty[5] = ')', expected_pretty[6] = '\0';
303 ¦ ¦ }
304 ¦ ¦ char actual_pretty[10] = {0};
305 ¦ ¦ if (get_or_insert(Memory, addr) < 256 && !iscntrl(get_or_insert(Memory, addr))) {
306 ¦ ¦ ¦
307 ¦ ¦ ¦ actual_pretty[0] = ' ', actual_pretty[1] = '(', actual_pretty[2] = '\'', actual_pretty[3] = static_cast<unsigned char>(get_or_insert(Memory, addr)), actual_pretty[4] = '\'', actual_pretty[5] = ')', actual_pretty[6] = '\0';
308 ¦ ¦ }
309
310 ¦ ¦ ostringstream color_phrase;
311 ¦ ¦ if (color != -1) color_phrase << " in color " << color;
312 ¦ ¦ if (Current_scenario && !Scenario_testing_scenario) {
313 ¦ ¦ ¦
314 ¦ ¦ ¦ raise << "\nF - " << Current_scenario->name << ": expected screen location (" << row << ", " << column << ") to contain " << curr << expected_pretty << color_phrase.str() << " instead of " << no_scientific(get_or_insert(Memory, addr)) << actual_pretty << '\n' << end();
315 ¦ ¦ ¦ dump_screen();
316 ¦ ¦ }
317 ¦ ¦ else {
318 ¦ ¦ ¦
319 ¦ ¦ ¦ raise << "expected screen location (" << row << ", " << column << ") to contain " << curr << expected_pretty << color_phrase.str() << " instead of " << no_scientific(get_or_insert(Memory, addr)) << actual_pretty << '\n' << end();
320 ¦ ¦ }
321 ¦ ¦ if (!Scenario_testing_scenario) Passed = false;
322 ¦ ¦ return;
323 ¦ }
324 ¦ if (cursor.get() != '.') {
325 ¦ ¦ raise << Current_scenario->name << ": row " << row << " of the expected screen is too long\n" << end();
326 ¦ ¦ if (!Scenario_testing_scenario) Passed = false;
327 ¦ ¦ return;
328 ¦ }
329 }
330 cursor.skip_whitespace_and_comments();
331 if (!cursor.at_end()) {
332 ¦ raise << Current_scenario->name << ": expected screen has too many rows\n" << end();
333 ¦ Passed = false;
334 }
335 }
336
337 raw_string_stream::raw_string_stream(const string& backing) :index(0), max(SIZE(backing)), buf(backing.c_str()) {}
338
339 bool raw_string_stream::at_end() const {
340 if (index >= max) return true;
341 if (tb_utf8_char_length(buf[index]) > max-index) {
342 ¦ raise << "unicode string seems corrupted at index "<< index << " character " << static_cast<int>(buf[index]) << '\n' << end();
343 ¦ return true;
344 }
345 return false;
346 }
347
348 uint32_t raw_string_stream::get() {
349 assert(index < max);
350 uint32_t result = 0;
351 int length = tb_utf8_char_to_unicode(&result, &buf[index]);
352 assert(length != TB_EOF);
353 index += length;
354 return result;
355 }
356
357 uint32_t raw_string_stream::peek() {
358 assert(index < max);
359 uint32_t result = 0;
360 int length = tb_utf8_char_to_unicode(&result, &buf[index]);
361 assert(length != TB_EOF);
362 return result;
363 }
364
365 void raw_string_stream::skip_whitespace_and_comments() {
366 while (!at_end()) {
367 ¦ if (isspace(peek())) get();
368 ¦ else if (peek() == '#') {
369 ¦ ¦
370 ¦ ¦ get();
371 ¦ ¦ while (peek() != '\n') get();
372 ¦ }
373 ¦ else break;
374 }
375 }
376
377 :(before "End Primitive Recipe Declarations")
378 _DUMP_SCREEN,
379 :(before "End Primitive Recipe Numbers")
380 put(Recipe_ordinal, "$dump-screen", _DUMP_SCREEN);
381 :(before "End Primitive Recipe Checks")
382 case _DUMP_SCREEN: {
383 break;
384 }
385 :(before "End Primitive Recipe Implementations")
386 case _DUMP_SCREEN: {
387 dump_screen();
388 break;
389 }
390
391 :(code)
392 void dump_screen() {
393 int screen_location = get_or_insert(Memory, SCREEN) + 1;
394 int width_offset = find_element_name(get(Type_ordinal, "screen"), "num-columns", "");
395 int screen_width = get_or_insert(Memory, screen_location+width_offset);
396 int height_offset = find_element_name(get(Type_ordinal, "screen"), "num-rows", "");
397 int screen_height = get_or_insert(Memory, screen_location+height_offset);
398 int data_offset = find_element_name(get(Type_ordinal, "screen"), "data", "");
399 assert(data_offset >= 0);
400 int screen_data_location = screen_location+data_offset;
401 int screen_data_start = get_or_insert(Memory, screen_data_location) + 1;
402 assert(get_or_insert(Memory, screen_data_start) == screen_width*screen_height);
403 int curr = screen_data_start+1;
404 for (int row = 0; row < screen_height; ++row) {
405 ¦ cerr << '.';
406 ¦ for (int col = 0; col < screen_width; ++col) {
407 ¦ ¦ if (get_or_insert(Memory, curr))
408 ¦ ¦ ¦ cerr << to_unicode(static_cast<uint32_t>(get_or_insert(Memory, curr)));
409 ¦ ¦ else
410 ¦ ¦ ¦ cerr << ' ';
411 ¦ ¦ curr += 2;
412 ¦ }
413 ¦ cerr << ".\n";
414 }
415 }