Skip to content

Commit 012fffe

Browse files
committed
start parser impl
1 parent 7485b7d commit 012fffe

5 files changed

Lines changed: 103 additions & 3 deletions

File tree

src/ast.gleam

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pub type Expr {
2+
Atom(String)
3+
Number(Int)
4+
List(List(Expr))
5+
Builtin(fn(List(Expr)) -> Result(Expr, String))
6+
}

src/core.gleam

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1 @@
1-
pub type LispVal {
2-
Atom(String)
3-
}
1+
File renamed without changes.

src/parser.gleam

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,88 @@
1+
////// Parses a list of tokens into an abstract syntax tree expression.
12

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+
}

src/tokenizer.gleam

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import gleam/list
2+
import gleam/string
3+
4+
pub fn tokenize(source: String) -> List(String) {
5+
let spaced = string.replace(source, "(", " ( ")
6+
let spaced = string.replace(spaced, ")", " ) ")
7+
string.split(spaced, " ")
8+
|> list.filter(fn(s) { s != "" })
9+
}

0 commit comments

Comments
 (0)