https://github.com/akkartik/mu/blob/master/082scenario_screen.cc
  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 instructions 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
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
/*
 * (C)opyright MMVI Anselm R. Garbe <garbeam at gmail dot com>
 * See LICENSE file for license details.
 */

#define TAGS \
const char *tags[] = { "0", "1", "2", "3", "4", NULL };

#define DEFMODE			dotile /* dofloat */
#define DEFTAG			1 /* index */
#define FONT			"fixed"
#define BGCOLOR			"#666699"
#define FGCOLOR			"#eeeeee"
#define MODKEY			Mod1Mask
#define NUMLOCKMASK		Mod2Mask
#define MASTERW			60 /* percent */

#define KEYS \
static Key key[] = { \
	/* modifier		key		function	arguments */ \
	{ MODKEY,		XK_0,		view,		{ .i = 0 } }, \
	{ MODKEY,		XK_1,		view,		{ .i = 1 } }, \
	{ MODKEY,		XK_2,		view,		{ .i = 2 } }, \
	{ MODKEY,		XK_3,		view,		{ .i = 3 } }, \
	{ MODKEY,		XK_4,		view,		{ .i = 4 } }, \
	{ MODKEY,		XK_h,		viewprev,	{ 0 } }, \
	{ MODKEY,		XK_j,		focusnext,	{ 0 } }, \
	{ MODKEY,		XK_k,		focusprev,	{ 0 } }, \
	{ MODKEY,		XK_l,		viewnext,	{ 0 } }, \
	{ MODKEY,		XK_m,		togglemax,	{ 0 } }, \
	{ MODKEY,		XK_space,	togglemode,	{ 0 } }, \
	{ MODKEY,		XK_Return,	zoom,		{ 0 } }, \
	{ MODKEY|ControlMask,	XK_0,		appendtag,	{ .i = 0 } }, \
	{ MODKEY|ControlMask,	XK_1,		appendtag,	{ .i = 1 } }, \
	{ MODKEY|ControlMask,	XK_2,		appendtag,	{ .i = 2 } }, \
	{ MODKEY|ControlMask,	XK_3,		appendtag,	{ .i = 3 } }, \
	{ MODKEY|ControlMask,	XK_4,		appendtag,	{ .i = 4 } }, \
	{ MODKEY|ShiftMask,	XK_0,		replacetag,	{ .i = 0 } }, \
	{ MODKEY|ShiftMask,	XK_1,		replacetag,	{ .i = 1 } }, \
	{ MODKEY|ShiftMask,	XK_2,		replacetag,	{ .i = 2 } }, \
	{ MODKEY|ShiftMask,	XK_3,		replacetag,	{ .i = 3 } }, \
	{ MODKEY|ShiftMask,	XK_4,		replacetag,	{ .i = 4 } }, \
	{ MODKEY|ShiftMask,	XK_c,		killclient,	{ 0 } }, \
	{ MODKEY|ShiftMask,	XK_q,		quit,		{ 0 } }, \
	{ MODKEY|ShiftMask,	XK_Return,	spawn,		{ .cmd = "exec xterm" } }, \
};

#define RULES \
static Rule rule[] = { \
	/* class:instance regex		tags regex	isfloat */ \
	{ "Firefox.*",			"2",		False }, \
	{ "Gimp.*",			NULL,		True}, \
};
106" class="LineNr">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: F - screen-in-scenario-color-error: expected screen location (0, 0) to contain 'a' 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 Reset") 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(/*size_of(address:screen)*/2); 149 // End Scenario Globals. 150 :(code) 151 int next_predefined_global_for_scenarios(int size) { 152 int result = Next_predefined_global_for_scenarios; 153 Next_predefined_global_for_scenarios += size; 154 return result; 155 } 156 157 //: give 'screen' a fixed location in scenarios 158 :(before "End Special Scenario Variable Names(r)") 159 Name[r]["screen"] = SCREEN; 160 //: make 'screen' always a raw location in scenarios 161 :(before "End is_special_name Special-cases") 162 if (s == "screen") return true; 163 164 :(before "End Rewrite Instruction(curr, recipe result)") 165 // rewrite 'assume-screen width, height' to 166 // 'screen:&:screen <- new-fake-screen width, height' 167 if (curr.name == "assume-screen") { 168 curr.name = "new-fake-screen"; 169 if (!curr.products.empty()) { 170 raise << result.name << ": 'assume-screen' has no products\n" << end(); 171 } 172 else if (!starts_with(result.name, "scenario_")) { 173 raise << result.name << ": 'assume-screen' can't be called here, only in scenarios\n" << end(); 174 } 175 else { 176 assert(curr.products.empty()); 177 curr.products.push_back(reagent("screen:&:screen/raw")); 178 curr.products.at(0).set_value(SCREEN); 179 } 180 } 181 182 :(scenario assume_screen_shows_up_in_errors) 183 % Hide_errors = true; 184 scenario assume-screen-shows-up-in-errors [ 185 assume-screen width, 5 186 ] 187 +error: assume-screen-shows-up-in-errors: missing type for 'width' in 'assume-screen width, 5' 188 189 //: screen-should-contain is a regular instruction 190 :(before "End Primitive Recipe Declarations") 191 SCREEN_SHOULD_CONTAIN, 192 :(before "End Primitive Recipe Numbers") 193 put(Recipe_ordinal, "screen-should-contain", SCREEN_SHOULD_CONTAIN); 194 :(before "End Primitive Recipe Checks") 195 case SCREEN_SHOULD_CONTAIN: { 196 if (SIZE(inst.ingredients) != 1) { 197 raise << maybe(get(Recipe, r).name) << "'screen-should-contain' requires exactly one ingredient, but got '" << to_original_string(inst) << "'\n" << end(); 198 break; 199 } 200 if (!is_literal_text(inst.ingredients.at(0))) { 201 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(); 202 break; 203 } 204 break; 205 } 206 :(before "End Primitive Recipe Implementations") 207 case SCREEN_SHOULD_CONTAIN: { 208 if (!Passed) break; 209 assert(scalar(ingredients.at(0))); 210 check_screen(current_instruction().ingredients.at(0).name, -1); 211 break; 212 } 213 214 :(before "End Primitive Recipe Declarations") 215 SCREEN_SHOULD_CONTAIN_IN_COLOR, 216 :(before "End Primitive Recipe Numbers") 217 put(Recipe_ordinal, "screen-should-contain-in-color", SCREEN_SHOULD_CONTAIN_IN_COLOR); 218 :(before "End Primitive Recipe Checks") 219 case SCREEN_SHOULD_CONTAIN_IN_COLOR: { 220 if (SIZE(inst.ingredients) != 2) { 221 raise << maybe(get(Recipe, r).name) << "'screen-should-contain-in-color' requires exactly two ingredients, but got '" << to_original_string(inst) << "'\n" << end(); 222 break; 223 } 224 if (!is_mu_number(inst.ingredients.at(0))) { 225 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(); 226 break; 227 } 228 if (!is_literal_text(inst.ingredients.at(1))) { 229 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(); 230 break; 231 } 232 break; 233 } 234 :(before "End Primitive Recipe Implementations") 235 case SCREEN_SHOULD_CONTAIN_IN_COLOR: { 236 if (!Passed) break; 237 assert(scalar(ingredients.at(0))); 238 assert(scalar(ingredients.at(1))); 239 check_screen(current_instruction().ingredients.at(1).name, ingredients.at(0).at(0)); 240 break; 241 } 242 243 :(before "End Types") 244 // scan an array of characters in a unicode-aware, bounds-checked manner 245 struct raw_string_stream { 246 int index; 247 const int max; 248 const char* buf; 249 250 raw_string_stream(const string&); 251 uint32_t get(); // unicode codepoint 252 uint32_t peek(); // unicode codepoint 253 bool at_end() const; 254 void skip_whitespace_and_comments(); 255 }; 256 257 :(code) 258 void check_screen(const string& expected_contents, const int color) { 259 int screen_location = get_or_insert(Memory, SCREEN+/*skip address alloc id*/1) + /*skip payload alloc id*/1; 260 reagent screen("x:screen"); // just to ensure screen.type is reclaimed 261 int screen_data_location = find_element_location(screen_location, "data", screen.type, "check_screen"); // type: address:array:character 262 assert(screen_data_location >= 0); 263 //? cerr << "screen data is at location " << screen_data_location << '\n'; 264 int screen_data_start = get_or_insert(Memory, screen_data_location+/*skip address alloc id*/1) + /*skip payload alloc id*/1; // type: array:character 265 //? cerr << "screen data start is at " << screen_data_start << '\n'; 266 int screen_width_location = find_element_location(screen_location, "num-columns", screen.type, "check_screen"); 267 //? cerr << "screen width is at location " << screen_width_location << '\n'; 268 int screen_width = get_or_insert(Memory, screen_width_location); 269 //? cerr << "screen width: " << screen_width << '\n'; 270 int screen_height_location = find_element_location(screen_location, "num-rows", screen.type, "check_screen"); 271 //? cerr << "screen height is at location " << screen_height_location << '\n'; 272 int screen_height = get_or_insert(Memory, screen_height_location); 273 //? cerr << "screen height: " << screen_height << '\n'; 274 int top_index_location= find_element_location(screen_location, "top-idx", screen.type, "check_screen"); 275 //? cerr << "top of screen is at location " << top_index_location << '\n'; 276 int top_index = get_or_insert(Memory, top_index_location); 277 //? cerr << "top of screen is index " << top_index << '\n'; 278 raw_string_stream cursor(expected_contents); 279 // todo: too-long expected_contents should fail 280 for (int i=0, row=top_index/screen_width; i < screen_height; ++i, row=(row+1)%screen_height) { 281 cursor.skip_whitespace_and_comments(); 282 if (cursor.at_end()) break; 283 if (cursor.get() != '.') { 284 raise << maybe(current_recipe_name()) << "each row of the expected screen should start with a '.'\n" << end(); 285 if (!Scenario_testing_scenario) Passed = false; 286 return; 287 } 288 int addr = screen_data_start+/*length*/1+row*screen_width* /*size of screen-cell*/2; 289 for (int column = 0; column < screen_width; ++column, addr+= /*size of screen-cell*/2) { 290 const int cell_color_offset = 1; 291 uint32_t curr = cursor.get(); 292 if (get_or_insert(Memory, addr) == 0 && isspace(curr)) continue; 293 if (curr == ' ' && color != -1 && color != get_or_insert(Memory, addr+cell_color_offset)) { 294 // filter out other colors 295 continue; 296 } 297 if (get_or_insert(Memory, addr) != 0 && get_or_insert(Memory, addr) == curr) { 298 if (color == -1 || color == get_or_insert(Memory, addr+cell_color_offset)) continue; 299 // contents match but color is off 300 if (!Hide_errors) cerr << '\n'; 301 raise << "F - " << maybe(current_recipe_name()) << "expected screen location (" << row << ", " << column << ") to contain '" << unicode_character_at(addr) << "' in color " << color << " instead of " << no_scientific(get_or_insert(Memory, addr+cell_color_offset)) << "\n" << end(); 302 if (!Hide_errors) dump_screen(); 303 if (!Scenario_testing_scenario) Passed = false; 304 return; 305 } 306 307 // really a mismatch 308 // can't print multi-byte unicode characters in errors just yet. not very useful for debugging anyway. 309 char expected_pretty[10] = {0}; 310 if (curr < 256 && !iscntrl(curr)) { 311 // " ('<curr>')" 312 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'; 313 } 314 char actual_pretty[10] = {0}; 315 if (get_or_insert(Memory, addr) < 256 && !iscntrl(get_or_insert(Memory, addr))) { 316 // " ('<curr>')" 317 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'; 318 } 319 320 ostringstream color_phrase; 321 if (color != -1) color_phrase << " in color " << color; 322 if (!Hide_errors) cerr << '\n'; 323 raise << "F - " << maybe(current_recipe_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(); 324 if (!Hide_errors) dump_screen(); 325 if (!Scenario_testing_scenario) Passed = false; 326 return; 327 } 328 if (cursor.get() != '.') { 329 raise << maybe(current_recipe_name()) << "row " << row << " of the expected screen is too long\n" << end(); 330 if (!Scenario_testing_scenario) Passed = false; 331 return; 332 } 333 } 334 cursor.skip_whitespace_and_comments(); 335 if (!cursor.at_end()) { 336 raise << maybe(current_recipe_name()) << "expected screen has too many rows\n" << end(); 337 Passed = false; 338 } 339 } 340 341 const char* unicode_character_at(int addr) { 342 int unicode_code_point = static_cast<int>(get_or_insert(Memory, addr)); 343 return to_unicode(unicode_code_point); 344 } 345 346 raw_string_stream::raw_string_stream(const string& backing) :index(0), max(SIZE(backing)), buf(backing.c_str()) {} 347 348 bool raw_string_stream::at_end() const { 349 if (index >= max) return true; 350 if (tb_utf8_char_length(buf[index]) > max-index) { 351 raise << "unicode string seems corrupted at index "<< index << " character " << static_cast<int>(buf[index]) << '\n' << end(); 352 return true; 353 } 354 return false; 355 } 356 357 uint32_t raw_string_stream::get() { 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 index += length; 363 return result; 364 } 365 366 uint32_t raw_string_stream::peek() { 367 assert(index < max); // caller must check bounds before calling 'get' 368 uint32_t result = 0; 369 int length = tb_utf8_char_to_unicode(&result, &buf[index]); 370 assert(length != TB_EOF); 371 return result; 372 } 373 374 void raw_string_stream::skip_whitespace_and_comments() { 375 while (!at_end()) { 376 if (isspace(peek())) get(); 377 else if (peek() == '#') { 378 // skip comment 379 get(); 380 while (peek() != '\n') get(); // implicitly also handles CRLF 381 } 382 else break; 383 } 384 } 385 386 :(before "End Primitive Recipe Declarations") 387 _DUMP_SCREEN, 388 :(before "End Primitive Recipe Numbers") 389 put(Recipe_ordinal, "$dump-screen", _DUMP_SCREEN); 390 :(before "End Primitive Recipe Checks") 391 case _DUMP_SCREEN: { 392 break; 393 } 394 :(before "End Primitive Recipe Implementations") 395 case _DUMP_SCREEN: { 396 dump_screen(); 397 break; 398 } 399 400 :(code) 401 void dump_screen() { 402 int screen_location = get_or_insert(Memory, SCREEN+/*skip address alloc id*/1) + /*skip payload alloc id*/1; 403 reagent screen("x:screen"); // just to ensure screen.type is reclaimed 404 int screen_data_location = find_element_location(screen_location, "data", screen.type, "check_screen"); // type: address:array:character 405 assert(screen_data_location >= 0); 406 //? cerr << "screen data is at location " << screen_data_location << '\n'; 407 int screen_data_start = get_or_insert(Memory, screen_data_location+/*skip address alloc id*/1) + /*skip payload alloc id*/1; // type: array:character 408 //? cerr << "screen data start is at " << screen_data_start << '\n'; 409 int screen_width_location = find_element_location(screen_location, "num-columns", screen.type, "check_screen"); 410 //? cerr << "screen width is at location " << screen_width_location << '\n'; 411 int screen_width = get_or_insert(Memory, screen_width_location); 412 //? cerr << "screen width: " << screen_width << '\n'; 413 int screen_height_location = find_element_location(screen_location, "num-rows", screen.type, "check_screen"); 414 //? cerr << "screen height is at location " << screen_height_location << '\n'; 415 int screen_height = get_or_insert(Memory, screen_height_location); 416 //? cerr << "screen height: " << screen_height << '\n'; 417 int top_index_location= find_element_location(screen_location, "top-idx", screen.type, "check_screen"); 418 //? cerr << "top of screen is at location " << top_index_location << '\n'; 419 int top_index = get_or_insert(Memory, top_index_location); 420 //? cerr << "top of screen is index " << top_index << '\n'; 421 for (int i=0, row=top_index/screen_width; i < screen_height; ++i, row=(row+1)%screen_height) { 422 cerr << '.'; 423 int curr = screen_data_start+/*length*/1+row*screen_width* /*size of screen-cell*/2; 424 for (int col = 0; col < screen_width; ++col) { 425 if (get_or_insert(Memory, curr)) 426 cerr << to_unicode(static_cast<uint32_t>(get_or_insert(Memory, curr))); 427 else 428 cerr << ' '; 429 curr += /*size of screen-cell*/2; 430 } 431 cerr << ".\n"; 432 } 433 }