# Wrappers around print primitives that take a 'screen' object and are thus # easier to test. container screen [ num-rows:integer num-columns:integer cursor-row:integer cursor-column:integer data:address:array:character ] recipe init-fake-screen [ default-space:address:array:location <- new location:type, 30:literal/capacity result:address:screen <- new screen:type width:address:integer <- get-address result:address:screen/deref, num-columns:offset width:address:integer/deref <- next-ingredient height:address:integer <- get-address result:address:screen/deref, num-rows:offset height:address:integer/deref <- next-ingredient row:address:integer <- get-address result:address:screen/deref, cursor-row:offset row:address:integer/deref <- copy 0:literal column:address:integer <- get-address result:address:screen/deref, cursor-column:offset column:address:integer/deref <- copy 0:literal bufsize:integer <- multiply width:address:integer/deref, height:address:integer/deref buf:address:address:array:character <- get-address result:address:screen/deref, data:offset buf:address:address:array:character/deref <- new character:literal, bufsize:integer clear-screen result:address:screen reply result:address:screen ] recipe clear-screen [ default-space:address:array:location <- new location:type, 30:literal x:address:screen <- next-ingredient # if x exists { break-unless x:address:screen # clear fake screen buf:address:array:character <- get x:address:screen/deref, data:offset max:integer <- length buf:address:array:character/deref i:integer <- copy 0:literal { done?:boolean <- greater-or-equal i:integer, max:integer break-if done?:boolean c:address:character <- index-address buf:address:array:character/deref, i:integer c:address:character/deref <- copy [ ] i:integer <- add i:integer, 1:literal loop } reply x:address:screen/same-as-ingredient:0 } # otherwise, real screen clear-display reply x:address:screen/same-as-ingredient:0 ] recipe print-character [ default-space:address:array:location <- new location:type, 30:literal x:address:screen <- next-ingredient c:character <- next-ingredient { # if x exists # (handle special cases exactly like in the real screen) break-unless x:address:screen row:address:integer <- get-address x:address:screen/deref, cursor-row:offset column:address:integer <- get-address x:address:screen/deref, cursor-column:offset width:integer <- get x:address:screen/deref, num-columns:offset height:integer <- get x:address:screen/deref, num-rows:offset max-row:integer <- subtract height:integer, 1:literal # special-case: newline { newline?:boolean <- equal c:character, 13:literal #? $print c:character, [ ], newline?:boolean, [ #? 1 #? ] #? 1 break-unless newline?:boolean { # unless cursor is already at bottom at-bottom?:boolean <- greater-or-equal row:address:integer/deref, max-row:integer break-if at-bottom?:boolean # move it to the next row column:address:integer/deref <- copy 0:literal row:address:integer/deref <- add row:address:integer/deref, 1:literal } reply x:address:screen/same-as-ingredient:0 } # save character in fake screen index:integer <- multiply row:address:integer/deref, width:integer index:integer <- add index:integer, column:address:integer/deref buf:address:array:character <- get x:address:screen/deref, data:offset cursor:address:character <- index-address buf:address:array:character/deref, index:integer # special-case: backspace { backspace?:boolean <- equal c:character, 8:literal break-unless backspace?:boolean { # unless cursor is already at left margin at-left?:boolean <- lesser-or-equal column:address:integer/deref, 0:literal break-if at-left?:boolean # clear previous location column:address:integer/deref <- subtract column:address:integer/deref, 1:literal cursor:address:character <- subtract cursor:address:character, 1:literal cursor:address:character/deref <- copy 32:literal/space } reply x:address:screen/same-as-ingredient:0 } cursor:address:character/deref <- copy c:character # increment column unless it's already all the way to the right { at-right?:boolean <- equal column:address:integer/deref, width:integer break-if at-right?:boolean column:address:integer/deref <- add column:address:integer/deref, 1:literal } reply x:address:screen/same-as-ingredient:0 } # otherwise, real screen print-character-to-display c:character reply x:address:screen/same-as-ingredient:0 ] scenario print-character-at-top-left [ run [ #? $start-tracing #? 3 1:address:screen <- init-fake-screen 3:literal/width, 2:literal/height 1:address:screen <- print-character 1:address:screen, 97:literal # 'a' 2:address:array:character <- get 1:address:screen/deref, data:offset 3:array:character <- copy 2:address:array:character/deref ] memory-should-contain [ 3 <- 6 # width*height 4 <- 97 # 'a' 5 <- 0 ] ] scenario print-backspace-character [ run [ #? $start-tracing #? 3 1:address:screen <- init-fake-screen 3:literal/width, 2:literal/height 1:address:screen <- print-character 1:address:screen, 97:literal # 'a' 1:address:screen <- print-character 1:address:screen, 8:literal # backspace 2:integer <- get 1:address:screen/deref, cursor-column:offset 3:address:array:character <- get 1:address:screen/deref, data:offset 4:array:character <- copy 3:address:array:character/deref ] memory-should-contain [ 2 <- 0 # cursor column 4 <- 6 # width*height 5 <- 32 # space, not 'a' 6 <- 0 ] ] scenario print-newline-character [ run [ #? $start-tracing #? 3 1:address:screen <- init-fake-screen 3:literal/width, 2:literal/height 1:address:screen <- print-character 1:address:screen, 97:literal # 'a' 1:address:screen <- print-character 1:address:screen, 13:literal/newline 2:integer <- get 1:address:screen/deref, cursor-row:offset 3:integer <- get 1:address:screen/deref, cursor-column:offset 4:address:array:character <- get 1:address:screen/deref, data:offset 5:array:character <- copy 4:address:array:character/deref ] memory-should-contain [ 2 <- 1 # cursor row 3 <- 0 # cursor column 5 <- 6 # width*height 6 <- 97 # 'a' 7 <- 0 ] ] recipe clear-line [ default-space:address:array:location <- new location:type, 30:literal x:address:screen <- next-ingredient # if x exists, clear line in fake screen { break-unless x:address:screen n:integer <- get x:address:screen/deref, num-columns:offset column:address:integer <- get-address x:address:screen/deref, cursor-column:offset original-column:integer <- copy column:address:integer/deref # space over the entire line #? $start-tracing #? 1 { #? $print column:address:integer/deref, [ #? 1 #? ] #? 1 done?:boolean <- greater-or-equal column:address:integer/deref, n:integer break-if done?:boolean print-character x:address:screen, [ ] # implicitly updates 'column' loop } # now back to where the cursor was column:address:integer/deref <- copy original-column:integer reply x:address:screen/same-as-ingredient:0 } # otherwise, real screen clear-line-on-display reply x:address:screen/same-as-ingredient:0 ] recipe cursor-position [ default-space:address:array:location <- new location:type, 30:literal x:address:screen <- next-ingredient # if x exists, lookup cursor in fake screen { break-unless x:address:screen row:integer <- get x:address:screen/deref, cursor-row:offset column:integer <- get x:address:screen/deref, cursor-column:offset reply row:integer, column:integer, x:address:screen/same-as-ingredient:0 } row:integer, column:integer <- cursor-position-on-display reply row:integer, column:integer, x:address:screen/same-as-ingredient:0 ] recipe move-cursor [ default-space:address:array:location <- new location:type, 30:literal x:address:screen <- next-ingredient new-row:integer <- next-ingredient new-column:integer <- next-ingredient # if x exists, move cursor in fake screen { break-unless x:address:screen row:address:integer <- get-address x:address:screen/deref cursor-row:offset row:address:integer/deref <- copy new-row:integer column:address:integer <- get-address x:address:screen/deref cursor-column:offset column:address:integer/deref <- copy new-column:integer reply x:address:screen/same-as-ingredient:0 } # otherwise, real screen move-cursor-on-display new-row:integer, new-column:integer reply x:address:screen/same-as-ingredient:0 ] scenario clear-line-erases-printed-characters [ run [ #? $start-tracing #? 4 1:address:screen <- init-fake-screen 3:literal/width, 2:literal/height # print a character 1:address:screen <- print-character 1:address:screen, 97:literal # 'a' # move cursor to start of line 1:address:screen <- move-cursor 1:address:screen, 0:literal/row, 0:literal/column # clear line 1:address:screen <- clear-line 1:address:screen 2:address:array:character <- get 1:address:screen/deref, data:offset 3:array:character <- copy 2:address:array:character/deref ] # screen should be blank memory-should-contain [ 3 <- 6 # width*height 4 <- 0 5 <- 0 6 <- 0 7 <- 0 8 <- 0 9 <- 0 ] ] recipe cursor-down [ default-space:address:array:location <- new location:type, 30:literal x:address:screen <- next-ingredient # if x exists, move cursor in fake screen { break-unless x:address:screen { # if row < height height:integer <- get x:address:screen/deref, num-rows:offset row:address:integer <- get-address x:address:screen/deref cursor-row:offset at-bottom?:boolean <- greater-or-equal row:address:integer/deref, height:integer break-if at-bottom?:boolean # row = row+1 #? $print [AAA: ], row:address:integer, [ -> ], row:address:integer/deref, [ #? 1 #? ] #? 1 row:address:integer/deref <- add row:address:integer/deref, 1:literal #? $print [BBB: ], row:address:integer, [ -> ], row:address:integer/deref, [ #? 1 #? ] #? 1 #? $start-tracing #? 1 } reply x:address:screen/same-as-ingredient:0 } # otherwise, real screen move-cursor-down-on-display reply x:address:screen/same-as-ingredient:0 ] recipe cursor-up [ default-space:address:array:location <- new location:type, 30:literal x:address:screen <- next-ingredient # if x exists, move cursor in fake screen { break-unless x:address:screen { # if row >= 0 row:address:integer <- get-address x:address:screen/deref cursor-row:offset at-top?:boolean <- lesser-than row:address:integer/deref, 0:literal break-if at-top?:boolean # row = row-1 row:address:integer/deref <- subtract row:address:integer/deref, 1:literal } reply x:address:screen/same-as-ingredient:0 } # otherwise, re