diff options
Diffstat (limited to 'parser')
| -rw-r--r-- | parser/parser.odin | 182 |
1 files changed, 182 insertions, 0 deletions
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) +} + |
