From 292c454d7f8f7ed5df067e504b9095f49dd83899 Mon Sep 17 00:00:00 2001 From: Domas TamaĊĦauskas Date: Sun, 26 Jul 2026 14:11:17 +0000 Subject: [PATCH] Split package ast into package parsing and package ast --- monkey | 0 ast/ast.odin | 13 ------------- ast/parser.odin | 96 ------------------------------------------------------------------------------------------------ ast/procs.odin | 128 -------------------------------------------------------------------------------------------------------------------------------- parsing/parser.odin | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ parsing/parsing.odin | 17 +++++++++++++++++ parsing/procs.odin | 129 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/tests.odin | 2 +- tests/ast/ast.odin | 335 ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- tests/parsing/parsing.odin | 331 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 10 file(s) changed, 575 insertion(s)(+), 573 deletion(s)(-) diff --git a/monkey b/monkey deleted file mode 100644 --- a/monkey +++ /dev/null diff --git a/ast/ast.odin b/ast/ast.odin --- a/ast/ast.odin +++ b/ast/ast.odin @@ -1,12 +1,6 @@ package ast -import "../token" - Error :: distinct string - -Program :: struct { - statements: [dynamic]Statement, -} Expression :: union { Identifier, @@ -55,11 +49,4 @@ Expression_Statement :: struct { expression: Expression, -} - -parse :: proc(tokens: []token.Token, allocator := context.allocator) -> (Program, [dynamic]Error) { - parser := init_parser(tokens) - statements := make([dynamic]Statement, allocator) - for statement in next(&parser) do append(&statements, statement) - return Program{statements = statements}, parser.errors } diff --git a/ast/parser.odin b/ast/parser.odin deleted file mode 100644 --- a/ast/parser.odin +++ /dev/null @@ -1,96 +0,0 @@ -#+private -package ast - -import "../token" -import "base:runtime" -import "core:fmt" - -Operator_Precedence :: enum { - Lowest, - Equals, - Ordering, - Sum, - Product, - Unary, - Call, -} - -Parser :: struct { - input: []token.Token, - left, right: int, - value: token.Token, - allocator: runtime.Allocator, - errors: [dynamic]Error, -} - -init_parser :: proc(input: []token.Token, allocator := context.allocator) -> Parser { - parser := Parser { - input = input, - allocator = allocator, - errors = make([dynamic]Error, allocator), - } - read_token(&parser) - return parser -} - -error :: proc(got, want: token.Type, allocator := context.allocator) -> Error { - return Error(fmt.aprintf("expected %s, but got %s", want, got, allocator = allocator)) -} - -expect_token :: proc(p: ^Parser, expected: token.Type) -> bool { - if p.right >= len(p.input) { - append(&p.errors, error(.Illegal, expected, p.allocator)) - return false - } - - current := p.input[p.right].type - if current != expected do append(&p.errors, error(current, expected, p.allocator)) - return current == expected -} - -peek_type :: proc(p: ^Parser) -> (token.Type, bool) { - if p.right >= len(p.input) do return .Illegal, false - return p.input[p.right].type, true -} - -read_token :: proc(p: ^Parser) { - if p.right >= len(p.input) { - p.value = token.Token{} - } else { - p.value = p.input[p.right] - } - - p.left = p.right - p.right += 1 -} - -next :: proc(p: ^Parser) -> (statement: Statement, ok: bool) { - #partial switch p.value.type { - case .Illegal: - ok = false - case .Let: - statement, ok = parse_let_statement(p) - case .Return: - statement, ok = parse_return_statement(p) - case: - statement, ok = parse_expression_statement(p) - } - - read_token(p) - return -} - -token_precedence :: proc(type: token.Type) -> Operator_Precedence { - #partial switch type { - case .Equal, .Not_Equal: - return .Equals - case .Less_Than, .Greater_Than: - return .Ordering - case .Plus, .Minus: - return .Sum - case .Slash, .Asterisk: - return .Product - } - - return .Lowest -} diff --git a/ast/procs.odin b/ast/procs.odin deleted file mode 100644 --- a/ast/procs.odin +++ /dev/null @@ -1,128 +0,0 @@ -#+private -package ast - -import "../token" -import "core:strconv" - -parse_let_statement :: proc(p: ^Parser) -> (statement: Statement, ok: bool) { - expect_token(p, .Identifier) or_return - read_token(p) - - name := parse_identifier(p) - - expect_token(p, .Assignment) or_return - read_token(p) - - expect_token(p, .Integer) or_return - read_token(p) - - value := parse_integer(p) or_return - - expect_token(p, .Semicolon) or_return - read_token(p) - return Let_Statement{name = name, value = value}, true -} - -parse_return_statement :: proc(p: ^Parser) -> (statement: Statement, ok: bool) { - expect_token(p, .Integer) or_return - read_token(p) - - expression := parse_integer(p) or_return - - expect_token(p, .Semicolon) or_return - read_token(p) - - return Return_Statement{value = expression}, true -} - -parse_expression_statement :: proc(p: ^Parser) -> (statement: Statement, ok: bool) { - expression := parse_expression(p, .Lowest) - if expression == nil do return - - expect_token(p, .Semicolon) or_return - read_token(p) - - return Expression_Statement{expression = expression}, true -} - -parse_expression :: proc(p: ^Parser, precedence: Operator_Precedence) -> (expr: Expression) { - parse := unary_proc(p.value.type) - if parse == nil do return nil - - expr = parse(p) - - for { - type := peek_type(p) or_break - if type == .Semicolon do break - if precedence >= token_precedence(type) do break - - parse := binary_proc(type) - if parse == nil do return nil - - read_token(p) - expr = parse(p, expr) - } - - return expr -} - -unary_proc :: proc(type: token.Type) -> proc(_: ^Parser) -> Expression { - #partial switch type { - case .Identifier: - return proc(p: ^Parser) -> Expression {return parse_identifier(p)} - case .Integer: - return proc(p: ^Parser) -> Expression { - expr, ok := parse_integer(p) - if !ok do return nil - return expr - } - case .True, .False: - return proc(p: ^Parser) -> Expression { - expr, ok := parse_boolean(p) - if !ok do return nil - return expr - } - case .Bang, .Minus: - return parse_unary_expression - case: - return nil - } -} - -binary_proc :: proc(type: token.Type) -> proc(_: ^Parser, _: Expression) -> Expression { - return parse_binary_expression -} - -parse_identifier :: proc(p: ^Parser) -> Identifier { - return {value = p.value.literal} -} - -parse_integer :: proc(p: ^Parser) -> (expr: Integer, ok: bool) { - value := strconv.parse_int(p.value.literal, 10) or_return - return {value = value}, true -} - -parse_boolean :: proc(p: ^Parser) -> (expr: Boolean, ok: bool) { - value := strconv.parse_bool(p.value.literal) or_return - return {value = value}, true -} - -parse_unary_expression :: proc(p: ^Parser) -> Expression { - operator := p.value.literal - read_token(p) - expression := new(Unary_Expression, p.allocator) - expression.operator = operator - expression.right = parse_expression(p, .Unary) - return expression -} - -parse_binary_expression :: proc(p: ^Parser, left: Expression) -> Expression { - operator := p.value.literal - precedence := token_precedence(p.value.type) - read_token(p) - expression := new(Binary_Expression, p.allocator) - expression.operator = operator - expression.left = left - expression.right = parse_expression(p, precedence) - return expression -} diff --git a/parsing/parser.odin b/parsing/parser.odin new file mode 100644 --- /dev/null +++ b/parsing/parser.odin @@ -0,0 +1,97 @@ +#+private +package parsing + +import "../ast" +import "../token" +import "base:runtime" +import "core:fmt" + +Operator_Precedence :: enum { + Lowest, + Equals, + Ordering, + Sum, + Product, + Unary, + Call, +} + +Parser :: struct { + input: []token.Token, + left, right: int, + value: token.Token, + allocator: runtime.Allocator, + errors: [dynamic]ast.Error, +} + +init :: proc(input: []token.Token, allocator := context.allocator) -> Parser { + parser := Parser { + input = input, + allocator = allocator, + errors = make([dynamic]ast.Error, allocator), + } + read_token(&parser) + return parser +} + +error :: proc(got, want: token.Type, allocator := context.allocator) -> ast.Error { + return ast.Error(fmt.aprintf("expected %s, but got %s", want, got, allocator = allocator)) +} + +expect_token :: proc(p: ^Parser, expected: token.Type) -> bool { + if p.right >= len(p.input) { + append(&p.errors, error(.Illegal, expected, p.allocator)) + return false + } + + current := p.input[p.right].type + if current != expected do append(&p.errors, error(current, expected, p.allocator)) + return current == expected +} + +peek_type :: proc(p: ^Parser) -> (token.Type, bool) { + if p.right >= len(p.input) do return .Illegal, false + return p.input[p.right].type, true +} + +read_token :: proc(p: ^Parser) { + if p.right >= len(p.input) { + p.value = token.Token{} + } else { + p.value = p.input[p.right] + } + + p.left = p.right + p.right += 1 +} + +next :: proc(p: ^Parser) -> (statement: ast.Statement, ok: bool) { + #partial switch p.value.type { + case .Illegal: + ok = false + case .Let: + statement, ok = parse_let_statement(p) + case .Return: + statement, ok = parse_return_statement(p) + case: + statement, ok = parse_expression_statement(p) + } + + read_token(p) + return +} + +token_precedence :: proc(type: token.Type) -> Operator_Precedence { + #partial switch type { + case .Equal, .Not_Equal: + return .Equals + case .Less_Than, .Greater_Than: + return .Ordering + case .Plus, .Minus: + return .Sum + case .Slash, .Asterisk: + return .Product + } + + return .Lowest +} diff --git a/parsing/parsing.odin b/parsing/parsing.odin new file mode 100644 --- /dev/null +++ b/parsing/parsing.odin @@ -0,0 +1,17 @@ +package parsing + +import "../ast" +import "../token" + +run :: proc( + tokens: []token.Token, + allocator := context.allocator, +) -> ( + [dynamic]ast.Statement, + [dynamic]ast.Error, +) { + parser := init(tokens) + statements := make([dynamic]ast.Statement, allocator) + for statement in next(&parser) do append(&statements, statement) + return statements, parser.errors +} diff --git a/parsing/procs.odin b/parsing/procs.odin new file mode 100644 --- /dev/null +++ b/parsing/procs.odin @@ -0,0 +1,129 @@ +#+private +package parsing + +import "../ast" +import "../token" +import "core:strconv" + +parse_let_statement :: proc(p: ^Parser) -> (statement: ast.Statement, ok: bool) { + expect_token(p, .Identifier) or_return + read_token(p) + + name := parse_identifier(p) + + expect_token(p, .Assignment) or_return + read_token(p) + + expect_token(p, .Integer) or_return + read_token(p) + + value := parse_integer(p) or_return + + expect_token(p, .Semicolon) or_return + read_token(p) + return ast.Let_Statement{name = name, value = value}, true +} + +parse_return_statement :: proc(p: ^Parser) -> (statement: ast.Statement, ok: bool) { + expect_token(p, .Integer) or_return + read_token(p) + + expression := parse_integer(p) or_return + + expect_token(p, .Semicolon) or_return + read_token(p) + + return ast.Return_Statement{value = expression}, true +} + +parse_expression_statement :: proc(p: ^Parser) -> (statement: ast.Statement, ok: bool) { + expression := parse_expression(p, .Lowest) + if expression == nil do return + + expect_token(p, .Semicolon) or_return + read_token(p) + + return ast.Expression_Statement{expression = expression}, true +} + +parse_expression :: proc(p: ^Parser, precedence: Operator_Precedence) -> (expr: ast.Expression) { + parse := unary_proc(p.value.type) + if parse == nil do return nil + + expr = parse(p) + + for { + type := peek_type(p) or_break + if type == .Semicolon do break + if precedence >= token_precedence(type) do break + + parse := binary_proc(type) + if parse == nil do return nil + + read_token(p) + expr = parse(p, expr) + } + + return expr +} + +unary_proc :: proc(type: token.Type) -> proc(_: ^Parser) -> ast.Expression { + #partial switch type { + case .Identifier: + return proc(p: ^Parser) -> ast.Expression {return parse_identifier(p)} + case .Integer: + return proc(p: ^Parser) -> ast.Expression { + expr, ok := parse_integer(p) + if !ok do return nil + return expr + } + case .True, .False: + return proc(p: ^Parser) -> ast.Expression { + expr, ok := parse_boolean(p) + if !ok do return nil + return expr + } + case .Bang, .Minus: + return parse_unary_expression + case: + return nil + } +} + +binary_proc :: proc(type: token.Type) -> proc(_: ^Parser, _: ast.Expression) -> ast.Expression { + return parse_binary_expression +} + +parse_identifier :: proc(p: ^Parser) -> ast.Identifier { + return {value = p.value.literal} +} + +parse_integer :: proc(p: ^Parser) -> (expr: ast.Integer, ok: bool) { + value := strconv.parse_int(p.value.literal, 10) or_return + return {value = value}, true +} + +parse_boolean :: proc(p: ^Parser) -> (expr: ast.Boolean, ok: bool) { + value := strconv.parse_bool(p.value.literal) or_return + return {value = value}, true +} + +parse_unary_expression :: proc(p: ^Parser) -> ast.Expression { + operator := p.value.literal + read_token(p) + expression := new(ast.Unary_Expression, p.allocator) + expression.operator = operator + expression.right = parse_expression(p, .Unary) + return expression +} + +parse_binary_expression :: proc(p: ^Parser, left: ast.Expression) -> ast.Expression { + operator := p.value.literal + precedence := token_precedence(p.value.type) + read_token(p) + expression := new(ast.Binary_Expression, p.allocator) + expression.operator = operator + expression.left = left + expression.right = parse_expression(p, precedence) + return expression +} diff --git a/tests/tests.odin b/tests/tests.odin --- a/tests/tests.odin +++ b/tests/tests.odin @@ -1,4 +1,4 @@ package tests -@(require) import "ast" @(require) import "lexing" +@(require) import "parsing" diff --git a/tests/ast/ast.odin b/tests/ast/ast.odin deleted file mode 100644 --- a/tests/ast/ast.odin +++ /dev/null @@ -1,335 +0,0 @@ -#+feature dynamic-literals -package ast_test - -import "../../ast" -import "../../expect" -import "../../token" -import "core:testing" - -@(test) -error :: proc(t: ^testing.T) { - tests := map[string]struct { - input: [dynamic]token.Token, - output: string, - } { - "let missing assignment" = { - input = [dynamic]token.Token { - {type = .Let}, - {type = .Identifier, literal = "x"}, - {type = .Integer, literal = "0"}, - {type = .Semicolon}, - }, - output = "expected Assignment, but got Integer", - }, - "let missing identifier" = { - input = [dynamic]token.Token { - {type = .Let}, - {type = .Assignment}, - {type = .Integer, literal = "0"}, - {type = .Semicolon}, - }, - output = "expected Identifier, but got Assignment", - }, - "let missing integer" = { - input = [dynamic]token.Token { - {type = .Let}, - {type = .Identifier, literal = "x"}, - {type = .Assignment}, - {type = .Semicolon}, - }, - output = "expected Integer, but got Semicolon", - }, - "let missing semicolon" = { - input = [dynamic]token.Token { - {type = .Let}, - {type = .Identifier, literal = "x"}, - {type = .Assignment}, - {type = .Integer, literal = "0"}, - }, - output = "expected Semicolon, but got Illegal", - }, - "return missing integer" = { - input = [dynamic]token.Token{{type = .Return}, {type = .Semicolon}}, - output = "expected Integer, but got Semicolon", - }, - "return missing semicolon" = { - input = [dynamic]token.Token{{type = .Return}, {type = .Integer, literal = "0"}}, - output = "expected Semicolon, but got Illegal", - }, - "identifier missing semicolon" = { - input = [dynamic]token.Token{{type = .Identifier, literal = "x"}}, - output = "expected Semicolon, but got Illegal", - }, - "integer missing semicolon" = { - input = [dynamic]token.Token{{type = .Integer, literal = "0"}}, - output = "expected Semicolon, but got Illegal", - }, - "boolean missing semicolon" = { - input = [dynamic]token.Token{{type = .True, literal = "true"}}, - output = "expected Semicolon, but got Illegal", - }, - "bang missing semicolon" = { - input = [dynamic]token.Token{{type = .Bang}, {type = .Integer, literal = "0"}}, - output = "expected Semicolon, but got Illegal", - }, - "minus missing semicolon" = { - input = [dynamic]token.Token{{type = .Minus}, {type = .Integer, literal = "0"}}, - output = "expected Semicolon, but got Illegal", - }, - } - defer free_all() - - for name, data in tests { - program, errors := ast.parse(data.input[:]) - expect.value(t, name, len(program.statements), 0) - expect.value(t, name, len(errors), 1) - expect.value(t, name, string(errors[0]), data.output) - } -} - -@(test) -statement :: proc(t: ^testing.T) { - tests := map[string]struct { - input: [dynamic]token.Token, - output: ast.Statement, - } { - "let" = { - input = [dynamic]token.Token { - {type = .Let}, - {type = .Identifier, literal = "x"}, - {type = .Assignment}, - {type = .Integer, literal = "0"}, - {type = .Semicolon}, - }, - output = ast.Let_Statement { - name = ast.Identifier{value = "x"}, - value = ast.Integer{value = 0}, - }, - }, - "return" = { - input = [dynamic]token.Token { - {type = .Return}, - {type = .Integer, literal = "0"}, - {type = .Semicolon}, - }, - output = ast.Return_Statement{value = ast.Integer{value = 0}}, - }, - } - - for name, data in tests { - program, errors := ast.parse(data.input[:]) - defer free_all() - - expect.value(t, name, len(program.statements), 1) - expect.value(t, name, len(errors), 0) - expect.value(t, name, program.statements[0], data.output) - } -} - -@(test) -expression :: proc(t: ^testing.T) { - tests := map[string]struct { - input: [dynamic]token.Token, - output: ast.Expression, - } { - "identifier" = { - input = [dynamic]token.Token { - {type = .Identifier, literal = "x"}, - {type = .Semicolon, literal = ";"}, - }, - output = ast.Identifier{value = "x"}, - }, - "integer" = { - input = [dynamic]token.Token { - {type = .Integer, literal = "0"}, - {type = .Semicolon, literal = ";"}, - }, - output = ast.Integer{value = 0}, - }, - "true" = { - input = [dynamic]token.Token { - {type = .True, literal = "true"}, - {type = .Semicolon, literal = ";"}, - }, - output = ast.Boolean{value = true}, - }, - "false" = { - input = [dynamic]token.Token { - {type = .False, literal = "false"}, - {type = .Semicolon, literal = ";"}, - }, - output = ast.Boolean{value = false}, - }, - } - defer free_all() - - for name, data in tests { - program, errors := ast.parse(data.input[:], context.temp_allocator) - defer free_all(context.temp_allocator) - - expect.value(t, name, len(program.statements), 1) - expect.value(t, name, len(errors), 0) - statement := program.statements[0].(ast.Expression_Statement) - expect.value(t, name, statement.expression, data.output) - } -} - -@(test) -unary_expression :: proc(t: ^testing.T) { - tests := map[string]struct { - input: [dynamic]token.Token, - output: ast.Unary_Expression, - } { - "bang" = { - input = [dynamic]token.Token { - {type = .Bang, literal = "!"}, - {type = .Integer, literal = "0"}, - {type = .Semicolon, literal = ";"}, - }, - output = ast.Unary_Expression{operator = "!", right = ast.Integer{value = 0}}, - }, - "minus" = { - input = [dynamic]token.Token { - {type = .Minus, literal = "-"}, - {type = .Integer, literal = "0"}, - {type = .Semicolon, literal = ";"}, - }, - output = ast.Unary_Expression{operator = "-", right = ast.Integer{value = 0}}, - }, - } - - for name, data in tests { - program, errors := ast.parse(data.input[:]) - defer free_all() - - expect.value(t, name, len(program.statements), 1) - expect.value(t, name, len(errors), 0) - statement := program.statements[0].(ast.Expression_Statement) - expression := statement.expression.(^ast.Unary_Expression) - expect.value(t, name, expression^, data.output) - } -} - -@(test) -binary_expression :: proc(t: ^testing.T) { - tests := map[string]struct { - input: [dynamic]token.Token, - output: ast.Binary_Expression, - } { - "plus" = { - input = [dynamic]token.Token { - {type = .Integer, literal = "0"}, - {type = .Plus, literal = "+"}, - {type = .Integer, literal = "0"}, - {type = .Semicolon, literal = ";"}, - }, - output = ast.Binary_Expression { - operator = "+", - left = ast.Integer{value = 0}, - right = ast.Integer{value = 0}, - }, - }, - "minus" = { - input = [dynamic]token.Token { - {type = .Integer, literal = "0"}, - {type = .Minus, literal = "-"}, - {type = .Integer, literal = "0"}, - {type = .Semicolon, literal = ";"}, - }, - output = ast.Binary_Expression { - operator = "-", - left = ast.Integer{value = 0}, - right = ast.Integer{value = 0}, - }, - }, - "multiply by" = { - input = [dynamic]token.Token { - {type = .Integer, literal = "0"}, - {type = .Asterisk, literal = "*"}, - {type = .Integer, literal = "0"}, - {type = .Semicolon, literal = ";"}, - }, - output = ast.Binary_Expression { - operator = "*", - left = ast.Integer{value = 0}, - right = ast.Integer{value = 0}, - }, - }, - "divide by" = { - input = [dynamic]token.Token { - {type = .Integer, literal = "0"}, - {type = .Slash, literal = "/"}, - {type = .Integer, literal = "0"}, - {type = .Semicolon, literal = ";"}, - }, - output = ast.Binary_Expression { - operator = "/", - left = ast.Integer{value = 0}, - right = ast.Integer{value = 0}, - }, - }, - "greater than" = { - input = [dynamic]token.Token { - {type = .Integer, literal = "0"}, - {type = .Greater_Than, literal = ">"}, - {type = .Integer, literal = "0"}, - {type = .Semicolon, literal = ";"}, - }, - output = ast.Binary_Expression { - operator = ">", - left = ast.Integer{value = 0}, - right = ast.Integer{value = 0}, - }, - }, - "less than" = { - input = [dynamic]token.Token { - {type = .Integer, literal = "0"}, - {type = .Less_Than, literal = "<"}, - {type = .Integer, literal = "0"}, - {type = .Semicolon, literal = ";"}, - }, - output = ast.Binary_Expression { - operator = "<", - left = ast.Integer{value = 0}, - right = ast.Integer{value = 0}, - }, - }, - "equal" = { - input = [dynamic]token.Token { - {type = .Integer, literal = "0"}, - {type = .Equal, literal = "=="}, - {type = .Integer, literal = "0"}, - {type = .Semicolon, literal = ";"}, - }, - output = ast.Binary_Expression { - operator = "==", - left = ast.Integer{value = 0}, - right = ast.Integer{value = 0}, - }, - }, - "not equal" = { - input = [dynamic]token.Token { - {type = .Integer, literal = "0"}, - {type = .Not_Equal, literal = "!="}, - {type = .Integer, literal = "0"}, - {type = .Semicolon, literal = ";"}, - }, - output = ast.Binary_Expression { - operator = "!=", - left = ast.Integer{value = 0}, - right = ast.Integer{value = 0}, - }, - }, - } - - for name, data in tests { - program, errors := ast.parse(data.input[:]) - defer free_all() - - expect.value(t, name, len(program.statements), 1) - expect.value(t, name, len(errors), 0) - statement := program.statements[0].(ast.Expression_Statement) - expression := statement.expression.(^ast.Binary_Expression) - expect.value(t, name, expression^, data.output) - } -} diff --git a/tests/parsing/parsing.odin b/tests/parsing/parsing.odin new file mode 100644 --- /dev/null +++ b/tests/parsing/parsing.odin @@ -0,0 +1,331 @@ +#+feature dynamic-literals +package parsing_test + +import "../../ast" +import "../../expect" +import "../../parsing" +import "../../token" +import "core:testing" + +@(test) +error :: proc(t: ^testing.T) { + tests := map[string]struct { + input: [dynamic]token.Token, + output: string, + } { + "let missing assignment" = { + input = [dynamic]token.Token { + {type = .Let}, + {type = .Identifier, literal = "x"}, + {type = .Integer, literal = "0"}, + {type = .Semicolon}, + }, + output = "expected Assignment, but got Integer", + }, + "let missing identifier" = { + input = [dynamic]token.Token { + {type = .Let}, + {type = .Assignment}, + {type = .Integer, literal = "0"}, + {type = .Semicolon}, + }, + output = "expected Identifier, but got Assignment", + }, + "let missing integer" = { + input = [dynamic]token.Token { + {type = .Let}, + {type = .Identifier, literal = "x"}, + {type = .Assignment}, + {type = .Semicolon}, + }, + output = "expected Integer, but got Semicolon", + }, + "let missing semicolon" = { + input = [dynamic]token.Token { + {type = .Let}, + {type = .Identifier, literal = "x"}, + {type = .Assignment}, + {type = .Integer, literal = "0"}, + }, + output = "expected Semicolon, but got Illegal", + }, + "return missing integer" = { + input = [dynamic]token.Token{{type = .Return}, {type = .Semicolon}}, + output = "expected Integer, but got Semicolon", + }, + "return missing semicolon" = { + input = [dynamic]token.Token{{type = .Return}, {type = .Integer, literal = "0"}}, + output = "expected Semicolon, but got Illegal", + }, + "identifier missing semicolon" = { + input = [dynamic]token.Token{{type = .Identifier, literal = "x"}}, + output = "expected Semicolon, but got Illegal", + }, + "integer missing semicolon" = { + input = [dynamic]token.Token{{type = .Integer, literal = "0"}}, + output = "expected Semicolon, but got Illegal", + }, + "boolean missing semicolon" = { + input = [dynamic]token.Token{{type = .True, literal = "true"}}, + output = "expected Semicolon, but got Illegal", + }, + "bang missing semicolon" = { + input = [dynamic]token.Token{{type = .Bang}, {type = .Integer, literal = "0"}}, + output = "expected Semicolon, but got Illegal", + }, + "minus missing semicolon" = { + input = [dynamic]token.Token{{type = .Minus}, {type = .Integer, literal = "0"}}, + output = "expected Semicolon, but got Illegal", + }, + } + defer free_all() + + for name, data in tests { + statements, errors := parsing.run(data.input[:]) + expect.value(t, name, len(statements), 0) + expect.value(t, name, len(errors), 1) + expect.value(t, name, string(errors[0]), data.output) + } +} + +@(test) +statement :: proc(t: ^testing.T) { + tests := map[string]struct { + input: [dynamic]token.Token, + output: ast.Statement, + } { + "let" = { + input = [dynamic]token.Token { + {type = .Let}, + {type = .Identifier, literal = "x"}, + {type = .Assignment}, + {type = .Integer, literal = "0"}, + {type = .Semicolon}, + }, + output = ast.Let_Statement { + name = ast.Identifier{value = "x"}, + value = ast.Integer{value = 0}, + }, + }, + "return" = { + input = [dynamic]token.Token { + {type = .Return}, + {type = .Integer, literal = "0"}, + {type = .Semicolon}, + }, + output = ast.Return_Statement{value = ast.Integer{value = 0}}, + }, + } + defer free_all() + + for name, data in tests { + statements, errors := parsing.run(data.input[:]) + expect.value(t, name, len(statements), 1) + expect.value(t, name, len(errors), 0) + expect.value(t, name, statements[0], data.output) + } +} + +@(test) +expression :: proc(t: ^testing.T) { + tests := map[string]struct { + input: [dynamic]token.Token, + output: ast.Expression, + } { + "identifier" = { + input = [dynamic]token.Token { + {type = .Identifier, literal = "x"}, + {type = .Semicolon, literal = ";"}, + }, + output = ast.Identifier{value = "x"}, + }, + "integer" = { + input = [dynamic]token.Token { + {type = .Integer, literal = "0"}, + {type = .Semicolon, literal = ";"}, + }, + output = ast.Integer{value = 0}, + }, + "true" = { + input = [dynamic]token.Token { + {type = .True, literal = "true"}, + {type = .Semicolon, literal = ";"}, + }, + output = ast.Boolean{value = true}, + }, + "false" = { + input = [dynamic]token.Token { + {type = .False, literal = "false"}, + {type = .Semicolon, literal = ";"}, + }, + output = ast.Boolean{value = false}, + }, + } + defer free_all() + + for name, data in tests { + statements, errors := parsing.run(data.input[:]) + expect.value(t, name, len(statements), 1) + expect.value(t, name, len(errors), 0) + statement := statements[0].(ast.Expression_Statement) + expect.value(t, name, statement.expression, data.output) + } +} + +@(test) +unary_expression :: proc(t: ^testing.T) { + tests := map[string]struct { + input: [dynamic]token.Token, + output: ast.Unary_Expression, + } { + "bang" = { + input = [dynamic]token.Token { + {type = .Bang, literal = "!"}, + {type = .Integer, literal = "0"}, + {type = .Semicolon, literal = ";"}, + }, + output = ast.Unary_Expression{operator = "!", right = ast.Integer{value = 0}}, + }, + "minus" = { + input = [dynamic]token.Token { + {type = .Minus, literal = "-"}, + {type = .Integer, literal = "0"}, + {type = .Semicolon, literal = ";"}, + }, + output = ast.Unary_Expression{operator = "-", right = ast.Integer{value = 0}}, + }, + } + defer free_all() + + for name, data in tests { + statements, errors := parsing.run(data.input[:]) + expect.value(t, name, len(statements), 1) + expect.value(t, name, len(errors), 0) + statement := statements[0].(ast.Expression_Statement) + expression := statement.expression.(^ast.Unary_Expression) + expect.value(t, name, expression^, data.output) + } +} + +@(test) +binary_expression :: proc(t: ^testing.T) { + tests := map[string]struct { + input: [dynamic]token.Token, + output: ast.Binary_Expression, + } { + "plus" = { + input = [dynamic]token.Token { + {type = .Integer, literal = "0"}, + {type = .Plus, literal = "+"}, + {type = .Integer, literal = "0"}, + {type = .Semicolon, literal = ";"}, + }, + output = ast.Binary_Expression { + operator = "+", + left = ast.Integer{value = 0}, + right = ast.Integer{value = 0}, + }, + }, + "minus" = { + input = [dynamic]token.Token { + {type = .Integer, literal = "0"}, + {type = .Minus, literal = "-"}, + {type = .Integer, literal = "0"}, + {type = .Semicolon, literal = ";"}, + }, + output = ast.Binary_Expression { + operator = "-", + left = ast.Integer{value = 0}, + right = ast.Integer{value = 0}, + }, + }, + "multiply by" = { + input = [dynamic]token.Token { + {type = .Integer, literal = "0"}, + {type = .Asterisk, literal = "*"}, + {type = .Integer, literal = "0"}, + {type = .Semicolon, literal = ";"}, + }, + output = ast.Binary_Expression { + operator = "*", + left = ast.Integer{value = 0}, + right = ast.Integer{value = 0}, + }, + }, + "divide by" = { + input = [dynamic]token.Token { + {type = .Integer, literal = "0"}, + {type = .Slash, literal = "/"}, + {type = .Integer, literal = "0"}, + {type = .Semicolon, literal = ";"}, + }, + output = ast.Binary_Expression { + operator = "/", + left = ast.Integer{value = 0}, + right = ast.Integer{value = 0}, + }, + }, + "greater than" = { + input = [dynamic]token.Token { + {type = .Integer, literal = "0"}, + {type = .Greater_Than, literal = ">"}, + {type = .Integer, literal = "0"}, + {type = .Semicolon, literal = ";"}, + }, + output = ast.Binary_Expression { + operator = ">", + left = ast.Integer{value = 0}, + right = ast.Integer{value = 0}, + }, + }, + "less than" = { + input = [dynamic]token.Token { + {type = .Integer, literal = "0"}, + {type = .Less_Than, literal = "<"}, + {type = .Integer, literal = "0"}, + {type = .Semicolon, literal = ";"}, + }, + output = ast.Binary_Expression { + operator = "<", + left = ast.Integer{value = 0}, + right = ast.Integer{value = 0}, + }, + }, + "equal" = { + input = [dynamic]token.Token { + {type = .Integer, literal = "0"}, + {type = .Equal, literal = "=="}, + {type = .Integer, literal = "0"}, + {type = .Semicolon, literal = ";"}, + }, + output = ast.Binary_Expression { + operator = "==", + left = ast.Integer{value = 0}, + right = ast.Integer{value = 0}, + }, + }, + "not equal" = { + input = [dynamic]token.Token { + {type = .Integer, literal = "0"}, + {type = .Not_Equal, literal = "!="}, + {type = .Integer, literal = "0"}, + {type = .Semicolon, literal = ";"}, + }, + output = ast.Binary_Expression { + operator = "!=", + left = ast.Integer{value = 0}, + right = ast.Integer{value = 0}, + }, + }, + } + defer free_all() + + for name, data in tests { + statements, errors := parsing.run(data.input[:]) + expect.value(t, name, len(statements), 1) + expect.value(t, name, len(errors), 0) + statement := statements[0].(ast.Expression_Statement) + expression := statement.expression.(^ast.Binary_Expression) + expect.value(t, name, expression^, data.output) + } +} -- tangled.sh