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