about summary refs log blame commit diff stats
path: root/405screen.mu
blob: 62bf10056b8ff4c515db41fd49869cebb675e52e (plain) (tree)
1
2
3
4
5
6
7
                                                                       


                                                                            


                                                                            




                                  


                              




                              
                































                                                                               



                                                                              
































                                                                        
                                        











                                                            
                                    






                            














                                                             
                                                     






































                                                                         







                                                           














                                                             







                                                     
                                                  
                              

          



   

                                                      


                   

                                   



                 
                                                     
                                                                      
                                
                                                                                              
     


                                                                    


                                        
                                                                        
                                






















                                                                                 
     
                                                             


                                                                       


                                                                             



                                                                            
                                  

                                       
                     
                              



   

                                                                                






                                                                                            

                                                           
                    

                             
                   
                      










                                                                      

 
















                                                                                                     
















                                                                                             
















                                                                                                        



































































                                                                                                      




                                                          














                                                  














                                                      










                                           

                                                                        




                                                                         














                                                        







                                                                        














                                     



                                                                        














                                          



                                                                        














                                              



                                                                        














                                         



                                                                        














                                      


                                                                  














                                      


                                                                  


   


                                                                                           
                                                                           
 




                                                                                                                                          
                                                       
                                                                







                                                                                       

                                                              
                                                               
                       
                                               

                                                               
                                       
                   
                    
                                                           
       

















                                                      
     
                    
                     

        

 

                                                                                                         
                                                                                                                                
                                                                      

 
                                                                                                                                                            









                                                                                       




                                                                  

                                                               
                                       
                   

                                                                


                                                              
                                       


                                                             











































                                                                 
       

                    
                     

        



                                                                                             

                                                                                                                                           

 










                                                                                                                                                                       




                                                                  

                                                               
                                       
                   

                                                                           


                                                              
                                       


                                                                        











































                                                                            
       

                    
                     

        

 

                                                                                                                      

 
                                                                                                                                                  









                                                                                       




                                                                  

                                                               
                                       
                   

                                                               


                                                     
                                       


                                                                 







































                                                                 
       

                    
                     

        

 

                                                                                                                           

 
                                                                                                                                                       









                                                                                       




                                                                  

                                                               
                                       
                   

                                                                    


                                                          
                                       


                                                                           







































                                                                           
       

                    
                     

        

 

                                                                                                                         

 
                                                                                                                                                     









                                                                                       




                                                                  

                                                               
                                       
                   

                                                                  


                                                        
                                       


                                                                       







































                                                                       
       

                    
                     

        

 

                                                                                                                          

 
                                                                                                                                                      









                                                                                       




                                                                  

                                                               
                                       
                   

                                                                   


                                                         
                                       


                                                                      







































                                                                      
       

                    

                     

        

 
                               




                                                          
                                                                                                    

 




                                                          




















                                                                                                 

 

























































































                                                                                                                       










                                                                               








                                                                                   





















                                                                                                                                    














                                                                              














                                                                                                    













                                                                         
 





















































                                                                                   
# Wrappers for real screen primitives that can be passed a fake screen.
# The tests here have been painstakingly validated against a real terminal
# emulator. I believe functionality here is broadly portable across terminal
# emulators.
#
# Remember: fake screen co-ordinates are 1-based, just like in real terminal
# emulators.

type screen {
  num-rows: int
  num-cols: int
  data: (handle array screen-cell)
  top-index: int  # 0-indexed
  cursor-row: int  # 1-indexed
  cursor-col: int  # 1-indexed
  cursor-hide?: boolean
  curr-attributes: screen-cell
}

type screen-cell {
  data: grapheme
  color: int
  background-color: int
  bold?: boolean
  underline?: boolean
  reverse?: boolean
  blink?: boolean
}

fn initialize-screen screen: (addr screen), nrows: int, ncols: int {
  var screen-addr/esi: (addr screen) <- copy screen
  var tmp/eax: int <- copy 0
  var dest/edi: (addr int) <- copy 0
  # screen->num-rows = nrows
  dest <- get screen-addr, num-rows
  tmp <- copy nrows
  copy-to *dest, tmp
  # screen->num-cols = ncols
  dest <- get screen-addr, num-cols
  tmp <- copy ncols
  copy-to *dest, tmp
  # screen->data = new screen-cell[nrows*ncols]
  {
    var data-addr/edi: (addr handle array screen-cell) <- get screen-addr, data
    tmp <- multiply nrows
    populate data-addr, tmp
  }
  # screen->cursor-row = 1
  dest <- get screen-addr, cursor-row
  copy-to *dest, 1
  # screen->cursor-col = 1
  dest <- get screen-addr, cursor-col
  copy-to *dest, 1
  # screen->curr-attributes->background-color = 7  (simulate light background)
  var tmp2/eax: (addr screen-cell) <- get screen-addr, curr-attributes
  dest <- get tmp2, background-color
  copy-to *dest, 7
}

fn screen-size screen: (addr screen) -> nrows/eax: int, ncols/ecx: int {
$screen-size:body: {
  compare screen, 0
  {
    break-if-!=
    nrows, ncols <- real-screen-size
    break $screen-size:body
  }
  {
    break-if-=
    # fake screen
    var screen-addr/esi: (addr screen) <- copy screen
    var tmp/edx: (addr int) <- get screen-addr, num-rows
    nrows <- copy *tmp
    tmp <- get screen-addr, num-cols
    ncols <- copy *tmp
  }
}
}

fn clear-screen screen: (addr screen) {
$clear-screen:body: {
  compare screen, 0
  {
    break-if-!=
    clear-real-screen
    break $clear-screen:body
  }
  {
    break-if-=
    # fake screen
    var space/edi: grapheme <- copy 0x20
    move-cursor screen, 1, 1
    var screen-addr/esi: (addr screen) <- copy screen
    var i/eax: int <- copy 1
    var nrows/ecx: (addr int) <- get screen-addr, num-rows
    {
      compare i, *nrows
      break-if->
      var j/edx: int <- copy 1
      var ncols/ebx: (addr int) <- get screen-addr, num-cols
      {
        compare j, *ncols
        break-if->
        print-grapheme screen, space
        j <- increment
        loop
      }
      i <- increment
      loop
    }
    move-cursor screen, 1, 1
  }
}
}

fn move-cursor screen: (addr screen), row: int, column: int {
$move-cursor:body: {
  compare screen, 0
  {
    break-if-!=
    move-cursor-on-real-screen row, column
    break $move-cursor:body
  }
  {
    break-if-=
    # fake screen
    var screen-addr/esi: (addr screen) <- copy screen
    # row < 0 is ignored
    {
      compare row, 0
      break-if-< $move-cursor:body
    }
    # row = 0 is treated same as 1
    {
      compare row, 0
      break-if-!=
      copy-to row, 1
    }
    # row > num-rows saturates to num-rows
    {
      var nrows-addr/eax: (addr int) <- get screen-addr, num-rows
      var nrows/eax: int <- copy *nrows-addr
      compare row, nrows
      break-if-<=
      copy-to row, nrows
    }
    # column < 0 is ignored
    {
      compare column, 0
      break-if-< $move-cursor:body
    }
    # column = 0 is treated same as 1
    {
      compare column, 0
      break-if-!=
      copy-to column, 1
    }
    # column > num-cols saturates to num-cols+1 (so wrapping to next row)
    {
      var ncols-addr/eax: (addr int) <- get screen-addr, num-cols
      var ncols/eax: int <- copy *ncols-addr
      compare column, ncols
      break-if-<=
      copy-to column, ncols
      increment column
    }
    # screen->cursor-row = row
    var dest/edi: (addr int) <- get screen-addr, cursor-row
    var src/eax: int <- copy row
    copy-to *dest, src
    # screen->cursor-col = column
    dest <- get screen-addr, cursor-col
    src <- copy column
    copy-to *dest, src
  }
}
}

fn print-string screen: (addr screen), s: (addr array byte) {
$print-string:body: {
  compare screen, 0
  {
    break-if-!=
    print-string-to-real-screen s
    break $print-string:body
  }
  {
    break-if-=
    # fake screen
    var s2: (stream byte 0x100)
    var s2-addr/esi: (addr stream byte) <- address s2
    write s2-addr, s
    var screen-addr/edi: (addr screen) <- copy screen
    {
      var done?/eax: boolean <- stream-empty? s2-addr
      compare done?, 0
      break-if-!=
      var g/eax: grapheme <- read-grapheme s2-addr
      print-grapheme screen, g
      loop
    }
  }
}
}

fn print-grapheme screen: (addr screen), c: grapheme {
$print-grapheme:body: {
  compare screen, 0
  {
    break-if-!=
    print-grapheme-to-real-screen c
    break $print-grapheme:body
  }
  {
    break-if-=
    # fake screen
    var screen-addr/esi: (addr screen) <- copy screen
    var cursor-col-addr/edx: (addr int) <- get screen-addr, cursor-col
    # adjust cursor if necessary
    # to avoid premature scrolling it's important to do this lazily, at the last possible time
    {
      # next row
      var num-cols-addr/ecx: (addr int) <- get screen-addr, num-cols
      var num-cols/ecx: int <- copy *num-cols-addr
      compare *cursor-col-addr, num-cols
      break-if-<=
      copy-to *cursor-col-addr, 1
      var cursor-row-addr/ebx: (addr int) <- get screen-addr, cursor-row
      increment *cursor-row-addr
      # scroll
      var num-rows-addr/eax: (addr int) <- get screen-addr, num-rows
      var num-rows/eax: int <- copy *num-rows-addr
      compare *cursor-row-addr, num-rows
      break-if-<=
      copy-to *cursor-row-addr, num-rows
      # if (top-index > data size) top-index = 0, otherwise top-index += num-cols
      $print-grapheme:perform-scroll: {
        var top-index-addr/ebx: (addr int) <- get screen-addr, top-index
        var data-ah/eax: (addr handle array screen-cell) <- get screen-addr, data
        var data/eax: (addr array screen-cell) <- lookup *data-ah
        var max-index/edi: int <- length data
        compare *top-index-addr, max-index
        {
          break-if->=
          add-to *top-index-addr, num-cols
          break $print-grapheme:perform-scroll
        }
        {
          break-if-<
          copy-to *top-index-addr, 0
        }
      }
    }
    var idx/ecx: int <- current-screen-cell-index screen-addr
#?     print-string-to-real-screen "printing grapheme at screen index "
#?     print-int32-hex-to-real-screen idx
#?     print-string-to-real-screen ": "
    var data-ah/eax: (addr handle array screen-cell) <- get screen-addr, data
    var data/eax: (addr array screen-cell) <- lookup *data-ah
    var offset/ecx: (offset screen-cell) <- compute-offset data, idx
    var dest-cell/ecx: (addr screen-cell) <- index data, offset
    var src-cell/eax: (addr screen-cell) <- get screen-addr, curr-attributes
    copy-object src-cell, dest-cell
    var dest/eax: (addr grapheme) <- get dest-cell, data
    var c2/ecx: grapheme <- copy c
#?     print-grapheme-to-real-screen c2
#?     print-string-to-real-screen "\n"
    copy-to *dest, c2
    increment *cursor-col-addr
  }
}
}

fn current-screen-cell-index screen-on-stack: (addr screen) -> result/ecx: int {
  var screen/esi: (addr screen) <- copy screen-on-stack
  var cursor-row-addr/ecx: (addr int) <- get screen, cursor-row
  var cursor-col-addr/eax: (addr int) <- get screen, cursor-col
  result <- screen-cell-index screen, *cursor-row-addr, *cursor-col-addr
}

fn screen-cell-index screen-on-stack: (addr screen), row: int, col: int -> result/ecx: int {
  var screen/esi: (addr screen) <- copy screen-on-stack
  var num-cols-addr/eax: (addr int) <- get screen, num-cols
  var num-cols/eax: int <- copy *num-cols-addr
  result <- copy row
  result <- subtract 1
  result <- multiply num-cols
  result <- add col
  result <- subtract 1
  # result = (result + top-index) % data length
  var top-index-addr/eax: (addr int) <- get screen, top-index
  result <- add *top-index-addr
  var data-ah/eax: (addr handle array screen-cell) <- get screen, data
  var data/eax: (addr array screen-cell) <- lookup *data-ah
  var max-index/eax: int <- length data
  compare result, max-index
  {
    break-if-<
    result <- subtract max-index
  }
}

fn screen-grapheme-at screen-on-stack: (addr screen), row: int, col: int -> result/eax: grapheme {
  var screen-addr/esi: (addr screen) <- copy screen-on-stack
  var idx/ecx: int <- screen-cell-index screen-addr, row, col
  result <- screen-grapheme-at-idx screen-addr, idx
}

fn screen-grapheme-at-idx screen-on-stack: (addr screen), idx-on-stack: int -> result/eax: grapheme {
  var screen-addr/esi: (addr screen) <- copy screen-on-stack
  var data-ah/eax: (addr handle array screen-cell) <- get screen-addr, data
  var data/eax: (addr array screen-cell) <- lookup *data-ah
  var idx/ecx: int <- copy idx-on-stack
  var offset/ecx: (offset screen-cell) <- compute-offset data, idx
  var cell/eax: (addr screen-cell) <- index data, offset
  var src/eax: (addr grapheme) <- get cell, data
  result <- copy *src
}

fn screen-color-at screen-on-stack: (addr screen), row: int, col: int -> result/eax: int {
  var screen-addr/esi: (addr screen) <- copy screen-on-stack
  var idx/ecx: int <- screen-cell-index screen-addr, row, col
  result <- screen-color-at-idx screen-addr, idx
}

fn screen-color-at-idx screen-on-stack: (addr screen), idx-on-stack: int -> result/eax: int {
  var screen-addr/esi: (addr screen) <- copy screen-on-stack
  var data-ah/eax: (addr handle array screen-cell) <- get screen-addr, data
  var data/eax: (addr array screen-cell) <- lookup *data-ah
  var idx/ecx: int <- copy idx-on-stack
  var offset/ecx: (offset screen-cell) <- compute-offset data, idx
  var cell/eax: (addr screen-cell) <- index data, offset
  var src/eax: (addr int) <- get cell, color
  result <- copy *src
}

fn screen-background-color-at screen-on-stack: (addr screen), row: int, col: int -> result/eax: int {
  var screen-addr/esi: (addr screen) <- copy screen-on-stack
  var idx/ecx: int <- screen-cell-index screen-addr, row, col
  result <- screen-background-color-at-idx screen-addr, idx
}

fn screen-background-color-at-idx screen-on-stack: (addr screen), idx-on-stack: int -> result/eax: int {
  var screen-addr/esi: (addr screen) <- copy screen-on-stack
  var data-ah/eax: (addr handle array screen-cell) <- get screen-addr, data
  var data/eax: (addr array screen-cell) <- lookup *data-ah
  var idx/ecx: int <- copy idx-on-stack
  var offset/ecx: (offset screen-cell) <- compute-offset data, idx
  var cell/eax: (addr screen-cell) <- index data, offset
  var src/eax: (addr int) <- get cell, background-color
  result <- copy *src
}

fn screen-bold-at? screen-on-stack: (addr screen), row: int, col: int -> result/eax: boolean {
  var screen-addr/esi: (addr screen) <- copy screen-on-stack
  var idx/ecx: int <- screen-cell-index screen-addr, row, col
  result <- screen-bold-at-idx? screen-addr, idx
}

fn screen-bold-at-idx? screen-on-stack: (addr screen), idx-on-stack: int -> result/eax: boolean {
  var screen-addr/esi: (addr screen) <- copy screen-on-stack
  var data-ah/eax: (addr handle array screen-cell) <- get screen-addr, data
  var data/eax: (addr array screen-cell) <- lookup *data-ah
  var idx/ecx: int <- copy idx-on-stack
  var offset/ecx: (offset screen-cell) <- compute-offset data, idx
  var cell/eax: (addr screen-cell) <- index data, offset
  var src/eax: (addr boolean) <- get cell, bold?
  result <- copy *src
}

fn screen-underline-at? screen-on-stack: (addr screen), row: int, col: int -> result/eax: boolean {
  var screen-addr/esi: (addr screen) <- copy screen-on-stack
  var idx/ecx: int <- screen-cell-index screen-addr, row, col
  result <- screen-underline-at-idx? screen-addr, idx
}

fn screen-underline-at-idx? screen-on-stack: (addr screen), idx-on-stack: int -> result/eax: boolean {
  var screen-addr/esi: (addr screen) <- copy screen-on-stack
  var data-ah/eax: (addr handle array screen-cell) <- get screen-addr, data
  var data/eax: (addr array screen-cell) <- lookup *data-ah
  var idx/ecx: int <- copy idx-on-stack
  var offset/ecx: (offset screen-cell) <- compute-offset data, idx
  var cell/eax: (addr screen-cell) <- index data, offset
  var src/eax: (addr boolean) <- get cell, underline?
  result <- copy *src
}

fn screen-reverse-at? screen-on-stack: (addr screen), row: int, col: int -> result/eax: boolean {
  var screen-addr/esi: (addr screen) <- copy screen-on-stack
  var idx/ecx: int <- screen-cell-index screen-addr, row, col
  result <- screen-reverse-at-idx? screen-addr, idx
}

fn screen-reverse-at-idx? screen-on-stack: (addr screen), idx-on-stack: int -> result/eax: boolean {
  var screen-addr/esi: (addr screen) <- copy screen-on-stack
  var data-ah/eax: (addr handle array screen-cell) <- get screen-addr, data
  var data/eax: (addr array screen-cell) <- lookup *data-ah
  var idx/ecx: int <- copy idx-on-stack
  var offset/ecx: (offset screen-cell) <- compute-offset data, idx
  var cell/eax: (addr screen-cell) <- index data, offset
  var src/eax: (addr boolean) <- get cell, reverse?
  result <- copy *src
}

fn screen-blink-at? screen-on-stack: (addr screen), row: int, col: int -> result/eax: boolean {
  var screen-addr/esi: (addr screen) <- copy screen-on-stack
  var idx/ecx: int <- screen-cell-index screen-addr, row, col
  result <- screen-blink-at-idx? screen-addr, idx
}

fn screen-blink-at-idx? screen-on-stack: (addr screen), idx-on-stack: int -> result/eax: boolean {
  var screen-addr/esi: (addr screen) <- copy screen-on-stack
  var data-ah/eax: (addr handle array screen-cell) <- get screen-addr, data
  var data/eax: (addr array screen-cell) <- lookup *data-ah
  var idx/ecx: int <- copy idx-on-stack
  var offset/ecx: (offset screen-cell) <- compute-offset data, idx
  var cell/eax: (addr screen-cell) <- index data, offset
  var src/eax: (addr boolean) <- get cell, blink?
  result <- copy *src
}

fn print-code-point screen: (addr screen), c: code-point {
  var g/eax: grapheme <- to-grapheme c
  print-grapheme screen, g
}

fn print-int32-hex screen: (addr screen), n: int {
$print-int32-hex:body: {
  compare screen, 0
  {
    break-if-!=
    print-int32-hex-to-real-screen n
    break $print-int32-hex:body
  }
  {
    break-if-=
    # fake screen
  }
}
}

fn print-int32-decimal screen: (addr screen), n: int {
$print-int32-decimal:body: {
  compare screen, 0
  {
    break-if-!=
    print-int32-decimal-to-real-screen n
    break $print-int32-decimal:body
  }
  {
    break-if-=
    # fake screen
  }
}
}

fn reset-formatting screen: (addr screen) {
$reset-formatting:body: {
  compare screen, 0
  {
    break-if-!=
    reset-formatting-on-real-screen
    break $reset-formatting:body
  }
  {
    break-if-=
    # fake screen
    var screen-addr/esi: (addr screen) <- copy screen
    var dest/ecx: (addr screen-cell) <- get screen-addr, curr-attributes
    var default-cell: screen-cell
    var bg/eax: (addr int) <- get default-cell, background-color
    copy-to *bg, 7
    var default-cell-addr/eax: (addr screen-cell) <- address default-cell
    copy-object default-cell-addr, dest
  }
}
}

fn start-color screen: (addr screen), fg: int, bg: int {
$start-color:body: {
  compare screen, 0
  {
    break-if-!=
    start-color-on-real-screen fg, bg
    break $start-color:body
  }
  {
    break-if-=
    # fake screen
    var screen-addr/esi: (addr screen) <- copy screen
    var attr/ecx: (addr screen-cell) <- get screen-addr, curr-attributes
    var dest/edx: (addr int) <- get attr, color
    var src/eax: int <- copy fg
    copy-to *dest, src
    var dest/edx: (addr int) <- get attr, background-color
    var src/eax: int <- copy bg
    copy-to *dest, src
  }
}
}

fn start-bold screen: (addr screen) {
$start-bold:body: {
  compare screen, 0
  {
    break-if-!=
    start-bold-on-real-screen
    break $start-bold:body
  }
  {
    break-if-=
    # fake screen
    var screen-addr/esi: (addr screen) <- copy screen
    var attr/ecx: (addr screen-cell) <- get screen-addr, curr-attributes
    var dest/edx: (addr boolean) <- get attr, bold?
    copy-to *dest, 1
  }
}
}

fn start-underline screen: (addr screen) {
$start-underline:body: {
  compare screen, 0
  {
    break-if-!=
    start-underline-on-real-screen
    break $start-underline:body
  }
  {
    break-if-=
    # fake screen
    var screen-addr/esi: (addr screen) <- copy screen
    var attr/ecx: (addr screen-cell) <- get screen-addr, curr-attributes
    var dest/edx: (addr boolean) <- get attr, underline?
    copy-to *dest, 1
  }
}
}

fn start-reverse-video screen: (addr screen) {
$start-reverse-video:body: {
  compare screen, 0
  {
    break-if-!=
    start-reverse-video-on-real-screen
    break $start-reverse-video:body
  }
  {
    break-if-=
    # fake screen
    var screen-addr/esi: (addr screen) <- copy screen
    var attr/ecx: (addr screen-cell) <- get screen-addr, curr-attributes
    var dest/edx: (addr boolean) <- get attr, reverse?
    copy-to *dest, 1
  }
}
}

fn start-blinking screen: (addr screen) {
$start-blinking:body: {
  compare screen, 0
  {
    break-if-!=
    start-blinking-on-real-screen
    break $start-blinking:body
  }
  {
    break-if-=
    # fake screen
    var screen-addr/esi: (addr screen) <- copy screen
    var attr/ecx: (addr screen-cell) <- get screen-addr, curr-attributes
    var dest/edx: (addr boolean) <- get attr, blink?
    copy-to *dest, 1
  }
}
}

fn hide-cursor screen: (addr screen) {
$hide-cursor:body: {
  compare screen, 0
  {
    break-if-!=
    hide-cursor-on-real-screen
    break $hide-cursor:body
  }
  {
    break-if-=
    # fake screen
    var screen-addr/esi: (addr screen) <- copy screen
    var hide?/ecx: (addr boolean) <- get screen-addr, cursor-hide?
    copy-to *hide?, 1
  }
}
}

fn show-cursor screen: (addr screen) {
$show-cursor:body: {
  compare screen, 0
  {
    break-if-!=
    show-cursor-on-real-screen
    break $show-cursor:body
  }
  {
    break-if-=
    # fake screen
    var screen-addr/esi: (addr screen) <- copy screen
    var hide?/ecx: (addr boolean) <- get screen-addr, cursor-hide?
    copy-to *hide?, 0
  }
}
}

# validate data on screen regardless of attributes (color, bold, etc.)
# Mu doesn't have multi-line strings, so we provide functions for rows or portions of rows.
# Tab characters (that translate into multiple screen cells) not supported.

fn check-screen-row screen: (addr screen), row-idx: int, expected: (addr array byte), msg: (addr array byte) {
  check-screen-row-from screen, row-idx, 1, expected, msg
}

fn check-screen-row-from screen-on-stack: (addr screen), row-idx: int, col-idx: int, expected: (addr array byte), msg: (addr array byte) {
  var screen/esi: (addr screen) <- copy screen-on-stack
  var idx/ecx: int <- screen-cell-index screen, row-idx, col-idx
  # compare 'expected' with the screen contents starting at 'idx', grapheme by grapheme
  var e: (stream byte 0x100)
  var e-addr/edx: (addr stream byte) <- address e
  write e-addr, expected
  {
    var done?/eax: boolean <- stream-empty? e-addr
    compare done?, 0
    break-if-!=
    var _g/eax: grapheme <- screen-grapheme-at-idx screen, idx
    var g/ebx: grapheme <- copy _g
    var expected-grapheme/eax: grapheme <- read-grapheme e-addr
    # compare graphemes
    $check-screen-row-from:compare-graphemes: {
      # if expected-grapheme is space, null grapheme is also ok
      {
        compare expected-grapheme, 0x20
        break-if-!=
        compare g, 0
        break-if-= $check-screen-row-from:compare-graphemes
      }
      # if (g == expected-grapheme) print "."
      compare g, expected-grapheme
      {
        break-if-!=
        print-string-to-real-screen "."
        break $check-screen-row-from:compare-graphemes
      }
      # otherwise print an error
      print-string-to-real-screen msg
      print-string-to-real-screen ": expected '"
      print-grapheme-to-real-screen expected-grapheme
      print-string-to-real-screen "' at ("
      print-int32-hex-to-real-screen row-idx
      print-string-to-real-screen ", "
      print-int32-hex-to-real-screen col-idx
      print-string-to-real-screen ") but observed '"
      print-grapheme-to-real-screen g
      print-string-to-real-screen "'\n"
    }
    idx <- increment
    increment col-idx
    loop
  }
}

# various variants by screen-cell attribute; spaces in the 'expected' data should not match the attribute

fn check-screen-row-in-color screen: (addr screen), fg: int, row-idx: int, expected: (addr array byte), msg: (addr array byte) {
  check-screen-row-in-color-from screen, fg, row-idx, 1, expected, msg
}

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) {
  var screen/esi: (addr screen) <- copy screen-on-stack
  var idx/ecx: int <- screen-cell-index screen, row-idx, col-idx
  # compare 'expected' with the screen contents starting at 'idx', grapheme by grapheme
  var e: (stream byte 0x100)
  var e-addr/edx: (addr stream byte) <- address e
  write e-addr, expected
  {
    var done?/eax: boolean <- stream-empty? e-addr
    compare done?, 0
    break-if-!=
    var _g/eax: grapheme <- screen-grapheme-at-idx screen, idx
    var g/ebx: grapheme <- copy _g
    var _expected-grapheme/eax: grapheme <- read-grapheme e-addr
    var expected-grapheme/edi: grapheme <- copy _expected-grapheme
    $check-screen-row-in-color-from:compare-cells: {
      # if expected-grapheme is space, null grapheme is also ok
      {
        compare expected-grapheme, 0x20
        break-if-!=
        compare g, 0
        break-if-= $check-screen-row-in-color-from:compare-cells
      }
      # if expected-grapheme is space, a different color is ok
      {
        compare expected-grapheme, 0x20
        break-if-!=
        var color/eax: int <- screen-color-at-idx screen, idx
        compare color, fg
        break-if-!= $check-screen-row-in-color-from:compare-cells
      }
      # compare graphemes
      $check-screen-row-in-color-from:compare-graphemes: {
        # if (g == expected-grapheme) print "."
        compare g, expected-grapheme
        {
          break-if-!=
          print-string-to-real-screen "."
          break $check-screen-row-in-color-from:compare-graphemes
        }
        # otherwise print an error
        print-string-to-real-screen msg
        print-string-to-real-screen ": expected '"
        print-grapheme-to-real-screen expected-grapheme
        print-string-to-real-screen "' at ("
        print-int32-hex-to-real-screen row-idx
        print-string-to-real-screen ", "
        print-int32-hex-to-real-screen col-idx
        print-string-to-real-screen ") but observed '"
        print-grapheme-to-real-screen g
        print-string-to-real-screen "'\n"
      }
      $check-screen-row-in-color-from:compare-colors: {
        var color/eax: int <- screen-color-at-idx screen, idx
        compare fg, color
        {
          break-if-!=
          print-string-to-real-screen "."
          break $check-screen-row-in-color-from:compare-colors
        }
        # otherwise print an error
        print-string-to-real-screen msg
        print-string-to-real-screen ": expected '"
        print-grapheme-to-real-screen expected-grapheme
        print-string-to-real-screen "' at ("
        print-int32-hex-to-real-screen row-idx
        print-string-to-real-screen ", "
        print-int32-hex-to-real-screen col-idx
        print-string-to-real-screen ") in color "
        print-int32-hex-to-real-screen fg
        print-string-to-real-screen " but observed color "
        print-int32-hex-to-real-screen color
        print-string-to-real-screen "\n"
      }
    }
    idx <- increment
    increment col-idx
    loop
  }
}

# background color is visible even for spaces, so 'expected' behaves as an array of booleans.
# non-space = given background must match; space = background must not match
fn check-screen-row-in-background-color screen: (addr screen), bg: int, row-idx: int, expected: (addr array byte), msg: (addr array byte) {
  check-screen-row-in-background-color-from screen, bg, row-idx, 1, expected, msg
}

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) {
  var screen/esi: (addr screen) <- copy screen-on-stack
  var idx/ecx: int <- screen-cell-index screen, row-idx, col-idx
  # compare 'expected' with the screen contents starting at 'idx', grapheme by grapheme
  var e: (stream byte 0x100)
  var e-addr/edx: (addr stream byte) <- address e
  write e-addr, expected
  {
    var done?/eax: boolean <- stream-empty? e-addr
    compare done?, 0
    break-if-!=
    var _g/eax: grapheme <- screen-grapheme-at-idx screen, idx
    var g/ebx: grapheme <- copy _g
    var _expected-grapheme/eax: grapheme <- read-grapheme e-addr
    var expected-grapheme/edx: grapheme <- copy _expected-grapheme
    $check-screen-row-in-background-color-from:compare-cells: {
      # if expected-grapheme is space, null grapheme is also ok
      {
        compare expected-grapheme, 0x20
        break-if-!=
        compare g, 0
        break-if-= $check-screen-row-in-background-color-from:compare-cells
      }
      # if expected-grapheme is space, a different color is ok
      {
        compare expected-grapheme, 0x20
        break-if-!=
        var color/eax: int <- screen-background-color-at-idx screen, idx
        compare color, bg
        break-if-!= $check-screen-row-in-background-color-from:compare-cells
      }
      # compare graphemes
      $check-screen-row-in-background-color-from:compare-graphemes: {
        # if (g == expected-grapheme) print "."
        compare g, expected-grapheme
        {
          break-if-!=
          print-string-to-real-screen "."
          break $check-screen-row-in-background-color-from:compare-graphemes
        }
        # otherwise print an error
        print-string-to-real-screen msg
        print-string-to-real-screen ": expected '"
        print-grapheme-to-real-screen expected-grapheme
        print-string-to-real-screen "' at ("
        print-int32-hex-to-real-screen row-idx
        print-string-to-real-screen ", "
        print-int32-hex-to-real-screen col-idx
        print-string-to-real-screen ") but observed '"
        print-grapheme-to-real-screen g
        print-string-to-real-screen "'\n"
      }
      $check-screen-row-in-background-color-from:compare-colors: {
        var color/eax: int <- screen-background-color-at-idx screen, idx
        compare bg, color
        {
          break-if-!=
          print-string-to-real-screen "."
          break $check-screen-row-in-background-color-from:compare-colors
        }
        # otherwise print an error
        print-string-to-real-screen msg
        print-string-to-real-screen ": expected '"
        print-grapheme-to-real-screen expected-grapheme
        print-string-to-real-screen "' at ("
        print-int32-hex-to-real-screen row-idx
        print-string-to-real-screen ", "
        print-int32-hex-to-real-screen col-idx
        print-string-to-real-screen ") in background color "
        print-int32-hex-to-real-screen bg
        print-string-to-real-screen " but observed color "
        print-int32-hex-to-real-screen color
        print-string-to-real-screen "\n"
      }
    }
    idx <- increment
    increment col-idx
    loop
  }
}

fn check-screen-row-in-bold screen: (addr screen), row-idx: int, expected: (addr array byte), msg: (addr array byte) {
  check-screen-row-in-bold-from screen, row-idx, 1, expected, msg
}

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) {
  var screen/esi: (addr screen) <- copy screen-on-stack
  var idx/ecx: int <- screen-cell-index screen, row-idx, col-idx
  # compare 'expected' with the screen contents starting at 'idx', grapheme by grapheme
  var e: (stream byte 0x100)
  var e-addr/edx: (addr stream byte) <- address e
  write e-addr, expected
  {
    var done?/eax: boolean <- stream-empty? e-addr
    compare done?, 0
    break-if-!=
    var _g/eax: grapheme <- screen-grapheme-at-idx screen, idx
    var g/ebx: grapheme <- copy _g
    var _expected-grapheme/eax: grapheme <- read-grapheme e-addr
    var expected-grapheme/edx: grapheme <- copy _expected-grapheme
    $check-screen-row-in-bold-from:compare-cells: {
      # if expected-grapheme is space, null grapheme is also ok
      {
        compare expected-grapheme, 0x20
        break-if-!=
        compare g, 0
        break-if-= $check-screen-row-in-bold-from:compare-cells
      }
      # if expected-grapheme is space, non-bold is ok
      {
        compare expected-grapheme, 0x20
        break-if-!=
        var bold?/eax: boolean <- screen-bold-at-idx? screen, idx
        compare bold?, 1
        break-if-!= $check-screen-row-in-bold-from:compare-cells
      }
      # compare graphemes
      $check-screen-row-in-bold-from:compare-graphemes: {
        # if (g == expected-grapheme) print "."
        compare g, expected-grapheme
        {
          break-if-!=
          print-string-to-real-screen "."
          break $check-screen-row-in-bold-from:compare-graphemes
        }
        # otherwise print an error
        print-string-to-real-screen msg
        print-string-to-real-screen ": expected '"
        print-grapheme-to-real-screen expected-grapheme
        print-string-to-real-screen "' at ("
        print-int32-hex-to-real-screen row-idx
        print-string-to-real-screen ", "
        print-int32-hex-to-real-screen col-idx
        print-string-to-real-screen ") but observed '"
        print-grapheme-to-real-screen g
        print-string-to-real-screen "'\n"
      }
      $check-screen-row-in-bold-from:compare-bold: {
        var bold?/eax: boolean <- screen-bold-at-idx? screen, idx
        compare bold?, 1
        {
          break-if-!=
          print-string-to-real-screen "."
          break $check-screen-row-in-bold-from:compare-bold
        }
        # otherwise print an error
        print-string-to-real-screen msg
        print-string-to-real-screen ": expected '"
        print-grapheme-to-real-screen expected-grapheme
        print-string-to-real-screen "' at ("
        print-int32-hex-to-real-screen row-idx
        print-string-to-real-screen ", "
        print-int32-hex-to-real-screen col-idx
        print-string-to-real-screen ") to be in bold\n"
      }
    }
    idx <- increment
    increment col-idx
    loop
  }
}

fn check-screen-row-in-underline screen: (addr screen), row-idx: int, expected: (addr array byte), msg: (addr array byte) {
  check-screen-row-in-underline-from screen, row-idx, 1, expected, msg
}

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) {
  var screen/esi: (addr screen) <- copy screen-on-stack
  var idx/ecx: int <- screen-cell-index screen, row-idx, col-idx
  # compare 'expected' with the screen contents starting at 'idx', grapheme by grapheme
  var e: (stream byte 0x100)
  var e-addr/edx: (addr stream byte) <- address e
  write e-addr, expected
  {
    var done?/eax: boolean <- stream-empty? e-addr
    compare done?, 0
    break-if-!=
    var _g/eax: grapheme <- screen-grapheme-at-idx screen, idx
    var g/ebx: grapheme <- copy _g
    var _expected-grapheme/eax: grapheme <- read-grapheme e-addr
    var expected-grapheme/edx: grapheme <- copy _expected-grapheme
    $check-screen-row-in-underline-from:compare-cells: {
      # if expected-grapheme is space, null grapheme is also ok
      {
        compare expected-grapheme, 0x20
        break-if-!=
        compare g, 0
        break-if-= $check-screen-row-in-underline-from:compare-cells
      }
      # if expected-grapheme is space, non-underline is ok
      {
        compare expected-grapheme, 0x20
        break-if-!=
        var underline?/eax: boolean <- screen-underline-at-idx? screen, idx
        compare underline?, 1
        break-if-!= $check-screen-row-in-underline-from:compare-cells
      }
      # compare graphemes
      $check-screen-row-in-underline-from:compare-graphemes: {
        # if (g == expected-grapheme) print "."
        compare g, expected-grapheme
        {
          break-if-!=
          print-string-to-real-screen "."
          break $check-screen-row-in-underline-from:compare-graphemes
        }
        # otherwise print an error
        print-string-to-real-screen msg
        print-string-to-real-screen ": expected '"
        print-grapheme-to-real-screen expected-grapheme
        print-string-to-real-screen "' at ("
        print-int32-hex-to-real-screen row-idx
        print-string-to-real-screen ", "
        print-int32-hex-to-real-screen col-idx
        print-string-to-real-screen ") but observed '"
        print-grapheme-to-real-screen g
        print-string-to-real-screen "'\n"
      }
      $check-screen-row-in-underline-from:compare-underline: {
        var underline?/eax: boolean <- screen-underline-at-idx? screen, idx
        compare underline?, 1
        {
          break-if-!=
          print-string-to-real-screen "."
          break $check-screen-row-in-underline-from:compare-underline
        }
        # otherwise print an error
        print-string-to-real-screen msg
        print-string-to-real-screen ": expected '"
        print-grapheme-to-real-screen expected-grapheme
        print-string-to-real-screen "' at ("
        print-int32-hex-to-real-screen row-idx
        print-string-to-real-screen ", "
        print-int32-hex-to-real-screen col-idx
        print-string-to-real-screen ") to be underlined\n"
      }
    }
    idx <- increment
    increment col-idx
    loop
  }
}

fn check-screen-row-in-reverse screen: (addr screen), row-idx: int, expected: (addr array byte), msg: (addr array byte) {
  check-screen-row-in-reverse-from screen, row-idx, 1, expected, msg
}

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) {
  var screen/esi: (addr screen) <- copy screen-on-stack
  var idx/ecx: int <- screen-cell-index screen, row-idx, col-idx
  # compare 'expected' with the screen contents starting at 'idx', grapheme by grapheme
  var e: (stream byte 0x100)
  var e-addr/edx: (addr stream byte) <- address e
  write e-addr, expected
  {
    var done?/eax: boolean <- stream-empty? e-addr
    compare done?, 0
    break-if-!=
    var _g/eax: grapheme <- screen-grapheme-at-idx screen, idx
    var g/ebx: grapheme <- copy _g
    var _expected-grapheme/eax: grapheme <- read-grapheme e-addr
    var expected-grapheme/edx: grapheme <- copy _expected-grapheme
    $check-screen-row-in-reverse-from:compare-cells: {
      # if expected-grapheme is space, null grapheme is also ok
      {
        compare expected-grapheme, 0x20
        break-if-!=
        compare g, 0
        break-if-= $check-screen-row-in-reverse-from:compare-cells
      }
      # if expected-grapheme is space, non-reverse is ok
      {
        compare expected-grapheme, 0x20
        break-if-!=
        var reverse?/eax: boolean <- screen-reverse-at-idx? screen, idx
        compare reverse?, 1
        break-if-!= $check-screen-row-in-reverse-from:compare-cells
      }
      # compare graphemes
      $check-screen-row-in-reverse-from:compare-graphemes: {
        # if (g == expected-grapheme) print "."
        compare g, expected-grapheme
        {
          break-if-!=
          print-string-to-real-screen "."
          break $check-screen-row-in-reverse-from:compare-graphemes
        }
        # otherwise print an error
        print-string-to-real-screen msg
        print-string-to-real-screen ": expected '"
        print-grapheme-to-real-screen expected-grapheme
        print-string-to-real-screen "' at ("
        print-int32-hex-to-real-screen row-idx
        print-string-to-real-screen ", "
        print-int32-hex-to-real-screen col-idx
        print-string-to-real-screen ") but observed '"
        print-grapheme-to-real-screen g
        print-string-to-real-screen "'\n"
      }
      $check-screen-row-in-reverse-from:compare-reverse: {
        var reverse?/eax: boolean <- screen-reverse-at-idx? screen, idx
        compare reverse?, 1
        {
          break-if-!=
          print-string-to-real-screen "."
          break $check-screen-row-in-reverse-from:compare-reverse
        }
        # otherwise print an error
        print-string-to-real-screen msg
        print-string-to-real-screen ": expected '"
        print-grapheme-to-real-screen expected-grapheme
        print-string-to-real-screen "' at ("
        print-int32-hex-to-real-screen row-idx
        print-string-to-real-screen ", "
        print-int32-hex-to-real-screen col-idx
        print-string-to-real-screen ") to be in reverse-video\n"
      }
    }
    idx <- increment
    increment col-idx
    loop
  }
}

fn check-screen-row-in-blinking screen: (addr screen), row-idx: int, expected: (addr array byte), msg: (addr array byte) {
  check-screen-row-in-blinking-from screen, row-idx, 1, expected, msg
}

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) {
  var screen/esi: (addr screen) <- copy screen-on-stack
  var idx/ecx: int <- screen-cell-index screen, row-idx, col-idx
  # compare 'expected' with the screen contents starting at 'idx', grapheme by grapheme
  var e: (stream byte 0x100)
  var e-addr/edx: (addr stream byte) <- address e
  write e-addr, expected
  {
    var done?/eax: boolean <- stream-empty? e-addr
    compare done?, 0
    break-if-!=
    var _g/eax: grapheme <- screen-grapheme-at-idx screen, idx
    var g/ebx: grapheme <- copy _g
    var _expected-grapheme/eax: grapheme <- read-grapheme e-addr
    var expected-grapheme/edx: grapheme <- copy _expected-grapheme
    $check-screen-row-in-blinking-from:compare-cells: {
      # if expected-grapheme is space, null grapheme is also ok
      {
        compare expected-grapheme, 0x20
        break-if-!=
        compare g, 0
        break-if-= $check-screen-row-in-blinking-from:compare-cells
      }
      # if expected-grapheme is space, non-blinking is ok
      {
        compare expected-grapheme, 0x20
        break-if-!=
        var blinking?/eax: boolean <- screen-blink-at-idx? screen, idx
        compare blinking?, 1
        break-if-!= $check-screen-row-in-blinking-from:compare-cells
      }
      # compare graphemes
      $check-screen-row-in-blinking-from:compare-graphemes: {
        # if (g == expected-grapheme) print "."
        compare g, expected-grapheme
        {
          break-if-!=
          print-string-to-real-screen "."
          break $check-screen-row-in-blinking-from:compare-graphemes
        }
        # otherwise print an error
        print-string-to-real-screen msg
        print-string-to-real-screen ": expected '"
        print-grapheme-to-real-screen expected-grapheme
        print-string-to-real-screen "' at ("
        print-int32-hex-to-real-screen row-idx
        print-string-to-real-screen ", "
        print-int32-hex-to-real-screen col-idx
        print-string-to-real-screen ") but observed '"
        print-grapheme-to-real-screen g
        print-string-to-real-screen "'\n"
      }
      $check-screen-row-in-blinking-from:compare-blinking: {
        var blinking?/eax: boolean <- screen-blink-at-idx? screen, idx
        compare blinking?, 1
        {
          break-if-!=
          print-string-to-real-screen "."
          break $check-screen-row-in-blinking-from:compare-blinking
        }
        # otherwise print an error
        print-string-to-real-screen msg
        print-string-to-real-screen ": expected '"
        print-grapheme-to-real-screen expected-grapheme
        print-string-to-real-screen "' at ("
        print-int32-hex-to-real-screen row-idx
        print-string-to-real-screen ", "
        print-int32-hex-to-real-screen col-idx
        print-string-to-real-screen ") to be blinking\n"
      }
    }
    idx <- increment
    increment col-idx

    loop
  }
}

fn test-print-single-grapheme {
  var screen-on-stack: screen
  var screen/esi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 5, 4
  var c/eax: grapheme <- copy 0x61  # 'a'
  print-grapheme screen, c
  check-screen-row screen, 1, "a", "F - test-print-single-grapheme"  # top-left corner of the screen
}

fn test-print-multiple-graphemes {
  var screen-on-stack: screen
  var screen/esi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 5, 4
  print-string screen, "Hello, 世界"
  check-screen-row screen, 1, "Hello, 世界", "F - test-print-multiple-graphemes"
}

fn test-move-cursor {
  var screen-on-stack: screen
  var screen/esi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 5, 4
  move-cursor screen, 1, 4
  var c/eax: grapheme <- copy 0x61  # 'a'
  print-grapheme screen, c
  check-screen-row screen, 1, "   a", "F - test-move-cursor"  # top row
}

fn test-move-cursor-zeroes {
  var screen-on-stack: screen
  var screen/esi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 5, 4
  move-cursor screen, 0, 0
  var c/eax: grapheme <- copy 0x61  # 'a'
  print-grapheme screen, c
  check-screen-row screen, 1, "a", "F - test-move-cursor-zeroes"  # top-left corner of the screen
}

fn test-move-cursor-zero-row {
  var screen-on-stack: screen
  var screen/esi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 5, 4
  move-cursor screen, 0, 2
  var c/eax: grapheme <- copy 0x61  # 'a'
  print-grapheme screen, c
  check-screen-row screen, 1, " a", "F - test-move-cursor-zero-row"  # top row
}

fn test-move-cursor-zero-column {
  var screen-on-stack: screen
  var screen/esi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 5, 4
  move-cursor screen, 4, 0
  var c/eax: grapheme <- copy 0x61  # 'a'
  print-grapheme screen, c
  check-screen-row screen, 4, "a", "F - test-move-cursor-zero-column"
}

fn test-move-cursor-negative-row {
  var screen-on-stack: screen
  var screen/esi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 5, 3
  move-cursor screen, -1, 2  # row -1
  var c/eax: grapheme <- copy 0x61  # 'a'
  print-grapheme screen, c
  # no move
  check-screen-row screen, 1, "a", "F - test-move-cursor-negative-row"
}

fn test-move-cursor-negative-column {
  var screen-on-stack: screen
  var screen/esi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 5, 3
  move-cursor screen, 2, -1  # column -1
  var c/eax: grapheme <- copy 0x61  # 'a'
  print-grapheme screen, c
  # no move
  check-screen-row screen, 1, "a", "F - test-move-cursor-negative-column"
}

fn test-move-cursor-column-too-large {
  var screen-on-stack: screen
  var screen/esi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 5, 3  # 5 rows, 3 columns
  move-cursor screen, 1, 4  # row 1, column 4 (overflow)
  var c/eax: grapheme <- copy 0x61  # 'a'
  print-grapheme screen, c
  # top row is empty
  check-screen-row screen, 1, "   ", "F - test-move-cursor-column-too-large"
  # character shows up on next row
  check-screen-row screen, 2, "a", "F - test-move-cursor-column-too-large"
}

fn test-move-cursor-column-too-large-saturates {
  var screen-on-stack: screen
  var screen/esi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 5, 3  # 5 rows, 3 columns
  move-cursor screen, 1, 6  # row 1, column 6 (overflow)
  var c/eax: grapheme <- copy 0x61  # 'a'
  print-grapheme screen, c
  # top row is empty
  check-screen-row screen, 1, "   ", "F - test-move-cursor-column-too-large-saturates"  # top-left corner of the screen
  # character shows up at the start of next row
  check-screen-row screen, 2, "a", "F - test-move-cursor-column-too-large-saturates"  # top-left corner of the screen
}

fn test-move-cursor-row-too-large {
  var screen-on-stack: screen
  var screen/esi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 5, 3  # 5 rows
  move-cursor screen, 6, 2  # row 6 (overflow)
  var c/eax: grapheme <- copy 0x61  # 'a'
  print-grapheme screen, c
  # bottom row shows the character
  check-screen-row screen, 5, " a", "F - test-move-cursor-row-too-large"
}

fn test-move-cursor-row-too-large-saturates {
  var screen-on-stack: screen
  var screen/esi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 5, 3  # 5 rows
  move-cursor screen, 9, 2  # row 9 (overflow)
  var c/eax: grapheme <- copy 0x61  # 'a'
  print-grapheme screen, c
  # bottom row shows the character
  check-screen-row screen, 5, " a", "F - test-move-cursor-row-too-large-saturates"
}

fn test-check-screen-row-from {
  var screen-on-stack: screen
  var screen/esi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 5, 4
  move-cursor screen, 1, 4
  var c/eax: grapheme <- copy 0x61  # 'a'
  print-grapheme screen, c
  check-screen-row screen, 1, "   a", "F - test-check-screen-row-from/baseline"
  check-screen-row-from screen, 1, 4, "a", "F - test-check-screen-row-from"
}

fn test-print-string-overflows-to-next-row {
  var screen-on-stack: screen
  var screen/esi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 5, 4  # 5 rows, 4 columns
  print-string screen, "abcdefg"
  check-screen-row screen, 1, "abcd", "F - test-print-string-overflows-to-next-row"
  check-screen-row screen, 2, "efg", "F - test-print-string-overflows-to-next-row"
}

fn test-check-screen-scrolls-on-overflow {
  var screen-on-stack: screen
  var screen/esi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 5, 4
  # single character starting at bottom right
  move-cursor screen, 5, 4
  var c/eax: grapheme <- copy 0x61  # 'a'
  print-grapheme screen, c
  check-screen-row-from screen, 5, 4, "a", "F - test-check-screen-scrolls-on-overflow/baseline"  # bottom-right corner of the screen
  # multiple characters starting at bottom right
  move-cursor screen, 5, 4
  print-string screen, "ab"
  # screen scrolled up one row
#?   check-screen-row screen, 1, "    ", "F - test-check-screen-scrolls-on-overflow/x1"
#?   check-screen-row screen, 2, "    ", "F - test-check-screen-scrolls-on-overflow/x2"
#?   check-screen-row screen, 3, "    ", "F - test-check-screen-scrolls-on-overflow/x3"
#?   check-screen-row screen, 4, "   a", "F - test-check-screen-scrolls-on-overflow/x4"
#?   check-screen-row screen, 5, "b   ", "F - test-check-screen-scrolls-on-overflow/x5"
  check-screen-row-from screen, 4, 4, "a", "F - test-check-screen-scrolls-on-overflow/1"
  check-screen-row-from screen, 5, 1, "b", "F - test-check-screen-scrolls-on-overflow/2"
}

fn test-check-screen-color {
  var screen-on-stack: screen
  var screen/esi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 5, 4
  var c/eax: grapheme <- copy 0x61  # 'a'
  print-grapheme screen, c
  start-color screen, 1, 0  # foreground=1
  c <- copy 0x62  # 'b'
  print-grapheme screen, c
  start-color screen, 0, 0  # back to default
  c <- copy 0x63  # 'c'
  print-grapheme screen, c
  check-screen-row-in-color screen, 0, 1, "a c", "F - test-check-screen-color"
}

fn test-check-screen-background-color {
  var screen-on-stack: screen
  var screen/esi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 5, 4
  var c/eax: grapheme <- copy 0x61  # 'a'
  print-grapheme screen, c
  start-color screen, 0, 1  # background=1
  c <- copy 0x62  # 'b'
  print-grapheme screen, c
  start-color screen, 0, 7  # back to default
  c <- copy 0x63  # 'c'
  print-grapheme screen, c
  check-screen-row-in-background-color screen, 7, 1, "a c", "F - test-check-screen-background-color"
}

fn test-check-screen-bold {
  var screen-on-stack: screen
  var screen/esi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 5, 4
  start-bold screen
  var c/eax: grapheme <- copy 0x61  # 'a'
  print-grapheme screen, c
  reset-formatting screen
  c <- copy 0x62  # 'b'
  print-grapheme screen, c
  start-bold screen
  c <- copy 0x63  # 'c'
  print-grapheme screen, c
  check-screen-row-in-bold screen, 1, "a c", "F - test-check-screen-bold"
}

fn test-check-screen-underline {
  var screen-on-stack: screen
  var screen/esi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 5, 4
  start-underline screen
  var c/eax: grapheme <- copy 0x61  # 'a'
  print-grapheme screen, c
  reset-formatting screen
  c <- copy 0x62  # 'b'
  print-grapheme screen, c
  start-underline screen
  c <- copy 0x63  # 'c'
  print-grapheme screen, c
  check-screen-row-in-underline screen, 1, "a c", "F - test-check-screen-underline"
}

fn test-check-screen-reverse {
  var screen-on-stack: screen
  var screen/esi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 5, 4
  start-reverse-video screen
  var c/eax: grapheme <- copy 0x61  # 'a'
  print-grapheme screen, c
  reset-formatting screen
  c <- copy 0x62  # 'b'
  print-grapheme screen, c
  start-reverse-video screen
  c <- copy 0x63  # 'c'
  print-grapheme screen, c
  check-screen-row-in-reverse screen, 1, "a c", "F - test-check-screen-reverse"
}

fn test-check-screen-blinking {
  var screen-on-stack: screen
  var screen/esi: (addr screen) <- address screen-on-stack
  initialize-screen screen, 5, 4
  start-blinking screen
  var c/eax: grapheme <- copy 0x61  # 'a'
  print-grapheme screen, c
  reset-formatting screen
  c <- copy 0x62  # 'b'
  print-grapheme screen, c
  start-blinking screen
  c <- copy 0x63  # 'c'
  print-grapheme screen, c
  check-screen-row-in-blinking screen, 1, "a c", "F - test-check-screen-blinking"
}

#? fn main -> exit-status/ebx: int {
#? #?   test-check-screen-color
#?   run-tests
#?   exit-status <- copy 0
#? }