Lox interpreter written in Gleam, following Crafting Intepreters
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add beginning of interpreter

authored by

beaudidly and committed by
beaudidly
5f55109e aa8579e5

+142 -4
+10 -4
src/glox.gleam
··· 4 4 import gleam/io 5 5 import gleam/string 6 6 import input 7 + import interpreter 7 8 import scan 8 9 import simplifile 9 10 ··· 40 41 let e = expr.parse(tokens) 41 42 io.println("Exprs: " <> string.inspect(e)) 42 43 43 - let eval = case e { 44 - Ok(#(e, _)) -> ast_printer.print(e) 45 - Error(e) -> "Error on parsing." <> e.msg 44 + let #(repr, val) = case e { 45 + Ok(#(e, _)) -> { 46 + let repr = ast_printer.print(e) 47 + let val = interpreter.evaluate(e) 48 + #(repr, val) 49 + } 50 + Error(e) -> #("Error on parsing." <> e.msg, interpreter.NilVal) 46 51 } 47 - io.println(eval) 52 + io.println("repr: " <> repr) 53 + io.println("val: " <> string.inspect(val)) 48 54 Nil 49 55 }
+127
src/interpreter.gleam
··· 1 + import expr.{type Expr} 2 + import gleam/float 3 + import scan.{type Token} 4 + 5 + pub type Value { 6 + StringVal(String) 7 + NumberVal(Float) 8 + BoolVal(Bool) 9 + NilVal 10 + } 11 + 12 + pub fn evaluate(e: Expr) -> Value { 13 + case e { 14 + expr.ExprLit(l) -> evaluate_literal(l) 15 + expr.ExprBinary(l, op, r) -> evaluate_binary(l, op, r) 16 + expr.ExprUnary(op, e) -> evaluate_unary(op, e) 17 + expr.ExprGrouping(e) -> evaluate_grouping(e) 18 + } 19 + } 20 + 21 + fn evaluate_grouping(e: Expr) { 22 + evaluate(e) 23 + } 24 + 25 + fn evaluate_unary(op: Token, e: Expr) { 26 + let v = evaluate(e) 27 + 28 + case op.token_type { 29 + scan.Bang -> { 30 + BoolVal(!is_truthy(v)) 31 + } 32 + scan.Minus -> { 33 + case v { 34 + NumberVal(nv) -> NumberVal(float.negate(nv)) 35 + _ -> todo as "Needs runtime error for non-numeric types" 36 + } 37 + } 38 + _ -> todo as "Shouldn't our type system restrict these?" 39 + } 40 + } 41 + 42 + fn evaluate_binary(l: Expr, op: Token, r: Expr) { 43 + let lv = evaluate(l) 44 + let rv = evaluate(r) 45 + 46 + // I feel like this should be on a more restricted type than all tokens 47 + case op.token_type { 48 + scan.Plus -> { 49 + case #(lv, rv) { 50 + #(NumberVal(lnv), NumberVal(rnv)) -> NumberVal(float.add(lnv, rnv)) 51 + #(StringVal(lsv), StringVal(rsv)) -> StringVal(lsv <> rsv) 52 + _ -> todo as "Needs runtime error for invalid types" 53 + } 54 + } 55 + scan.Minus -> numeric_op(lv, rv, fn(l, r) { float.subtract(l, r) }) 56 + scan.Star -> numeric_op(lv, rv, fn(l, r) { float.multiply(l, r) }) 57 + scan.Slash -> { 58 + numeric_op(lv, rv, fn(l, r) { 59 + case float.divide(l, r) { 60 + Ok(v) -> v 61 + _ -> todo as "Handle division errors" 62 + } 63 + }) 64 + } 65 + scan.Greater -> comparison_op(lv, rv, fn(l, r) { l >. r }) 66 + scan.GreaterEqual -> comparison_op(lv, rv, fn(l, r) { l >=. r }) 67 + scan.Less -> comparison_op(lv, rv, fn(l, r) { l <. r }) 68 + scan.LessEqual -> comparison_op(lv, rv, fn(l, r) { l <=. r }) 69 + 70 + scan.EqualEqual -> BoolVal(lv == rv) 71 + scan.BangEqual -> BoolVal(lv != rv) 72 + _ -> todo as "Handle other binary operators" 73 + } 74 + } 75 + 76 + fn comparison_op(lv: Value, rv: Value, op: fn(Float, Float) -> Bool) -> Value { 77 + case #(lv, rv) { 78 + #(NumberVal(lv), NumberVal(rv)) -> BoolVal(op(lv, rv)) 79 + _ -> todo as "Handle comparison error for non numeric types" 80 + } 81 + } 82 + 83 + fn numeric_op(lv: Value, rv: Value, f: fn(Float, Float) -> Float) -> Value { 84 + case #(lv, rv) { 85 + #(NumberVal(lnv), NumberVal(rnv)) -> NumberVal(f(lnv, rnv)) 86 + _ -> 87 + todo as "Generic runtime error needs to be filled in here, or parameterized" 88 + } 89 + } 90 + 91 + fn evaluate_literal(l: expr.ExprLit) -> Value { 92 + case l { 93 + expr.LitString(t) -> { 94 + case t.literal { 95 + scan.LitString(s) -> StringVal(s) 96 + _ -> todo as "type system should have made this impossible no?" 97 + } 98 + } 99 + expr.LitNumber(t) -> { 100 + case t.literal { 101 + scan.LitNumber(n) -> NumberVal(n) 102 + _ -> todo as "type system should have made this impossible no?" 103 + } 104 + } 105 + expr.LitBool(t) -> { 106 + case t.token_type { 107 + scan.TrueKw -> BoolVal(True) 108 + scan.FalseKw -> BoolVal(False) 109 + // How can we do the typing here better? We can't just say it uses a subset of a type 110 + _ -> todo as "type system should have made this impossible no?" 111 + } 112 + } 113 + expr.LitNil -> NilVal 114 + } 115 + } 116 + 117 + fn is_equal(l: Value, r: Value) -> Bool { 118 + todo 119 + } 120 + 121 + fn is_truthy(v: Value) { 122 + case v { 123 + BoolVal(b) -> b 124 + NilVal -> False 125 + _ -> True 126 + } 127 + }
+5
test/interpreter_test.gleam
··· 1 + import interpreter 2 + 3 + pub fn interpreter_test() { 4 + assert interpreter.NumberVal(10.0) == interpreter.NumberVal(10.0) 5 + }