https://github.com/akkartik/mu/blob/master/405screen.mu
   1 # Wrappers for real screen primitives that can be passed a fake screen.
   2 # The tests here have been painstakingly validated against a real terminal
   3 # emulator. I believe functionality here is broadly portable across terminal
   4 # emulators.
   5 #
   6 # Remember: fake screen co-ordinates are 1-based, just like in real terminal
   7 # emulators.
   8 
   9 type screen {
  10   num-rows: int
  11   num-cols: int
  12   data: (handle array screen-cell)
  13   top-index: int  # 0-indexed
  14   cursor-row: int  # 1-indexed
  15   cursor-col: int  # 1-indexed
  16   cursor-hide?: boolean
  17   curr-attributes: screen-cell
  18 }
  19 
  20 type screen-cell {
  21   data: grapheme
  22   color: int
  23   background-color: int
  24   bold?: boolean
  25   underline?: boolean
  26   reverse?: boolean
  27   blink?: boolean
  28 }
  29 
  30 fn initialize-screen screen: (addr screen), nrows: int, ncols: int {
  31   var screen-addr/esi: (addr screen) <- copy screen
  32   var tmp/eax: int <- copy 0
  33   var dest/edi: (addr int) <- copy 0
  34   # screen->num-rows = nrows
  35   dest <- get screen-addr, num-rows
  36   tmp <- copy nrows
  37   copy-to *dest, tmp
  38   # screen->num-cols = ncols
  39   dest <- get screen-addr, num-cols
  40   tmp <- copy ncols
  41   copy-to *dest, tmp
  42   # screen->data = new screen-cell[nrows*ncols]
  43   {
  44     var data-addr/edi: (addr handle array screen-cell) <- get screen-addr, data
  45     tmp <- multiply nrows
  46     populate data-addr, tmp
  47   }
  48   # screen->cursor-row = 1
  49   dest <- get screen-addr, cursor-row
  50   copy-to *dest, 1
  51   # screen->cursor-col = 1
  52   dest <- get screen-addr, cursor-col
  53   copy-to *dest, 1
  54   # screen->curr-attributes->background-color = 7  (simulate light background)
  55   var tmp2/eax: (addr screen-cell) <- get screen-addr, curr-attributes
  56   dest <- get tmp2, background-color
  57   copy-to *dest, 7
  58 }
  59 
  60 fn screen-size screen: (addr screen) -> nrows/eax: int, ncols/ecx: int {
  61 $screen-size:body: {
  62   compare screen, 0
  63   {
  64     break-if-!=
  65     nrows, ncols <- real-screen-size
  66     break $screen-size:body
  67   }
  68   {
  69     break-if-=
  70     # fake screen
  71     var screen-addr/esi: (addr screen) <- copy screen
  72     var tmp/edx: (addr int) <- get screen-addr, num-rows
  73     nrows <- copy *tmp
  74     tmp <- get screen-addr, num-cols
  75     ncols <- copy *tmp
  76   }
  77 }
  78 }
  79 
  80 fn clear-screen screen: (addr screen) {
  81 $clear-screen:body: {
  82   compare screen, 0
  83   {
  84     break-if-!=
  85     clear-real-screen
  86     break $clear-screen:body
  87   }
  88   {
  89     break-if-=
  90     # fake screen
  91     var space/edi: grapheme <- copy 0x20
  92     move-cursor screen, 1, 1
  93     var screen-addr/esi: (addr screen) <- copy screen
  94     var i/eax: int <- copy 1
  95     var nrows/ecx: (addr int) <- get screen-addr, num-rows
  96     {
  97       compare i, *nrows
  98       break-if->
  99       var j/edx: int <- copy 1
 100       var ncols/ebx: (addr int) <- get screen-addr, num-cols
 101       {
 102         compare j, *ncols
 103         break-if->
 104         print-grapheme screen, space
 105         j <- increment
 106         loop
 107       }
 108       i <- increment
 109       loop
 110     }
 111     move-cursor screen, 1, 1
 112   }
 113 }
 114 }
 115 
 116 fn move-cursor screen: (addr screen), row: int, column: int {
 117 $move-cursor:body: {
 118   compare screen, 0
 119   {
 120     break-if-!=
 121     move-cursor-on-real-screen row, column
 122     break $move-cursor:body
 123   }
 124   {
 125     break-if-=
 126     # fake screen
 127     var screen-addr/esi: (addr screen) <- copy screen
 128     # row < 0 is ignored
 129     {
 130       compare row, 0
 131       break-if-< $move-cursor:body
 132     }
 133     # row = 0 is treated same as 1
 134     {
 135       compare row, 0
 136       break-if-!=
 137       copy-to row, 1
 138     }
 139     # row > num-rows saturates to num-rows
 140     {
 141       var nrows-addr/eax: (addr int) <- get screen-addr, num-rows
 142       var nrows/eax: int <- copy *nrows-addr
 143       compare row, nrows
 144       break-if-<=
 145       copy-to row, nrows
 146     }
 147     # column < 0 is ignored
 148     {
 149       compare column, 0
 150       break-if-< $move-cursor:body
 151     }
 152     # column = 0 is treated same as 1
 153     {
 154       compare column, 0
 155       break-if-!=
 156       copy-to column, 1
 157     }
 158     # column > num-cols saturates to num-cols+1 (so wrapping to next row)
 159     {
 160       var ncols-addr/eax: (addr int) <- get screen-addr, num-cols
 161       var ncols/eax: int <- copy *ncols-addr
 162       compare column, ncols
 163       break-if-<=
 164       copy-to column, ncols
 165       increment column
 166     }
 167     # screen->cursor-row = row
 168     var dest/edi: (addr int) <- get screen-addr, cursor-row
 169     var src/eax: int <- copy row
 170     copy-to *dest, src
 171     # screen->cursor-col = column
 172     dest <- get screen-addr, cursor-col
 173     src <- copy column
 174     copy-to *dest, src
 175   }
 176 }
 177 }
 178 
 179 fn print-string screen: (addr screen), s: (addr array byte) {
 180 $print-string:body: {
 181   compare screen, 0
 182   {
 183     break-if-!=
 184     print-string-to-real-screen s
 185     break $print-string:body
 186   }
 187   {
 188     break-if-=
 189     # fake screen
 190     var s2: (stream byte 0x100)
 191     var s2-addr/esi: (addr stream byte) <- address s2
 192     write s2-addr, s
 193     var screen-addr/edi: (addr screen) <- copy screen
 194     {
 195       var done?/eax: boolean <- stream-empty? s2-addr
 196       compare done?, 0
 197       break-if-!=
 198       var g/eax: grapheme <- read-grapheme s2-addr
 199       print-grapheme screen, g
 200       loop
 201     }
 202   }
 203 }
 204 }
 205 
 206 fn print-grapheme screen: (addr screen), c: grapheme {
 207 $print-grapheme:body: {
 208   compare screen, 0
 209   {
 210     break-if-!=
 211     print-grapheme-to-real-screen c
 212     break $print-grapheme:body
 213   }
 214   {
 215     break-if-=
 216     # fake screen
 217     var screen-addr/esi: (addr screen) <- copy screen
 218     var cursor-col-addr/edx: (addr int) <- get screen-addr, cursor-col
 219     # adjust cursor if necessary
 220     # to avoid premature scrolling it's important to do this lazily, at the last possible time
 221     {
 222       # next row
 223       var num-cols-addr/ecx: (addr int) <- get screen-addr, num-cols
 224       var num-cols/ecx: int <- copy *num-cols-addr
 225       compare *cursor-col-addr, num-cols
 226       break-if-<=
 227       copy-to *cursor-col-addr, 1
 228       var cursor-row-addr/ebx: (addr int) <- get screen-addr, cursor-row
 229       increment *cursor-row-addr
 230       # scroll
 231       var num-rows-addr/eax: (addr int) <- get screen-addr, num-rows
 232       var num-rows/eax: int <- copy *num-rows-addr
 233       compare *cursor-row-addr, num-rows
 234       break-if-<=
 235       copy-to *cursor-row-addr, num-rows
 236       # if (top-index > data size) top-index = 0, otherwise top-index += num-cols
 237       $print-grapheme:perform-scroll: {
 238         var top-index-addr/ebx: (addr int) <- get screen-addr, top-index
 239         var data-ah/eax: (addr handle array screen-cell) <- get screen-addr, data
 240         var data/eax: (addr array screen-cell) <- lookup *data-ah
 241         var max-index/edi: int <- length data
 242         compare *top-index-addr, max-index
 243         {
 244           break-if->=
 245           add-to *top-index-addr, num-cols
 246           break $print-grapheme:perform-scroll
 247         }
 248         {
 249           break-if-<
 250           copy-to *top-index-addr, 0
 251         }
 252       }
 253     }
 254     var idx/ecx: int <- current-screen-cell-index screen-addr
 255 #?     print-string-to-real-screen "printing grapheme at screen index "
 256 #?     print-int32-hex-to-real-screen idx
 257 #?     print-string-to-real-screen ": "
 258     var data-ah/eax: (addr handle array screen-cell) <- get screen-addr, data
 259     var data/eax: (addr array screen-cell) <- lookup *data-ah
 260     var offset/ecx: (offset screen-cell) <- compute-offset data, idx
 261     var dest-cell/ecx: (addr screen-cell) <- index data, offset
 262     var src-cell/eax: (addr screen-cell) <- get screen-addr, curr-attributes
 263     copy-object src-cell, dest-cell
 264     var dest/eax: (addr grapheme) <- get dest-cell, data
 265     var c2/ecx: grapheme <- copy c
 266 #?     print-grapheme-to-real-screen c2
 267 #?     print-string-to-real-screen "\n"
 268     copy-to *dest, c2
 269     increment *cursor-col-addr
 270   }
 271 }
 272 }
 273 
 274 fn current-screen-cell-index screen-on-stack: (addr screen) -> result/ecx: int {
 275   var screen/esi: (addr screen) <- copy screen-on-stack
 276   var cursor-row-addr/ecx: (addr int) <- get screen, cursor-row
 277   var cursor-col-addr/eax: (addr int) <- get screen, cursor-col
 278   result <- screen-cell-index screen, *cursor-row-addr, *cursor-col-addr
 279 }
 280 
 281 fn screen-cell-index screen-on-stack: (addr screen), row: int, col: int -> result/ecx: int {
 282   var screen/esi: (addr screen) <- copy screen-on-stack
 283   var num-cols-addr/eax: (addr int) <- get screen, num-cols
 284   var num-cols/eax: int <- copy *num-cols-addr
 285   result <- copy row
 286   result <- subtract 1
 287   result <- multiply num-cols
 288   result <- add col
 289   result <- subtract 1
 290   # result = (result + top-index) % data length
 291   var top-index-addr/eax: (addr int) <- get screen, top-index
 292   result <- add *top-index-addr
 293   var data-ah/eax: (addr handle array screen-cell) <- get screen, data
 294   var data/eax: (addr array screen-cell) <- lookup *data-ah
 295   var max-index/eax: int <- length data
 296   compare result, max-index
 297   {
 298     break-if-<
 299     result <- subtract max-index
 300   }
 301 }
 302 
 303 fn screen-grapheme-at screen-on-stack: (addr screen), row: int, col: int -> result/eax: grapheme {
 304   var screen-addr/esi: (addr screen) <- copy screen-on-stack
 305   var idx/ecx: int <- screen-cell-index screen-addr, row, col
 306   result <- screen-grapheme-at-idx screen-addr, idx
 307 }
 308 
 309 fn screen-grapheme-at-idx screen-on-stack: (addr screen), idx-on-stack: int -> result/eax: grapheme {
 310   var screen-addr/esi: (addr screen) <- copy screen-on-stack
 311   var data-ah/eax: (addr handle array screen-cell) <- get screen-addr, data
 312   var data/eax: (addr array screen-cell) <- lookup *data-ah
 313   var idx/ecx: int <- copy idx-on-stack
 314   var offset/ecx: (offset screen-cell) <- compute-offset data, idx
 315   var cell/eax: (addr screen-cell) <- index data, offset
 316   var src/eax: (addr grapheme) <- get cell, data
 317   result <- copy *src
 318 }
 319 
 320 fn screen-color-at screen-on-stack: (addr screen), row: int, col: int -> result/eax: int {
 321   var screen-addr/esi: (addr screen) <- copy screen-on-stack
 322   var idx/ecx: int <- screen-cell-index screen-addr, row, col
 323   result <- screen-color-at-idx screen-addr, idx
 324 }
 325 
 326 fn screen-color-at-idx screen-on-stack: (addr screen), idx-on-stack: int -> result/eax: int {
 327   var screen-addr/esi: (addr screen) <- copy screen-on-stack
 328   var data-ah/eax: (addr handle array screen-cell) <- get screen-addr, data
 329   var data/eax: (addr array screen-cell) <- lookup *data-ah
 330   var idx/ecx: int <- copy idx-on-stack
 331   var offset/ecx: (offset screen-cell) <- compute-offset data, idx
 332   var cell/eax: (addr screen-cell) <- index data, offset
 333   var src/eax: (addr int) <- get cell, color
 334   result <- copy *src
 335 }
 336 
 337 fn screen-background-color-at screen-on-stack: (addr screen), row: int, col: int -> result/eax: int {
 338   var screen-addr/esi: (addr screen) <- copy screen-on-stack
 339   var idx/ecx: int <- screen-cell-index screen-addr, row, col
 340   result <- screen-background-color-at-idx screen-addr, idx
 341 }
 342 
 343 fn screen-background-color-at-idx screen-on-stack: (addr screen), idx-on-stack: int -> result/eax: int {
 344   var screen-addr/esi: (addr screen) <- copy screen-on-stack
 345   var data-ah/eax: (addr handle array screen-cell) <- get screen-addr, data
 346   var data/eax: (addr array screen-cell) <- lookup *data-ah
 347   var idx/ecx: int <- copy idx-on-stack
 348   var offset/ecx: (offset screen-cell) <- compute-offset data, idx
 349   var cell/eax: (addr screen-cell) <- index data, offset
 350   var src/eax: (addr int) <- get cell, background-color
 351   result <- copy *src
 352 }
 353 
 354 fn screen-bold-at? screen-on-stack: (addr screen), row: int, col: int -> result/eax: boolean {
 355   var screen-addr/esi: (addr screen) <- copy screen-on-stack
 356   var idx/ecx: int <- screen-cell-index screen-addr, row, col
 357   result <- screen-bold-at-idx? screen-addr, idx
 358 }
 359 
 360 fn screen-bold-at-idx? screen-on-stack: (addr screen), idx-on-stack: int -> result/eax: boolean {
 361   var screen-addr/esi: (addr screen) <- copy screen-on-stack
 362   var data-ah/eax: (addr handle array screen-cell) <- get screen-addr, data
 363   var data/eax: (addr array screen-cell) <- lookup *data-ah
 364   var idx/ecx: int <- copy idx-on-stack
 365   var offset/ecx: (offset screen-cell) <- compute-offset data, idx
 366   var cell/eax: (addr screen-cell) <- index data, offset
 367   var src/eax: (addr boolean) <- get cell, bold?
 368   result <- copy *src
 369 }
 370 
 371 fn screen-underline-at? screen-on-stack: (addr screen), row: int, col: int -> result/eax: boolean {
 372   var screen-addr/esi: (addr screen) <- copy screen-on-stack
 373   var idx/ecx: int <- screen-cell-index screen-addr, row, col
 374   result <- screen-underline-at-idx? screen-addr, idx
 375 }
 376 
 377 fn screen-underline-at-idx? screen-on-stack: (addr screen), idx-on-stack: int -> result/eax: boolean {
 378   var screen-addr/esi: (addr screen) <- copy screen-on-stack
 379   var data-ah/eax: (addr handle array screen-cell) <- get screen-addr, data
 380   var data/eax: (addr array screen-cell) <- lookup *data-ah
 381   var idx/ecx: int <- copy idx-on-stack
 382   var offset/ecx: (offset screen-cell) <- compute-offset data, idx
 383   var cell/eax: (addr screen-cell) <- index data, offset
 384   var src/eax: (addr boolean) <- get cell, underline?
 385   result <- copy *src
 386 }
 387 
 388 fn screen-reverse-at? screen-on-stack: (addr screen), row: int, col: int -> result/eax: boolean {
 389   var screen-addr/esi: (addr screen) <- copy screen-on-stack
 390   var idx/ecx: int <- screen-cell-index screen-addr, row, col
 391   result <- screen-reverse-at-idx? screen-addr, idx
 392 }
 393 
 394 fn screen-reverse-at-idx? screen-on-stack: (addr screen), idx-on-stack: int -> result/eax: boolean {
 395   var screen-addr/esi: (addr screen) <- copy screen-on-stack
 396   var data-ah/eax: (addr handle array screen-cell) <- get screen-addr, data
 397   var data/eax: (addr array screen-cell) <- lookup *data-ah
 398   var idx/ecx: int <- copy idx-on-stack
 399   var offset/ecx: (offset screen-cell) <- compute-offset data, idx
 400   var cell/eax: (addr screen-cell) <- index data, offset
 401   var src/eax: (addr boolean) <- get cell, reverse?
 402   result <- copy *src
 403 }
 404 
 405 fn screen-blink-at? screen-on-stack: (addr screen), row: int, col: int -> result/eax: boolean {
 406   var screen-addr/esi: (addr screen) <- copy screen-on-stack
 407   var idx/ecx: int <- screen-cell-index screen-addr, row, col
 408   result <- screen-blink-at-idx? screen-addr, idx
 409 }
 410 
 411 fn screen-blink-at-idx? screen-on-stack: (addr screen), idx-on-stack: int -> result/eax: boolean {
 412   var screen-addr/esi: (addr screen) <- copy screen-on-stack
 413   var data-ah/eax: (addr handle array screen-cell) <- get screen-addr, data
 414   var data/eax: (addr array screen-cell) <- lookup *data-ah
 415   var idx/ecx: int <- copy idx-on-stack
 416   var offset/ecx: (offset screen-cell) <- compute-offset data, idx
 417   var cell/eax: (addr screen-cell) <- index data, offset
 418   var src/eax: (addr boolean) <- get cell, blink?
 419   result <- copy *src
 420 }
 421 
 422 fn print-code-point screen: (addr screen), c: code-point {
 423   var g/eax: grapheme <- to-grapheme c
 424   print-grapheme screen, g
 425 }
 426 
 427 fn print-int32-hex screen: (addr screen), n: int {
 428 $print-int32-hex:body: {
 429   compare screen, 0
 430   {
 431     break-if-!=
 432     print-int32-hex-to-real-screen n
 433     break $print-int32-hex:body
 434   }
 435   {
 436     break-if-=
 437     # fake screen
 438   }
 439 }
 440 }
 441 
 442 fn reset-formatting screen: (addr screen) {
 443 $reset-formatting:body: {
 444   compare screen, 0
 445   {
 446     break-if-!=
 447     reset-formatting-on-real-screen
 448     break $reset-formatting:body
 449   }
 450   {
 451     break-if-=
 452     # fake screen
 453     var screen-addr/esi: (addr screen) <- copy screen
 454     var dest/ecx: (addr screen-cell) <- get screen-addr, curr-attributes
 455     var default-cell: screen-cell
 456     var bg/eax: (addr int) <- get default-cell, background-color
 457     copy-to *bg, 7
 458     var default-cell-addr/eax: (addr screen-cell) <- address default-cell
 459     copy-object default-cell-addr, dest
 460   }
 461 }
 462 }
 463 
 464 fn start-color screen: (addr screen), fg: int, bg: int {
 465 $start-color:body: {
 466   compare screen, 0
 467   {
 468     break-if-!=
 469     start-color-on-real-screen fg, bg
 470     break $start-color:body
 471   }
 472   {
 473     break-if-=
 474     # fake screen
 475     var screen-addr/esi: (addr screen) <- copy screen
 476     var attr/ecx: (addr screen-cell) <- get screen-addr, curr-attributes
 477     var dest/edx: (addr int) <- get attr, color
 478     var src/eax: int <- copy fg
 479     copy-to *dest, src
 480     var dest/edx: (addr int) <- get attr, background-color
 481     var src/eax: int <- copy bg
 482     copy-to *dest, src
 483   }
 484 }
 485 }
 486 
 487 fn start-bold screen: (addr screen) {
 488 $start-bold:body: {
 489   compare screen, 0
 490   {
 491     break-if-!=
 492     start-bold-on-real-screen
 493     break $start-bold:body
 494   }
 495   {
 496     break-if-=
 497     # fake screen
 498     var screen-addr/esi: (addr screen) <- copy screen
 499     var attr/ecx: (addr screen-cell) <- get screen-addr, curr-attributes
 500     var dest/edx: (addr boolean) <- get attr, bold?
 501     copy-to *dest, 1
 502   }
 503 }
 504 }
 505 
 506 fn start-underline screen: (addr screen) {
 507 $start-underline:body: {
 508   compare screen, 0
 509   {
 510     break-if-!=
 511     start-underline-on-real-screen
 512     break $start-underline:body
 513   }
 514   {
 515     break-if-=
 516     # fake screen
 517     var screen-addr/esi: (addr screen) <- copy screen
 518     var attr/ecx: (addr screen-cell) <- get screen-addr, curr-attributes
 519     var dest/edx: (addr boolean) <- get attr, underline?
 520     copy-to *dest, 1
 521   }
 522 }
 523 }
 524 
 525 fn start-reverse-video screen: (addr screen) {
 526 $start-reverse-video:body: {
 527   compare screen, 0
 528   {
 529     break-if-!=
 530     start-reverse-video-on-real-screen
 531     break $start-reverse-video:body
 532   }
 533   {
 534     break-if-=
 535     # fake screen
 536     var screen-addr/esi: (addr screen) <- copy screen
 537     var attr/ecx: (addr screen-cell) <- get screen-addr, curr-attributes
 538     var dest/edx: (addr boolean) <- get attr, reverse?
 539     copy-to *dest, 1
 540   }
 541 }
 542 }
 543 
 544 fn start-blinking screen: (addr screen) {
 545 $start-blinking:body: {
 546   compare screen, 0
 547   {
 548     break-if-!=
 549     start-blinking-on-real-screen
 550     break $start-blinking:body
 551   }
 552   {
 553     break-if-=
 554     # fake screen
 555     var screen-addr/esi: (addr screen) <- copy screen
 556     var attr/ecx: (addr screen-cell) <- get screen-addr, curr-attributes
 557     var dest/edx: (addr boolean) <- get attr, blink?
 558     copy-to *dest, 1
 559   }
 560 }
 561 }
 562 
 563 fn hide-cursor screen: (addr screen) {
 564 $hide-cursor:body: {
 565   compare screen, 0
 566   {
 567     break-if-!=
 568     hide-cursor-on-real-screen
 569     break $hide-cursor:body
 570   }
 571   {
 572     break-if-=
 573     # fake screen
 574     var screen-addr/esi: (addr screen) <- copy screen
 575     var hide?/ecx: (addr boolean) <- get screen-addr, cursor-hide?
 576     copy-to *hide?, 1
 577   }
 578 }
 579 }
 580 
 581 fn show-cursor screen: (addr screen) {
 582 $show-cursor:body: {
 583   compare screen, 0
 584   {
 585     break-if-!=
 586     show-cursor-on-real-screen
 587     break $show-cursor:body
 588   }
 589   {
 590     break-if-=
 591     # fake screen
 592     var screen-addr/esi: (addr screen) <- copy screen
 593     var hide?/ecx: (addr boolean) <- get screen-addr, cursor-hide?
 594     copy-to *hide?, 0
 595   }
 596 }
 597 }
 598 
 599 # validate data on screen regardless of attributes (color, bold, etc.)
 600 # Mu doesn't have multi-line strings, so we provide functions for rows or portions of rows.
 601 # Tab characters (that translate into multiple screen cells) not supported.
 602 
 603 fn check-screen-row screen: (addr screen), row-idx: int, expected: (addr array byte), msg: (addr array byte) {
 604   check-screen-row-from screen, row-idx, 1, expected, msg
 605 }
 606 
 607 fn check-screen-row-from screen-on-stack: (addr screen), row-idx: int, col-idx: int, expected: (addr array byte), msg: (addr array byte) {
 608   var screen/esi: (addr screen) <- copy screen-on-stack
 609   var idx/ecx: int <- screen-cell-index screen, row-idx, col-idx
 610   # compare 'expected' with the screen contents starting at 'idx', grapheme by grapheme
 611   var e: (stream byte 0x100)
 612   var e-addr/edx: (addr stream byte) <- address e
 613   write e-addr, expected
 614   {
 615     var done?/eax: boolean <- stream-empty? e-addr
 616     compare done?, 0
 617     break-if-!=
 618     var _g/eax: grapheme <- screen-grapheme-at-idx screen, idx
 619     var g/ebx: grapheme <- copy _g
 620     var expected-grapheme/eax: grapheme <- read-grapheme e-addr
 621     # compare graphemes
 622     $check-screen-row-from:compare-graphemes: {
 623       # if expected-grapheme is space, null grapheme is also ok
 624       {
 625         compare expected-grapheme, 0x20
 626         break-if-!=
 627         compare g, 0
 628         break-if-= $check-screen-row-from:compare-graphemes
 629       }
 630       # if (g == expected-grapheme) print "."
 631       compare g, expected-grapheme
 632       {
 633         break-if-!=
 634         print-string-to-real-screen "."
 635         break $check-screen-row-from:compare-graphemes
 636       }
 637       # otherwise print an error
 638       print-string-to-real-screen msg
 639       print-string-to-real-screen ": expected '"
 640       print-grapheme-to-real-screen expected-grapheme
 641       print-string-to-real-screen "' at ("
 642       print-int32-hex-to-real-screen row-idx
 643       print-string-to-real-screen ", "
 644       print-int32-hex-to-real-screen col-idx
 645       print-string-to-real-screen ") but observed '"
 646       print-grapheme-to-real-screen g
 647       print-string-to-real-screen "'\n"
 648     }
 649     idx <- increment
 650     increment col-idx
 651     loop
 652   }
 653 }
 654 
 655 # various variants by screen-cell attribute; spaces in the 'expected' data should not match the attribute
 656 
 657 fn check-screen-row-in-color screen: (addr screen), fg: int, row-idx: int, expected: (addr array byte), msg: (addr array byte) {
 658   check-screen-row-in-color-from screen, fg, row-idx, 1, expected, msg
 659 }
 660 
 661 fn check-screen-row-in-color-from screen-on-stack: (addr screen), fg: int, row-idx: int, col-idx: int, expected: (addr array byte), msg: (addr array byte) {
 662   var screen/esi: (addr screen) <- copy screen-on-stack
 663   var idx/ecx: int <- screen-cell-index screen, row-idx, col-idx
 664   # compare 'expected' with the screen contents starting at 'idx', grapheme by grapheme
 665   var e: (stream byte 0x100)
 666   var e-addr/edx: (addr stream byte) <- address e
 667   write e-addr, expected
 668   {
 669     var done?/eax: boolean <- stream-empty? e-addr
 670     compare done?, 0
 671     break-if-!=
 672     var _g/eax: grapheme <- screen-grapheme-at-idx screen, idx
 673     var g/ebx: grapheme <- copy _g
 674     var _expected-grapheme/eax: grapheme <- read-grapheme e-addr
 675     var expected-grapheme/edi: grapheme <- copy _expected-grapheme
 676     $check-screen-row-in-color-from:compare-cells: {
 677       # if expected-grapheme is space, null grapheme is also ok
 678       {
 679         compare expected-grapheme, 0x20
 680         break-if-!=
 681         compare g, 0
 682         break-if-= $check-screen-row-in-color-from:compare-cells
 683       }
 684       # if expected-grapheme is space, a different color is ok
 685       {
 686         compare expected-grapheme, 0x20
 687         break-if-!=
 688         var color/eax: int <- screen-color-at-idx screen, idx
 689         compare color, fg
 690         break-if-!= $check-screen-row-in-color-from:compare-cells
 691       }
 692       # compare graphemes
 693       $check-screen-row-in-color-from:compare-graphemes: {
 694         # if (g == expected-grapheme) print "."
 695         compare g, expected-grapheme
 696         {
 697           break-if-!=
 698           print-string-to-real-screen "."
 699           break $check-screen-row-in-color-from:compare-graphemes
 700         }
 701         # otherwise print an error
 702         print-string-to-real-screen msg
 703         print-string-to-real-screen ": expected '"
 704         print-grapheme-to-real-screen expected-grapheme
 705         print-string-to-real-screen "' at ("
 706         print-int32-hex-to-real-screen row-idx
 707         print-string-to-real-screen ", "
 708         print-int32-hex-to-real-screen col-idx
 709         print-string-to-real-screen ") but observed '"
 710         print-grapheme-to-real-screen g
 711         print-string-to-real-screen "'\n"
 712       }
 713       $check-screen-row-in-color-from:compare-colors: {
 714         var color/eax: int <- screen-color-at-idx screen, idx
 715         compare fg, color
 716         {
 717           break-if-!=
 718           print-string-to-real-screen "."
 719           break $check-screen-row-in-color-from:compare-colors
 720         }
 721         # otherwise print an error
 722         print-string-to-real-screen msg
 723         print-string-to-real-screen ": expected '"
 724         print-grapheme-to-real-screen expected-grapheme
 725         print-string-to-real-screen "' at ("
 726         print-int32-hex-to-real-screen row-idx
 727         print-string-to-real-screen ", "
 728         print-int32-hex-to-real-screen col-idx
 729         print-string-to-real-screen ") in color "
 730         print-int32-hex-to-real-screen fg
 731         print-string-to-real-screen " but observed color "
 732         print-int32-hex-to-real-screen color
 733         print-string-to-real-screen "\n"
 734       }
 735     }
 736     idx <- increment
 737     increment col-idx
 738     loop
 739   }
 740 }
 741 
 742 # background color is visible even for spaces, so 'expected' behaves as an array of booleans.
 743 # non-space = given background must match; space = background must not match
 744 fn check-screen-row-in-background-color screen: (addr screen), bg: int, row-idx: int, expected: (addr array byte), msg: (addr array byte) {
 745   check-screen-row-in-background-color-from screen, bg, row-idx, 1, expected, msg
 746 }
 747 
 748 fn check-screen-row-in-background-color-from screen-on-stack: (addr screen), bg: int, row-idx: int, col-idx: int, expected: (addr array byte), msg: (addr array byte) {
 749   var screen/esi: (addr screen) <- copy screen-on-stack
 750   var idx/ecx: int <- screen-cell-index screen, row-idx, col-idx
 751   # compare 'expected' with the screen contents starting at 'idx', grapheme by grapheme
 752   var e: (stream byte 0x100)
 753   var e-addr/edx: (addr stream byte) <- address e
 754   write e-addr, expected
 755   {
 756     var done?/eax: boolean <- stream-empty? e-addr
 757     compare done?, 0
 758     break-if-!=
 759     var _g/eax: grapheme <- screen-grapheme-at-idx screen, idx
 760     var g/ebx: grapheme <- copy _g
 761     var _expected-grapheme/eax: grapheme <- read-grapheme e-addr
 762     var expected-grapheme/edx: grapheme <- copy _expected-grapheme
 763     $check-screen-row-in-background-color-from:compare-cells: {
 764       # if expected-grapheme is space, null grapheme is also ok
 765       {
 766         compare expected-grapheme, 0x20
 767         break-if-!=
 768         compare g, 0
 769         break-if-= $check-screen-row-in-background-color-from:compare-cells
 770       }
 771       # if expected-grapheme is space, a different color is ok
 772       {
 773         compare expected-grapheme, 0x20
 774         break-if-!=
 775         var color/eax: int <- screen-background-color-at-idx screen, idx
 776         compare color, bg
 777         break-if-!= $check-screen-row-in-background-color-from:compare-cells
 778       }
 779       # compare graphemes
 780       $check-screen-row-in-background-color-from:compare-graphemes: {
 781         # if (g == expected-grapheme) print "."
 782         compare g, expected-grapheme
 783         {
 784           break-if-!=
 785           print-string-to-real-screen "."
 786           break $check-screen-row-in-background-color-from:compare-graphemes
 787         }
 788         # otherwise print an error
 789         print-string-to-real-screen msg
 790         print-string-to-real-screen ": expected '"
 791         print-grapheme-to-real-screen expected-grapheme
 792         print-string-to-real-screen "' at ("
 793         print-int32-hex-to-real-screen row-idx
 794         print-string-to-real-screen ", "
 795         print-int32-hex-to-real-screen col-idx
 796         print-string-to-real-screen ") but observed '"
 797         print-grapheme-to-real-screen g
 798         print-string-to-real-screen "'\n"
 799       }
 800       $check-screen-row-in-background-color-from:compare-colors: {
 801         var color/eax: int <- screen-background-color-at-idx screen, idx
 802         compare bg, color
 803         {
 804           break-if-!=
 805           print-string-to-real-screen "."
 806           break $check-screen-row-in-background-color-from:compare-colors
 807         }
 808         # otherwise print an error
 809         print-string-to-real-screen msg
 810         print-string-to-real-screen ": expected '"
 811         print-grapheme-to-real-screen expected-grapheme
 812         print-string-to-real-screen "' at ("
 813         print-int32-hex-to-real-screen row-idx
 814         print-string-to-real-screen ", "
 815         print-int32-hex-to-real-screen col-idx
 816         print-string-to-real-screen ") in background color "
 817         print-int32-hex-to-real-screen bg
 818         print-string-to-real-screen " but observed color "
 819         print-int32-hex-to-real-screen color
 820         print-string-to-real-screen "\n"
 821       }
 822     }
 823     idx <- increment
 824     increment col-idx
 825     loop
 826   }
 827 }
 828 
 829 fn check-screen-row-in-bold screen: (addr screen), row-idx: int, expected: (addr array byte), msg: (addr array byte) {
 830   check-screen-row-in-bold-from screen, row-idx, 1, expected, msg
 831 }
 832 
 833 fn check-screen-row-in-bold-from screen-on-stack: (addr screen), row-idx: int, col-idx: int, expected: (addr array byte), msg: (addr array byte) {
 834   var screen/esi: (addr screen) <- copy screen-on-stack
 835   var idx/ecx: int <- screen-cell-index screen, row-idx, col-idx
 836   # compare 'expected' with the screen contents starting at 'idx', grapheme by grapheme
 837   var e: (stream byte 0x100)
 838   var e-addr/edx: (addr stream byte) <- address e
 839   write e-addr, expected
 840   {
 841     var done?/eax: boolean <- stream-empty? e-addr
 842     compare done?, 0
 843     break-if-!=
 844     var _g/eax: grapheme <- screen-grapheme-at-idx screen, idx
 845     var g/ebx: grapheme <- copy _g
 846     var _expected-grapheme/eax: grapheme <- read-grapheme e-addr
 847     var expected-grapheme/edx: grapheme <- copy _expected-grapheme
 848     $check-screen-row-in-bold-from:compare-cells: {
 849       # if expected-grapheme is space, null grapheme is also ok
 850       {
 851         compare expected-grapheme, 0x20
 852         break-if-!=
 853         compare g, 0
 854         break-if-= $check-screen-row-in-bold-from:compare-cells
 855       }
 856       # if expected-grapheme is space, non-bold is ok
 857       {
 858         compare expected-grapheme, 0x20
 859         break-if-!=
 860         var bold?/eax: boolean <- screen-bold-at-idx? screen, idx
 861         compare bold?, 1
 862         break-if-!= $check-screen-row-in-bold-from:compare-cells
 863       }
 864       # compare graphemes
 865       $check-screen-row-in-bold-from:compare-graphemes: {
 866         # if (g == expected-grapheme) print "."
 867         compare g, expected-grapheme
 868         {
 869           break-if-!=
 870           print-string-to-real-screen "."
 871           break $check-screen-row-in-bold-from:compare-graphemes
 872         }
 873         # otherwise print an error
 874         print-string-to-real-screen msg
 875         print-string-to-real-screen ": expected '"
 876         print-grapheme-to-real-screen expected-grapheme
 877         print-string-to-real-screen "' at ("
 878         print-int32-hex-to-real-screen row-idx
 879         print-string-to-real-screen ", "
 880         print-int32-hex-to-real-screen col-idx
 881         print-string-to-real-screen ") but observed '"
 882         print-grapheme-to-real-screen g
 883         print-string-to-real-screen "'\n"
 884       }
 885       $check-screen-row-in-bold-from:compare-bold: {
 886         var bold?/eax: boolean <- screen-bold-at-idx? screen, idx
 887         compare bold?, 1
 888         {
 889           break-if-!=
 890           print-string-to-real-screen "."
 891           break $check-screen-row-in-bold-from:compare-bold
 892         }
 893         # otherwise print an error
 894         print-string-to-real-screen msg
 895         print-string-to-real-screen ": expected '"
 896         print-grapheme-to-real-screen expected-grapheme
 897         print-string-to-real-screen "' at ("
 898         print-int32-hex-to-real-screen row-idx
 899         print-string-to-real-screen ", "
 900         print-int32-hex-to-real-screen col-idx
 901         print-string-to-real-screen ") to be in bold\n"
 902       }
 903     }
 904     idx <- increment
 905     increment col-idx
 906     loop
 907   }
 908 }
 909 
 910 fn check-screen-row-in-underline screen: (addr screen), row-idx: int, expected: (addr array byte), msg: (addr array byte) {
 911   check-screen-row-in-underline-from screen, row-idx, 1, expected, msg
 912 }
 913 
 914 fn check-screen-row-in-underline-from screen-on-stack: (addr screen), row-idx: int, col-idx: int, expected: (addr array byte), msg: (addr array byte) {
 915   var screen/esi: (addr screen) <- copy screen-on-stack
 916   var idx/ecx: int <- screen-cell-index screen, row-idx, col-idx
 917   # compare 'expected' with the screen contents starting at 'idx', grapheme by grapheme
 918   var e: (stream byte 0x100)
 919   var e-addr/edx: (addr stream byte) <- address e
 920   write e-addr, expected
 921   {
 922     var done?/eax: boolean <- stream-empty? e-addr
 923     compare done?, 0
 924     break-if-!=
 925     var _g/eax: grapheme <- screen-grapheme-at-idx screen, idx
 926     var g/ebx: grapheme <- copy _g
 927     var _expected-grapheme/eax: grapheme <- read-grapheme e-addr
 928     var expected-grapheme/edx: grapheme <- copy _expected-grapheme
 929     $check-screen-row-in-underline-from:compare-cells: {
 930       # if expected-grapheme is space, null grapheme is also ok
 931       {
 932         compare expected-grapheme, 0x20
 933         break-if-!=
 934         compare g, 0
 935         break-if-= $check-screen-row-in-underline-from:compare-cells
 936       }
 937       # if expected-grapheme is space, non-underline is ok
 938       {
 939         compare expected-grapheme, 0x20
 940         break-if-!=
 941         var underline?/eax: boolean <- screen-underline-at-idx? screen, idx
 942         compare underline?, 1
 943         break-if-!= $check-screen-row-in-underline-from:compare-cells
 944       }
 945       # compare graphemes
 946       $check-screen-row-in-underline-from:compare-graphemes: {
 947         # if (g == expected-grapheme) print "."
 948         compare g, expected-grapheme
 949         {
 950           break-if-!=
 951           print-string-to-real-screen "."
 952           break $check-screen-row-in-underline-from:compare-graphemes
 953         }
 954         # otherwise print an error
 955         print-string-to-real-screen msg
 956         print-string-to-real-screen ": expected '"
 957         print-grapheme-to-real-screen expected-grapheme
 958         print-string-to-real-screen "' at ("
 959         print-int32-hex-to-real-screen row-idx
 960         print-string-to-real-screen ", "
 961         print-int32-hex-to-real-screen col-idx
 962         print-string-to-real-screen ") but observed '"
 963         print-grapheme-to-real-screen g
 964         print-string-to-real-screen "'\n"
 965       }
 966       $check-screen-row-in-underline-from:compare-underline: {
 967         var underline?/eax: boolean <- screen-underline-at-idx? screen, idx
 968         compare underline?, 1
 969         {
 970           break-if-!=
 971           print-string-to-real-screen "."
 972           break $check-screen-row-in-underline-from:compare-underline
 973         }
 974         # otherwise print an error
 975         print-string-to-real-screen msg
 976         print-string-to-real-screen ": expected '"
 977         print-grapheme-to-real-screen expected-grapheme
 978         print-string-to-real-screen "' at ("
 979         print-int32-hex-to-real-screen row-idx
 980         print-string-to-real-screen ", "
 981         print-int32-hex-to-real-screen col-idx
 982         print-string-to-real-screen ") to be underlined\n"
 983       }
 984     }
 985     idx <- increment
 986     increment col-idx
 987     loop
 988   }
 989 }
 990 
 991 fn check-screen-row-in-reverse screen: (addr screen), row-idx: int, expected: (addr array byte), msg: (addr array byte) {
 992   check-screen-row-in-reverse-from screen, row-idx, 1, expected, msg
 993 }
 994 
 995 fn check-screen-row-in-reverse-from screen-on-stack: (addr screen), row-idx: int, col-idx: int, expected: (addr array byte), msg: (addr array byte) {
 996   var screen/esi: (addr screen) <- copy screen-on-stack
 997   var idx/ecx: int <- screen-cell-index screen, row-idx, col-idx
 998   # compare 'expected' with the screen contents starting at 'idx', grapheme by grapheme
 999   var e: (stream byte 0x100)
1000   var e-addr/edx: (addr stream byte) <- address e
1001   write e-addr, expected
1002   {
1003     var done?/eax: boolean <- stream-empty? e-addr
1004     compare done?, 0
1005     break-if-!=
1006     var _g/eax: grapheme <- screen-grapheme-at-idx screen, idx
1007     var g/ebx: grapheme <- copy _g
1008     var _expected-grapheme/eax: grapheme <- read-grapheme e-addr
1009     var expected-grapheme/edx: grapheme <- copy _expected-grapheme
1010     $check-screen-row-in-reverse-from:compare-cells: {
1011       # if expected-grapheme is space, null grapheme is also ok
1012       {
1013         compare expected-grapheme, 0x20
1014         break-if-!=
1015         compare g, 0
1016         break-if-= $check-screen-row-in-reverse-from:compare-cells
1017       }
1018       # if expected-grapheme is space, non-reverse is ok
1019       {
1020         compare expected-grapheme, 0x20
1021         break-if-!=
1022         var reverse?/eax: boolean <- screen-reverse-at-idx? screen, idx
1023         compare reverse?, 1
1024         break-if-!= $check-screen-row-in-reverse-from:compare-cells
1025       }
1026       # compare graphemes
1027       $check-screen-row-in-reverse-from:compare-graphemes: {
1028         # if (g == expected-grapheme) print "."
1029         compare g, expected-grapheme
1030         {
1031           break-if-!=
1032           print-string-to-real-screen "."
1033           break $check-screen-row-in-reverse-from:compare-graphemes
1034         }
1035         # otherwise print an error
1036         print-string-to-real-screen msg
1037         print-string-to-real-screen ": expected '"
1038         print-grapheme-to-real-screen expected-grapheme
1039         print-string-to-real-screen "' at ("
1040         print-int32-hex-to-real-screen row-idx
1041         print-string-to-real-screen ", "
1042         print-int32-hex-to-real-screen col-idx
1043         print-string-to-real-screen ") but observed '"
1044         print-grapheme-to-real-screen g
1045         print-string-to-real-screen "'\n"
1046       }
1047       $check-screen-row-in-reverse-from:compare-reverse: {
1048         var reverse?/eax: boolean <- screen-reverse-at-idx? screen, idx
1049         compare reverse?, 1
1050         {
1051           break-if-!=
1052           print-string-to-real-screen "."
1053           break $check-screen-row-in-reverse-from:compare-reverse
1054         }
1055         # otherwise print an error
1056         print-string-to-real-screen msg
1057         print-string-to-real-screen ": expected '"
1058         print-grapheme-to-real-screen expected-grapheme
1059         print-string-to-real-screen "' at ("
1060         print-int32-hex-to-real-screen row-idx
1061         print-string-to-real-screen ", "
1062         print-int32-hex-to-real-screen col-idx
1063         print-string-to-real-screen ") to be in reverse-video\n"
1064       }
1065     }
1066     idx <- increment
1067     increment col-idx
1068     loop
1069   }
1070 }
1071 
1072 fn check-screen-row-in-blinking screen: (addr screen), row-idx: int, expected: (addr array byte), msg: (addr array byte) {
1073   check-screen-row-in-blinking-from screen, row-idx, 1, expected, msg
1074 }
1075 
1076 fn check-screen-row-in-blinking-from screen-on-stack: (addr screen), row-idx: int, col-idx: int, expected: (addr array byte), msg: (addr array byte) {
1077   var screen/esi: (addr screen) <- copy screen-on-stack
1078   var idx/ecx: int <- screen-cell-index screen, row-idx, col-idx
1079   # compare 'expected' with the screen contents starting at 'idx', grapheme by grapheme
1080   var e: (stream byte 0x100)
1081   var e-addr/edx: (addr stream byte) <- address e
1082   write e-addr, expected
1083   {
1084     var done?/eax: boolean <- stream-empty? e-addr
1085     compare done?, 0
1086     break-if-!=
1087     var _g/eax: grapheme <- screen-grapheme-at-idx screen, idx
1088     var g/ebx: grapheme <- copy _g
1089     var _expected-grapheme/eax: grapheme <- read-grapheme e-addr
1090     var expected-grapheme/edx: grapheme <- copy _expected-grapheme
1091     $check-screen-row-in-blinking-from:compare-cells: {
1092       # if expected-grapheme is space, null grapheme is also ok
1093       {
1094         compare expected-grapheme, 0x20
1095         break-if-!=
1096         compare g, 0
1097         break-if-= $check-screen-row-in-blinking-from:compare-cells
1098       }
1099       # if expected-grapheme is space, non-blinking is ok
1100       {
1101         compare expected-grapheme, 0x20
1102         break-if-!=
1103         var blinking?/eax: boolean <- screen-blink-at-idx? screen, idx
1104         compare blinking?, 1
1105         break-if-!= $check-screen-row-in-blinking-from:compare-cells
1106       }
1107       # compare graphemes
1108       $check-screen-row-in-blinking-from:compare-graphemes: {
1109         # if (g == expected-grapheme) print "."
1110         compare g, expected-grapheme
1111         {
1112           break-if-!=
1113           print-string-to-real-screen "."
1114           break $check-screen-row-in-blinking-from:compare-graphemes
1115         }
1116         # otherwise print an error
1117         print-string-to-real-screen msg
1118         print-string-to-real-screen ": expected '"
1119         print-grapheme-to-real-screen expected-grapheme
1120         print-string-to-real-screen "' at ("
1121         print-int32-hex-to-real-screen row-idx
1122         print-string-to-real-screen ", "
1123         print-int32-hex-to-real-screen col-idx
1124         print-string-to-real-screen ") but observed '"
1125         print-grapheme-to-real-screen g
1126         print-string-to-real-screen "'\n"
1127       }
1128       $check-screen-row-in-blinking-from:compare-blinking: {
1129         var blinking?/eax: boolean <- screen-blink-at-idx? screen, idx
1130         compare blinking?, 1
1131         {
1132           break-if-!=
1133           print-string-to-real-screen "."
1134           break $check-screen-row-in-blinking-from:compare-blinking
1135         }
1136         # otherwise print an error
1137         print-string-to-real-screen msg
1138         print-string-to-real-screen ": expected '"
1139         print-grapheme-to-real-screen expected-grapheme
1140         print-string-to-real-screen "' at ("
1141         print-int32-hex-to-real-screen row-idx
1142         print-string-to-real-screen ", "
1143         print-int32-hex-to-real-screen col-idx
1144         print-string-to-real-screen ") to be blinking\n"
1145       }
1146     }
1147     idx <- increment
1148     increment col-idx
1149 
1150     loop
1151   }
1152 }
1153 
1154 fn test-print-single-grapheme {
1155   var screen-on-stack: screen
1156   var screen/esi: (addr screen) <- address screen-on-stack
1157   initialize-screen screen, 5, 4
1158   var c/eax: grapheme <- copy 0x61  # 'a'
1159   print-grapheme screen, c
1160   check-screen-row screen, 1, "a", "F - test-print-single-grapheme"  # top-left corner of the screen
1161 }
1162 
1163 fn test-print-multiple-graphemes {
1164   var screen-on-stack: screen
1165   var screen/esi: (addr screen) <- address screen-on-stack
1166   initialize-screen screen, 5, 4
1167   print-string screen, "Hello, 世界"
1168   check-screen-row screen, 1, "Hello, 世界", "F - test-print-multiple-graphemes"
1169 }
1170 
1171 fn test-move-cursor {
1172   var screen-on-stack: screen
1173   var screen/esi: (addr screen) <- address screen-on-stack
1174   initialize-screen screen, 5, 4
1175   move-cursor screen, 1, 4
1176   var c/eax: grapheme <- copy 0x61  # 'a'
1177   print-grapheme screen, c
1178   check-screen-row screen, 1, "   a", "F - test-move-cursor"  # top row
1179 }
1180 
1181 fn test-move-cursor-zeroes {
1182   var screen-on-stack: screen
1183   var screen/esi: (addr screen) <- address screen-on-stack
1184   initialize-screen screen, 5, 4
1185   move-cursor screen, 0, 0
1186   var c/eax: grapheme <- copy 0x61  # 'a'
1187   print-grapheme screen, c
1188   check-screen-row screen, 1, "a", "F - test-move-cursor-zeroes"  # top-left corner of the screen
1189 }
1190 
1191 fn test-move-cursor-zero-row {
1192   var screen-on-stack: screen
1193   var screen/esi: (addr screen) <- address screen-on-stack
1194   initialize-screen screen, 5, 4
1195   move-cursor screen, 0, 2
1196   var c/eax: grapheme <- copy 0x61  # 'a'
1197   print-grapheme screen, c
1198   check-screen-row screen, 1, " a", "F - test-move-cursor-zero-row"  # top row
1199 }
1200 
1201 fn test-move-cursor-zero-column {
1202   var screen-on-stack: screen
1203   var screen/esi: (addr screen) <- address screen-on-stack
1204   initialize-screen screen, 5, 4
1205   move-cursor screen, 4, 0
1206   var c/eax: grapheme <- copy 0x61  # 'a'
1207   print-grapheme screen, c
1208   check-screen-row screen, 4, "a", "F - test-move-cursor-zero-column"
1209 }
1210 
1211 fn test-move-cursor-negative-row {
1212   var screen-on-stack: screen
1213   var screen/esi: (addr screen) <- address screen-on-stack
1214   initialize-screen screen, 5, 3
1215   move-cursor screen, -1, 2  # row -1
1216   var c/eax: grapheme <- copy 0x61  # 'a'
1217   print-grapheme screen, c
1218   # no move
1219   check-screen-row screen, 1, "a", "F - test-move-cursor-negative-row"
1220 }
1221 
1222 fn test-move-cursor-negative-column {
1223   var screen-on-stack: screen
1224   var screen/esi: (addr screen) <- address screen-on-stack
1225   initialize-screen screen, 5, 3
1226   move-cursor screen, 2, -1  # column -1
1227   var c/eax: grapheme <- copy 0x61  # 'a'
1228   print-grapheme screen, c
1229   # no move
1230   check-screen-row screen, 1, "a", "F - test-move-cursor-negative-column"
1231 }
1232 
1233 fn test-move-cursor-column-too-large {
1234   var screen-on-stack: screen
1235   var screen/esi: (addr screen) <- address screen-on-stack
1236   initialize-screen screen, 5, 3  # 5 rows, 3 columns
1237   move-cursor screen, 1, 4  # row 1, column 4 (overflow)
1238   var c/eax: grapheme <- copy 0x61  # 'a'
1239   print-grapheme screen, c
1240   # top row is empty
1241   check-screen-row screen, 1, "   ", "F - test-move-cursor-column-too-large"
1242   # character shows up on next row
1243   check-screen-row screen, 2, "a", "F - test-move-cursor-column-too-large"
1244 }
1245 
1246 fn test-move-cursor-column-too-large-saturates {
1247   var screen-on-stack: screen
1248   var screen/esi: (addr screen) <- address screen-on-stack
1249   initialize-screen screen, 5, 3  # 5 rows, 3 columns
1250   move-cursor screen, 1, 6  # row 1, column 6 (overflow)
1251   var c/eax: grapheme <- copy 0x61  # 'a'
1252   print-grapheme screen, c
1253   # top row is empty
1254   check-screen-row screen, 1, "   ", "F - test-move-cursor-column-too-large-saturates"  # top-left corner of the screen
1255   # character shows up at the start of next row
1256   check-screen-row screen, 2, "a", "F - test-move-cursor-column-too-large-saturates"  # top-left corner of the screen
1257 }
1258 
1259 fn test-move-cursor-row-too-large {
1260   var screen-on-stack: screen
1261   var screen/esi: (addr screen) <- address screen-on-stack
1262   initialize-screen screen, 5, 3  # 5 rows
1263   move-cursor screen, 6, 2  # row 6 (overflow)
1264   var c/eax: grapheme <- copy 0x61  # 'a'
1265   print-grapheme screen, c
1266   # bottom row shows the character
1267   check-screen-row screen, 5, " a", "F - test-move-cursor-row-too-large"
1268 }
1269 
1270 fn test-move-cursor-row-too-large-saturates {
1271   var screen-on-stack: screen
1272   var screen/esi: (addr screen) <- address screen-on-stack
1273   initialize-screen screen, 5, 3  # 5 rows
1274   move-cursor screen, 9, 2  # row 9 (overflow)
1275   var c/eax: grapheme <- copy 0x61  # 'a'
1276   print-grapheme screen, c
1277   # bottom row shows the character
1278   check-screen-row screen, 5, " a", "F - test-move-cursor-row-too-large-saturates"
1279 }
1280 
1281 fn test-check-screen-row-from {
1282   var screen-on-stack: screen
1283   var screen/esi: (addr screen) <- address screen-on-stack
1284   initialize-screen screen, 5, 4
1285   move-cursor screen, 1, 4
1286   var c/eax: grapheme <- copy 0x61  # 'a'
1287   print-grapheme screen, c
1288   check-screen-row screen, 1, "   a", "F - test-check-screen-row-from/baseline"
1289   check-screen-row-from screen, 1, 4, "a", "F - test-check-screen-row-from"
1290 }
1291 
1292 fn test-print-string-overflows-to-next-row {
1293   var screen-on-stack: screen
1294   var screen/esi: (addr screen) <- address screen-on-stack
1295   initialize-screen screen, 5, 4  # 5 rows, 4 columns
1296   print-string screen, "abcdefg"
1297   check-screen-row screen, 1, "abcd", "F - test-print-string-overflows-to-next-row"
1298   check-screen-row screen, 2, "efg", "F - test-print-string-overflows-to-next-row"
1299 }
1300 
1301 fn test-check-screen-scrolls-on-overflow {
1302   var screen-on-stack: screen
1303   var screen/esi: (addr screen) <- address screen-on-stack
1304   initialize-screen screen, 5, 4
1305   # single character starting at bottom right
1306   move-cursor screen, 5, 4
1307   var c/eax: grapheme <- copy 0x61  # 'a'
1308   print-grapheme screen, c
1309   check-screen-row-from screen, 5, 4, "a", "F - test-check-screen-scrolls-on-overflow/baseline"  # bottom-right corner of the screen
1310   # multiple characters starting at bottom right
1311   move-cursor screen, 5, 4
1312   print-string screen, "ab"
1313   # screen scrolled up one row
1314 #?   check-screen-row screen, 1, "    ", "F - test-check-screen-scrolls-on-overflow/x1"
1315 #?   check-screen-row screen, 2, "    ", "F - test-check-screen-scrolls-on-overflow/x2"
1316 #?   check-screen-row screen, 3, "    ", "F - test-check-screen-scrolls-on-overflow/x3"
1317 #?   check-screen-row screen, 4, "   a", "F - test-check-screen-scrolls-on-overflow/x4"
1318 #?   check-screen-row screen, 5, "b   ", "F - test-check-screen-scrolls-on-overflow/x5"
1319   check-screen-row-from screen, 4, 4, "a", "F - test-check-screen-scrolls-on-overflow/1"
1320   check-screen-row-from screen, 5, 1, "b", "F - test-check-screen-scrolls-on-overflow/2"
1321 }
1322 
1323 fn test-check-screen-color {
1324   var screen-on-stack: screen
1325   var screen/esi: (addr screen) <- address screen-on-stack
1326   initialize-screen screen, 5, 4
1327   var c/eax: grapheme <- copy 0x61  # 'a'
1328   print-grapheme screen, c
1329   start-color screen, 1, 0  # foreground=1
1330   c <- copy 0x62  # 'b'
1331   print-grapheme screen, c
1332   start-color screen, 0, 0  # back to default
1333   c <- copy 0x63  # 'c'
1334   print-grapheme screen, c
1335   check-screen-row-in-color screen, 0, 1, "a c", "F - test-check-screen-color"
1336 }
1337 
1338 fn test-check-screen-background-color {
1339   var screen-on-stack: screen
1340   var screen/esi: (addr screen) <- address screen-on-stack
1341   initialize-screen screen, 5, 4
1342   var c/eax: grapheme <- copy 0x61  # 'a'
1343   print-grapheme screen, c
1344   start-color screen, 0, 1  # background=1
1345   c <- copy 0x62  # 'b'
1346   print-grapheme screen, c
1347   start-color screen, 0, 7  # back to default
1348   c <- copy 0x63  # 'c'
1349   print-grapheme screen, c
1350   check-screen-row-in-background-color screen, 7, 1, "a c", "F - test-check-screen-background-color"
1351 }
1352 
1353 fn test-check-screen-bold {
1354   var screen-on-stack: screen
1355   var screen/esi: (addr screen) <- address screen-on-stack
1356   initialize-screen screen, 5, 4
1357   start-bold screen
1358   var c/eax: grapheme <- copy 0x61  # 'a'
1359   print-grapheme screen, c
1360   reset-formatting screen
1361   c <- copy 0x62  # 'b'
1362   print-grapheme screen, c
1363   start-bold screen
1364   c <- copy 0x63  # 'c'
1365   print-grapheme screen, c
1366   check-screen-row-in-bold screen, 1, "a c", "F - test-check-screen-bold"
1367 }
1368 
1369 fn test-check-screen-underline {
1370   var screen-on-stack: screen
1371   var screen/esi: (addr screen) <- address screen-on-stack
1372   initialize-screen screen, 5, 4
1373   start-underline screen
1374   var c/eax: grapheme <- copy 0x61  # 'a'
1375   print-grapheme screen, c
1376   reset-formatting screen
1377   c <- copy 0x62  # 'b'
1378   print-grapheme screen, c
1379   start-underline screen
1380   c <- copy 0x63  # 'c'
1381   print-grapheme screen, c
1382   check-screen-row-in-underline screen, 1, "a c", "F - test-check-screen-underline"
1383 }
1384 
1385 fn test-check-screen-reverse {
1386   var screen-on-stack: screen
1387   var screen/esi: (addr screen) <- address screen-on-stack
1388   initialize-screen screen, 5, 4
1389   start-reverse-video screen
1390   var c/eax: grapheme <- copy 0x61  # 'a'
1391   print-grapheme screen, c
1392   reset-formatting screen
1393   c <- copy 0x62  # 'b'
1394   print-grapheme screen, c
1395   start-reverse-video screen
1396   c <- copy 0x63  # 'c'
1397   print-grapheme screen, c
1398   check-screen-row-in-reverse screen, 1, "a c", "F - test-check-screen-reverse"
1399 }
1400 
1401 fn test-check-screen-blinking {
1402   var screen-on-stack: screen
1403   var screen/esi: (addr screen) <- address screen-on-stack
1404   initialize-screen screen, 5, 4
1405   start-blinking screen
1406   var c/eax: grapheme <- copy 0x61  # 'a'
1407   print-grapheme screen, c
1408   reset-formatting screen
1409   c <- copy 0x62  # 'b'
1410   print-grapheme screen, c
1411   start-blinking screen
1412   c <- copy 0x63  # 'c'
1413   print-grapheme screen, c
1414   check-screen-row-in-blinking screen, 1, "a c", "F - test-check-screen-blinking"
1415 }
1416 
1417 #? fn main -> exit-status/ebx: int {
1418 #? #?   test-check-screen-color
1419 #?   run-tests
1420 #?   exit-status <- copy 0
1421 #? }