container screen [
num-rows:number
num-columns:number
cursor-row:number
cursor-column:number
data:address:array:screen-cell
]
container screen-cell [
contents:character
color:number
]
recipe new-fake-screen [
local-scope
result:address:screen <- new screen:type
width:address:number <- get-address *result, num-columns:offset
*width <- next-ingredient
height:address:number <- get-address *result, num-rows:offset
*height <- next-ingredient
row:address:number <- get-address *result, cursor-row:offset
*row <- copy 0
column:address:number <- get-address *result, cursor-column:offset
*column <- copy 0
bufsize:number <- multiply *width, *height
buf:address:address:array:screen-cell <- get-address *result, data:offset
*buf <- new screen-cell:type, bufsize
clear-screen result
reply result
]
recipe clear-screen [
local-scope
sc:address:screen <- next-ingredient
{
break-unless sc
buf:address:array:screen-cell <- get *sc, data:offset
max:number <- length *buf
i:number <- copy 0
{
done?:boolean <- greater-or-equal i, max
break-if done?
curr:address:screen-cell <- index-address *buf, i
curr-content:address:character <- get-address *curr, contents:offset
*curr-content <- copy [ ]
curr-color:address:character <- get-address *curr, color:offset
*curr-color <- copy 7/white
i <- add i, 1
loop
}
x:address:number <- get-address *sc, cursor-row:offset
*x <- copy 0
x <- get-address *sc, cursor-column:offset
*x <- copy 0
reply sc/same-as-ingredient:0
}
clear-display
reply sc/same-as-ingredient:0
]
recipe fake-screen-is-empty? [
local-scope
sc:address:screen <- next-ingredient
reply-unless sc, 1/true
buf:address:array:screen-cell <- get *sc, data:offset
i:number <- copy 0
len:number <- length *buf
{
done?:boolean <- greater-or-equal i, len
break-if done?
curr:screen-cell <- index *buf, i
curr-contents:character <- get curr, contents:offset
i <- add i, 1
loop-unless curr-contents
reply 0/false
}
reply 1/true
]
recipe print-character [
local-scope
sc:address:screen <- next-ingredient
c:character <- next-ingredient
color:number, color-found?:boolean <- next-ingredient
{
break-if color-found?
color <- copy 7/white
}
bg-color:number, bg-color-found?:boolean <- next-ingredient
{
break-if bg-color-found?
bg-color <- copy 0/black
}
{
break-unless sc
width:number <- get *sc, num-columns:offset
height:number <- get *sc, num-rows:offset
row:address:number <- get-address *sc, cursor-row:offset
legal?:boolean <- greater-or-equal *row, 0
reply-unless legal?, sc
legal? <- lesser-than *row, height
reply-unless legal?, sc
column:address:number <- get-address *sc, cursor-column:offset
legal? <- greater-or-equal *column, 0
reply-unless legal?, sc
legal? <- lesser-than *column, width
reply-unless legal?, sc
{
newline?:boolean <- equal c, 10/newline
break-unless newline?
{
bottom:number <- subtract height, 1
at-bottom?:boolean <- greater-or-equal *row, bottom
break-if at-bottom?
*column <- copy 0
*row <- add *row, 1
}
reply sc/same-as-ingredient:0
}
index:number <- multiply *row, width
index <- add index, *column
buf:address:array:screen-cell <- get *sc, data:offset
len:number <- length *buf
{
backspace?:boolean <- equal c, 8
break-unless backspace?
{
at-left?:boolean <- lesser-or-equal *column, 0
break-if at-left?
*column <- subtract *column, 1
index <- subtract index, 1
cursor:address:screen-cell <- index-address *buf, index
cursor-contents:address:character <- get-address *cursor, contents:offset
*cursor-contents <- copy 32/space
cursor-color:address:number <- get-address *cursor, color:offset
*cursor-color <- copy 7/white
}
reply sc/same-as-ingredient:0
}
cursor:address:screen-cell <- index-address *buf, index
cursor-contents:address:character <- get-address *cursor, contents:offset
*cursor-contents <- copy c
cursor-color:address:number <- get-address *cursor, color:offset
*cursor-color <- copy color
{
right:number <- subtract width, 1
at-right?:boolean <- greater-or-equal *column, right
break-if at-right?
*column <- add *column, 1
}
reply sc/same-as-ingredient:0
}
print-character-to-display c, color, bg-color
reply sc/same-as-ingredient:0
]
scenario print-character-at-top-left [
run [
1:address:screen <- new-fake-screen 3/width, 2/height
1:address:screen <- print-character 1:address:screen, 97
2:address:array:screen-cell <- get *1:address:screen, data:offset
3:array:screen-cell <- copy *2:address:array:screen-cell
]
memory-should-contain [
3 <- 6
4 <- 97
5 <- 7
6 <- 0
]
]
scenario print-character-color [
run [
1:address:screen <- new-fake-screen 3/width, 2/height
1:address:screen <- print-character 1:address:screen, 97/a, 1/red
2:address:array:screen-cell <- get *1:address:screen, data:offset
3:array:screen-cell <- copy *2:address:array:screen-cell
]
memory-should-contain [
3 <- 6
4 <- 97
5 <- 1
6 <- 0
]
]
scenario print-backspace-character [
run [
1:address:screen <- new-fake-screen 3/width, 2/height
1:address:screen <- print-character 1:address:screen, 97
1:address:screen <- print-character 1:address:screen, 8
2:number <- get *1:address:screen, cursor-column:offset
3:address:array:screen-cell <- get *1:address:screen, data:offset
4:array:screen-cell <- copy *3:address:array:screen-cell
]
memory-should-contain [
2 <- 0
4 <- 6
5 <- 32
6 <- 7
7 <- 0
]
]
scenario print-extra-backspace-character [
run [
1:address:screen <- new-fake-screen 3/width, 2/height
1:address:screen <- print-character 1:address:screen, 97
1:address:screen <- print-character 1:address:screen, 8
1:address:screen <- print-character 1:address:screen, 8
2:number <- get *1:address:screen, cursor-column:offset
3:address:array:screen-cell <- get *1:address:screen, data:offset
4:array:screen-cell <- copy *3:address:array:screen-cell
]
memory-should-contain [
2 <- 0
4 <- 6
5 <- 32
6 <- 7
7 <- 0
]
]
scenario print-at-right-margin [
run [
1:address:screen <- new-fake-screen 2/width, 2/height
1:address:screen <- print-character 1:address:screen, 97
1:address:screen <- print-character 1:address:screen, 98
1:address:screen <- print-character 1:address:screen, 99
2:number <- get *1:address:screen, cursor-column:offset
3:address:array:screen-cell <- get *1:address:screen, data:offset
4:array:screen-cell <- copy *3:address:array:screen-cell
]
memory-should-contain [
2 <- 1
4 <- 4
5 <- 97
6 <- 7
7 <- 99
8 <- 7
9 <- 0
]
]
scenario print-newline-character [
run [
1:address:screen <- new-fake-screen 3/width, 2/height
1:address:screen <- print-character 1:address:screen, 97
1:address:screen <- print-character 1:address:screen, 10/newline
2:number <- get *1:address:screen, cursor-row:offset
3:number <- get *1:address:screen, cursor-column:offset
4:address:array:screen-cell <- get *1:address:screen, data:offset
5:array:screen-cell <- copy *4:address:array:screen-cell
]
memory-should-contain [
2 <- 1
3 <- 0
5 <- 6
6 <- 97
7 <- 7
8 <- 0
]
]
scenario print-newline-at-bottom-line [
run [
1:address:screen <- new-fake-screen 3/width, 2/height
1:address:screen <- print-character 1:address:screen, 10/newline
1:address:screen <- print-character 1:address:screen, 10/newline
1:address:screen <- print-character 1:address:screen, 10/newline
2:number <- get *1:address:screen, cursor-row:offset
3:number <- get *1:address:screen, cursor-column:offset
]
memory-should-contain [
2 <- 1
3 <- 0
]
]
scenario print-at-bottom-right [
run [
1:address:screen <- new-fake-screen 2/width, 2/height
1:address:screen <- print-character 1:address:screen, 10/newline
1:address:screen <- print-character 1:address:screen, 97
1:address:screen <- print-character 1:address:screen, 98
1:address:screen <- print-character 1:address:screen, 99
1:address:screen <- print-character 1:address:screen, 10/newline
1:address:screen <- print-character 1:address:screen, 100
2:number <- get *1:address:screen, cursor-row:offset
3:number <- get *1:address:screen, cursor-column:offset
4:address:array:screen-cell <- get *1:address:screen, data:offset
5:array:screen-cell <- copy *4:address:array:screen-cell
]
memory-should-contain [
2 <- 1
3 <- 1
5 <- 4
6 <- 0
7 <- 7
8 <- 0
9 <- 7
10 <- 97
11 <- 7
12 <- 100
13 <- 7
14 <- 0
]
]
recipe clear-line [
local-scope
sc:address:screen <- next-ingredient
{
break-unless sc
width:number <- get *sc, num-columns:offset
column:address:number <- get-address *sc, cursor-column:offset
original-column:number <- copy *column
{
right:number <- subtract width, 1
done?:boolean <- greater-or-equal *column, right
break-if done?
print-character sc, [ ]
loop
}
*column <- copy original-column
reply sc/same-as-ingredient:0
}
clear-line-on-display
reply sc/same-as-ingredient:0
]
recipe cursor-position [
local-scope
sc:address:screen <- next-ingredient
{
break-unless sc
row:number <- get *sc, cursor-row:offset
column:number <- get *sc, cursor-column:offset
reply row, column, sc/same-as-ingredient:0
}
row, column <- cursor-position-on-display
reply row, column, sc/same-as-ingredient:0
]
recipe move-cursor [
local-scope
sc:address:screen <- next-ingredient
new-row:number <- next-ingredient
new-column:number <- next-ingredient
{
break-unless sc
row:address:number <- get-address *sc, cursor-row:offset
*row <- copy new-row
column:address:number <- get-address *sc, cursor-column:offset
*column <- copy new-column
reply sc/same-as-ingredient:0
}
move-cursor-on-display new-row, new-column
reply sc/same-as-ingredient:0
]
scenario clear-line-erases-printed-characters [
run [
1:address:screen <- new-fake-screen 3/width, 2/height
1:address:screen <- print-character 1:address:screen, 97
1:address:screen <- move-cursor 1:address:screen, 0/row, 0/column
1:address:screen <- clear-line 1:address:screen
2:address:array:screen-cell <- get *1:address:screen, data:offset
3:array:screen-cell <- copy *2:address:array:screen-cell
]
memory-should-contain [
3 <- 6
4 <- 0
5 <- 7
6 <- 0
7 <- 7
8 <- 0
9 <- 7
10 <- 0
11 <- 7
12 <- 0
13 <- 7
14 <- 0
15 <- 7
]
]
recipe cursor-down [
local-scope
sc:address:screen <- next-ingredient
{
break-unless sc
{
height:number <- get *sc, num-rows:offset
row:address:number <- get-address *sc, cursor-row:offset
max:number <- subtract height, 1
at-bottom?:boolean <- greater-or-equal *row, max
break-if at-bottom?
*row <- add *row, 1
}
reply sc/same-as-ingredient:0
}
move-cursor-down-on-display
reply sc/same-as-ingredient:0
]
recipe cursor-up [
local-scope
sc:address:screen <- next-ingredient
{
break-unless sc
{
row:address:number <- get-address *sc, cursor-row:offset
at-top?:boolean <- lesser-or-equal *row, 0
break-if at-top?
*row <- subtract *row, 1
}
reply sc/same-as-ingredient:0
}
move-cursor-up-on-display
reply sc/same-as-ingredient:0
]
recipe cursor-right [
local-scope
sc:address:screen <- next-ingredient
{
break-unless sc
{
width:number <- get *sc, num-columns:offset
column:address:number <- get-address *sc, cursor-column:offset
max:number <- subtract width, 1
at-bottom?:boolean <- greater-or-equal *column, max
break-if at-bottom?
*column <- add *column, 1
}
reply sc/same-as-ingredient:0
}
move-cursor-right-on-display
reply sc/same-as-ingredient:0
]
recipe cursor-left [
local-scope
sc:address:screen <- next-ingredient
{
break-unless sc
{
column:address:number <- get-address *sc, cursor-column:offset
at-top?:boolean <- lesser-or-equal *column, 0
break-if at-top?
*column <- subtract *column, 1
}
reply sc/same-as-ingredient:0
}
move-cursor-left-on-display
reply sc/same-as-ingredient:0
]
recipe cursor-to-start-of-line [
local-scope
sc:address:screen <- next-ingredient
row:number, _, sc <- cursor-position sc
column:number <- copy 0
sc <- move-cursor sc, row, column
reply sc/same-as-ingredient:0
]
recipe cursor-to-next-line [
local-scope
screen:address <- next-ingredient
screen <- cursor-down screen
screen <- cursor-to-start-of-line screen
reply screen/same-as-ingredient:0
]
recipe screen-width [
local-scope
sc:address:screen <- next-ingredient
{
break-unless sc
width:number <- get *sc, num-columns:offset
reply width
}
width:number <- display-width
reply width
]
recipe screen-height [
local-scope
sc:address:screen <- next-ingredient
{
break-unless sc
height:number <- get *sc, num-rows:offset
reply height
}
height:number <- display-height
reply height
]
recipe hide-cursor [
local-scope
screen:address <- next-ingredient
{
break-unless screen
reply screen
}
hide-cursor-on-display
reply screen
]
recipe show-cursor [
local-scope
screen:address <- next-ingredient
{
break-unless screen
reply screen
}
show-cursor-on-display
reply screen
]
recipe hide-screen [
local-scope
screen:address <- next-ingredient
{
break-unless screen
reply screen
}
hide-display
reply screen
]
recipe show-screen [
local-scope
screen:address <- next-ingredient
{
break-unless screen
reply screen
}
show-display
reply screen
]
recipe print-string [
local-scope
screen:address <- next-ingredient
s:address:array:character <- next-ingredient
color:number, color-found?:boolean <- next-ingredient
{
break-if color-found?
color <- copy 7/white
}
bg-color:number, bg-color-found?:boolean <- next-ingredient
{
break-if bg-color-found?
bg-color <- copy 0/black
}
len:number <- length *s
i:number <- copy 0
{
done?:boolean <- greater-or-equal i, len
break-if done?
c:character <- index *s, i
print-character screen, c, color, bg-color
i <- add i, 1
loop
}
reply screen/same-as-ingredient:0
]
scenario print-string-stops-at-right-margin [
run [
1:address:screen <- new-fake-screen 3/width, 2/height
2:address:array:character <- new [abcd]
1:address:screen <- print-string 1:address:screen, 2:address:array:character
3:address:array:screen-cell <- get *1:address:screen, data:offset
4:array:screen-cell <- copy *3:address:array:screen-cell
]
memory-should-contain [
4 <- 6
5 <- 97
6 <- 7
7 <- 98
8 <- 7
9 <- 100
10 <- 7
11 <- 0
]
]
recipe print-integer [
local-scope
screen:address <- next-ingredient
n:number <- next-ingredient
color:number, color-found?:boolean <- next-ingredient
{
break-if color-found?
color <- copy 7/white
}
bg-color:number, bg-color-found?:boolean <- next-ingredient
{
break-if bg-color-found?
bg-color <- copy 0/black
}
s:address:array:character <- integer-to-decimal-string n
print-string screen, s, color, bg-color
reply screen/same-as-ingredient:0
]