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