|
| 1 | +////// Parses a list of tokens into an abstract syntax tree expression. |
1 | 2 |
|
| 3 | +import ast.{type Expr, Atom, List, Number} |
| 4 | +import gleam/int |
| 5 | +import gleam/list |
| 6 | +import gleam/result |
| 7 | +import gleam/string |
| 8 | + |
| 9 | +/// |
| 10 | +/// This function takes a list of string tokens and attempts to parse them |
| 11 | +/// into a structured expression according to a simple Lisp-like syntax. |
| 12 | +/// It can recognize atoms, numbers, and nested lists. |
| 13 | +/// |
| 14 | +/// ## Examples |
| 15 | +/// |
| 16 | +/// ``` |
| 17 | +/// parse(["(", "hello", "world", ")"]) |
| 18 | +/// // -> Ok(List([Atom("hello"), Atom("world")])) |
| 19 | +/// |
| 20 | +/// parse(["42"]) |
| 21 | +/// // -> Ok(Number(42)) |
| 22 | +/// |
| 23 | +/// parse(["missing_paren", "("]) |
| 24 | +/// // -> Error("Unclosed list") |
| 25 | +/// ``` |
| 26 | +pub fn parse(tokens: List(String)) -> Result(Expr, String) { |
| 27 | + case parse_tokens(tokens) { |
| 28 | + Ok(#(expr, [])) -> Ok(expr) |
| 29 | + Ok(#(_, _)) -> Error("Unexpected tokens after valid expression") |
| 30 | + Error(msg) -> Error(msg) |
| 31 | + } |
| 32 | +} |
| 33 | + |
| 34 | +fn is_digit(token: String) -> Bool { |
| 35 | + result.is_ok(int.parse(token)) |
| 36 | +} |
| 37 | + |
| 38 | +fn as_int_or(token: String, ex: Int) -> Int { |
| 39 | + result.unwrap(int.parse(token), ex) |
| 40 | +} |
| 41 | + |
| 42 | +fn parse_tokens(tokens: List(String)) -> Result(#(Expr, List(String)), String) { |
| 43 | + case tokens { |
| 44 | + [] -> Error("Unexpected EOF") |
| 45 | + [head, ..rest] -> |
| 46 | + case head { |
| 47 | + "(" -> parse_list([], rest) |
| 48 | + ")" -> Error("Unexpected ')'") |
| 49 | + _ -> |
| 50 | + case is_digit(head) { |
| 51 | + True -> Ok(#(Number(as_int_or(head, 0)), rest)) |
| 52 | + False -> Ok(#(Atom(head), rest)) |
| 53 | + } |
| 54 | + } |
| 55 | + } |
| 56 | +} |
| 57 | + |
| 58 | +fn flip(arr: List(ast.Expr)) -> List(ast.Expr) { |
| 59 | + list.reverse(arr) |
| 60 | +} |
| 61 | + |
| 62 | +fn parse_list( |
| 63 | + acc: List(Expr), |
| 64 | + tokens: List(String), |
| 65 | +) -> Result(#(Expr, List(String)), String) { |
| 66 | + case tokens { |
| 67 | + [] -> Error("Unclosed list") |
| 68 | + [head, ..rest] -> |
| 69 | + case head { |
| 70 | + ")" -> Ok(#(List(flip(acc)), rest)) |
| 71 | + "(" -> { |
| 72 | + // Parse the inner list |
| 73 | + case parse_list([], rest) { |
| 74 | + Ok(#(inner_expr, remaining)) -> |
| 75 | + // Continue parsing with the remaining tokens |
| 76 | + parse_list([inner_expr, ..acc], remaining) |
| 77 | + Error(msg) -> Error(msg) |
| 78 | + } |
| 79 | + } |
| 80 | + _ -> { |
| 81 | + case parse_tokens([head]) { |
| 82 | + Ok(#(item, _)) -> parse_list([item, ..acc], rest) |
| 83 | + Error(msg) -> Error(msg) |
| 84 | + } |
| 85 | + } |
| 86 | + } |
| 87 | + } |
| 88 | +} |
0 commit comments