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