Files
lux/projects/mini-interpreter/main.lux
Brandon Lucas 730112a917 feat: add stress test projects and testing documentation
- 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>
2026-02-13 18:53:27 -05:00

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 {}