summaryrefslogtreecommitdiff
path: root/parser/parser.odin
diff options
context:
space:
mode:
Diffstat (limited to 'parser/parser.odin')
-rw-r--r--parser/parser.odin182
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)
+}
+