# A calculator: parses the expression into a tree, then works it out.
# Bad input is an Err value. Overflow is a checked panic, never a wrong answer.
enum Expr:
Num(value: int)
Neg(inner: Expr)
Bin(op: str, left: Expr, right: Expr)
struct Parser:
cs: [str]
pos: int
fn peek(self) -> str:
return self.cs[self.pos] if self.pos < len(self.cs) else ""
fn skip_space(ref self):
while self.peek() == " ":
self.pos += 1
fn expr(ref self) -> Result[Expr, str]: # a + b - c
var e = self.term()?
self.skip_space()
while self.peek() == "+" or self.peek() == "-":
let op = self.peek()
self.pos += 1
e = Expr.Bin(op, e, self.term()?)
self.skip_space()
return Ok(e)
fn term(ref self) -> Result[Expr, str]: # a * b / c % d
var e = self.unary()?
self.skip_space()
while self.peek() in ["*", "/", "%"]:
let op = self.peek()
self.pos += 1
e = Expr.Bin(op, e, self.unary()?)
self.skip_space()
return Ok(e)
fn unary(ref self) -> Result[Expr, str]: # -a, a ^ b
self.skip_space()
if self.peek() == "-":
self.pos += 1
return Ok(Expr.Neg(self.unary()?))
let base = self.atom()?
self.skip_space()
if self.peek() == "^":
self.pos += 1
return Ok(Expr.Bin("^", base, self.unary()?))
return Ok(base)
fn atom(ref self) -> Result[Expr, str]: # 42, (a)
self.skip_space()
let c = self.peek()
if c == "(":
self.pos += 1
let e = self.expr()?
self.skip_space()
if self.peek() != ")":
return Err(f"expected ) at column {self.pos + 1}")
self.pos += 1
return Ok(e)
if not c.is_digit():
let found = "the end" if c == "" else f"'{c}'"
return Err(f"expected a number at column {self.pos + 1}, found {found}")
var digits = ""
while self.peek().is_digit():
digits += self.peek()
self.pos += 1
if len(digits) > 18:
return Err(f"{digits} is too big for an int")
return Ok(Expr.Num(int(digits)))
fn parse(src: str) -> Result[Expr, str]:
var p = Parser(cs=src.chars(), pos=0)
let e = p.expr()?
if p.pos < len(p.cs):
return Err(f"unexpected '{p.peek()}' at column {p.pos + 1}")
return Ok(e)
fn eval(e: Expr) -> Result[int, str]:
match e:
case Expr.Num(v):
return Ok(v)
case Expr.Neg(x):
return Ok(-eval(x)?)
case Expr.Bin(op, l, r):
let (a, b) = (eval(l)?, eval(r)?)
match op:
case "+":
return Ok(a + b) # overflow panics
case "-":
return Ok(a - b)
case "*":
return Ok(a * b)
case "/" | "%":
if b == 0:
return Err("division by zero")
return Ok(a / b if op == "/" else a % b)
case "^":
if b < 0:
return Err("negative exponent")
var (p, base, n) = (1, a, b)
while n > 0: # by squaring
if n % 2 == 1:
p *= base
n /= 2
if n > 0:
base *= base
return Ok(p)
case _:
return Err(f"unknown operator {op}")
fn show(e: Expr) -> str:
match e:
case Expr.Num(v):
return str(v)
case Expr.Neg(x):
return f"(- {show(x)})"
case Expr.Bin(op, l, r):
return f"({op} {show(l)} {show(r)})"
fn main() -> int:
let src = " ".join(args())
match parse(src):
case Err(msg):
print(f"error: {msg}")
return 1
case Ok(tree):
print(f"tree: {show(tree)}")
match eval(tree):
case Ok(v):
print(f"value: {v}")
return 0
case Err(msg):
print(f"error: {msg}")
return 1