summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIbrahim Muftee <ibrahim@muftee.net>2026-07-20 18:44:45 -0500
committerIbrahim Muftee <ibrahim@muftee.net>2026-07-23 20:38:35 -0500
commite5ab0622364911a8a7364689d3b59e856a6c09e9 (patch)
treeaac69f2f2da06cadf786c12494ed9d27eef0136b
parenta84bf3b30c9cc3b2b5eeea542855a56915e26099 (diff)
chapter 2.6
-rw-r--r--ast/ast.odin62
-rw-r--r--main.odin1
-rw-r--r--parser/parser.odin182
-rw-r--r--token/token.odin62
4 files changed, 307 insertions, 0 deletions
diff --git a/ast/ast.odin b/ast/ast.odin
new file mode 100644
index 0000000..67039a3
--- /dev/null
+++ b/ast/ast.odin
@@ -0,0 +1,62 @@
+package ast
+
+import "../token"
+
+Node :: union {
+ Program,
+ Statement,
+ Expression,
+}
+
+Program :: struct {
+ statements: []Statement,
+}
+
+Statement :: union {
+ Let_Statement,
+ Return_Statement,
+}
+
+Let_Statement :: struct {
+ token: token.Token,
+ name: Identifier,
+ value: Expression,
+}
+
+Return_Statement :: struct {
+ token: token.Token,
+ return_value: Expression,
+}
+
+Expression :: union {
+ Identifier,
+}
+
+Identifier :: struct {
+ token: token.Token,
+ value: string,
+}
+
+
+token_literal :: proc(node: Node) -> (literal: string) {
+ switch n in node {
+ case Program:
+ if len(n.statements) > 0 {
+ stmt: Node = n.statements[0]
+ literal = token_literal(stmt)
+ }
+ case Statement:
+ switch s in n {
+ case Let_Statement:
+ literal = token.literal(s.token)
+ case Return_Statement:
+ literal = token.literal(s.token)
+ }
+ case Expression:
+ switch e in n {
+ case Identifier:
+ literal = token.literal(e.token)
+ }
+ }
+ return literal
+}
diff --git a/main.odin b/main.odin
index 8a4e1cb..dbd2c1e 100644
--- a/main.odin
+++ b/main.odin
@@ -3,6 +3,7 @@ package main
import "core:fmt"
import "core:os"
import "repl"
+import "token"
main :: proc() {
user, ok := os.lookup_env_alloc("USER", context.allocator)
diff --git a/parser/parser.odin b/parser/parser.odin
new file mode 100644
index 0000000..289ba1f
--- /dev/null
+++ b/parser/parser.odin
@@ -0,0 +1,182 @@
+package parser
+
+import "../ast"
+import "../lexer"
+import "../token"
+import "core:fmt"
+import "core:mem"
+import "core:testing"
+
+Parser :: struct {
+ l: ^lexer.Lexer,
+ cur_token: token.Token,
+ peek_token: token.Token,
+ errors: [dynamic]string,
+}
+
+new :: proc(l: ^lexer.Lexer) -> Parser {
+ p := Parser{l, token.Illegal{}, token.Illegal{}, make([dynamic]string, context.temp_allocator)}
+ next_token(&p)
+ next_token(&p)
+ return p
+}
+
+errors :: proc(p: ^Parser) -> []string {
+ return p.errors[:]
+}
+
+peek_error :: proc(p: ^Parser, $T: typeid) {
+ msg := fmt.tprintf("expected next token to be %s, got %v instead", typeid_of(T), p.peek_token)
+ append(&p.errors, msg)
+}
+
+next_token :: proc(p: ^Parser) {
+ p.cur_token = p.peek_token
+ p.peek_token = lexer.next_token(p.l)
+}
+
+parse_program :: proc(p: ^Parser) -> ast.Program {
+ buf := make([dynamic]ast.Statement, context.temp_allocator)
+ for !cur_token_is(p, token.EOF) {
+ if stmt, ok := parse_statement(p); ok {
+ append(&buf, stmt)
+ }
+ next_token(p)
+ }
+ return ast.Program{buf[:]}
+}
+
+parse_statement :: proc(p: ^Parser) -> (stmt: ast.Statement, ok: bool) {
+ #partial switch t in p.cur_token {
+ case token.Let:
+ stmt, ok = parse_let_statement(p)
+ case token.Return:
+ stmt, ok = parse_return_statement(p)
+ case:
+ ok = false
+ }
+ return
+}
+
+parse_let_statement :: proc(p: ^Parser) -> (stmt: ast.Let_Statement, ok: bool) {
+ stmt.token = p.cur_token
+
+ if !expect_peek(p, token.Identifier) {
+ ok = false
+ return
+ }
+
+ stmt.name = ast.Identifier{p.cur_token, token.literal(p.cur_token)}
+
+ if !expect_peek(p, token.Equal_Sign) {
+ ok = false
+ return
+ }
+
+ for !cur_token_is(p, token.Semicolon) {
+ next_token(p)
+ }
+
+ ok = true
+ return
+}
+
+parse_return_statement :: proc(p: ^Parser) -> (stmt: ast.Return_Statement, ok: bool) {
+ stmt.token = p.cur_token
+ next_token(p)
+ for !cur_token_is(p, token.Semicolon) {
+ next_token(p)
+ }
+ ok = true
+ return
+}
+
+cur_token_is :: proc(p: ^Parser, $T: typeid) -> bool {
+ _, ok := p.cur_token.(T)
+ return ok
+}
+
+peek_token_is :: proc(p: ^Parser, $T: typeid) -> bool {
+ _, ok := p.peek_token.(T)
+ return ok
+}
+
+expect_peek :: proc(p: ^Parser, $T: typeid) -> bool {
+ if peek_token_is(p, T) {
+ next_token(p)
+ return true
+ } else {
+ peek_error(p, T)
+ return false
+ }
+}
+
+@(test)
+test_let_statements :: proc(t: ^testing.T) {
+ input := `
+let x = 5;
+let y = 10;
+let foobar = 838383;
+`
+ l := lexer.new(input)
+ p := new(&l)
+
+ program := parse_program(&p)
+ defer free_all(context.allocator)
+ check_parse_errors(t, &p)
+
+ testing.expect_value(t, len(program.statements), 3)
+
+ expected := []string{"x", "y", "foobar"}
+ for identifier, index in expected {
+ _ = program.statements[index]
+ stmt := program.statements[index]
+ test_let_statement(t, stmt, identifier)
+ }
+}
+
+@(test)
+test_return_statements :: proc(t: ^testing.T) {
+ input := `
+return 5;
+return 10;
+return 993322;
+`
+ l := lexer.new(input)
+ p := new(&l)
+
+ program := parse_program(&p)
+ defer free_all(context.allocator)
+ check_parse_errors(t, &p)
+
+ testing.expect_value(t, len(program.statements), 3)
+ for stmt in program.statements {
+ return_stmt, ok := stmt.(ast.Return_Statement)
+ testing.expectf(t, ok, "Type of s is not `ast.Return_Statement`. got=%T", stmt)
+ testing.expect_value(t, ast.token_literal(return_stmt), "return")
+ }
+}
+
+test_let_statement :: proc(t: ^testing.T, s: ast.Statement, name: string) {
+ testing.expect_value(t, ast.token_literal(s), "let")
+ let_stmt, ok := s.(ast.Let_Statement)
+ testing.expectf(t, ok, "Type of s is not `ast.Let_Statement`. got=%T", s)
+
+ testing.expect_value(t, let_stmt.name.value, name)
+ testing.expect_value(t, ast.token_literal(let_stmt.name), name)
+}
+
+check_parse_errors :: proc(t: ^testing.T, p: ^Parser) {
+ errs := errors(p)
+ if len(errs) == 0 {
+ return
+ }
+
+ testing.expectf(t, len(errs) == 0, "parser had %d errors", len(errs))
+ for msg in errs {
+ testing.expectf(t, false, "parser error: %q", msg)
+ }
+
+ testing.fail_now(t)
+}
+
diff --git a/token/token.odin b/token/token.odin
index 20efd6f..f6d0701 100644
--- a/token/token.odin
+++ b/token/token.odin
@@ -1,5 +1,7 @@
package token
+import "core:fmt"
+
Token :: union {
Asterisk,
Bang,
@@ -82,3 +84,63 @@ lookup_identifier :: proc(ident: string) -> Token {
return Identifier{ident}
}
}
+
+literal :: proc(token: Token) -> (literal: string) {
+ switch t in token {
+ case Asterisk:
+ literal = "*"
+ case Bang:
+ literal = "!"
+ case Comma:
+ literal = ","
+ case EOF:
+ literal = "EOF"
+ case Else:
+ literal = "else"
+ case Equal_Sign:
+ literal = "="
+ case Equals:
+ literal = "=="
+ case False:
+ literal = "false"
+ case Forward_Slash:
+ literal = "/"
+ case Function:
+ literal = "fn"
+ case Greater_Than:
+ literal = ">"
+ case Identifier:
+ literal = t.literal
+ case If:
+ literal = "if"
+ case Illegal:
+ literal = "ILLEGAL"
+ case Integer:
+ literal = fmt.tprint(t.literal)
+ case Left_Brace:
+ literal = "{"
+ case Left_Parenthesis:
+ literal = "("
+ case Less_Than:
+ literal = "<"
+ case Let:
+ literal = "let"
+ case Minus:
+ literal = "-"
+ case Not_Equals:
+ literal = "!="
+ case Plus:
+ literal = "+"
+ case Return:
+ literal = "return"
+ case Right_Brace:
+ literal = "}"
+ case Right_Parenthesis:
+ literal = ")"
+ case Semicolon:
+ literal = ";"
+ case True:
+ literal = "true"
+ }
+ return literal
+}