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