diff options
| author | Ibrahim Muftee <ibrahim@muftee.net> | 2026-07-20 18:44:45 -0500 |
|---|---|---|
| committer | Ibrahim Muftee <ibrahim@muftee.net> | 2026-07-23 20:38:35 -0500 |
| commit | e5ab0622364911a8a7364689d3b59e856a6c09e9 (patch) | |
| tree | aac69f2f2da06cadf786c12494ed9d27eef0136b | |
| parent | a84bf3b30c9cc3b2b5eeea542855a56915e26099 (diff) | |
chapter 2.6
| -rw-r--r-- | ast/ast.odin | 62 | ||||
| -rw-r--r-- | main.odin | 1 | ||||
| -rw-r--r-- | parser/parser.odin | 182 | ||||
| -rw-r--r-- | token/token.odin | 62 |
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 +} @@ -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 +} |
