# Tutorial: Building a Calculator Build a REPL calculator that evaluates arithmetic expressions. ## What You'll Learn - Defining algebraic data types - Pattern matching - Recursive functions - REPL loops with effects ## The Goal ``` Calculator REPL > 2 + 3 5 > (10 - 4) * 2 12 > 100 / 5 + 3 23 > quit Goodbye! ``` ## Step 1: Define the Expression Type First, we model arithmetic expressions: ```lux // calculator.lux type Expr = | Num(Int) | Add(Expr, Expr) | Sub(Expr, Expr) | Mul(Expr, Expr) | Div(Expr, Expr) ``` This is an *algebraic data type* (ADT). An `Expr` is one of: - A number: `Num(42)` - An addition: `Add(Num(2), Num(3))` - And so on... ## Step 2: Evaluate Expressions Now we evaluate expressions to integers: ```lux fn eval(e: Expr): Result = match e { Num(n) => Ok(n), Add(a, b) => evalBinOp(a, b, fn(x: Int, y: Int): Int => x + y), Sub(a, b) => evalBinOp(a, b, fn(x: Int, y: Int): Int => x - y), Mul(a, b) => evalBinOp(a, b, fn(x: Int, y: Int): Int => x * y), Div(a, b) => { match (eval(a), eval(b)) { (Ok(x), Ok(0)) => Err("Division by zero"), (Ok(x), Ok(y)) => Ok(x / y), (Err(e), _) => Err(e), (_, Err(e)) => Err(e) } } } fn evalBinOp(a: Expr, b: Expr, op: fn(Int, Int): Int): Result = match (eval(a), eval(b)) { (Ok(x), Ok(y)) => Ok(op(x, y)), (Err(e), _) => Err(e), (_, Err(e)) => Err(e) } ``` Pattern matching makes this clear: - `Num(n)` just returns the number - Operations evaluate both sides, then apply the operator - Division checks for zero ## Step 3: Parse Expressions For simplicity, we'll parse a limited format. A real parser would be more complex. ```lux fn parseSimple(input: String): Result = { let trimmed = String.trim(input) // Try to parse as a number if isNumber(trimmed) then Ok(Num(parseInt(trimmed))) else // Try to find an operator match findOperator(trimmed) { Some((left, op, right)) => { match (parseSimple(left), parseSimple(right)) { (Ok(l), Ok(r)) => Ok(makeOp(l, op, r)), (Err(e), _) => Err(e), (_, Err(e)) => Err(e) } }, None => Err("Cannot parse: " + trimmed) } } fn isNumber(s: String): Bool = { let chars = String.chars(s) List.all(chars, fn(c: String): Bool => c == "-" || (c >= "0" && c <= "9") ) } fn parseInt(s: String): Int = { // Simplified - assumes valid integer List.fold(String.chars(s), 0, fn(acc: Int, c: String): Int => if c == "-" then acc else acc * 10 + charToDigit(c) ) * (if String.startsWith(s, "-") then -1 else 1) } fn charToDigit(c: String): Int = match c { "0" => 0, "1" => 1, "2" => 2, "3" => 3, "4" => 4, "5" => 5, "6" => 6, "7" => 7, "8" => 8, "9" => 9, _ => 0 } fn findOperator(s: String): Option<(String, String, String)> = { // Find last +/- at depth 0 (lowest precedence) // Then *// (higher precedence) // This is a simplified approach let addSub = findOpAtDepth(s, ["+", "-"], 0) match addSub { Some(r) => Some(r), None => findOpAtDepth(s, ["*", "/"], 0) } } fn makeOp(left: Expr, op: String, right: Expr): Expr = match op { "+" => Add(left, right), "-" => Sub(left, right), "*" => Mul(left, right), "/" => Div(left, right), _ => Num(0) // Should not happen } ``` ## Step 4: The REPL Loop Now we build the interactive loop: ```lux fn repl(): Unit with {Console} = { Console.print("Calculator REPL (type 'quit' to exit)") replLoop() } fn replLoop(): Unit with {Console} = { Console.print("> ") let input = Console.readLine() if input == "quit" then Console.print("Goodbye!") else { match parseSimple(input) { Ok(expr) => { match eval(expr) { Ok(result) => Console.print(toString(result)), Err(e) => Console.print("Error: " + e) } }, Err(e) => Console.print("Parse error: " + e) } replLoop() // Continue the loop } } fn main(): Unit with {Console} = repl() let output = run main() with {} ``` ## Step 5: Test It ```bash $ lux calculator.lux Calculator REPL (type 'quit' to exit) > 2 + 3 5 > 10 - 4 6 > 6 * 7 42 > 100 / 0 Error: Division by zero > quit Goodbye! ``` ## Extending the Calculator Try adding: 1. **Parentheses**: Parse `(2 + 3) * 4` 2. **Variables**: Store results in named variables 3. **Functions**: `sqrt`, `abs`, `pow` 4. **History**: Use State effect to track previous results ### Adding Variables (Bonus) ```lux effect Variables { fn get(name: String): Option fn set(name: String, value: Int): Unit } fn evalWithVars(e: Expr): Result with {Variables} = match e { Var(name) => { match Variables.get(name) { Some(v) => Ok(v), None => Err("Unknown variable: " + name) } }, // ... other cases } handler memoryVars: Variables { fn get(name) = resume(State.get(name)) fn set(name, value) = { State.put(name, value) resume(()) } } ``` ## Complete Code See `examples/tutorials/calculator.lux` for the full implementation. ## What You Learned - **ADTs** model structured data - **Pattern matching** destructures data cleanly - **Recursion** processes nested structures - **Result** handles errors without exceptions - **REPL loops** combine effects naturally ## Next Tutorial [Todo App](todo.md) - Build a task manager with file persistence.