116 lines
2.5 KiB
Plaintext

; A reverse-Polish calculator: a tokenizer, a data type for tokens, and an
; evaluator that signals on a bad program and lets the caller decide.
data Token
Num(n: i64)
Op(c: u8)
Word(w: [const u8])
End
struct Underflow
op: u8
struct Unknown
word: [const u8]
const max-depth = 16
struct Machine
stack: [max-depth i64]
depth: i32
; Read the token that starts at pos; the position after it comes back too.
fn next-token(src: [const u8], pos: Ptr(i32)) -> Token
let n = length(src)
while deref(pos) < n and space?(src[deref(pos)])
++(deref(pos))
if deref(pos) >= n
return Token.End
let start = deref(pos)
until deref(pos) >= n or space?(src[deref(pos)])
++(deref(pos))
let text = slice(src, start, deref(pos))
let c = text[0]
if digit?(c) or (c == \- and length(text) > 1)
match parse-i64(text)
Some(v) -> Token.Num{.n v}
None -> Token.Word{.w text}
elif length(text) == 1 and some?(index-of(bytes-view("+-*/%"), c))
Token.Op{.c c}
else
Token.Word{.w text}
fn push!(m: Ptr(Machine), v: i64) -> ()
m.stack[m.depth] = v
m.depth += 1
fn pop!(m: Ptr(Machine), op: u8) -> i64
if m.depth == 0
error(Underflow{.op op})
m.depth -= 1
m.stack[m.depth]
fn apply(op: u8, a: i64, b: i64) -> i64
match op
\+ -> a + b
\- -> a - b
\* -> a * b
\/ -> if b == 0 then 0 else a / b
_ -> a % b
fn run(src: string) -> i64
let m: Machine = zeroed()
let text = bytes-view(src)
let pos = 0
while :tokens true
match next-token(text, addr(pos))
End -> break :tokens
Num(n) -> push!(addr(m), n)
Op(c) ->
let b = pop!(addr(m), c)
let a = pop!(addr(m), c)
push!(addr(m), apply(c, a, b))
Word(w) ->
if bytes=?(w, bytes-view("dup"))
let v = pop!(addr(m), \d)
push!(addr(m), v)
push!(addr(m), v)
elif bytes=?(w, bytes-view("drop"))
pop!(addr(m), \d)
continue
else
error(Unknown{.word w})
pop!(addr(m), \=)
fn show(src: string) -> ()
let r =
handler-case
run(src)
on Underflow(u)
println(src, "=> stack empty at", string-of-byte(u.op))
return
on Unknown(u)
println(src, "=> unknown word", string(u.word))
return
println(src, "=>", r)
fn string-of-byte(b: u8) -> string
match b
\+ -> "+"
\- -> "-"
\* -> "*"
\/ -> "/"
\d -> "dup"
_ -> "?"
fn main() -> i32
show("1 2 +")
show("3 4 * 5 -")
show("-7 2 /")
show("10 dup *")
show("1 2 drop 9 %")
show("1 +")
show("2 3 swap")
show("8 0 /")
0