import "@std/mem" import "@std/testing" import "@std/arraylist" import "@source/diagnostics" import "@source/strpool" import "@source/lexer" import "@source/ast" parses_expression_precedence_through_tree_views test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: "value :: 1 + 2 * 3\n" scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(1, ast.package_decl_count(&tree)) declaration :: ast.package_decl(&tree, 0) statement :: ast.statement_decl(&tree, declaration) try testing.expect_equal("value", ast.source_text(&tree, statement.name)) try testing.expect_equal("value", strpool.get_str(&strings, ast.name_id(&tree, declaration))?) try testing.expect_equal(ast.ExprKind.binary, ast.expr_kind(&tree, statement.initializer)) add :: ast.binary_expr(&tree, statement.initializer) try testing.expect_equal(ast.BinaryOperator.add, add.operator) try testing.expect_equal(ast.ExprKind.literal_int, ast.expr_kind(&tree, add.left)) try testing.expect_equal(ast.ExprKind.binary, ast.expr_kind(&tree, add.right)) multiply :: ast.binary_expr(&tree, add.right) try testing.expect_equal(ast.BinaryOperator.multiply, multiply.operator) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) } parses_grouped_expression_precedence test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: "value :: a + ((b - c)) * 2 / 3\n" scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) try testing.expect_equal(1, ast.package_decl_count(&tree)) statement :: ast.statement_decl(&tree, ast.package_decl(&tree, 0)) add :: ast.binary_expr(&tree, statement.initializer) try testing.expect_equal(ast.BinaryOperator.add, add.operator) try testing.expect_equal("a", ast.source_text(&tree, ast.node_span(&tree, add.left))) divide :: ast.binary_expr(&tree, add.right) try testing.expect_equal(ast.BinaryOperator.divide, divide.operator) try testing.expect_equal("3", ast.source_text(&tree, ast.node_span(&tree, divide.right))) multiply :: ast.binary_expr(&tree, divide.left) try testing.expect_equal(ast.BinaryOperator.multiply, multiply.operator) try testing.expect_equal("2", ast.source_text(&tree, ast.node_span(&tree, multiply.right))) subtract :: ast.binary_expr(&tree, multiply.left) try testing.expect_equal(ast.BinaryOperator.subtract, subtract.operator) try testing.expect_equal("b", ast.source_text(&tree, ast.node_span(&tree, subtract.left))) try testing.expect_equal("c", ast.source_text(&tree, ast.node_span(&tree, subtract.right))) } parses_function_structure_through_tree_views test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `grouped proc(a, b T, c U) void { ` value int :: 1 `} scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) declaration :: ast.package_decl(&tree, 0) try testing.expect_equal(ast.DeclKind.function, ast.decl_kind(&tree, declaration)) function :: ast.function_decl(&tree, declaration) try testing.expect_equal("grouped", ast.source_text(&tree, function.name)) try testing.expect_equal("grouped", strpool.get_str(&strings, ast.name_id(&tree, declaration))?) try testing.expect_equal(3, ast.function_param_count(&tree, declaration)) first :: ast.param(&tree, ast.function_param(&tree, declaration, 0)) second :: ast.param(&tree, ast.function_param(&tree, declaration, 1)) third :: ast.param(&tree, ast.function_param(&tree, declaration, 2)) try testing.expect_equal("a", ast.source_text(&tree, first.name)) try testing.expect_equal("b", ast.source_text(&tree, second.name)) try testing.expect_equal("c", ast.source_text(&tree, third.name)) try testing.expect_equal( "a", strpool.get_str(&strings, ast.name_id(&tree, ast.function_param(&tree, declaration, 0)))?, ) try testing.expect_equal( "b", strpool.get_str(&strings, ast.name_id(&tree, ast.function_param(&tree, declaration, 1)))?, ) try testing.expect_equal( "c", strpool.get_str(&strings, ast.name_id(&tree, ast.function_param(&tree, declaration, 2)))?, ) try testing.expect_equal("T", strpool.get_str(&strings, ast.name_id(&tree, first.type))?) try testing.expect_equal("U", strpool.get_str(&strings, ast.name_id(&tree, third.type))?) try testing.expect_equal(first.type, second.type) try testing.expect(first.type != third.type) try testing.expect(!first.comptime) try testing.expect(!second.comptime) try testing.expect(!third.comptime) try testing.expect(!ast.function_generic(&tree, declaration)) try testing.expect_equal(1, ast.block_statement_count(&tree, function.body)) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) } parses_compile_time_parameter_groups_and_callbacks test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `generic proc($T type, $K, $V type, $N usize, $first, second T, $build proc(value T) T, legacy @proc(value T) T) T {} scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) declaration :: ast.package_decl(&tree, 0) try testing.expect(ast.function_generic(&tree, declaration)) expected [8]bool :: [true, true, true, true, true, false, true, false] try testing.expect_equal(expected.len, ast.function_param_count(&tree, declaration)) for (expected) |comptime, index| { parameter :: ast.param(&tree, ast.function_param(&tree, declaration, index)) try testing.expect_equal(comptime, parameter.comptime) } first :: ast.param(&tree, ast.function_param(&tree, declaration, 0)) key :: ast.param(&tree, ast.function_param(&tree, declaration, 1)) value :: ast.param(&tree, ast.function_param(&tree, declaration, 2)) count :: ast.param(&tree, ast.function_param(&tree, declaration, 3)) try testing.expect_equal("T", ast.source_text(&tree, first.name)) try testing.expect_equal("$T type", ast.source_text(&tree, first.span)) try testing.expect_equal("$K, $V type", ast.source_text(&tree, key.span)) try testing.expect_equal("$V type", ast.source_text(&tree, value.span)) try testing.expect_equal(key.type, value.type) try testing.expect_equal("usize", ast.source_text(&tree, ast.node_span(&tree, count.type))) callback :: ast.param(&tree, ast.function_param(&tree, declaration, 6)).type legacy :: ast.param(&tree, ast.function_param(&tree, declaration, 7)).type try testing.expect_equal(ast.ExprKind.function_type, ast.expr_kind(&tree, callback)) try testing.expect_equal(ast.ExprKind.function_type, ast.expr_kind(&tree, legacy)) try testing.expect_equal("proc(value T) T", ast.source_text(&tree, ast.node_span(&tree, callback))) try testing.expect_equal("@proc(value T) T", ast.source_text(&tree, ast.node_span(&tree, legacy))) try testing.expect_equal(1, ast.function_param_count(&tree, callback)) try testing.expect(!ast.param(&tree, ast.function_param(&tree, callback, 0)).comptime) } parses_dependent_type_calls_in_annotations_and_constructors test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `example proc($T type, $N usize, input library.Buffer(T, N)) library.ArrayList(T) { ` direct library.Buffer(T, N) :: library.Buffer(T, N){} ` chained library.Factory(T).Buffer(N) :: library.Factory(T).Buffer(N){} `} scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) declaration :: ast.package_decl(&tree, 0) function :: ast.function_decl(&tree, declaration) input :: ast.param(&tree, ast.function_param(&tree, declaration, 2)) try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, input.type)) try testing.expect_equal(2, ast.call_arg_count(&tree, input.type)) try testing.expect_equal("library.Buffer", ast.source_text(&tree, ast.node_span(&tree, ast.call_expr(&tree, input.type).callee))) try testing.expect_equal("T", ast.source_text(&tree, ast.node_span(&tree, ast.call_arg(&tree, input.type, 0)))) try testing.expect_equal("N", ast.source_text(&tree, ast.node_span(&tree, ast.call_arg(&tree, input.type, 1)))) try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, function.return_type)) try testing.expect_equal(1, ast.call_arg_count(&tree, function.return_type)) direct :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 0)) try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, direct.type?)) constructor :: ast.record_literal(&tree, direct.initializer).type? try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, constructor)) try testing.expect_equal("library.Buffer(T, N)", ast.source_text(&tree, ast.node_span(&tree, constructor))) chained :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 1)) selected :: ast.selection_expr(&tree, ast.call_expr(&tree, chained.type?).callee) try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, selected.base)) try testing.expect_equal("Buffer", ast.source_text(&tree, ast.node_span(&tree, selected.member))) try testing.expect_equal(1, ast.call_arg_count(&tree, chained.type?)) } recovers_invalid_declaration_and_preserves_sibling test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: "@ broken\nvalid :: 1\n" scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(2, ast.package_decl_count(&tree)) invalid_decl :: ast.package_decl(&tree, 0) valid_decl :: ast.package_decl(&tree, 1) try testing.expect_equal(ast.DeclKind.invalid, ast.decl_kind(&tree, invalid_decl)) try testing.expect_equal(ast.DeclKind.statement, ast.decl_kind(&tree, valid_decl)) invalid_view :: ast.invalid(&tree, invalid_decl) try testing.expect(diagnostics.get(&diagnostic_store, invalid_view.diagnostic) != null) valid_statement :: ast.statement_decl(&tree, valid_decl) try testing.expect_equal("valid", ast.source_text(&tree, valid_statement.name)) } recovers_malformed_groups_and_preserves_following_statements test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `main proc() void { ` empty :: () ` operand :: (1 + ) ` missing :: (2 ` valid :: (3) `} `after :: 4 scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(3, diagnostics.count(&diagnostic_store)) try testing.expect_equal(2, ast.package_decl_count(&tree)) function :: ast.function_decl(&tree, ast.package_decl(&tree, 0)) try testing.expect_equal(4, ast.block_statement_count(&tree, function.body)) empty :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 0)) try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, empty.initializer)) operand :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 1)) add :: ast.binary_expr(&tree, operand.initializer) try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, add.right)) missing :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 2)) try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, missing.initializer)) valid :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 3)) try testing.expect_equal(ast.ExprKind.literal_int, ast.expr_kind(&tree, valid.initializer)) try testing.expect_equal("3", ast.source_text(&tree, ast.node_span(&tree, valid.initializer))) after :: ast.statement_decl(&tree, ast.package_decl(&tree, 1)) try testing.expect_equal("after", ast.source_text(&tree, after.name)) try testing.expect_equal("4", ast.source_text(&tree, ast.node_span(&tree, after.initializer))) } parses_call_statements_and_postfix_precedence test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `main proc() void { ` ping() ` result int := -factory()(add(1, 2 * 3), (4 + 5),) + 6 ` (handler)(result) `} scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) function :: ast.function_decl(&tree, ast.package_decl(&tree, 0)) try testing.expect_equal(3, ast.block_statement_count(&tree, function.body)) ping_stmt :: ast.block_statement(&tree, function.body, 0) try testing.expect_equal(ast.StmtKind.expression, ast.stmt_kind(&tree, ping_stmt)) ping :: ast.statement_expr(&tree, ping_stmt) try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, ping)) try testing.expect_equal(0, ast.call_arg_count(&tree, ping)) try testing.expect_equal( "ping", ast.source_text(&tree, ast.node_span(&tree, ast.call_expr(&tree, ping).callee)), ) try testing.expect_equal("ping()", ast.source_text(&tree, ast.call_expr(&tree, ping).span)) result :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 1)) try testing.expect_equal(ast.Mutability.variable, result.mutability) add :: ast.binary_expr(&tree, result.initializer) try testing.expect_equal(ast.BinaryOperator.add, add.operator) try testing.expect_equal("6", ast.source_text(&tree, ast.node_span(&tree, add.right))) negate :: ast.unary_expr(&tree, add.left) call :: negate.operand try testing.expect_equal(2, ast.call_arg_count(&tree, call)) factory :: ast.call_expr(&tree, call).callee try testing.expect_equal(0, ast.call_arg_count(&tree, factory)) try testing.expect_equal( "factory", ast.source_text(&tree, ast.node_span(&tree, ast.call_expr(&tree, factory).callee)), ) nested :: ast.call_arg(&tree, call, 0) try testing.expect_equal(2, ast.call_arg_count(&tree, nested)) try testing.expect_equal( "add", ast.source_text(&tree, ast.node_span(&tree, ast.call_expr(&tree, nested).callee)), ) try testing.expect_equal( "1", ast.source_text(&tree, ast.node_span(&tree, ast.call_arg(&tree, nested, 0))), ) multiply :: ast.binary_expr(&tree, ast.call_arg(&tree, nested, 1)) try testing.expect_equal(ast.BinaryOperator.multiply, multiply.operator) try testing.expect_equal("2", ast.source_text(&tree, ast.node_span(&tree, multiply.left))) try testing.expect_equal("3", ast.source_text(&tree, ast.node_span(&tree, multiply.right))) grouped :: ast.binary_expr(&tree, ast.call_arg(&tree, call, 1)) try testing.expect_equal(ast.BinaryOperator.add, grouped.operator) try testing.expect_equal("4", ast.source_text(&tree, ast.node_span(&tree, grouped.left))) try testing.expect_equal("5", ast.source_text(&tree, ast.node_span(&tree, grouped.right))) handler :: ast.statement_expr(&tree, ast.block_statement(&tree, function.body, 2)) try testing.expect_equal( "handler", ast.source_text(&tree, ast.node_span(&tree, ast.call_expr(&tree, handler).callee)), ) try testing.expect_equal(1, ast.call_arg_count(&tree, handler)) try testing.expect_equal( "result", ast.source_text(&tree, ast.node_span(&tree, ast.call_arg(&tree, handler, 0))), ) } parses_namespace_selection_with_calls_indices_and_prefixes test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: "main proc() int { return -library.factory()[0].member(1) + 2 }" scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) function :: ast.function_decl(&tree, ast.package_decl(&tree, 0)) returned :: ast.return_stmt(&tree, ast.block_statement(&tree, function.body, 0)).value? binary :: ast.binary_expr(&tree, returned) try testing.expect_equal(ast.BinaryOperator.add, binary.operator) call :: ast.unary_expr(&tree, binary.left).operand selected :: ast.selection_expr(&tree, ast.call_expr(&tree, call).callee) try testing.expect_equal("member", ast.source_text(&tree, ast.node_span(&tree, selected.member))) index :: ast.index_expr(&tree, selected.base) factory :: ast.selection_expr(&tree, ast.call_expr(&tree, index.base).callee) try testing.expect_equal("library", ast.source_text(&tree, ast.node_span(&tree, factory.base))) try testing.expect_equal("factory", ast.source_text(&tree, ast.node_span(&tree, factory.member))) try testing.expect_equal("library.factory()[0].member", ast.source_text(&tree, selected.span)) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) } recovers_malformed_calls_and_preserves_siblings test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `main proc() void { ` bad(1 2) ` good() ` missing(1 ` after :: 3 ` holes(, 2) `} `tail :: f() scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(3, diagnostics.count(&diagnostic_store)) try testing.expect_equal(2, ast.package_decl_count(&tree)) function :: ast.function_decl(&tree, ast.package_decl(&tree, 0)) try testing.expect_equal(5, ast.block_statement_count(&tree, function.body)) for ([0, 2]) |index| { statement :: ast.block_statement(&tree, function.body, usize(index)) try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, statement)) try testing.expect( diagnostics.get(&diagnostic_store, ast.invalid(&tree, statement).diagnostic) != null, ) } good :: ast.statement_expr(&tree, ast.block_statement(&tree, function.body, 1)) try testing.expect_equal( "good", ast.source_text(&tree, ast.node_span(&tree, ast.call_expr(&tree, good).callee)), ) after :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 3)) try testing.expect_equal("after", ast.source_text(&tree, after.name)) holes :: ast.statement_expr(&tree, ast.block_statement(&tree, function.body, 4)) try testing.expect_equal(2, ast.call_arg_count(&tree, holes)) try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, ast.call_arg(&tree, holes, 0))) try testing.expect_equal( "2", ast.source_text(&tree, ast.node_span(&tree, ast.call_arg(&tree, holes, 1))), ) tail :: ast.statement_decl(&tree, ast.package_decl(&tree, 1)) try testing.expect_equal("tail", ast.source_text(&tree, tail.name)) try testing.expect_equal(0, ast.call_arg_count(&tree, tail.initializer)) } parses_return_boundaries_and_recovers_trailing_tokens test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `return 1 `main proc() void { ` return ` 2 ` return (3 + 4) ` return 5 stray ` after :: 6 ` return } scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(2, ast.package_decl_count(&tree)) try testing.expect_equal(ast.DeclKind.invalid, ast.decl_kind(&tree, ast.package_decl(&tree, 0))) body :: ast.function_decl(&tree, ast.package_decl(&tree, 1)).body try testing.expect_equal(6, ast.block_statement_count(&tree, body)) first :: ast.block_statement(&tree, body, 0) try testing.expect_equal(ast.StmtKind.return_stmt, ast.stmt_kind(&tree, first)) try testing.expect(ast.return_stmt(&tree, first).value == null) try testing.expect_equal("return", ast.source_text(&tree, ast.return_stmt(&tree, first).span)) try testing.expect_equal(ast.StmtKind.expression, ast.stmt_kind(&tree, ast.block_statement(&tree, body, 1))) grouped :: ast.return_stmt(&tree, ast.block_statement(&tree, body, 2)) try testing.expect_equal("return (3 + 4)", ast.source_text(&tree, grouped.span)) if (grouped.value) |value| { try testing.expect_equal(ast.ExprKind.binary, ast.expr_kind(&tree, value)) } else { return .expectation_failed } try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, ast.block_statement(&tree, body, 3))) after :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 4)) try testing.expect_equal("after", ast.source_text(&tree, after.name)) last :: ast.return_stmt(&tree, ast.block_statement(&tree, body, 5)) try testing.expect(last.value == null) try testing.expect_equal("return", ast.source_text(&tree, last.span)) try testing.expect_equal(2, diagnostics.count(&diagnostic_store)) } parses_boolean_comparison_precedence_and_associativity test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `value :: -f() * 2 + 3 < 4 <= 5 > 6 >= 7 == true != false scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) different :: ast.binary_expr(&tree, ast.statement_decl(&tree, ast.package_decl(&tree, 0)).initializer) try testing.expect_equal(ast.BinaryOperator.not_equal, different.operator) try testing.expect_equal(ast.ExprKind.literal_bool, ast.expr_kind(&tree, different.right)) try testing.expect_equal("false", ast.source_text(&tree, ast.node_span(&tree, different.right))) equal :: ast.binary_expr(&tree, different.left) try testing.expect_equal(ast.BinaryOperator.equal, equal.operator) try testing.expect_equal(ast.ExprKind.literal_bool, ast.expr_kind(&tree, equal.right)) relation := equal.left operators [4]ast.BinaryOperator :: [.greater_equal, .greater, .less_equal, .less] for (operators) |operator| { binary :: ast.binary_expr(&tree, relation) try testing.expect_equal(operator, binary.operator) relation = binary.left } addition :: ast.binary_expr(&tree, relation) try testing.expect_equal(ast.BinaryOperator.add, addition.operator) product :: ast.binary_expr(&tree, addition.left) try testing.expect_equal(ast.BinaryOperator.multiply, product.operator) negate :: ast.unary_expr(&tree, product.left) try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, negate.operand)) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) } parses_factorial_and_canonical_conditional_arms test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `factorial proc(n int) int { ` if (n <= 1) return 1 else return n * factorial(n - 1) `} `main proc() int { ` if true { ` local :: factorial(5) ` if (false) return 0 else return local ` } ` else if ready() return 2 else { return 3 } `} scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) factorial :: ast.function_decl(&tree, ast.package_decl(&tree, 0)) try testing.expect_equal(1, ast.block_statement_count(&tree, factorial.body)) guard :: ast.if_stmt(&tree, ast.block_statement(&tree, factorial.body, 0)) try testing.expect_equal("n <= 1", ast.source_text(&tree, ast.node_span(&tree, guard.condition))) try testing.expect_equal(1, ast.block_statement_count(&tree, guard.then_block)) try testing.expect_equal("return 1", ast.source_text(&tree, ast.return_stmt(&tree, ast.block_statement(&tree, guard.then_block, 0)).span)) try testing.expect_equal(1, ast.block_statement_count(&tree, guard.else_block?)) try testing.expect_equal(ast.StmtKind.return_stmt, ast.stmt_kind(&tree, ast.block_statement(&tree, guard.else_block?, 0))) main :: ast.function_decl(&tree, ast.package_decl(&tree, 1)) outer :: ast.if_stmt(&tree, ast.block_statement(&tree, main.body, 0)) try testing.expect_equal(2, ast.block_statement_count(&tree, outer.then_block)) try testing.expect_equal(ast.StmtKind.declaration, ast.stmt_kind(&tree, ast.block_statement(&tree, outer.then_block, 0))) nested :: ast.if_stmt(&tree, ast.block_statement(&tree, outer.then_block, 1)) try testing.expect(nested.else_block != null) try testing.expect_equal(1, ast.block_statement_count(&tree, outer.else_block?)) chain :: ast.if_stmt(&tree, ast.block_statement(&tree, outer.else_block?, 0)) try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, chain.condition)) try testing.expect(chain.else_block != null) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) } attaches_dangling_else_to_nearest_if test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: "main proc() int { if (true) if (false) return 1 else return 2 else return 3 }" scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) body :: ast.function_decl(&tree, ast.package_decl(&tree, 0)).body outer :: ast.if_stmt(&tree, ast.block_statement(&tree, body, 0)) inner :: ast.if_stmt(&tree, ast.block_statement(&tree, outer.then_block, 0)) inner_return :: ast.return_stmt(&tree, ast.block_statement(&tree, inner.else_block?, 0)) outer_return :: ast.return_stmt(&tree, ast.block_statement(&tree, outer.else_block?, 0)) try testing.expect_equal("2", ast.source_text(&tree, ast.node_span(&tree, inner_return.value?))) try testing.expect_equal("3", ast.source_text(&tree, ast.node_span(&tree, outer_return.value?))) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) } recovers_conditional_conditions_and_arms_without_losing_siblings test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `main proc() void { ` if { good() } else { other() } ` if (true) else return ` if broken( { kept() } ` after :: 7 ` return 1 else return 2 ` final :: 8 `} `tail :: 9 scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(2, ast.package_decl_count(&tree)) body :: ast.function_decl(&tree, ast.package_decl(&tree, 0)).body try testing.expect_equal(6, ast.block_statement_count(&tree, body)) first :: ast.if_stmt(&tree, ast.block_statement(&tree, body, 0)) try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, first.condition)) condition_diagnostic :: diagnostics.get(&diagnostic_store, ast.invalid(&tree, first.condition).diagnostic)? try testing.expect_equal("{", ast.source_text(&tree, condition_diagnostic.span)) try testing.expect_equal(1, ast.block_statement_count(&tree, first.then_block)) second :: ast.if_stmt(&tree, ast.block_statement(&tree, body, 1)) bad_arm :: ast.block_statement(&tree, second.then_block, 0) try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, bad_arm)) arm_diagnostic :: diagnostics.get(&diagnostic_store, ast.invalid(&tree, bad_arm).diagnostic)? try testing.expect_equal("else", ast.source_text(&tree, arm_diagnostic.span)) try testing.expect(second.else_block != null) third :: ast.if_stmt(&tree, ast.block_statement(&tree, body, 2)) try testing.expect_equal(1, ast.block_statement_count(&tree, third.then_block)) after :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 3)) try testing.expect_equal("after", ast.source_text(&tree, after.name)) try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, ast.block_statement(&tree, body, 4))) final :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 5)) try testing.expect_equal("final", ast.source_text(&tree, final.name)) try testing.expect_equal(4, diagnostics.count(&diagnostic_store)) } recovers_invalid_guard_syntax_and_missing_arm_at_enclosing_brace test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `main proc() int { ` if true return 1 else return 2 ` if (true junk) { kept() } else { other() } ` sibling :: 3 ` if (false) } `tail :: 4 scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(2, ast.package_decl_count(&tree)) body :: ast.function_decl(&tree, ast.package_decl(&tree, 0)).body try testing.expect_equal(4, ast.block_statement_count(&tree, body)) first :: ast.if_stmt(&tree, ast.block_statement(&tree, body, 0)) try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, first.condition)) guard_error :: diagnostics.get(&diagnostic_store, ast.invalid(&tree, first.condition).diagnostic)? try testing.expect_equal("return", ast.source_text(&tree, guard_error.span)) try testing.expect(first.else_block != null) second :: ast.if_stmt(&tree, ast.block_statement(&tree, body, 1)) try testing.expect_equal(1, ast.block_statement_count(&tree, second.then_block)) try testing.expect(second.else_block != null) sibling :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 2)) try testing.expect_equal("sibling", ast.source_text(&tree, sibling.name)) last :: ast.if_stmt(&tree, ast.block_statement(&tree, body, 3)) missing :: ast.block_statement(&tree, last.then_block, 0) try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, missing)) arm_error :: diagnostics.get(&diagnostic_store, ast.invalid(&tree, missing).diagnostic)? try testing.expect_equal("}", ast.source_text(&tree, arm_error.span)) try testing.expect_equal(3, diagnostics.count(&diagnostic_store)) } parses_iterative_factorial_assignments_and_loop test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `factorial proc(n int) int { ` remaining := n ` product := 1 ` while remaining > 1 { ` product = product * remaining ` remaining = remaining - 1 ` } ` return product `} scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) body :: ast.function_decl(&tree, ast.package_decl(&tree, 0)).body try testing.expect_equal(4, ast.block_statement_count(&tree, body)) loop_id :: ast.block_statement(&tree, body, 2) try testing.expect_equal(ast.StmtKind.while_stmt, ast.stmt_kind(&tree, loop_id)) loop :: ast.while_stmt(&tree, loop_id) try testing.expect_equal(ast.BinaryOperator.greater, ast.binary_expr(&tree, loop.condition).operator) try testing.expect_equal(2, ast.block_statement_count(&tree, loop.body)) update_id :: ast.block_statement(&tree, loop.body, 0) try testing.expect_equal(ast.StmtKind.assignment_stmt, ast.stmt_kind(&tree, update_id)) update :: ast.assignment_stmt(&tree, update_id) try testing.expect_equal(ast.ExprKind.identifier, ast.expr_kind(&tree, update.target)) try testing.expect_equal("product", ast.source_text(&tree, ast.node_span(&tree, update.target))) try testing.expect_equal(ast.BinaryOperator.multiply, ast.binary_expr(&tree, update.value).operator) decrement :: ast.assignment_stmt(&tree, ast.block_statement(&tree, loop.body, 1)) try testing.expect_equal(ast.BinaryOperator.subtract, ast.binary_expr(&tree, decrement.value).operator) } parses_assignment_precedence_grouped_span_and_rhs_boundaries test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `main proc() void { ` value = (1 + 2 * 3) ` if (true) value = 4 else value = 5 ` while ready() value = value - 1 ` value = 6 } scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) body :: ast.function_decl(&tree, ast.package_decl(&tree, 0)).body try testing.expect_equal(4, ast.block_statement_count(&tree, body)) assignment :: ast.assignment_stmt(&tree, ast.block_statement(&tree, body, 0)) try testing.expect_equal("value = (1 + 2 * 3)", ast.source_text(&tree, assignment.span)) add :: ast.binary_expr(&tree, assignment.value) try testing.expect_equal(ast.BinaryOperator.add, add.operator) try testing.expect_equal(ast.BinaryOperator.multiply, ast.binary_expr(&tree, add.right).operator) conditional :: ast.if_stmt(&tree, ast.block_statement(&tree, body, 1)) first :: ast.assignment_stmt(&tree, ast.block_statement(&tree, conditional.then_block, 0)) second :: ast.assignment_stmt(&tree, ast.block_statement(&tree, conditional.else_block?, 0)) try testing.expect_equal("4", ast.source_text(&tree, ast.node_span(&tree, first.value))) try testing.expect_equal("5", ast.source_text(&tree, ast.node_span(&tree, second.value))) loop :: ast.while_stmt(&tree, ast.block_statement(&tree, body, 2)) try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, loop.condition)) try testing.expect_equal(1, ast.block_statement_count(&tree, loop.body)) try testing.expect_equal(ast.StmtKind.assignment_stmt, ast.stmt_kind(&tree, ast.block_statement(&tree, loop.body, 0))) last :: ast.assignment_stmt(&tree, ast.block_statement(&tree, body, 3)) try testing.expect_equal("6", ast.source_text(&tree, ast.node_span(&tree, last.value))) } parses_nested_loops_and_preserves_nearest_if_else test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `main proc() void { ` if (true) while (false) if (true) x = 1 else x = 2 else x = 3 ` while (true) while (false) x = 4 ` while (true) { ` while false { x = 5 } ` } `} scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) body :: ast.function_decl(&tree, ast.package_decl(&tree, 0)).body try testing.expect_equal(3, ast.block_statement_count(&tree, body)) outer :: ast.if_stmt(&tree, ast.block_statement(&tree, body, 0)) loop :: ast.while_stmt(&tree, ast.block_statement(&tree, outer.then_block, 0)) inner :: ast.if_stmt(&tree, ast.block_statement(&tree, loop.body, 0)) inner_else :: ast.assignment_stmt(&tree, ast.block_statement(&tree, inner.else_block?, 0)) outer_else :: ast.assignment_stmt(&tree, ast.block_statement(&tree, outer.else_block?, 0)) try testing.expect_equal("2", ast.source_text(&tree, ast.node_span(&tree, inner_else.value))) try testing.expect_equal("3", ast.source_text(&tree, ast.node_span(&tree, outer_else.value))) braceless :: ast.while_stmt(&tree, ast.block_statement(&tree, body, 1)) nested :: ast.while_stmt(&tree, ast.block_statement(&tree, braceless.body, 0)) try testing.expect_equal(1, ast.block_statement_count(&tree, nested.body)) braced :: ast.while_stmt(&tree, ast.block_statement(&tree, body, 2)) child :: ast.while_stmt(&tree, ast.block_statement(&tree, braced.body, 0)) try testing.expect_equal(ast.ExprKind.literal_bool, ast.expr_kind(&tree, child.condition)) try testing.expect_equal(1, ast.block_statement_count(&tree, child.body)) } recovers_loop_guards_bodies_and_assignments_without_losing_braces test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `main proc() void { ` while { x = 1 } ` while true x = 2 ` while broken( { x = 3 } ` x = ` x = 1 junk ` if (true) x = else x = 4 ` kept :: 7 ` while (false) } `tail :: 8 scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect(diagnostics.count(&diagnostic_store) > 0) try testing.expect_equal(2, ast.package_decl_count(&tree)) body :: ast.function_decl(&tree, ast.package_decl(&tree, 0)).body try testing.expect_equal(8, ast.block_statement_count(&tree, body)) for 0..3 |index| { loop :: ast.while_stmt(&tree, ast.block_statement(&tree, body, usize(index))) try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, loop.condition)) try testing.expect_equal(1, ast.block_statement_count(&tree, loop.body)) try testing.expect_equal(ast.StmtKind.assignment_stmt, ast.stmt_kind(&tree, ast.block_statement(&tree, loop.body, 0))) } missing :: ast.assignment_stmt(&tree, ast.block_statement(&tree, body, 3)) try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, missing.value)) try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, ast.block_statement(&tree, body, 4))) conditional :: ast.if_stmt(&tree, ast.block_statement(&tree, body, 5)) missing_arm :: ast.assignment_stmt(&tree, ast.block_statement(&tree, conditional.then_block, 0)) try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, missing_arm.value)) try testing.expect(conditional.else_block != null) kept :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 6)) try testing.expect_equal("kept", ast.source_text(&tree, kept.name)) last :: ast.while_stmt(&tree, ast.block_statement(&tree, body, 7)) try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, ast.block_statement(&tree, last.body, 0))) tail :: ast.statement_decl(&tree, ast.package_decl(&tree, 1)) try testing.expect_equal("tail", ast.source_text(&tree, tail.name)) } rejects_top_level_assignment_loop_else_and_assignment_expressions test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `x = 1 `main proc() void { ` while (true) x = 2 else x = 3 ` x = y = 4 ` kept :: 5 `} `tail :: 6 scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect(diagnostics.count(&diagnostic_store) > 0) try testing.expect_equal(3, ast.package_decl_count(&tree)) try testing.expect_equal(ast.DeclKind.invalid, ast.decl_kind(&tree, ast.package_decl(&tree, 0))) body :: ast.function_decl(&tree, ast.package_decl(&tree, 1)).body try testing.expect_equal(4, ast.block_statement_count(&tree, body)) loop :: ast.while_stmt(&tree, ast.block_statement(&tree, body, 0)) try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, ast.block_statement(&tree, loop.body, 0))) try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, ast.block_statement(&tree, body, 1))) try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, ast.block_statement(&tree, body, 2))) kept :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 3)) try testing.expect_equal("kept", ast.source_text(&tree, kept.name)) } parses_logical_precedence_and_associativity test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: "value :: !probe() == a + b * c < d and e and f or g or h\n" scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) value :: ast.statement_decl(&tree, ast.package_decl(&tree, 0)) outer_or :: ast.binary_expr(&tree, value.initializer) try testing.expect_equal(ast.BinaryOperator.logical_or, outer_or.operator) try testing.expect_equal("h", ast.source_text(&tree, ast.node_span(&tree, outer_or.right))) inner_or :: ast.binary_expr(&tree, outer_or.left) try testing.expect_equal(ast.BinaryOperator.logical_or, inner_or.operator) outer_and :: ast.binary_expr(&tree, inner_or.left) try testing.expect_equal(ast.BinaryOperator.logical_and, outer_and.operator) try testing.expect_equal("f", ast.source_text(&tree, ast.node_span(&tree, outer_and.right))) inner_and :: ast.binary_expr(&tree, outer_and.left) try testing.expect_equal(ast.BinaryOperator.logical_and, inner_and.operator) equality :: ast.binary_expr(&tree, inner_and.left) try testing.expect_equal(ast.BinaryOperator.equal, equality.operator) logical_not :: ast.unary_expr(&tree, equality.left) try testing.expect_equal(ast.UnaryOperator.logical_not, logical_not.operator) try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, logical_not.operand)) try testing.expect_equal("!probe()", ast.source_text(&tree, logical_not.span)) comparison :: ast.binary_expr(&tree, equality.right) try testing.expect_equal(ast.BinaryOperator.less, comparison.operator) addition :: ast.binary_expr(&tree, comparison.left) try testing.expect_equal(ast.BinaryOperator.add, addition.operator) multiply :: ast.binary_expr(&tree, addition.right) try testing.expect_equal(ast.BinaryOperator.multiply, multiply.operator) } parses_right_associative_mixed_prefix_operators test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: "value :: !-!probe()\nother :: -!false\n" scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) value :: ast.statement_decl(&tree, ast.package_decl(&tree, 0)) first :: ast.unary_expr(&tree, value.initializer) second :: ast.unary_expr(&tree, first.operand) third :: ast.unary_expr(&tree, second.operand) try testing.expect_equal(ast.UnaryOperator.logical_not, first.operator) try testing.expect_equal(ast.UnaryOperator.negate, second.operator) try testing.expect_equal(ast.UnaryOperator.logical_not, third.operator) try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, third.operand)) try testing.expect_equal("!-!probe()", ast.source_text(&tree, first.span)) try testing.expect_equal("-!probe()", ast.source_text(&tree, second.span)) other :: ast.statement_decl(&tree, ast.package_decl(&tree, 1)) negate :: ast.unary_expr(&tree, other.initializer) try testing.expect_equal(ast.UnaryOperator.negate, negate.operator) try testing.expect_equal(ast.UnaryOperator.logical_not, ast.unary_expr(&tree, negate.operand).operator) } parses_logical_expressions_in_all_expression_positions test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `global :: ( ` false ` and absent `) `main proc() bool { ` value := ( ` true ` or absent ` ) ` value = ( ` ! ` value ` and ready( ` ) ` ) ` !value ` if ( ` !value ` or ready() ` ) { value = false } ` while ( ` value ` and !done() ` ) { value = false } ` return ( ` choose( ` ( ` value ` or false ` ), ` nested( ` !value ` and true ` ), ` ) ` ) `} scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) global :: ast.statement_decl(&tree, ast.package_decl(&tree, 0)) global_and :: ast.binary_expr(&tree, global.initializer) try testing.expect_equal(ast.BinaryOperator.logical_and, global_and.operator) try testing.expect_equal("absent", ast.source_text(&tree, ast.node_span(&tree, global_and.right))) function :: ast.function_decl(&tree, ast.package_decl(&tree, 1)) try testing.expect_equal(6, ast.block_statement_count(&tree, function.body)) local :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 0)) try testing.expect_equal(ast.BinaryOperator.logical_or, ast.binary_expr(&tree, local.initializer).operator) assignment :: ast.assignment_stmt(&tree, ast.block_statement(&tree, function.body, 1)) try testing.expect_equal(ast.BinaryOperator.logical_and, ast.binary_expr(&tree, assignment.value).operator) expression :: ast.statement_expr(&tree, ast.block_statement(&tree, function.body, 2)) try testing.expect_equal(ast.UnaryOperator.logical_not, ast.unary_expr(&tree, expression).operator) conditional :: ast.if_stmt(&tree, ast.block_statement(&tree, function.body, 3)) try testing.expect_equal(ast.BinaryOperator.logical_or, ast.binary_expr(&tree, conditional.condition).operator) loop :: ast.while_stmt(&tree, ast.block_statement(&tree, function.body, 4)) try testing.expect_equal(ast.BinaryOperator.logical_and, ast.binary_expr(&tree, loop.condition).operator) returned :: ast.return_stmt(&tree, ast.block_statement(&tree, function.body, 5)).value? try testing.expect_equal(2, ast.call_arg_count(&tree, returned)) first_argument :: ast.call_arg(&tree, returned, 0) try testing.expect_equal(ast.BinaryOperator.logical_or, ast.binary_expr(&tree, first_argument).operator) nested :: ast.call_arg(&tree, returned, 1) nested_argument :: ast.call_arg(&tree, nested, 0) try testing.expect_equal(ast.BinaryOperator.logical_and, ast.binary_expr(&tree, nested_argument).operator) } multiline_group_errors_preserve_statement_boundaries test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `main proc() void { ` value := ( ` 1 + ` next int :: 2 ` value = 3 ` return ( ` true `} `tail :: 9 scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(3, diagnostics.count(&diagnostic_store)) try testing.expect_equal(2, ast.package_decl_count(&tree)) body :: ast.function_decl(&tree, ast.package_decl(&tree, 0)).body try testing.expect_equal(4, ast.block_statement_count(&tree, body)) next :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 1)) try testing.expect_equal("next", ast.source_text(&tree, next.name)) assignment :: ast.assignment_stmt(&tree, ast.block_statement(&tree, body, 2)) try testing.expect_equal("3", ast.source_text(&tree, ast.node_span(&tree, assignment.value))) try testing.expect_equal(ast.StmtKind.return_stmt, ast.stmt_kind(&tree, ast.block_statement(&tree, body, 3))) tail :: ast.statement_decl(&tree, ast.package_decl(&tree, 1)) try testing.expect_equal("tail", ast.source_text(&tree, tail.name)) } recovers_missing_logical_operands_and_preserves_enclosing_nodes test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `main proc() bool { ` first :: true and ` second :: true and or false ` third :: choose(!, false or) ` if ! { return false } ` return true or } `after :: true scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(2, ast.package_decl_count(&tree)) function :: ast.function_decl(&tree, ast.package_decl(&tree, 0)) try testing.expect_equal(5, ast.block_statement_count(&tree, function.body)) first :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 0)) first_and :: ast.binary_expr(&tree, first.initializer) try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, first_and.right)) second :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 1)) second_or :: ast.binary_expr(&tree, second.initializer) try testing.expect_equal(ast.BinaryOperator.logical_or, second_or.operator) second_and :: ast.binary_expr(&tree, second_or.left) try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, second_and.right)) missing :: ast.invalid(&tree, second_and.right) diagnostic :: diagnostics.get(&diagnostic_store, missing.diagnostic)? try testing.expect_equal(diagnostics.Code.unexpected_token, diagnostic.code) try testing.expect_equal(missing.span.start, diagnostic.span.start) try testing.expect_equal(missing.span.end, diagnostic.span.end) try testing.expect_equal(ast.ExprKind.literal_bool, ast.expr_kind(&tree, second_or.right)) third :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 2)) try testing.expect_equal(2, ast.call_arg_count(&tree, third.initializer)) missing_prefix :: ast.unary_expr(&tree, ast.call_arg(&tree, third.initializer, 0)) try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, missing_prefix.operand)) missing_rhs :: ast.binary_expr(&tree, ast.call_arg(&tree, third.initializer, 1)) try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, missing_rhs.right)) conditional :: ast.if_stmt(&tree, ast.block_statement(&tree, function.body, 3)) try testing.expect_equal(1, ast.block_statement_count(&tree, conditional.then_block)) returned :: ast.return_stmt(&tree, ast.block_statement(&tree, function.body, 4)).value? try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, ast.binary_expr(&tree, returned).right)) after :: ast.statement_decl(&tree, ast.package_decl(&tree, 1)) try testing.expect_equal("after", ast.source_text(&tree, after.name)) } parses_leaf_loop_controls_and_nearest_else test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `main proc() void { ` while (true) { ` if (true) if (false) break else continue ` while (false) continue ` break ` } ` while (false) if (true) break else continue `} scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) body :: ast.function_decl(&tree, ast.package_decl(&tree, 0)).body try testing.expect_equal(2, ast.block_statement_count(&tree, body)) loop :: ast.while_stmt(&tree, ast.block_statement(&tree, body, 0)) try testing.expect_equal(3, ast.block_statement_count(&tree, loop.body)) outer_if :: ast.if_stmt(&tree, ast.block_statement(&tree, loop.body, 0)) try testing.expect(outer_if.else_block == null) inner_if :: ast.if_stmt(&tree, ast.block_statement(&tree, outer_if.then_block, 0)) transfer :: ast.block_statement(&tree, inner_if.then_block, 0) try testing.expect_equal(ast.StmtKind.break_stmt, ast.stmt_kind(&tree, transfer)) try testing.expect_equal("break", ast.source_text(&tree, ast.node_span(&tree, transfer))) if (inner_if.else_block) |other| { alternate :: ast.block_statement(&tree, other, 0) try testing.expect_equal(ast.StmtKind.continue_stmt, ast.stmt_kind(&tree, alternate)) try testing.expect_equal("continue", ast.source_text(&tree, ast.node_span(&tree, alternate))) } else return .expectation_failed nested :: ast.while_stmt(&tree, ast.block_statement(&tree, loop.body, 1)) try testing.expect_equal(ast.StmtKind.continue_stmt, ast.stmt_kind(&tree, ast.block_statement(&tree, nested.body, 0))) braceless :: ast.while_stmt(&tree, ast.block_statement(&tree, body, 1)) conditional :: ast.if_stmt(&tree, ast.block_statement(&tree, braceless.body, 0)) try testing.expect(conditional.else_block != null) } rejects_control_tails_and_preserves_following_declarations test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `break `continue `bad :: break `bad_continue :: continue `main proc() void { ` while (true) { ` break 1 ` first :: 1 ` continue label ` second :: 2 ` if (true) break label else continue ` if (true) continue 2 else break ` break 3 } ` after :: 3 `} `last :: 4 scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(6, ast.package_decl_count(&tree)) for (usize(0)..usize(2)) |index| try testing.expect_equal(ast.DeclKind.invalid, ast.decl_kind(&tree, ast.package_decl(&tree, index))) for (usize(2)..usize(4)) |index| { declaration :: ast.package_decl(&tree, index) try testing.expect_equal(ast.DeclKind.statement, ast.decl_kind(&tree, declaration)) try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, ast.statement_decl(&tree, declaration).initializer)) invalid :: ast.invalid(&tree, ast.statement_decl(&tree, declaration).initializer) diagnostic :: diagnostics.get(&diagnostic_store, invalid.diagnostic)? try testing.expect_equal(diagnostics.Code.unexpected_token, diagnostic.code) try testing.expect_equal(invalid.span.start, diagnostic.span.start) try testing.expect_equal(invalid.span.end, diagnostic.span.end) } body :: ast.function_decl(&tree, ast.package_decl(&tree, 4)).body try testing.expect_equal(2, ast.block_statement_count(&tree, body)) loop :: ast.while_stmt(&tree, ast.block_statement(&tree, body, 0)) try testing.expect_equal(7, ast.block_statement_count(&tree, loop.body)) for ([usize(1), usize(3)]) |index| { try testing.expect_equal(ast.StmtKind.declaration, ast.stmt_kind(&tree, ast.block_statement(&tree, loop.body, index))) } conditional :: ast.if_stmt(&tree, ast.block_statement(&tree, loop.body, 4)) try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, ast.block_statement(&tree, conditional.then_block, 0))) if (conditional.else_block) |other| { try testing.expect_equal(ast.StmtKind.continue_stmt, ast.stmt_kind(&tree, ast.block_statement(&tree, other, 0))) } else return .expectation_failed try testing.expect_equal(ast.StmtKind.declaration, ast.stmt_kind(&tree, ast.block_statement(&tree, body, 1))) try testing.expect_equal(ast.DeclKind.statement, ast.decl_kind(&tree, ast.package_decl(&tree, 5))) } preserves_control_bodies_after_malformed_guards test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `main proc() void { ` while (true break ` while predicate( continue ` while (true) { continue } ` after :: 1 `} scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(2, diagnostics.count(&diagnostic_store)) body :: ast.function_decl(&tree, ast.package_decl(&tree, 0)).body try testing.expect_equal(4, ast.block_statement_count(&tree, body)) expected [3]ast.StmtKind :: [.break_stmt, .continue_stmt, .continue_stmt] for (expected) |kind, index| { loop :: ast.while_stmt(&tree, ast.block_statement(&tree, body, index)) try testing.expect_equal(kind, ast.stmt_kind(&tree, ast.block_statement(&tree, loop.body, 0))) if (index < 2) try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, loop.condition)) } try testing.expect_equal(ast.StmtKind.declaration, ast.stmt_kind(&tree, ast.block_statement(&tree, body, 3))) } parses_range_endpoints_and_capture_shapes test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `stored :: (1 + 2)..=(limit() * 2) `run proc() void { ` for (-2)..1 |item| { item } ` for (0..10) |_| continue ` for 0..10 |@item, _| {} ` for 0..10 |item, index| { index } ` for ` 0..10 ` |item| ` { item } `} scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) stored :: ast.statement_decl(&tree, ast.package_decl(&tree, 0)).initializer interval :: ast.range_expr(&tree, stored) try testing.expect(interval.inclusive) try testing.expect_equal(ast.ExprKind.binary, ast.expr_kind(&tree, interval.start)) try testing.expect_equal(ast.ExprKind.binary, ast.expr_kind(&tree, interval.end)) body :: ast.function_decl(&tree, ast.package_decl(&tree, 1)).body try testing.expect_equal(5, ast.block_statement_count(&tree, body)) first :: ast.for_stmt(&tree, ast.block_statement(&tree, body, 0)) if (first.capture) |capture| { try testing.expect_equal(ast.ExprKind.identifier, ast.expr_kind(&tree, capture)) try testing.expect_equal("item", ast.source_text(&tree, ast.node_span(&tree, capture))) try testing.expect_equal("item", strpool.get_str(&strings, ast.name_id(&tree, capture))?) reference :: ast.statement_expr(&tree, ast.block_statement(&tree, first.body, 0)) try testing.expect_equal(ast.name_id(&tree, capture), ast.name_id(&tree, reference)) } else return .expectation_failed second :: ast.for_stmt(&tree, ast.block_statement(&tree, body, 1)) try testing.expect(second.capture == null) try testing.expect_equal(ast.StmtKind.continue_stmt, ast.stmt_kind( &tree, ast.block_statement(&tree, second.body, 0), )) third :: ast.for_stmt(&tree, ast.block_statement(&tree, body, 2)) try testing.expect(third.pointer_capture and third.index_capture and third.index == null) fourth :: ast.for_stmt(&tree, ast.block_statement(&tree, body, 3)) try testing.expect(fourth.index_capture and fourth.index != null) try testing.expect_equal(ast.name_id(&tree, first.capture?), ast.name_id(&tree, third.capture?)) try testing.expect_equal(ast.name_id(&tree, first.capture?), ast.name_id(&tree, fourth.capture?)) try testing.expect_equal("index", strpool.get_str(&strings, ast.name_id(&tree, fourth.index?))?) index_reference :: ast.statement_expr(&tree, ast.block_statement(&tree, fourth.body, 0)) try testing.expect_equal(ast.name_id(&tree, fourth.index?), ast.name_id(&tree, index_reference)) } rejects_bro_invalid_range_headers_without_losing_siblings test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) cases [13][]u8 :: [ "for -2..1 |i| {}", "for 0..limit+1 |i| {}", "for (0..limit+1) |i| {}", "for 0..10 |i| continue", "for (0)..10 |i| continue", "for 0..10 {}", "for 0..10 || {}", "for 0..10 |i int| {}", "for 0..10 |i,| {}", "for 0..10 |i, j, k| {}", "for 0..10 |i\n| {}", "for ..10 |i| {}", "for 0.. |i| {}", ] for (cases) |header| { diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source arraylist.ArrayList(u8) := arraylist.init(mem.c_allocator) defer arraylist.deinit(&source) for ("run proc() void {\n") |byte| try arraylist.append(&source, byte) for (header) |byte| try arraylist.append(&source, byte) for ("\nkept :: 7\n}\nafter :: 9\n") |byte| try arraylist.append(&source, byte) scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source.items, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source.items, &diagnostic_store) defer ast.deinit(&tree) try testing.expect(diagnostics.count(&diagnostic_store) > 0) try testing.expect_equal(2, ast.package_decl_count(&tree)) try testing.expect_equal("after", ast.source_text( &tree, ast.statement_decl(&tree, ast.package_decl(&tree, 1)).name, )) body :: ast.function_decl(&tree, ast.package_decl(&tree, 0)).body last :: ast.block_statement(&tree, body, ast.block_statement_count(&tree, body) - 1) try testing.expect_equal(ast.StmtKind.declaration, ast.stmt_kind(&tree, last)) try testing.expect_equal("kept", ast.source_text(&tree, ast.statement_decl(&tree, last).name)) } } range_loop_preserves_nearest_else_and_canonical_body test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `run proc() void { ` if (true) for (0..2) |i| if (false) break else continue ` for values() |item| return `} scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) body :: ast.function_decl(&tree, ast.package_decl(&tree, 0)).body outer :: ast.if_stmt(&tree, ast.block_statement(&tree, body, 0)) try testing.expect(outer.else_block == null) loop :: ast.for_stmt(&tree, ast.block_statement(&tree, outer.then_block, 0)) nested :: ast.if_stmt(&tree, ast.block_statement(&tree, loop.body, 0)) try testing.expect(nested.else_block != null) call_loop :: ast.for_stmt(&tree, ast.block_statement(&tree, body, 1)) try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, call_loop.iterable)) try testing.expect_equal(1, ast.block_statement_count(&tree, call_loop.body)) } parses_recursive_array_annotations_literals_and_grouped_parameters test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `matrix [2]mut [width]mut i64 := [ ` [1, 2,], ` [3, 4], `] `inferred [_]f64 :: [1.0,] `empty :: [] `width usize :: 2 `choose proc(a, b [width]mut [2]i64, index usize) [_]f64 { ` return [] `} scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) try testing.expect_equal(5, ast.package_decl_count(&tree)) declaration :: ast.statement_decl(&tree, ast.package_decl(&tree, 0)) outer :: ast.array_type(&tree, declaration.type?) try testing.expect(outer.mutable) try testing.expect_equal(ast.ExprKind.literal_int, ast.expr_kind(&tree, outer.count?)) inner :: ast.array_type(&tree, outer.element) try testing.expect(inner.mutable) try testing.expect_equal(ast.ExprKind.identifier, ast.expr_kind(&tree, inner.count?)) try testing.expect_equal("i64", ast.source_text(&tree, ast.node_span(&tree, inner.element))) try testing.expect_equal( ast.name_id(&tree, ast.package_decl(&tree, 3)), ast.name_id(&tree, inner.count?), ) try testing.expect_equal("i64", strpool.get_str(&strings, ast.name_id(&tree, inner.element))?) try testing.expect_equal(2, ast.array_element_count(&tree, declaration.initializer)) for (0..ast.array_element_count(&tree, declaration.initializer)) |i| { try testing.expect_equal(2, ast.array_element_count( &tree, ast.array_element(&tree, declaration.initializer, i), )) } inferred :: ast.statement_decl(&tree, ast.package_decl(&tree, 1)) try testing.expect_equal(null, ast.array_type(&tree, inferred.type?).count) try testing.expect_equal(1, ast.array_element_count(&tree, inferred.initializer)) empty :: ast.statement_decl(&tree, ast.package_decl(&tree, 2)) try testing.expect_equal(0, ast.array_element_count(&tree, empty.initializer)) function :: ast.package_decl(&tree, 4) try testing.expect_equal(3, ast.function_param_count(&tree, function)) first :: ast.param(&tree, ast.function_param(&tree, function, 0)) second :: ast.param(&tree, ast.function_param(&tree, function, 1)) try testing.expect_equal(first.type, second.type) try testing.expect_equal(ast.ExprKind.array_type, ast.expr_kind(&tree, first.type)) try testing.expect_equal(null, ast.array_type(&tree, ast.function_decl(&tree, function).return_type).count) } parses_postfix_indices_and_general_assignment_targets test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `value :: -factory()[position()][0] + ([1, 2])[0] `main proc() void { ` (matrix[1])[0] = rhs()[0] ` (matrix) = [[1]] ` 1 = 2 `} scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) expression :: ast.statement_decl(&tree, ast.package_decl(&tree, 0)).initializer add :: ast.binary_expr(&tree, expression) negative :: ast.unary_expr(&tree, add.left) outer :: ast.index_expr(&tree, negative.operand) inner :: ast.index_expr(&tree, outer.base) try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, inner.base)) try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, inner.index)) try testing.expect_equal(ast.ExprKind.literal_int, ast.expr_kind(&tree, outer.index)) literal_index :: ast.index_expr(&tree, add.right) try testing.expect_equal(ast.ExprKind.array_literal, ast.expr_kind(&tree, literal_index.base)) body :: ast.function_decl(&tree, ast.package_decl(&tree, 1)).body try testing.expect_equal(3, ast.block_statement_count(&tree, body)) assignment :: ast.assignment_stmt(&tree, ast.block_statement(&tree, body, 0)) target :: ast.index_expr(&tree, assignment.target) try testing.expect_equal(ast.ExprKind.index, ast.expr_kind(&tree, target.base)) try testing.expect_equal(ast.ExprKind.index, ast.expr_kind(&tree, assignment.value)) grouped :: ast.assignment_stmt(&tree, ast.block_statement(&tree, body, 1)) try testing.expect_equal(ast.ExprKind.identifier, ast.expr_kind(&tree, grouped.target)) invalid_target :: ast.assignment_stmt(&tree, ast.block_statement(&tree, body, 2)) try testing.expect_equal(ast.ExprKind.literal_int, ast.expr_kind(&tree, invalid_target.target)) } mirrors_array_type_and_leading_declaration_ambiguities test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `ambiguous :: [1, 2][0] `shape :: [1]mut [2]i64 `main proc() void { ` matrix[1][0] = 9 ` (matrix[1])[0] = 9 ` result :: matrix[1][0] `} scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(2, diagnostics.count(&diagnostic_store)) try testing.expect_equal(3, ast.package_decl_count(&tree)) try testing.expect_equal(ast.DeclKind.invalid, ast.decl_kind(&tree, ast.package_decl(&tree, 0))) shape :: ast.statement_decl(&tree, ast.package_decl(&tree, 1)).initializer try testing.expect_equal(ast.ExprKind.array_type, ast.expr_kind(&tree, shape)) try testing.expect_equal(ast.ExprKind.array_type, ast.expr_kind(&tree, ast.array_type(&tree, shape).element)) body :: ast.function_decl(&tree, ast.package_decl(&tree, 2)).body try testing.expect_equal(3, ast.block_statement_count(&tree, body)) try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, ast.block_statement(&tree, body, 0))) try testing.expect_equal(ast.StmtKind.assignment_stmt, ast.stmt_kind(&tree, ast.block_statement(&tree, body, 1))) result :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 2)).initializer try testing.expect_equal(ast.ExprKind.index, ast.expr_kind(&tree, result)) } preserves_array_error_ids_control_bodies_and_following_siblings test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `main proc() void { ` bad [~]i64 :: [~, 2] ` while (items[0) { break } ` for items[0 |i| { continue } ` unfinished :: [1, ` kept :: [] `} `tail :: 4 scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) try testing.expect_equal(2, diagnostics.count(&diagnostic_store)) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(2, ast.package_decl_count(&tree)) body :: ast.function_decl(&tree, ast.package_decl(&tree, 0)).body try testing.expect_equal(5, ast.block_statement_count(&tree, body)) bad :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 0)) count :: ast.array_type(&tree, bad.type?).count? element :: ast.array_element(&tree, bad.initializer, 0) try testing.expect_equal(diagnostics.DiagnosticId(0), ast.invalid(&tree, count).diagnostic) try testing.expect_equal(diagnostics.DiagnosticId(1), ast.invalid(&tree, element).diagnostic) while_loop :: ast.while_stmt(&tree, ast.block_statement(&tree, body, 1)) try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, while_loop.condition)) try testing.expect_equal(ast.StmtKind.break_stmt, ast.stmt_kind( &tree, ast.block_statement(&tree, while_loop.body, 0), )) for_loop :: ast.for_stmt(&tree, ast.block_statement(&tree, body, 2)) try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, for_loop.iterable)) try testing.expect_equal(ast.StmtKind.continue_stmt, ast.stmt_kind( &tree, ast.block_statement(&tree, for_loop.body, 0), )) unfinished :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 3)) try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, unfinished.initializer)) kept :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 4)) try testing.expect_equal("kept", ast.source_text(&tree, kept.name)) try testing.expect_equal(0, ast.array_element_count(&tree, kept.initializer)) } recovers_nested_square_and_parenthesis_delimiters_without_consuming_siblings test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `main proc() void { ` bad [call(1, ` 2)]i64 = [3] ` kept :: [] ` missing [2 ` sibling :: [4] `} `tail :: 5 scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect(diagnostics.count(&diagnostic_store) > 0) try testing.expect_equal(2, ast.package_decl_count(&tree)) body :: ast.function_decl(&tree, ast.package_decl(&tree, 0)).body try testing.expect_equal(4, ast.block_statement_count(&tree, body)) try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, ast.block_statement(&tree, body, 0))) kept :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 1)) sibling :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 3)) try testing.expect_equal(0, ast.array_element_count(&tree, kept.initializer)) try testing.expect_equal(1, ast.array_element_count(&tree, sibling.initializer)) } parses_import_forms_with_shared_source_provenance test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) builder := ast.init_builder(mem.c_allocator, "") defer ast.deinit_builder(&builder) first_text :: "value :: 1\n" first_source :: try ast.set_source(&builder, "first.hon", first_text) first_scan := lexer.init(mem.c_allocator) defer lexer.deinit(&first_scan) first_scan.source_id = first_source try lexer.scan(&first_scan, first_text, &strings, &diagnostic_store) first_root :: try parse_source(&builder, first_scan.tokens.items, &diagnostic_store) first_view :: ast.view(&builder, first_root) first_node_count :: ast.node_count(&first_view) second_text :: `import "relative" # preserve imports after comments `named :: import "@root/package" `test import "test_only" `test helper :: import "test_alias" `run proc(parameter i64) i64 { ` values [2]i64 :: [1, 2] ` if (true) { values[0] = parameter + -1 } else { values[1] = 2 } ` while (false) { break } ` for 0..1 |i| { continue } ` return values[0] `} second_source :: try ast.set_source(&builder, "second.hon", second_text) second_scan := lexer.init(mem.c_allocator) defer lexer.deinit(&second_scan) second_scan.source_id = second_source try lexer.scan(&second_scan, second_text, &strings, &diagnostic_store) second_root :: try parse_source(&builder, second_scan.tokens.items, &diagnostic_store) tree :: ast.view(&builder, second_root) try testing.expect(first_root != second_root) try testing.expect_equal(1, ast.package_decl_count_at(&tree, first_root)) first :: ast.package_decl_at(&tree, first_root, 0) try testing.expect_equal(first_source, ast.statement_decl(&tree, first).name.source) try testing.expect_equal("value", ast.source_text(&tree, ast.statement_decl(&tree, first).name)) try testing.expect_equal(5, ast.package_decl_count(&tree)) paths [4][]u8 :: ["relative", "@root/package", "test_only", "test_alias"] for (paths) |path, index| { declaration :: ast.package_decl(&tree, index) try testing.expect_equal(ast.DeclKind.import, ast.decl_kind(&tree, declaration)) imported :: ast.import_decl(&tree, declaration) try testing.expect_equal(path, ast.source_text(&tree, imported.path)) try testing.expect_equal(second_source, imported.path.source) try testing.expect_equal(index >= 2, imported.test_only) if (index == 0 or index == 2) { try testing.expect_equal(null, imported.namespace) } else { expected :: if (index == 1) "named" else "helper" try testing.expect_equal([]u8, expected, strpool.get_str(&strings, imported.namespace?)?) } } function_id :: ast.package_decl(&tree, 4) function :: ast.function_decl(&tree, function_id) try testing.expect_equal(second_source, function.name.source) try testing.expect_equal("run", ast.source_text(&tree, function.name)) parameter :: ast.param(&tree, ast.function_param(&tree, function_id, 0)) try testing.expect_equal(second_source, parameter.name.source) try testing.expect_equal("parameter", ast.source_text(&tree, parameter.name)) for (usize(0)..ast.node_count(&tree)) |index| { expected :: if (index < first_node_count) first_source else second_source span :: ast.node_span(&tree, ast.NodeId(index)) try testing.expect_equal(expected, span.source) _ = ast.source_text(&tree, span) } try testing.expect_equal(2, ast.source_files(&tree).len) try testing.expect_equal("first.hon", ast.source_files(&tree)[usize(first_source)].path) try testing.expect_equal("second.hon", ast.source_files(&tree)[usize(second_source)].path) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) } rejects_empty_escaped_and_control_import_paths_without_losing_siblings test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: "import \"\"\nimport \"bad\\\\path\"\nimport \"bad\tpath\"\nkept :: 1\n" scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(4, ast.package_decl_count(&tree)) try testing.expect_equal(3, diagnostics.count(&diagnostic_store)) for (usize(0)..usize(3)) |index| { declaration :: ast.package_decl(&tree, index) try testing.expect_equal(ast.DeclKind.invalid, ast.decl_kind(&tree, declaration)) diagnostic :: diagnostics.get(&diagnostic_store, ast.invalid(&tree, declaration).diagnostic)? try testing.expect_equal(diagnostics.Code.invalid_import_path, diagnostic.code) } kept :: ast.statement_decl(&tree, ast.package_decl(&tree, 3)) try testing.expect_equal("kept", ast.source_text(&tree, kept.name)) } @hide:file AllocationBudget :: struct { remaining usize } @hide:file budget_alloc proc(context ?@mut anyopaque, size usize, alignment usize) ?*mut u8 { budget @mut AllocationBudget :: ptrcast!(context?) if (budget.remaining == 0) return null budget.remaining -= 1 return mem.raw_alloc(mem.c_allocator, size, alignment) } @hide:file budget_realloc proc( context ?@mut anyopaque, memory ?*mut u8, old_size, new_size, alignment usize, ) ?*mut u8 { budget @mut AllocationBudget :: ptrcast!(context?) if (budget.remaining == 0) return null budget.remaining -= 1 return mem.raw_realloc(mem.c_allocator, memory, old_size, new_size, alignment) } @hide:file budget_free proc(context ?@mut anyopaque, memory ?*mut u8, size, alignment usize) void { _ = context mem.raw_free(mem.c_allocator, memory, size, alignment) } @hide:file budget_vtable :: mem.AllocatorVTable{ alloc = budget_alloc, realloc = budget_realloc, free = budget_free } rolls_back_shared_nodes_extra_spans_and_diagnostics_on_allocation_failure test { failures := 0 for (usize(0)..usize(16)) |remaining| { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) budget := AllocationBudget{ remaining = maxval!(usize) } allocator :: mem.Allocator{ context = &budget, vtable = &budget_vtable } builder := ast.init_builder(allocator, "") defer ast.deinit_builder(&builder) first_text :: "value :: 1\n" first_source :: try ast.set_source(&builder, "first.hon", first_text) first_scan := lexer.init(mem.c_allocator) defer lexer.deinit(&first_scan) first_scan.source_id = first_source try lexer.scan(&first_scan, first_text, &strings, &diagnostic_store) first_root :: try parse_source(&builder, first_scan.tokens.items, &diagnostic_store) node_count :: builder.storage.nodes.items.len extra_count :: builder.storage.extra.items.len second_text :: "another :: [1, 2, 3]\n@ broken\nlast :: 4\n" second_source :: try ast.set_source(&builder, "second.hon", second_text) second_scan := lexer.init(mem.c_allocator) defer lexer.deinit(&second_scan) second_scan.source_id = second_source try lexer.scan(&second_scan, second_text, &strings, &diagnostic_store) budget.remaining = remaining _ = parse_source(&builder, second_scan.tokens.items, &diagnostic_store) catch |error| { try testing.expect(error == .out_of_memory) failures += 1 try testing.expect_equal(node_count, builder.storage.nodes.items.len) try testing.expect_equal(node_count, builder.storage.spans.items.len) try testing.expect_equal(extra_count, builder.storage.extra.items.len) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) ast.remove_source(&builder) tree :: ast.view(&builder, first_root) declaration :: ast.statement_decl(&tree, ast.package_decl(&tree, 0)) try testing.expect_equal("value", ast.source_text(&tree, declaration.name)) try testing.expect_equal(1, ast.source_files(&tree).len) try testing.expect_equal(first_source, builder.source_id) continue } } try testing.expect(failures > 0) } parses_enum_union_wrappers_and_keyword_variants test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `Tag :: enum(i32) { if = -2, else = 3 # preserve the member boundary ` return `} `Payload :: union(Tag) { if i32, else void, return struct { value i32 } } `Auto :: union(enum) { value i32, empty void } `Raw :: union { integer i32, real f64 } `Id :: distinct i32 `Other :: alias Id `value Payload :: Payload{ if = 4 } `context Payload :: .return{value = 5} `empty Payload :: .else scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) try testing.expect_equal(9, ast.package_decl_count(&tree)) enumeration :: ast.statement_decl(&tree, ast.package_decl(&tree, 0)).initializer try testing.expect(ast.enum_type(&tree, enumeration).backing != null) try testing.expect_equal(3, ast.enum_member_count(&tree, enumeration)) member :: ast.field(&tree, ast.enum_member(&tree, enumeration, 0)) try testing.expect_equal("if", strpool.get_str(&strings, ast.name_id(&tree, member.name?))?) try testing.expect_equal(ast.ExprKind.unary, ast.expr_kind(&tree, member.value?)) tagged :: ast.statement_decl(&tree, ast.package_decl(&tree, 1)).initializer automatic :: ast.statement_decl(&tree, ast.package_decl(&tree, 2)).initializer raw :: ast.statement_decl(&tree, ast.package_decl(&tree, 3)).initializer try testing.expect(ast.union_type(&tree, tagged).tag != null) try testing.expect(ast.union_type(&tree, automatic).auto_tag) try testing.expect(!ast.union_type(&tree, raw).auto_tag) try testing.expect_equal(2, ast.record_field_count(&tree, raw)) distinct_type :: ast.statement_decl(&tree, ast.package_decl(&tree, 4)).initializer alias_value :: ast.statement_decl(&tree, ast.package_decl(&tree, 5)).initializer try testing.expect_equal(ast.ExprKind.distinct_type, ast.expr_kind(&tree, distinct_type)) try testing.expect_equal(ast.ExprKind.alias_expr, ast.expr_kind(&tree, alias_value)) contextual :: ast.statement_decl(&tree, ast.package_decl(&tree, 7)).initializer variant :: ast.record_literal(&tree, contextual).type? try testing.expect_equal(ast.ExprKind.enum_literal, ast.expr_kind(&tree, variant)) try testing.expect_equal("return", strpool.get_str(&strings, ast.name_id(&tree, ast.enum_literal(&tree, variant)))?) } parses_match_patterns_payload_captures_and_statement_arms test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `choose proc(value int) int { ` return match value { ` 0, 1..=3: 10, ` 4: 20 # preserve the arm boundary ` else: 30 ` } `} `use proc(value int) void { ` match value { ` .if, .return |payload|: { local :: payload } ` .else: return, ` else: {} ` } `} scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect_equal(0, diagnostics.count(&diagnostic_store)) choose :: ast.function_decl(&tree, ast.package_decl(&tree, 0)) value_match :: ast.return_stmt(&tree, ast.block_statement(&tree, choose.body, 0)).value? try testing.expect_equal(3, ast.match_arm_count(&tree, value_match)) first :: ast.match_arm(&tree, value_match, 0) try testing.expect_equal(2, ast.match_pattern_count(&tree, first)) try testing.expect_equal(ast.ExprKind.range, ast.expr_kind(&tree, ast.match_pattern(&tree, first, 1))) try testing.expect(ast.match_arm_view(&tree, first).expression) use :: ast.function_decl(&tree, ast.package_decl(&tree, 1)) statement_match :: ast.statement_expr(&tree, ast.block_statement(&tree, use.body, 0)) captured :: ast.match_arm_view(&tree, ast.match_arm(&tree, statement_match, 0)) try testing.expect(captured.capture != null) try testing.expect(!captured.expression) try testing.expect(ast.match_arm_view(&tree, ast.match_arm(&tree, statement_match, 2)).catch_all) } rejects_pointer_match_captures_and_preserves_siblings test { strings := strpool.init(mem.c_allocator) defer strpool.deinit(&strings) diagnostic_store := diagnostics.init(mem.c_allocator) defer diagnostics.deinit(&diagnostic_store) source :: `broken :: enum { first `after :: 1 `use proc(value int) void { ` match value { ` .item |@payload|: payload ` else: {} ` } `} `last :: 2 scan_state := lexer.init(mem.c_allocator) defer lexer.deinit(&scan_state) try lexer.scan(&scan_state, source, &strings, &diagnostic_store) tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store) defer ast.deinit(&tree) try testing.expect(diagnostics.count(&diagnostic_store) > 0) try testing.expect_equal(4, ast.package_decl_count(&tree)) try testing.expect_equal("after", ast.source_text(&tree, ast.statement_decl(&tree, ast.package_decl(&tree, 1)).name)) try testing.expect_equal("last", ast.source_text(&tree, ast.statement_decl(&tree, ast.package_decl(&tree, 3)).name)) }