From 204dae921abff0c70e017215bb3c91fa6ca11aff Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Mon, 26 Dec 2016 11:44:14 -0800 Subject: 3710 Turns out we don't need to explicitly add anchors for each line. Vim's TOhtml has magic for that out of the box. --- html/082scenario_screen.cc.html | 830 ++++++++++++++++++++-------------------- 1 file changed, 415 insertions(+), 415 deletions(-) (limited to 'html/082scenario_screen.cc.html') diff --git a/html/082scenario_screen.cc.html b/html/082scenario_screen.cc.html index dd3b9475..d4242807 100644 --- a/html/082scenario_screen.cc.html +++ b/html/082scenario_screen.cc.html @@ -59,421 +59,421 @@ if ('onhashchange' in window) {
-  1 //: Clean syntax to manipulate and check the screen in scenarios.
-  2 //: Instructions 'assume-screen' and 'screen-should-contain' implicitly create
-  3 //: a variable called 'screen' that is accessible to later instructions in the
-  4 //: scenario. 'screen-should-contain' can check unicode characters in the fake
-  5 //: screen
-  6 
-  7 //: first make sure we don't mangle these functions in other transforms
-  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   #  01234
- 23     .a    .
- 24     .     .
- 25     .     .
- 26   ]
- 27 ]
- 28 # checks are inside scenario
- 29 
- 30 :(scenario screen_in_scenario_unicode)
- 31 # screen-should-contain can check unicode characters in the fake screen
- 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   #  01234
- 43     .λa   .
- 44     .     .
- 45     .     .
- 46   ]
- 47 ]
- 48 # checks are inside scenario
- 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   # screen-should-contain shows everything
- 61   screen-should-contain [
- 62   #  01234
- 63     .λa   .
- 64     .     .
- 65     .     .
- 66   ]
- 67   # screen-should-contain-in-color filters out everything except the given
- 68   # color, all you see is the 'a' in white.
- 69   screen-should-contain-in-color 7/white, [
- 70   #  01234
- 71     . a   .
- 72     .     .
- 73     .     .
- 74   ]
- 75   # ..and the λ in red.
- 76   screen-should-contain-in-color 1/red, [
- 77   #  01234
- 78     .λ    .
- 79     .     .
- 80     .     .
- 81   ]
- 82 ]
- 83 # checks are inside scenario
- 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   #  01234
- 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 # screen-should-contain can check unicode characters in the fake screen
-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   #  01234
-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 //: It's easier to implement assume-screen and other similar scenario-only
-135 //: primitives if they always write to a fixed location. So we'll assign a
-136 //: single fixed location for the per-scenario screen, keyboard, file system,
-137 //: etc. Carve space for these fixed locations out of the reserved-for-test
-138 //: locations.
-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 // Scenario Globals.
-148 extern const int SCREEN = Next_predefined_global_for_scenarios++;
-149 // End Scenario Globals.
-150 
-151 //: give 'screen' a fixed location in scenarios
-152 :(before "End Special Scenario Variable Names(r)")
-153 Name[r]["screen"] = SCREEN;
-154 //: make 'screen' always a raw location in scenarios
-155 :(before "End is_special_name Special-cases")
-156 if (s == "screen") return true;
-157 
-158 :(before "End Rewrite Instruction(curr, recipe result)")
-159 // rewrite `assume-screen width, height` to
-160 // `screen:&:screen <- new-fake-screen width, height`
-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 //: screen-should-contain is a regular instruction
-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 '" << inst.original_string << "'\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 '" << inst.original_string << "'\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 // scan an array of characters in a unicode-aware, bounds-checked manner
-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();  // unicode codepoint
-246   uint32_t peek();  // unicode codepoint
-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)+/*skip refcount*/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;  // type: address:array:character
-257   int screen_data_start = get_or_insert(Memory, screen_data_location) + /*skip refcount*/1;  // type: array:character
-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   // todo: too-long expected_contents should fail
-264   int addr = screen_data_start+/*skip length*/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+= /*size of screen-cell*/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         // filter out other colors
-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         // contents match but color is off
-284         if (Current_scenario && !Scenario_testing_scenario) {
-285           // genuine test in a .mu file
-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           // just testing check_screen
-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       // really a mismatch
-298       // can't print multi-byte unicode characters in errors just yet. not very useful for debugging anyway.
-299       char expected_pretty[10] = {0};
-300       if (curr < 256 && !iscntrl(curr)) {
-301         // " ('<curr>')"
-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         // " ('<curr>')"
-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         // genuine test in a .mu file
-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         // just testing check_screen
-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);  // caller must check bounds before calling 'get'
-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);  // caller must check bounds before calling 'get'
-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       // skip comment
-370       get();
-371       while (peek() != '\n') get();  // implicitly also handles CRLF
-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) + /*skip refcount*/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;  // type: address:array:character
-401   int screen_data_start = get_or_insert(Memory, screen_data_location) + /*skip refcount*/1;  // type: array:character
-402   assert(get_or_insert(Memory, screen_data_start) == screen_width*screen_height);
-403   int curr = screen_data_start+1;  // skip length
-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 += /*size of screen-cell*/2;
-412     }
-413     cerr << ".\n";
-414   }
-415 }
+  1 //: Clean syntax to manipulate and check the screen in scenarios.
+  2 //: Instructions 'assume-screen' and 'screen-should-contain' implicitly create
+  3 //: a variable called 'screen' that is accessible to later instructions in the
+  4 //: scenario. 'screen-should-contain' can check unicode characters in the fake
+  5 //: screen
+  6 
+  7 //: first make sure we don't mangle these functions in other transforms
+  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   #  01234
+ 23     .a    .
+ 24     .     .
+ 25     .     .
+ 26   ]
+ 27 ]
+ 28 # checks are inside scenario
+ 29 
+ 30 :(scenario screen_in_scenario_unicode)
+ 31 # screen-should-contain can check unicode characters in the fake screen
+ 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   #  01234
+ 43     .λa   .
+ 44     .     .
+ 45     .     .
+ 46   ]
+ 47 ]
+ 48 # checks are inside scenario
+ 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   # screen-should-contain shows everything
+ 61   screen-should-contain [
+ 62   #  01234
+ 63     .λa   .
+ 64     .     .
+ 65     .     .
+ 66   ]
+ 67   # screen-should-contain-in-color filters out everything except the given
+ 68   # color, all you see is the 'a' in white.
+ 69   screen-should-contain-in-color 7/white, [
+ 70   #  01234
+ 71     . a   .
+ 72     .     .
+ 73     .     .
+ 74   ]
+ 75   # ..and the λ in red.
+ 76   screen-should-contain-in-color 1/red, [
+ 77   #  01234
+ 78     .λ    .
+ 79     .     .
+ 80     .     .
+ 81   ]
+ 82 ]
+ 83 # checks are inside scenario
+ 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   #  01234
+ 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 # screen-should-contain can check unicode characters in the fake screen
+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   #  01234
+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 //: It's easier to implement assume-screen and other similar scenario-only
+135 //: primitives if they always write to a fixed location. So we'll assign a
+136 //: single fixed location for the per-scenario screen, keyboard, file system,
+137 //: etc. Carve space for these fixed locations out of the reserved-for-test
+138 //: locations.
+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 // Scenario Globals.
+148 extern const int SCREEN = Next_predefined_global_for_scenarios++;
+149 // End Scenario Globals.
+150 
+151 //: give 'screen' a fixed location in scenarios
+152 :(before "End Special Scenario Variable Names(r)")
+153 Name[r]["screen"] = SCREEN;
+154 //: make 'screen' always a raw location in scenarios
+155 :(before "End is_special_name Special-cases")
+156 if (s == "screen") return true;
+157 
+158 :(before "End Rewrite Instruction(curr, recipe result)")
+159 // rewrite `assume-screen width, height` to
+160 // `screen:&:screen <- new-fake-screen width, height`
+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 //: screen-should-contain is a regular instruction
+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 '" << inst.original_string << "'\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 '" << inst.original_string << "'\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 // scan an array of characters in a unicode-aware, bounds-checked manner
+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();  // unicode codepoint
+246   uint32_t peek();  // unicode codepoint
+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)+/*skip refcount*/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;  // type: address:array:character
+257   int screen_data_start = get_or_insert(Memory, screen_data_location) + /*skip refcount*/1;  // type: array:character
+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   // todo: too-long expected_contents should fail
+264   int addr = screen_data_start+/*skip length*/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+= /*size of screen-cell*/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         // filter out other colors
+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         // contents match but color is off
+284         if (Current_scenario && !Scenario_testing_scenario) {
+285           // genuine test in a .mu file
+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           // just testing check_screen
+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       // really a mismatch
+298       // can't print multi-byte unicode characters in errors just yet. not very useful for debugging anyway.
+299       char expected_pretty[10] = {0};
+300       if (curr < 256 && !iscntrl(curr)) {
+301         // " ('<curr>')"
+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         // " ('<curr>')"
+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         // genuine test in a .mu file
+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         // just testing check_screen
+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);  // caller must check bounds before calling 'get'
+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);  // caller must check bounds before calling 'get'
+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       // skip comment
+370       get();
+371       while (peek() != '\n') get();  // implicitly also handles CRLF
+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) + /*skip refcount*/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;  // type: address:array:character
+401   int screen_data_start = get_or_insert(Memory, screen_data_location) + /*skip refcount*/1;  // type: array:character
+402   assert(get_or_insert(Memory, screen_data_start) == screen_width*screen_height);
+403   int curr = screen_data_start+1;  // skip length
+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 += /*size of screen-cell*/2;
+412     }
+413     cerr << ".\n";
+414   }
+415 }
 
-- cgit 1.4.1-2-gfad0