116 lines
2.6 KiB
Plaintext
116 lines
2.6 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 is-space(src[deref(pos)])
|
|
++(deref(pos))
|
|
if deref(pos) >= n
|
|
return Token.End
|
|
let start = deref(pos)
|
|
until deref(pos) >= n or is-space(src[deref(pos)])
|
|
++(deref(pos))
|
|
let text = slice(src, start, deref(pos))
|
|
let c = text[0]
|
|
if is-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 is-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 small-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: str) -> 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 = small-pop(addr(m), c)
|
|
let a = small-pop(addr(m), c)
|
|
push!(addr(m), apply(c, a, b))
|
|
Word(w) ->
|
|
if is-bytes-equal(w, bytes-view("dup"))
|
|
let v = small-pop(addr(m), \d)
|
|
push!(addr(m), v)
|
|
push!(addr(m), v)
|
|
elif is-bytes-equal(w, bytes-view("drop"))
|
|
small-pop(addr(m), \d)
|
|
continue
|
|
else
|
|
error(Unknown{.word w})
|
|
small-pop(addr(m), \=)
|
|
|
|
fn show(src: str) -> ()
|
|
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", str(u.word))
|
|
return
|
|
println(src, "=>", r)
|
|
|
|
fn string-of-byte(b: u8) -> str
|
|
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
|