- Add String.fromChar function to convert Char to String - Create four stress test projects demonstrating Lux features: - json-parser: recursive descent parsing with Char handling - markdown-converter: string manipulation and ADTs - todo-app: list operations and pattern matching - mini-interpreter: AST evaluation and environments - Add comprehensive testing documentation (docs/testing.md) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
181 lines
5.9 KiB
Plaintext
181 lines
5.9 KiB
Plaintext
// Mini Interpreter - Stress tests complex ADTs, recursion, and pattern matching
|
|
//
|
|
// This demonstrates:
|
|
// - Abstract syntax trees
|
|
// - Recursive evaluation
|
|
// - Pattern matching on complex types
|
|
// - Error handling
|
|
|
|
// Value types in our mini language
|
|
type Value =
|
|
| VInt(Int)
|
|
| VBool(Bool)
|
|
| VString(String)
|
|
| VUnit
|
|
|
|
// Expression AST
|
|
type Expr =
|
|
| EInt(Int)
|
|
| EBool(Bool)
|
|
| EString(String)
|
|
| EVar(String)
|
|
| EAdd(Expr, Expr)
|
|
| ESub(Expr, Expr)
|
|
| EMul(Expr, Expr)
|
|
| EDiv(Expr, Expr)
|
|
| EEq(Expr, Expr)
|
|
| ELt(Expr, Expr)
|
|
| EGt(Expr, Expr)
|
|
| EIf(Expr, Expr, Expr)
|
|
| ELet(String, Expr, Expr)
|
|
|
|
// Environment (variable bindings)
|
|
type Env =
|
|
| EmptyEnv
|
|
| ExtendEnv(String, Value, Env)
|
|
|
|
// Environment operations
|
|
fn envLookup(env: Env, name: String): Option<Value> =
|
|
match env {
|
|
EmptyEnv => None,
|
|
ExtendEnv(n, v, rest) => if n == name then Some(v) else envLookup(rest, name)
|
|
}
|
|
|
|
fn envExtend(env: Env, name: String, value: Value): Env =
|
|
ExtendEnv(name, value, env)
|
|
|
|
// Value pretty printing
|
|
fn valueToString(v: Value): String =
|
|
match v {
|
|
VInt(n) => toString(n),
|
|
VBool(b) => if b then "true" else "false",
|
|
VString(s) => "\"" + s + "\"",
|
|
VUnit => "()"
|
|
}
|
|
|
|
// Main evaluator
|
|
fn eval(expr: Expr, env: Env): Result<Value, String> =
|
|
match expr {
|
|
EInt(n) => Ok(VInt(n)),
|
|
EBool(b) => Ok(VBool(b)),
|
|
EString(s) => Ok(VString(s)),
|
|
EVar(name) => match envLookup(env, name) {
|
|
None => Err("Unbound variable: " + name),
|
|
Some(v) => Ok(v)
|
|
},
|
|
EAdd(left, right) => evalBinOp(left, right, env, fn(a: Int, b: Int): Int => a + b),
|
|
ESub(left, right) => evalBinOp(left, right, env, fn(a: Int, b: Int): Int => a - b),
|
|
EMul(left, right) => evalBinOp(left, right, env, fn(a: Int, b: Int): Int => a * b),
|
|
EDiv(left, right) => evalDiv(left, right, env),
|
|
EEq(left, right) => evalCompare(left, right, env, fn(a: Int, b: Int): Bool => a == b),
|
|
ELt(left, right) => evalCompare(left, right, env, fn(a: Int, b: Int): Bool => a < b),
|
|
EGt(left, right) => evalCompare(left, right, env, fn(a: Int, b: Int): Bool => a > b),
|
|
EIf(cond, thenBranch, elseBranch) => evalIf(cond, thenBranch, elseBranch, env),
|
|
ELet(name, valueExpr, bodyExpr) => evalLet(name, valueExpr, bodyExpr, env)
|
|
}
|
|
|
|
fn evalBinOp(left: Expr, right: Expr, env: Env, op: fn(Int, Int): Int): Result<Value, String> =
|
|
match eval(left, env) {
|
|
Err(e) => Err(e),
|
|
Ok(leftVal) => match eval(right, env) {
|
|
Err(e) => Err(e),
|
|
Ok(rightVal) => match (leftVal, rightVal) {
|
|
(VInt(a), VInt(b)) => Ok(VInt(op(a, b))),
|
|
(_, _) => Err("Type error: expected Int")
|
|
}
|
|
}
|
|
}
|
|
|
|
fn evalDiv(left: Expr, right: Expr, env: Env): Result<Value, String> =
|
|
match eval(left, env) {
|
|
Err(e) => Err(e),
|
|
Ok(leftVal) => match eval(right, env) {
|
|
Err(e) => Err(e),
|
|
Ok(rightVal) => match (leftVal, rightVal) {
|
|
(VInt(a), VInt(b)) => if b == 0 then Err("Division by zero") else Ok(VInt(a / b)),
|
|
(_, _) => Err("Type error: expected Int")
|
|
}
|
|
}
|
|
}
|
|
|
|
fn evalCompare(left: Expr, right: Expr, env: Env, op: fn(Int, Int): Bool): Result<Value, String> =
|
|
match eval(left, env) {
|
|
Err(e) => Err(e),
|
|
Ok(leftVal) => match eval(right, env) {
|
|
Err(e) => Err(e),
|
|
Ok(rightVal) => match (leftVal, rightVal) {
|
|
(VInt(a), VInt(b)) => Ok(VBool(op(a, b))),
|
|
(_, _) => Err("Type error: expected Int")
|
|
}
|
|
}
|
|
}
|
|
|
|
fn evalIf(cond: Expr, thenBranch: Expr, elseBranch: Expr, env: Env): Result<Value, String> =
|
|
match eval(cond, env) {
|
|
Err(e) => Err(e),
|
|
Ok(condVal) => match condVal {
|
|
VBool(true) => eval(thenBranch, env),
|
|
VBool(false) => eval(elseBranch, env),
|
|
_ => Err("Condition must be boolean")
|
|
}
|
|
}
|
|
|
|
fn evalLet(name: String, valueExpr: Expr, bodyExpr: Expr, env: Env): Result<Value, String> =
|
|
match eval(valueExpr, env) {
|
|
Err(e) => Err(e),
|
|
Ok(value) => eval(bodyExpr, envExtend(env, name, value))
|
|
}
|
|
|
|
// Run a program and print result
|
|
fn runProgram(expr: Expr): Unit with {Console} =
|
|
match eval(expr, EmptyEnv) {
|
|
Ok(value) => Console.print("=> " + valueToString(value)),
|
|
Err(e) => Console.print("Error: " + e)
|
|
}
|
|
|
|
// Test programs
|
|
fn program1(): Expr = ELet("x", EInt(10), ELet("y", EInt(20), EAdd(EVar("x"), EVar("y"))))
|
|
|
|
fn program2(): Expr = ELet("a", EInt(5), EMul(EVar("a"), EAdd(EVar("a"), EInt(1))))
|
|
|
|
fn program3(): Expr = EIf(EGt(EInt(10), EInt(5)), EString("yes"), EString("no"))
|
|
|
|
fn program4(): Expr = ELet("n", EInt(5), ELet("a", EMul(EVar("n"), ESub(EVar("n"), EInt(1))), ELet("b", EMul(EVar("a"), ESub(EVar("n"), EInt(2))), ELet("c", EMul(EVar("b"), ESub(EVar("n"), EInt(3))), EMul(EVar("c"), ESub(EVar("n"), EInt(4)))))))
|
|
|
|
fn program5(): Expr = EDiv(EInt(10), EInt(0))
|
|
|
|
fn program6(): Expr = EVar("undefined")
|
|
|
|
// Main
|
|
fn main(): Unit with {Console} = {
|
|
Console.print("========================================")
|
|
Console.print(" MINI INTERPRETER DEMO")
|
|
Console.print("========================================")
|
|
Console.print("")
|
|
|
|
Console.print("Program 1: let x = 10 in let y = 20 in x + y")
|
|
runProgram(program1())
|
|
Console.print("")
|
|
|
|
Console.print("Program 2: let a = 5 in a * (a + 1)")
|
|
runProgram(program2())
|
|
Console.print("")
|
|
|
|
Console.print("Program 3: if 10 > 5 then \"yes\" else \"no\"")
|
|
runProgram(program3())
|
|
Console.print("")
|
|
|
|
Console.print("Program 4: 5! computed iteratively")
|
|
runProgram(program4())
|
|
Console.print("")
|
|
|
|
Console.print("Program 5: Division by zero error")
|
|
runProgram(program5())
|
|
Console.print("")
|
|
|
|
Console.print("Program 6: Unbound variable error")
|
|
runProgram(program6())
|
|
}
|
|
|
|
let output = run main() with {}
|