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: added statements

authored by

beaudidly and committed by
beaudidly
58e9787e f5081494

+167 -54
+26 -2
src/ast_printer.gleam
··· 4 4 } 5 5 import gleam/list 6 6 import gleam/string 7 + import stmt.{type Stmt} 7 8 8 - pub fn print(e: Expr) -> String { 9 + pub fn print(stmts: List(Stmt)) -> String { 10 + list.map(stmts, fn(stmt) { 11 + "\t" 12 + <> case stmt { 13 + stmt.ExprStmt(e) -> surround(print_expr(e), Square) 14 + stmt.PrintStmt(_, e) -> surround("PRINT" <> print_expr(e), Square) 15 + } 16 + }) 17 + |> string.join("\n") 18 + } 19 + 20 + type Wrapper { 21 + Square 22 + Paren 23 + } 24 + 25 + fn surround(thing: String, wrapper: Wrapper) -> String { 26 + case wrapper { 27 + Paren -> "(" <> thing <> ")" 28 + Square -> "[" <> thing <> "]" 29 + } 30 + } 31 + 32 + fn print_expr(e: Expr) -> String { 9 33 case e { 10 34 ExprLit(LitNil) -> "nil" 11 35 ExprLit(LitBool(tok, _)) -> tok.lexeme ··· 19 43 } 20 44 21 45 fn parenthesize(name: String, exprs: List(Expr)) -> String { 22 - let parts = list.map(exprs, print) 46 + let parts = list.map(exprs, print_expr) 23 47 "(" <> name <> " " <> string.join(parts, " ") <> ")" 24 48 }
+14 -29
src/expr.gleam
··· 1 1 /////// Binary 2 2 3 3 import gleam/dict.{type Dict, from_list} 4 - import gleam/list 5 4 import gleam/option.{type Option, None, Some} 6 5 import gleam/result 6 + import parse_common.{type ParseError, ParseError} 7 7 import scan.{type Token, type TokenType, Token} 8 8 9 - pub type ParseError { 10 - ParseError(tok: Option(Token), msg: String) 11 - } 9 + pub type ExprResult = 10 + Result(#(Expr, List(Token)), ParseError) 12 11 13 12 pub type Expr { 14 13 ExprLit(ExprLit) ··· 43 42 BinOpComma(token: Token) 44 43 } 45 44 46 - type ParserResult = 47 - Result(#(Expr, List(Token)), ParseError) 48 - 49 - pub fn parse(tokens: List(Token)) -> ParserResult { 50 - expression(tokens) 51 - } 52 - 53 - fn synchronize(tokens: List(Token)) -> List(Token) { 54 - // Seek until we find a sync point (;, or statement-beginning token) 55 - todo 56 - } 57 - 58 45 fn bin_loop_left( 59 46 acc: Expr, 60 47 tokens: List(Token), 61 48 ops: Dict(TokenType, fn(Token) -> BinOp), 62 - next: fn(List(Token)) -> ParserResult, 63 - ) -> ParserResult { 49 + next: fn(List(Token)) -> ExprResult, 50 + ) -> ExprResult { 64 51 case tokens { 65 52 [tok, ..rest] -> { 66 53 case dict.get(ops, tok.token_type) { ··· 78 65 pub fn bin_left( 79 66 tokens: List(Token), 80 67 ops: Dict(TokenType, fn(Token) -> BinOp), 81 - next: fn(List(Token)) -> ParserResult, 82 - ) -> ParserResult { 68 + next: fn(List(Token)) -> ExprResult, 69 + ) -> ExprResult { 83 70 use #(expr, rest) <- result.try(next(tokens)) 84 71 85 72 bin_loop_left(expr, rest, ops, next) 86 73 } 87 74 88 - pub fn expression(tokens: List(Token)) -> ParserResult { 75 + pub fn expression(tokens: List(Token)) -> ExprResult { 89 76 comma(tokens) 90 77 } 91 78 92 - pub fn comma(tokens: List(Token)) -> ParserResult { 79 + pub fn comma(tokens: List(Token)) -> ExprResult { 93 80 let ops = from_list([#(scan.Comma, BinOpComma)]) 94 81 bin_left(tokens, ops, equality) 95 82 } 96 83 97 - pub fn equality(tokens: List(Token)) -> ParserResult { 84 + pub fn equality(tokens: List(Token)) -> ExprResult { 98 85 let ops = 99 86 from_list([ 100 87 #(scan.BangEqual, BinOpNotEquals), ··· 103 90 bin_left(tokens, ops, comparison) 104 91 } 105 92 106 - pub fn comparison(tokens: List(Token)) -> ParserResult { 93 + pub fn comparison(tokens: List(Token)) -> ExprResult { 107 94 let ops = 108 95 from_list([ 109 96 #(scan.Greater, BinOpGT), ··· 114 101 bin_left(tokens, ops, term) 115 102 } 116 103 117 - pub fn term(tokens: List(Token)) -> ParserResult { 104 + pub fn term(tokens: List(Token)) -> ExprResult { 118 105 let ops = 119 106 from_list([ 120 107 #(scan.Plus, BinOpPlus), ··· 123 110 bin_left(tokens, ops, factor) 124 111 } 125 112 126 - pub fn factor(tokens: List(Token)) -> ParserResult { 113 + pub fn factor(tokens: List(Token)) -> ExprResult { 127 114 let ops = from_list([#(scan.Slash, BinOpDiv), #(scan.Star, BinOpMult)]) 128 115 bin_left(tokens, ops, unary) 129 116 } ··· 139 126 } 140 127 } 141 128 142 - pub fn primary( 143 - tokens: List(Token), 144 - ) -> Result(#(Expr, List(Token)), ParseError) { 129 + pub fn primary(tokens: List(Token)) -> Result(#(Expr, List(Token)), ParseError) { 145 130 case tokens { 146 131 [Token(token_type: scan.FalseKw, ..) as tok, ..rest] -> 147 132 Ok(#(ExprLit(LitBool(tok, False)), rest))
+5 -4
src/glox.gleam
··· 1 1 import argv 2 2 import ast_printer 3 - import expr 4 3 import gleam/io 5 4 import gleam/option.{None, Some} 6 5 import gleam/string 7 6 import input 8 7 import interpreter 8 + import parser 9 9 import scan 10 10 import simplifile 11 11 ··· 39 39 let assert Ok(tokens) = scan.get_tokens(lox) 40 40 io.println("Tokens: " <> string.inspect(tokens)) 41 41 42 - let e = expr.parse(tokens) 42 + let e = parser.parse(tokens) 43 43 io.println("Exprs: " <> string.inspect(e)) 44 44 45 45 let #(repr, val) = case e { 46 46 Ok(#(e, _)) -> { 47 47 let repr = ast_printer.print(e) 48 - let val = interpreter.evaluate(e) 48 + let val = interpreter.eval(e) 49 49 #(repr, Some(val)) 50 50 } 51 51 Error(e) -> #("Error on parsing." <> e.msg, None) 52 52 } 53 - io.println("repr: " <> repr) 53 + io.println("repr:\n" <> repr) 54 + 54 55 case val { 55 56 Some(Ok(val)) -> { 56 57 io.println("val: " <> string.inspect(val))
+48 -19
src/interpreter.gleam
··· 1 1 import expr.{type Expr} 2 + import gleam/bool 2 3 import gleam/float 3 4 import gleam/int 5 + import gleam/io 6 + import gleam/list 4 7 import gleam/result 5 8 import scan.{type Token} 9 + import stmt.{type Stmt} 6 10 7 11 pub type Value { 8 12 StringVal(String) ··· 19 23 err.msg <> "\n[line " <> int.to_string(err.tok.line) <> "]" 20 24 } 21 25 22 - pub type EvalResult = 26 + pub fn value_str(val: Value) { 27 + case val { 28 + StringVal(s) -> s 29 + NumberVal(n) -> float.to_string(n) 30 + BoolVal(b) -> bool.to_string(b) 31 + NilVal -> "nil" 32 + } 33 + } 34 + 35 + pub type ValueResult = 23 36 Result(Value, RuntimeError) 24 37 25 - pub fn evaluate(e: Expr) -> EvalResult { 38 + pub fn eval(e: List(Stmt)) -> Result(Nil, RuntimeError) { 39 + list.try_map(e, fn(stmt) { eval_stmt(stmt) }) 40 + |> result.map(fn(_) { Nil }) 41 + } 42 + 43 + fn eval_stmt(s: Stmt) -> Result(Nil, RuntimeError) { 44 + case s { 45 + stmt.PrintStmt(_, e) -> { 46 + use v <- result.try(eval_expr(e)) 47 + io.println(value_str(v)) 48 + Ok(Nil) 49 + } 50 + stmt.ExprStmt(e) -> result.map(eval_expr(e), fn(_) { Nil }) 51 + } 52 + } 53 + 54 + fn eval_expr(e: Expr) -> ValueResult { 26 55 case e { 27 - expr.ExprLit(l) -> Ok(evaluate_literal(l)) 28 - expr.ExprBinary(l, op, r) -> evaluate_binary(l, op, r) 29 - expr.ExprUnary(op, e) -> evaluate_unary(op, e) 30 - expr.ExprGrouping(e) -> evaluate_grouping(e) 56 + expr.ExprLit(l) -> Ok(eval_literal(l)) 57 + expr.ExprBinary(l, op, r) -> eval_binary(l, op, r) 58 + expr.ExprUnary(op, e) -> eval_unary(op, e) 59 + expr.ExprGrouping(e) -> eval_grouping(e) 31 60 } 32 61 } 33 62 34 - fn evaluate_grouping(e: Expr) -> EvalResult { 35 - evaluate(e) 63 + fn eval_grouping(e: Expr) -> ValueResult { 64 + eval_expr(e) 36 65 } 37 66 38 - fn evaluate_unary(op: Token, e: Expr) -> EvalResult { 39 - use v <- result.try(evaluate(e)) 67 + fn eval_unary(op: Token, e: Expr) -> ValueResult { 68 + use v <- result.try(eval_expr(e)) 40 69 41 70 case op.token_type { 42 71 scan.Bang -> { ··· 56 85 } 57 86 } 58 87 59 - fn evaluate_binary(l: Expr, op: expr.BinOp, r: Expr) -> EvalResult { 60 - use lv <- result.try(evaluate(l)) 61 - use rv <- result.try(evaluate(r)) 88 + fn eval_binary(l: Expr, op: expr.BinOp, r: Expr) -> ValueResult { 89 + use lv <- result.try(eval_expr(l)) 90 + use rv <- result.try(eval_expr(r)) 62 91 63 92 // I feel like this should be on a more restricted type than all tokens 64 93 case op { ··· 68 97 #(StringVal(lsv), StringVal(rsv)) -> Ok(StringVal(lsv <> rsv)) 69 98 _ -> 70 99 Error(RuntimeError( 71 - op, 100 + tok, 72 101 "Operand " 73 - <> op.lexeme 102 + <> tok.lexeme 74 103 <> " requires both values to be both strings or both numbers.", 75 104 )) 76 105 } ··· 104 133 lv: Value, 105 134 rv: Value, 106 135 f: fn(Float, Float) -> Bool, 107 - ) -> EvalResult { 136 + ) -> ValueResult { 108 137 case #(lv, rv) { 109 138 #(NumberVal(lv), NumberVal(rv)) -> Ok(BoolVal(f(lv, rv))) 110 139 _ -> ··· 120 149 lv: Value, 121 150 rv: Value, 122 151 f: fn(Float, Float) -> Result(Float, RuntimeError), 123 - ) -> EvalResult { 152 + ) -> ValueResult { 124 153 case #(lv, rv) { 125 154 #(NumberVal(lnv), NumberVal(rnv)) -> { 126 155 use v <- result.try(f(lnv, rnv)) ··· 130 159 } 131 160 } 132 161 133 - fn binary_op_error(op: Token) -> EvalResult { 162 + fn binary_op_error(op: Token) -> ValueResult { 134 163 Error(RuntimeError( 135 164 op, 136 165 "Operand " <> op.lexeme <> " requires both values to be numbers.", 137 166 )) 138 167 } 139 168 140 - fn evaluate_literal(l: expr.ExprLit) -> Value { 169 + fn eval_literal(l: expr.ExprLit) -> Value { 141 170 case l { 142 171 expr.LitString(_, s) -> StringVal(s) 143 172 expr.LitNumber(_, n) -> NumberVal(n)
+7
src/parse_common.gleam
··· 1 + // Need to find a better name for this? 2 + import gleam/option.{type Option} 3 + import scan.{type Token} 4 + 5 + pub type ParseError { 6 + ParseError(tok: Option(Token), msg: String) 7 + }
+14
src/parser.gleam
··· 1 + import expr.{type ExprResult, expression} 2 + import scan.{type Token} 3 + import stmt.{type ManyStmtResult, stmts} 4 + 5 + pub fn parse(tokens: List(Token)) -> ManyStmtResult { 6 + //expression(tokens) 7 + stmts(tokens) 8 + // need to validate theres no more tokens 9 + } 10 + 11 + fn synchronize(tokens: List(Token)) -> List(Token) { 12 + // Seek until we find a sync point (;, or statement-beginning token) 13 + todo 14 + }
+53
src/stmt.gleam
··· 1 + import expr.{type Expr} 2 + import gleam/list 3 + import gleam/option.{None, Some} 4 + import gleam/result 5 + import parse_common.{type ParseError, ParseError} 6 + import scan.{type Token, Token} 7 + 8 + pub type StmtResult = 9 + Result(#(Stmt, List(Token)), ParseError) 10 + 11 + pub type ManyStmtResult = 12 + Result(#(List(Stmt), List(Token)), ParseError) 13 + 14 + pub type Stmt { 15 + PrintStmt(Token, Expr) 16 + ExprStmt(Expr) 17 + } 18 + 19 + pub fn stmts(tokens: List(Token)) -> ManyStmtResult { 20 + stmt_helper(tokens, []) 21 + } 22 + 23 + fn stmt_helper(tokens: List(Token), acc: List(Stmt)) -> ManyStmtResult { 24 + case tokens { 25 + [] -> Error(ParseError(None, "Somehow ran out of tokens??")) 26 + [scan.Token(token_type: scan.Eof, ..)] -> Ok(#(acc, tokens)) 27 + [scan.Token(token_type: scan.Print, ..) as tok, ..rest] -> { 28 + use #(stmt, rest) <- result.try(expr_stmt( 29 + fn(e) { PrintStmt(tok, e) }, 30 + rest, 31 + )) 32 + 33 + stmt_helper(rest, list.append(acc, [stmt])) 34 + } 35 + _ -> { 36 + use #(stmt, rest) <- result.try(expr_stmt(ExprStmt, tokens)) 37 + 38 + stmt_helper(rest, list.append(acc, [stmt])) 39 + } 40 + } 41 + } 42 + 43 + fn expr_stmt(ctor: fn(Expr) -> Stmt, tokens: List(Token)) -> StmtResult { 44 + use #(e, rest) <- result.try(expr.expression(tokens)) 45 + 46 + case rest { 47 + [Token(token_type: scan.Semicolon, ..), ..rest] -> { 48 + Ok(#(ctor(e), rest)) 49 + } 50 + [t, ..] -> Error(ParseError(Some(t), "Expect ';' after value.")) 51 + _ -> Error(ParseError(None, "Expect ';' after value.")) 52 + } 53 + }