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 g2/ebx: int <- copy g
 620     var expected-grapheme/eax: grapheme <- read-grapheme e-addr
 621     var expected-grapheme2/eax: int <- copy expected-grapheme
 622     # compare graphemes
 623     $check-screen-row-from:compare-graphemes: {
 624       # if expected-grapheme is space, null grapheme is also ok
 625       {
 626         compare expected-grapheme2, 0x20
 627         break-if-!=
 628         compare g2, 0
 629         break-if-= $check-screen-row-from:compare-graphemes
 630       }
 631       check-ints-equal g2, expected-grapheme2, msg
 632     }
 633     idx <- increment
 634     loop
 635   }
 636 }
 637 
 638 # various variants by screen-cell attribute; spaces in the 'expected' data should not match the attribute
 639 
 640 fn check-screen-row-in-color screen: (addr screen), fg: int, row-idx: int, expected: (addr array byte), msg: (addr array byte) {
 641   check-screen-row-in-color-from screen, fg, row-idx, 1, expected, msg
 642 }
 643 
 644 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) {
 645   var screen/esi: (addr screen) <- copy screen-on-stack
 646   var idx/ecx: int <- screen-cell-index screen, row-idx, col-idx
 647   # compare 'expected' with the screen contents starting at 'idx', grapheme by grapheme
 648   var e: (stream byte 0x100)
 649   var e-addr/edx: (addr stream byte) <- address e
 650   write e-addr, expected
 651   {
 652     var done?/eax: boolean <- stream-empty? e-addr
 653     compare done?, 0
 654     break-if-!=
 655     var g/eax: grapheme <- screen-grapheme-at-idx screen, idx
 656     var g2/ebx: int <- copy g
 657     var expected-grapheme/eax: grapheme <- read-grapheme e-addr
 658     var expected-grapheme2/edx: int <- copy expected-grapheme
 659     # compare graphemes
 660     $check-screen-row-in-color-from:compare-graphemes: {
 661       # if expected-grapheme is space, null grapheme is also ok
 662       {
 663         compare expected-grapheme2, 0x20
 664         break-if-!=
 665         compare g2, 0
 666         break-if-= $check-screen-row-in-color-from:compare-graphemes
 667       }
 668       # if expected-grapheme is space, a different color is ok
 669       {
 670         compare expected-grapheme2, 0x20
 671         break-if-!=
 672         var color/eax: int <- screen-color-at-idx screen, idx
 673         compare color, fg
 674         break-if-!= $check-screen-row-in-color-from:compare-graphemes
 675       }
 676       check-ints-equal g2, expected-grapheme2, msg
 677       var color/eax: int <- screen-color-at-idx screen, idx
 678       check-ints-equal color, fg, msg
 679     }
 680     idx <- increment
 681     loop
 682   }
 683 }
 684 
 685 # background color is visible even for spaces, so 'expected' behaves as an array of booleans.
 686 # non-space = given background must match; space = background must not match
 687 fn check-screen-row-in-background-color screen: (addr screen), bg: int, row-idx: int, expected: (addr array byte), msg: (addr array byte) {
 688   check-screen-row-in-background-color-from screen, bg, row-idx, 1, expected, msg
 689 }
 690 
 691 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) {
 692   var screen/esi: (addr screen) <- copy screen-on-stack
 693   var idx/ecx: int <- screen-cell-index screen, row-idx, col-idx
 694   # compare 'expected' with the screen contents starting at 'idx', grapheme by grapheme
 695   var e: (stream byte 0x100)
 696   var e-addr/edx: (addr stream byte) <- address e
 697   write e-addr, expected
 698   {
 699     var done?/eax: boolean <- stream-empty? e-addr
 700     compare done?, 0
 701     break-if-!=
 702     var g/eax: grapheme <- screen-grapheme-at-idx screen, idx
 703     var g2/ebx: int <- copy g
 704     var expected-grapheme/eax: grapheme <- read-grapheme e-addr
 705     var expected-grapheme2/edx: int <- copy expected-grapheme
 706     # compare graphemes
 707     $check-screen-row-in-background-color-from:compare-graphemes: {
 708       # if expected-grapheme is space, null grapheme is also ok
 709       {
 710         compare expected-grapheme2, 0x20
 711         break-if-!=
 712         compare g2, 0
 713         break-if-= $check-screen-row-in-background-color-from:compare-graphemes
 714       }
 715       # if expected-grapheme is space, a different color is ok
 716       {
 717         compare expected-grapheme2, 0x20
 718         break-if-!=
 719         var color/eax: int <- screen-background-color-at-idx screen, idx
 720         compare color, bg
 721         break-if-!= $check-screen-row-in-background-color-from:compare-graphemes
 722       }
 723       check-ints-equal g2, expected-grapheme2, msg
 724       var color/eax: int <- screen-background-color-at-idx screen, idx
 725       check-ints-equal color, bg, msg
 726     }
 727     idx <- increment
 728     loop
 729   }
 730 }
 731 
 732 fn check-screen-row-in-bold screen: (addr screen), row-idx: int, expected: (addr array byte), msg: (addr array byte) {
 733   check-screen-row-in-bold-from screen, row-idx, 1, expected, msg
 734 }
 735 
 736 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) {
 737   var screen/esi: (addr screen) <- copy screen-on-stack
 738   var idx/ecx: int <- screen-cell-index screen, row-idx, col-idx
 739   # compare 'expected' with the screen contents starting at 'idx', grapheme by grapheme
 740   var e: (stream byte 0x100)
 741   var e-addr/edx: (addr stream byte) <- address e
 742   write e-addr, expected
 743   {
 744     var done?/eax: boolean <- stream-empty? e-addr
 745     compare done?, 0
 746     break-if-!=
 747     var g/eax: grapheme <- screen-grapheme-at-idx screen, idx
 748     var g2/ebx: int <- copy g
 749     var expected-grapheme/eax: grapheme <- read-grapheme e-addr
 750     var expected-grapheme2/edx: int <- copy expected-grapheme
 751     # compare graphemes
 752     $check-screen-row-in-bold-from:compare-graphemes: {
 753       # if expected-grapheme is space, null grapheme is also ok
 754       {
 755         compare expected-grapheme2, 0x20
 756         break-if-!=
 757         compare g2, 0
 758         break-if-= $check-screen-row-in-bold-from:compare-graphemes
 759       }
 760       # if expected-grapheme is space, non-bold is ok
 761       {
 762         compare expected-grapheme2, 0x20
 763         break-if-!=
 764         var bold?/eax: boolean <- screen-bold-at-idx? screen, idx
 765         compare bold?, 1
 766         break-if-!= $check-screen-row-in-bold-from:compare-graphemes
 767       }
 768       check-ints-equal g2, expected-grapheme2, msg
 769       var bold?/eax: boolean <- screen-bold-at-idx? screen, idx
 770       var bold/eax: int <- copy bold?
 771       check-ints-equal bold, 1, msg
 772     }
 773     idx <- increment
 774     loop
 775   }
 776 }
 777 
 778 fn check-screen-row-in-underline screen: (addr screen), row-idx: int, expected: (addr array byte), msg: (addr array byte) {
 779   check-screen-row-in-underline-from screen, row-idx, 1, expected, msg
 780 }
 781 
 782 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) {
 783   var screen/esi: (addr screen) <- copy screen-on-stack
 784   var idx/ecx: int <- screen-cell-index screen, row-idx, col-idx
 785   # compare 'expected' with the screen contents starting at 'idx', grapheme by grapheme
 786   var e: (stream byte 0x100)
 787   var e-addr/edx: (addr stream byte) <- address e
 788   write e-addr, expected
 789   {
 790     var done?/eax: boolean <- stream-empty? e-addr
 791     compare done?, 0
 792     break-if-!=
 793     var g/eax: grapheme <- screen-grapheme-at-idx screen, idx
 794     var g2/ebx: int <- copy g
 795     var expected-grapheme/eax: grapheme <- read-grapheme e-addr
 796     var expected-grapheme2/edx: int <- copy expected-grapheme
 797     # compare graphemes
 798     $check-screen-row-in-underline-from:compare-graphemes: {
 799       # if expected-grapheme is space, null grapheme is also ok
 800       {
 801         compare expected-grapheme2, 0x20
 802         break-if-!=
 803         compare g2, 0
 804         break-if-= $check-screen-row-in-underline-from:compare-graphemes
 805       }
 806       # if expected-grapheme is space, non-underline is ok
 807       {
 808         compare expected-grapheme2, 0x20
 809         break-if-!=
 810         var underline?/eax: boolean <- screen-underline-at-idx? screen, idx
 811         compare underline?, 1
 812         break-if-!= $check-screen-row-in-underline-from:compare-graphemes
 813       }
 814       check-ints-equal g2, expected-grapheme2, msg
 815       var underline?/eax: boolean <- screen-underline-at-idx? screen, idx
 816       var underline/eax: int <- copy underline?
 817       check-ints-equal underline, 1, msg
 818     }
 819     idx <- increment
 820     loop
 821   }
 822 }
 823 
 824 fn check-screen-row-in-reverse screen: (addr screen), row-idx: int, expected: (addr array byte), msg: (addr array byte) {
 825   check-screen-row-in-reverse-from screen, row-idx, 1, expected, msg
 826 }
 827 
 828 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) {
 829   var screen/esi: (addr screen) <- copy screen-on-stack
 830   var idx/ecx: int <- screen-cell-index screen, row-idx, col-idx
 831   # compare 'expected' with the screen contents starting at 'idx', grapheme by grapheme
 832   var e: (stream byte 0x100)
 833   var e-addr/edx: (addr stream byte) <- address e
 834   write e-addr, expected
 835   {
 836     var done?/eax: boolean <- stream-empty? e-addr
 837     compare done?, 0
 838     break-if-!=
 839     var g/eax: grapheme <- screen-grapheme-at-idx screen, idx
 840     var g2/ebx: int <- copy g
 841     var expected-grapheme/eax: grapheme <- read-grapheme e-addr
 842     var expected-grapheme2/edx: int <- copy expected-grapheme
 843     # compare graphemes
 844     $check-screen-row-in-reverse-from:compare-graphemes: {
 845       # if expected-grapheme is space, null grapheme is also ok
 846       {
 847         compare expected-grapheme2, 0x20
 848         break-if-!=
 849         compare g2, 0
 850         break-if-= $check-screen-row-in-reverse-from:compare-graphemes
 851       }
 852       # if expected-grapheme is space, non-reverse is ok
 853       {
 854         compare expected-grapheme2, 0x20
 855         break-if-!=
 856         var reverse?/eax: boolean <- screen-reverse-at-idx? screen, idx
 857         compare reverse?, 1
 858         break-if-!= $check-screen-row-in-reverse-from:compare-graphemes
 859       }
 860       check-ints-equal g2, expected-grapheme2, msg
 861       var reverse?/eax: boolean <- screen-reverse-at-idx? screen, idx
 862       var reverse/eax: int <- copy reverse?
 863       check-ints-equal reverse, 1, msg
 864     }
 865     idx <- increment
 866     loop
 867   }
 868 }
 869 
 870 fn check-screen-row-in-blinking screen: (addr screen), row-idx: int, expected: (addr array byte), msg: (addr array byte) {
 871   check-screen-row-in-blinking-from screen, row-idx, 1, expected, msg
 872 }
 873 
 874 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) {
 875   var screen/esi: (addr screen) <- copy screen-on-stack
 876   var idx/ecx: int <- screen-cell-index screen, row-idx, col-idx
 877   # compare 'expected' with the screen contents starting at 'idx', grapheme by grapheme
 878   var e: (stream byte 0x100)
 879   var e-addr/edx: (addr stream byte) <- address e
 880   write e-addr, expected
 881   {
 882     var done?/eax: boolean <- stream-empty? e-addr
 883     compare done?, 0
 884     break-if-!=
 885     var g/eax: grapheme <- screen-grapheme-at-idx screen, idx
 886     var g2/ebx: int <- copy g
 887     var expected-grapheme/eax: grapheme <- read-grapheme e-addr
 888     var expected-grapheme2/edx: int <- copy expected-grapheme
 889     # compare graphemes
 890     $check-screen-row-in-blinking-from:compare-graphemes: {
 891       # if expected-grapheme is space, null grapheme is also ok
 892       {
 893         compare expected-grapheme2, 0x20
 894         break-if-!=
 895         compare g2, 0
 896         break-if-= $check-screen-row-in-blinking-from:compare-graphemes
 897       }
 898       # if expected-grapheme is space, non-blinking is ok
 899       {
 900         compare expected-grapheme2, 0x20
 901         break-if-!=
 902         var blinking?/eax: boolean <- screen-blink-at-idx? screen, idx
 903         compare blinking?, 1
 904         break-if-!= $check-screen-row-in-blinking-from:compare-graphemes
 905       }
 906       check-ints-equal g2, expected-grapheme2, msg
 907       var blinking?/eax: boolean <- screen-blink-at-idx? screen, idx
 908       var blinking/eax: int <- copy blinking?
 909       check-ints-equal blinking, 1, msg
 910     }
 911     idx <- increment
 912     loop
 913   }
 914 }
 915 
 916 fn test-print-single-grapheme {
 917   var screen-on-stack: screen
 918   var screen/esi: (addr screen) <- address screen-on-stack
 919   initialize-screen screen, 5, 4
 920   var c/eax: grapheme <- copy 0x61  # 'a'
 921   print-grapheme screen, c
 922   check-screen-row screen, 1, "a", "F - test-print-single-grapheme"  # top-left corner of the screen
 923 }
 924 
 925 fn test-print-multiple-graphemes {
 926   var screen-on-stack: screen
 927   var screen/esi: (addr screen) <- address screen-on-stack
 928   initialize-screen screen, 5, 4
 929   print-string screen, "Hello, 世界"
 930   check-screen-row screen, 1, "Hello, 世界", "F - test-print-multiple-graphemes"
 931 }
 932 
 933 fn test-move-cursor {
 934   var screen-on-stack: screen
 935   var screen/esi: (addr screen) <- address screen-on-stack
 936   initialize-screen screen, 5, 4
 937   move-cursor screen, 1, 4
 938   var c/eax: grapheme <- copy 0x61  # 'a'
 939   print-grapheme screen, c
 940   check-screen-row screen, 1, "   a", "F - test-move-cursor"  # top row
 941 }
 942 
 943 fn test-move-cursor-zeroes {
 944   var screen-on-stack: screen
 945   var screen/esi: (addr screen) <- address screen-on-stack
 946   initialize-screen screen, 5, 4
 947   move-cursor screen, 0, 0
 948   var c/eax: grapheme <- copy 0x61  # 'a'
 949   print-grapheme screen, c
 950   check-screen-row screen, 1, "a", "F - test-move-cursor-zeroes"  # top-left corner of the screen
 951 }
 952 
 953 fn test-move-cursor-zero-row {
 954   var screen-on-stack: screen
 955   var screen/esi: (addr screen) <- address screen-on-stack
 956   initialize-screen screen, 5, 4
 957   move-cursor screen, 0, 2
 958   var c/eax: grapheme <- copy 0x61  # 'a'
 959   print-grapheme screen, c
 960   check-screen-row screen, 1, " a", "F - test-move-cursor-zero-row"  # top row
 961 }
 962 
 963 fn test-move-cursor-zero-column {
 964   var screen-on-stack: screen
 965   var screen/esi: (addr screen) <- address screen-on-stack
 966   initialize-screen screen, 5, 4
 967   move-cursor screen, 4, 0
 968   var c/eax: grapheme <- copy 0x61  # 'a'
 969   print-grapheme screen, c
 970   check-screen-row screen, 4, "a", "F - test-move-cursor-zero-column"
 971 }
 972 
 973 fn test-move-cursor-negative-row {
 974   var screen-on-stack: screen
 975   var screen/esi: (addr screen) <- address screen-on-stack
 976   initialize-screen screen, 5, 3
 977   move-cursor screen, -1, 2  # row -1
 978   var c/eax: grapheme <- copy 0x61  # 'a'
 979   print-grapheme screen, c
 980   # no move
 981   check-screen-row screen, 1, "a", "F - test-move-cursor-negative-row"
 982 }
 983 
 984 fn test-move-cursor-negative-column {
 985   var screen-on-stack: screen
 986   var screen/esi: (addr screen) <- address screen-on-stack
 987   initialize-screen screen, 5, 3
 988   move-cursor screen, 2, -1  # column -1
 989   var c/eax: grapheme <- copy 0x61  # 'a'
 990   print-grapheme screen, c
 991   # no move
 992   check-screen-row screen, 1, "a", "F - test-move-cursor-negative-column"
 993 }
 994 
 995 fn test-move-cursor-column-too-large {
 996   var screen-on-stack: screen
 997   var screen/esi: (addr screen) <- address screen-on-stack
 998   initialize-screen screen, 5, 3  # 5 rows, 3 columns
 999   move-cursor screen, 1, 4  # row 1, column 4 (overflow)
1000   var c/eax: grapheme <- copy 0x61  # 'a'
1001   print-grapheme screen, c
1002   # top row is empty
1003   check-screen-row screen, 1, "   ", "F - test-move-cursor-column-too-large"
1004   # character shows up on next row
1005   check-screen-row screen, 2, "a", "F - test-move-cursor-column-too-large"
1006 }
1007 
1008 fn test-move-cursor-column-too-large-saturates {
1009   var screen-on-stack: screen
1010   var screen/esi: (addr screen) <- address screen-on-stack
1011   initialize-screen screen, 5, 3  # 5 rows, 3 columns
1012   move-cursor screen, 1, 6  # row 1, column 6 (overflow)
1013   var c/eax: grapheme <- copy 0x61  # 'a'
1014   print-grapheme screen, c
1015   # top row is empty
1016   check-screen-row screen, 1, "   ", "F - test-move-cursor-column-too-large-saturates"  # top-left corner of the screen
1017   # character shows up at the start of next row
1018   check-screen-row screen, 2, "a", "F - test-move-cursor-column-too-large-saturates"  # top-left corner of the screen
1019 }
1020 
1021 fn test-move-cursor-row-too-large {
1022   var screen-on-stack: screen
1023   var screen/esi: (addr screen) <- address screen-on-stack
1024   initialize-screen screen, 5, 3  # 5 rows
1025   move-cursor screen, 6, 2  # row 6 (overflow)
1026   var c/eax: grapheme <- copy 0x61  # 'a'
1027   print-grapheme screen, c
1028   # bottom row shows the character
1029   check-screen-row screen, 5, " a", "F - test-move-cursor-row-too-large"
1030 }
1031 
1032 fn test-move-cursor-row-too-large-saturates {
1033   var screen-on-stack: screen
1034   var screen/esi: (addr screen) <- address screen-on-stack
1035   initialize-screen screen, 5, 3  # 5 rows
1036   move-cursor screen, 9, 2  # row 9 (overflow)
1037   var c/eax: grapheme <- copy 0x61  # 'a'
1038   print-grapheme screen, c
1039   # bottom row shows the character
1040   check-screen-row screen, 5, " a", "F - test-move-cursor-row-too-large-saturates"
1041 }
1042 
1043 fn test-check-screen-row-from {
1044   var screen-on-stack: screen
1045   var screen/esi: (addr screen) <- address screen-on-stack
1046   initialize-screen screen, 5, 4
1047   move-cursor screen, 1, 4
1048   var c/eax: grapheme <- copy 0x61  # 'a'
1049   print-grapheme screen, c
1050   check-screen-row screen, 1, "   a", "F - test-check-screen-row-from/baseline"
1051   check-screen-row-from screen, 1, 4, "a", "F - test-check-screen-row-from"
1052 }
1053 
1054 fn test-print-string-overflows-to-next-row {
1055   var screen-on-stack: screen
1056   var screen/esi: (addr screen) <- address screen-on-stack
1057   initialize-screen screen, 5, 4  # 5 rows, 4 columns
1058   print-string screen, "abcdefg"
1059   check-screen-row screen, 1, "abcd", "F - test-print-string-overflows-to-next-row"
1060   check-screen-row screen, 2, "efg", "F - test-print-string-overflows-to-next-row"
1061 }
1062 
1063 fn test-check-screen-scrolls-on-overflow {
1064   var screen-on-stack: screen
1065   var screen/esi: (addr screen) <- address screen-on-stack
1066   initialize-screen screen, 5, 4
1067   # single character starting at bottom right
1068   move-cursor screen, 5, 4
1069   var c/eax: grapheme <- copy 0x61  # 'a'
1070   print-grapheme screen, c
1071   check-screen-row-from screen, 5, 4, "a", "F - test-check-screen-scrolls-on-overflow/baseline"  # bottom-right corner of the screen
1072   # multiple characters starting at bottom right
1073   move-cursor screen, 5, 4
1074   print-string screen, "ab"
1075   # screen scrolled up one row
1076 #?   check-screen-row screen, 1, "    ", "F - test-check-screen-scrolls-on-overflow/x1"
1077 #?   check-screen-row screen, 2, "    ", "F - test-check-screen-scrolls-on-overflow/x2"
1078 #?   check-screen-row screen, 3, "    ", "F - test-check-screen-scrolls-on-overflow/x3"
1079 #?   check-screen-row screen, 4, "   a", "F - test-check-screen-scrolls-on-overflow/x4"
1080 #?   check-screen-row screen, 5, "b   ", "F - test-check-screen-scrolls-on-overflow/x5"
1081   check-screen-row-from screen, 4, 4, "a", "F - test-check-screen-scrolls-on-overflow/1"
1082   check-screen-row-from screen, 5, 1, "b", "F - test-check-screen-scrolls-on-overflow/2"
1083 }
1084 
1085 fn test-check-screen-color {
1086   var screen-on-stack: screen
1087   var screen/esi: (addr screen) <- address screen-on-stack
1088   initialize-screen screen, 5, 4
1089   var c/eax: grapheme <- copy 0x61  # 'a'
1090   print-grapheme screen, c
1091   start-color screen, 1, 0  # foreground=1
1092   c <- copy 0x62  # 'b'
1093   print-grapheme screen, c
1094   start-color screen, 0, 0  # back to default
1095   c <- copy 0x63  # 'c'
1096   print-grapheme screen, c
1097   check-screen-row-in-color screen, 0, 1, "a c", "F - test-check-screen-color"
1098 }
1099 
1100 fn test-check-screen-background-color {
1101   var screen-on-stack: screen
1102   var screen/esi: (addr screen) <- address screen-on-stack
1103   initialize-screen screen, 5, 4
1104   var c/eax: grapheme <- copy 0x61  # 'a'
1105   print-grapheme screen, c
1106   start-color screen, 0, 1  # background=1
1107   c <- copy 0x62  # 'b'
1108   print-grapheme screen, c
1109   start-color screen, 0, 7  # back to default
1110   c <- copy 0x63  # 'c'
1111   print-grapheme screen, c
1112   check-screen-row-in-background-color screen, 7, 1, "a c", "F - test-check-screen-background-color"
1113 }
1114 
1115 fn test-check-screen-bold {
1116   var screen-on-stack: screen
1117   var screen/esi: (addr screen) <- address screen-on-stack
1118   initialize-screen screen, 5, 4
1119   start-bold screen
1120   var c/eax: grapheme <- copy 0x61  # 'a'
1121   print-grapheme screen, c
1122   reset-formatting screen
1123   c <- copy 0x62  # 'b'
1124   print-grapheme screen, c
1125   start-bold screen
1126   c <- copy 0x63  # 'c'
1127   print-grapheme screen, c
1128   check-screen-row-in-bold screen, 1, "a c", "F - test-check-screen-bold"
1129 }
1130 
1131 fn test-check-screen-underline {
1132   var screen-on-stack: screen
1133   var screen/esi: (addr screen) <- address screen-on-stack
1134   initialize-screen screen, 5, 4
1135   start-underline screen
1136   var c/eax: grapheme <- copy 0x61  # 'a'
1137   print-grapheme screen, c
1138   reset-formatting screen
1139   c <- copy 0x62  # 'b'
1140   print-grapheme screen, c
1141   start-underline screen
1142   c <- copy 0x63  # 'c'
1143   print-grapheme screen, c
1144   check-screen-row-in-underline screen, 1, "a c", "F - test-check-screen-underline"
1145 }
1146 
1147 fn test-check-screen-reverse {
1148   var screen-on-stack: screen
1149   var screen/esi: (addr screen) <- address screen-on-stack
1150   initialize-screen screen, 5, 4
1151   start-reverse-video screen
1152   var c/eax: grapheme <- copy 0x61  # 'a'
1153   print-grapheme screen, c
1154   reset-formatting screen
1155   c <- copy 0x62  # 'b'
1156   print-grapheme screen, c
1157   start-reverse-video screen
1158   c <- copy 0x63  # 'c'
1159   print-grapheme screen, c
1160   check-screen-row-in-reverse screen, 1, "a c", "F - test-check-screen-reverse"
1161 }
1162 
1163 fn test-check-screen-blinking {
1164   var screen-on-stack: screen
1165   var screen/esi: (addr screen) <- address screen-on-stack
1166   initialize-screen screen, 5, 4
1167   start-blinking screen
1168   var c/eax: grapheme <- copy 0x61  # 'a'
1169   print-grapheme screen, c
1170   reset-formatting screen
1171   c <- copy 0x62  # 'b'
1172   print-grapheme screen, c
1173   start-blinking screen
1174   c <- copy 0x63  # 'c'
1175   print-grapheme screen, c
1176   check-screen-row-in-blinking screen, 1, "a c", "F - test-check-screen-blinking"
1177 }
1178 
1179 #? fn main -> exit-status/ebx: int {
1180 #? #?   test-check-screen-color
1181 #?   run-tests
1182 #?   exit-status <- copy 0
1183 #? }