// E# — a verified example from the E# language corpus (CLR language; .es, not ECMAScript). // provenance: calculator.es topic: choice status: verified // hand-authored, idiomatic E# — verified through the E# compiler namespace Demo // ═════════════════════════════════════════════════════════════════════════════ // A tiny arithmetic calculator, end to end. // // It turns a string like "1 + 2 * 3" into a number — and gets 7, not 9, because it // respects operator precedence (multiplication binds tighter than addition). It is // written the way a real E# program is: a recursive-descent parser that builds an // abstract syntax tree (AST), then a recursive evaluator that walks it. // // If this is the first E# you have ever read, here is the whole tour in one file: // // • `ref choice` — a tagged union (a "sum type"). The AST is one of three shapes: // a number, an addition, or a multiplication. Because it is a // *ref* choice it is a reference type, so a variant may hold // another `Expr` and the tree can nest to any depth. // • `data` + `*P` — a small mutable parser-state struct. `data` is a value type // (copying it copies the bits), so to let every parse step share // and advance ONE cursor we put it on the heap with `new`, which // hands back a pointer, written `*P`. // • `new` — the heap-allocation operator: `new P { ... }` allocates a value // `data` on the heap and yields a `*P`. It is the only way to mint // a fresh pointer. // • `Result` — error handling without exceptions. A parse step returns either // `ok(tree)` or `error("message")`. The `?` after a call means // "if that was an error, stop and return the same error from here", // so the happy path stays flat and readable. // • `match` — exhaustive, binding pattern dispatch over a choice's variants. // ═════════════════════════════════════════════════════════════════════════════ // The AST. Each variant carries its own payload. `add` and `mul` hold two more // `Expr`s — legal because `ref choice` is a reference type, so the tree nests freely. ref choice Expr { num(value: int) // a literal integer, e.g. 42 add(left: Expr, right: Expr) // left + right mul(left: Expr, right: Expr) // left * right } // The parser's mutable state: the input text plus a cursor into it. It is a value // type (`data`), but we will hand around a *pointer* (`*P`) so every parse step // advances the same shared `pos` instead of mutating a private copy. data P { var src: string var pos: int } // --- small cursor helpers ----------------------------------------------------- // Each takes `*P` (a pointer). A pointer parameter is NOT promoted to a method, so // these stay plain free functions you call as `atEnd(p)` — only a direct `data` // receiver (e.g. `func f(v: Vec)`) would become `v.f()`. // True once the cursor has consumed the whole string. func atEnd(p: *P) -> bool { return p.pos >= p.src.Length } // Advance past spaces so the grammar can ignore whitespace. func skipWs(p: *P) { while !atEnd(p) { if p.src[p.pos] == ' ' { p.pos += 1 } else { return } } } // --- the grammar, highest-precedence first ------------------------------------ // // expr = term ('+' term)* (addition — lowest precedence) // term = factor ('*' factor)* (multiplication — binds tighter) // factor = number (a run of digits) // // Each rule returns `Result`: the parsed subtree, or an error message. // factor = number func parseFactor(p: *P) -> Result { skipWs(p) if atEnd(p) { return error("expected a number") } if !char.IsDigit(p.src[p.pos]) { return error("expected a digit") } let start = p.pos while !atEnd(p) && char.IsDigit(p.src[p.pos]) { p.pos += 1 } let text = p.src.Substring(start, p.pos - start) return ok(Expr.num(int.Parse(text))) } // term = factor ('*' factor)* — left-associative: fold each '*' into the tree. func parseTerm(p: *P) -> Result { var left = parseFactor(p)? // `?` unwraps ok, or returns the error from here while true { skipWs(p) if atEnd(p) || p.src[p.pos] != '*' { return ok(left) } p.pos += 1 let right = parseFactor(p)? left = Expr.mul(left, right) } return ok(left) } // expr = term ('+' term)* — same shape, one precedence level down. func parseExpr(p: *P) -> Result { var left = parseTerm(p)? while true { skipWs(p) if atEnd(p) || p.src[p.pos] != '+' { return ok(left) } p.pos += 1 let right = parseTerm(p)? left = Expr.add(left, right) } return ok(left) } // Entry point: put the cursor on the heap with `new`, parse a whole expression, and // confirm nothing is left over. func parse(src: string) -> Result { var p: *P = new P { src: src, pos: 0 } // `new` → heap value `data`, yields *P let tree = parseExpr(p)? skipWs(p) if !atEnd(p) { return error("trailing characters") } return ok(tree) } // --- evaluation --------------------------------------------------------------- // Walk the tree to an int. `match` dispatches on the variant and binds a "case view" // — `.add(n)` gives an `n` whose members are that variant's payloads, reached by their // declared names (`n.left`, `n.right`, `n.value`). Recursion follows the tree's shape. func eval(e: Expr) -> int { // `Expr` is a choice → not promoted → a free function match e { .num(n) { return n.value } .add(n) { return eval(n.left) + eval(n.right) } .mul(n) { return eval(n.left) * eval(n.right) } } return 0 } // Parse, then evaluate. Returns the answer, or -1 if the input did not parse — so the // precedence is visible in the result: 1 + 2 * 3 == 7, never 9. func calc(src: string) -> int { let r = parse(src) if r.IsError { return -1 } return eval(r.Value) } // The entry point is a `main` method on a `ref data Program` (the OO application shape, // the class-style alternative to a bare top-level `func main`). That method IS the entry // point; the compiler constructs the program (`Program()`) and calls `.main()`. ref data Program { func main() -> int { return calc("1 + 2 * 3") // 7 } }