Files

2501 lines
131 KiB
Plaintext

import "@std/mem"
import "@std/testing"
import "@std/arraylist"
import "@source/diagnostics"
import "@source/strpool"
import "@source/lexer"
import "@source/ast"
import "@source/ast/renderer"
parses_characters_as_integer_literals_with_original_spans test {
source :: "values :: ['a', '\\0', '\\n', '\\r', '\\t', '\\\\', '\\'', 'é', '😀']"
expected [9][]u8 :: ["97", "0", "10", "13", "9", "92", "39", "233", "128512"]
strings := strpool.init(mem.c_allocator)
defer strpool.deinit(&strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
scan_state := lexer.init(mem.c_allocator)
defer lexer.deinit(&scan_state)
builder := ast.init_builder(mem.c_allocator, "")
defer ast.deinit_builder(&builder)
_ = try ast.set_source(&builder, "other.hon", "")
source_id :: try ast.set_source(&builder, "characters.hon", source)
scan_state.source_id = source_id
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
root :: try parse_source(&builder, scan_state.tokens.items, &diagnostic_store)
tree :: ast.view(&builder, root)
values :: ast.statement_decl(&tree, ast.package_decl(&tree, 0)).initializer
try testing.expect_equal(expected.len, ast.array_element_count(&tree, values))
for (expected) |text, index| {
value :: ast.array_element(&tree, values, index)
try testing.expect_equal(ast.ExprKind.literal_int, ast.expr_kind(&tree, value))
try testing.expect_equal(text, ast.literal_text(&tree, value))
span :: ast.node_span(&tree, value)
try testing.expect_equal(source_id, span.source)
try testing.expect_equal(u8('\''), ast.source_text(&tree, span)[0])
try testing.expect_equal(scan_state.tokens.items[3 + index * 2].span.start, span.start)
try testing.expect_equal(scan_state.tokens.items[3 + index * 2].span.end, span.end)
}
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
}
diagnoses_malformed_character_values_without_losing_sibling_declarations test {
sources [7][]u8 :: [
"bad :: ''\nnext :: 1",
"bad :: 'ab'\nnext :: 1",
"bad :: '\\x41'\nnext :: 1",
"bad :: '\\q'\nnext :: 1",
"bad :: '\xC0\xAF'\nnext :: 1",
"bad :: '\xED\xA0\x80'\nnext :: 1",
"bad :: '\xF4\x90\x80\x80'\nnext :: 1",
]
for (sources) |source| {
strings := strpool.init(mem.c_allocator)
defer strpool.deinit(&strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
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, diagnostics.count(&diagnostic_store))
try testing.expect_equal(2, ast.package_decl_count(&tree))
bad :: ast.statement_decl(&tree, ast.package_decl(&tree, 0)).initializer
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, bad))
diagnostic :: diagnostics.get(&diagnostic_store, ast.invalid(&tree, bad).diagnostic)?
try testing.expect_equal(diagnostics.Code.invalid_character_literal, diagnostic.code)
try testing.expect_equal(scan_state.tokens.items[2].span.start, diagnostic.span.start)
try testing.expect_equal(scan_state.tokens.items[2].span.end, diagnostic.span.end)
try testing.expect_equal("next", ast.source_text(&tree, ast.statement_decl(&tree, ast.package_decl(&tree, 1)).name))
}
}
parses_multiline_conditional_values_and_hide_scopes test {
source ::
`@hide
`hidden :: if (true)
` '0'
`else if (false)
` 'A'
`else
` 'a'
`@hide:package scoped :: 2
`@hide:file local :: 3
`visible :: 4
strings := strpool.init(mem.c_allocator)
defer strpool.deinit(&strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
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(4, ast.package_decl_count(&tree))
for (usize(0)..4) |index| {
declaration :: ast.package_decl(&tree, index)
try testing.expect_equal(index < 2, ast.package_local(&tree, declaration))
try testing.expect_equal(index == 2, ast.file_local(&tree, declaration))
}
conditional :: ast.statement_decl(&tree, ast.package_decl(&tree, 0)).initializer
try testing.expect_equal(ast.ExprKind.match_expr, ast.expr_kind(&tree, conditional))
}
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))
}
parses_inline_loops_and_clones_capture_metadata 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 {
` inline for 0..=2 |item| {
` for "ab" |byte, index| { continue }
` }
` for [1, 2] |value, index| {}
` inline for [i32, u8] |@T, _| {}
` inline for ("xy") |byte, index| continue
`}
scan_state := lexer.init(mem.c_allocator)
defer lexer.deinit(&scan_state)
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
builder := ast.init_builder(mem.c_allocator, source)
defer ast.deinit_builder(&builder)
root :: try parse_source(&builder, scan_state.tokens.items, &diagnostic_store)
original :: ast.view(&builder, root)
mapping :: try mem.alloc(ast.NodeId, mem.c_allocator, ast.node_count(&original))
defer mem.free(mem.c_allocator, mapping)
mem.set(mapping, maxval!(ast.NodeId))
cloned :: try ast.clone_subtree(&builder, root, mapping)
tree :: ast.view(&builder, root)
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
roots [2]ast.NodeId :: [root, cloned]
for (roots) |package| {
body :: ast.function_decl(&tree, ast.child(&tree, package, 0)).body
try testing.expect_equal(4, ast.block_statement_count(&tree, body))
first :: ast.for_stmt(&tree, ast.block_statement(&tree, body, 0))
try testing.expect(first.is_inline and !first.pointer_capture and !first.index_capture)
try testing.expect(ast.range_expr(&tree, first.iterable).inclusive)
try testing.expect_equal("inline", ast.source_text(&tree, ast.Span{
start = first.span.start, end = first.span.start + 6, source = first.span.source,
}))
nested :: ast.for_stmt(&tree, ast.block_statement(&tree, first.body, 0))
try testing.expect(!nested.is_inline and nested.index_capture)
try testing.expect_equal(ast.ExprKind.literal_string, ast.expr_kind(&tree, nested.iterable))
ordinary :: ast.for_stmt(&tree, ast.block_statement(&tree, body, 1))
try testing.expect(!ordinary.is_inline and ordinary.index_capture)
try testing.expect_equal(2, ast.array_element_count(&tree, ordinary.iterable))
pointer :: ast.for_stmt(&tree, ast.block_statement(&tree, body, 2))
try testing.expect(pointer.is_inline and pointer.pointer_capture and pointer.index_capture)
try testing.expect(pointer.index == null)
try testing.expect_equal("T", strpool.get_str(&strings, ast.name_id(&tree, pointer.capture?))?)
indexed :: ast.for_stmt(&tree, ast.block_statement(&tree, body, 3))
try testing.expect(indexed.is_inline and !indexed.pointer_capture and indexed.index_capture)
try testing.expect_equal("index", strpool.get_str(&strings, ast.name_id(&tree, indexed.index?))?)
try testing.expect_equal(ast.StmtKind.continue_stmt, ast.stmt_kind(
&tree, ast.block_statement(&tree, indexed.body, 0),
))
}
original_body :: ast.function_decl(&tree, ast.child(&tree, root, 0)).body
cloned_body :: ast.function_decl(&tree, ast.child(&tree, cloned, 0)).body
first :: ast.for_stmt(&tree, ast.block_statement(&tree, original_body, 0))
copy :: ast.for_stmt(&tree, ast.block_statement(&tree, cloned_body, 0))
try testing.expect(copy.capture? != first.capture?)
try testing.expect_equal(mapping[usize(first.capture?)], copy.capture?)
try testing.expect_equal(first.span.start, copy.span.start)
try testing.expect_equal(first.span.end, copy.span.end)
try testing.expect_equal(first.span.source, copy.span.source)
}
rejects_malformed_inline_forms_without_losing_siblings test {
strings := strpool.init(mem.c_allocator)
defer strpool.deinit(&strings)
cases [7][]u8 :: [
"inline while true {}",
"inline match value { else: {} }",
"inline 0..2 |item| {}",
"inline inline for 0..2 |item| {}",
"inline",
"inline for 0..2 || {}",
"inline for 0..2 |item,| {}",
]
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 ("\ninline for (0..2) |item| continue\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))
body :: ast.function_decl(&tree, ast.package_decl(&tree, 0)).body
try testing.expect_equal(3, ast.block_statement_count(&tree, body))
preserved :: ast.for_stmt(&tree, ast.block_statement(&tree, body, 1))
try testing.expect(preserved.is_inline)
try testing.expect_equal(ast.StmtKind.continue_stmt, ast.stmt_kind(
&tree, ast.block_statement(&tree, preserved.body, 0),
))
kept :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 2))
try testing.expect_equal("kept", ast.source_text(&tree, kept.name))
try testing.expect_equal("after", ast.source_text(
&tree, ast.statement_decl(&tree, ast.package_decl(&tree, 1)).name,
))
}
}
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))
}
parses_and_clones_inline_match_captures_and_body_forms 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(value int) void {
` match value { .if, .return: 1, inline |payload, tag|: payload }
` match value { inline |_, _|: return }
` match value { inline |_|: value = 1 }
`}
scan_state := lexer.init(mem.c_allocator)
defer lexer.deinit(&scan_state)
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
builder := ast.init_builder(mem.c_allocator, source)
defer ast.deinit_builder(&builder)
root :: try parse_source(&builder, scan_state.tokens.items, &diagnostic_store)
original :: ast.view(&builder, root)
mapping :: try mem.alloc(ast.NodeId, mem.c_allocator, ast.node_count(&original))
defer mem.free(mem.c_allocator, mapping)
mem.set(mapping, maxval!(ast.NodeId))
cloned :: try ast.clone_subtree(&builder, root, mapping)
tree :: ast.view(&builder, root)
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
roots [2]ast.NodeId :: [root, cloned]
for (roots) |package| {
body :: ast.function_decl(&tree, ast.child(&tree, package, 0)).body
matched :: ast.statement_expr(&tree, ast.block_statement(&tree, body, 0))
ordinary :: ast.match_arm(&tree, matched, 0)
try testing.expect_equal(2, ast.match_pattern_count(&tree, ordinary))
try testing.expect(!ast.match_arm_view(&tree, ordinary).is_inline)
arm_id :: ast.match_arm(&tree, matched, 1)
arm :: ast.match_arm_view(&tree, arm_id)
try testing.expect(arm.is_inline and arm.expression and arm.tag_capture_present)
try testing.expect_equal(0, ast.match_pattern_count(&tree, arm_id))
try testing.expect_equal(3, ast.child_count(&tree, arm_id))
try testing.expect_equal(arm.body, ast.child(&tree, arm_id, 0))
try testing.expect_equal(arm.capture?, ast.child(&tree, arm_id, 1))
try testing.expect_equal(arm.tag_capture?, ast.child(&tree, arm_id, 2))
try testing.expect_equal("payload", ast.source_text(&tree, ast.node_span(&tree, arm.capture?)))
try testing.expect_equal("tag", ast.source_text(&tree, ast.node_span(&tree, arm.tag_capture?)))
for (usize(1)..usize(3)) |i| {
statement_match :: ast.statement_expr(&tree, ast.block_statement(&tree, body, i))
discarded :: ast.match_arm_view(&tree, ast.match_arm(&tree, statement_match, 0))
try testing.expect(discarded.is_inline and !discarded.expression)
try testing.expect(discarded.capture == null and discarded.tag_capture == null)
try testing.expect_equal(i == 1, discarded.tag_capture_present)
}
}
}
rejects_malformed_inline_match_captures test {
sources [8][]u8 :: [
"run proc() void { match value { inline ||: 1 } }",
"run proc() void { match value { inline |a, b, c|: 1 } }",
"run proc() void { match value { inline |@a|: 1 } }",
"run proc() void { match value { inline |*a|: 1 } }",
"run proc() void { match value { inline |a, @b|: 1 } }",
"run proc() void { match value { inline |a: 1 } }",
"run proc() void { match value { inline a|: 1 } }",
"run proc() void { match value { inline |a,|: 1 } }",
]
for (sources) |source| {
strings := strpool.init(mem.c_allocator)
defer strpool.deinit(&strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
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)
}
}
parses_generated_foreign_bindings_and_preserves_callable_metadata test {
strings := strpool.init(mem.c_allocator)
defer strpool.deinit(&strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
source ::
`Handle :: opaque
`Record :: c_struct {
` context ?*mut Handle
` callback ?*c_func(_ c_int, _ ?*anyopaque) void
`}
`printf c_func(_ ?*c_char, ...) c_int
`@hide:file
`helper proc() int {
` if (true) return 42
` else return 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(0, diagnostics.count(&diagnostic_store))
try testing.expect_equal(4, ast.package_decl_count(&tree))
handle_type :: ast.statement_decl(&tree, ast.package_decl(&tree, 0)).initializer
record :: ast.statement_decl(&tree, ast.package_decl(&tree, 1)).initializer
try testing.expect(ast.record_opaque(&tree, handle_type))
try testing.expect(ast.record_c_layout(&tree, record))
callback :: ast.type_operand(&tree, ast.field(&tree, ast.record_field(&tree, record, 1)).type?)
try testing.expect(ast.callable_foreign(&tree, callback))
try testing.expect_equal(2, ast.function_param_count(&tree, callback))
try testing.expect(ast.callable_variadic(&tree, ast.package_decl(&tree, 2)))
helper :: ast.package_decl(&tree, 3)
try testing.expect(ast.file_local(&tree, helper))
branch :: ast.if_stmt(&tree, ast.block_statement(&tree, ast.function_decl(&tree, helper).body, 0))
try testing.expect(branch.else_block != null)
}
keeps_match_default_arm_after_newline_single_statement_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(value int) void {
` match value {
` 1: if (true) return
` else: 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
expression :: ast.statement_expr(&tree, ast.block_statement(&tree, body, 0))
try testing.expect_equal(2, ast.match_arm_count(&tree, expression))
first :: ast.match_arm_view(&tree, ast.match_arm(&tree, expression, 0))
branch :: ast.if_stmt(&tree, ast.block_statement(&tree, first.body, 0))
try testing.expect(branch.else_block == null)
try testing.expect(ast.match_arm_view(&tree, ast.match_arm(&tree, expression, 1)).catch_all)
}
parses_value_blocks_and_clones_yield_children 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(flag bool) int {
` result :: {
` local :: 3
` { if (flag) yield local }
` yield { yield 4 }
` }
` return if (flag) { yield result } else { yield 5 }
`}
scan_state := lexer.init(mem.c_allocator)
defer lexer.deinit(&scan_state)
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
builder := ast.init_builder(mem.c_allocator, source)
defer ast.deinit_builder(&builder)
root :: try parse_source(&builder, scan_state.tokens.items, &diagnostic_store)
original :: ast.view(&builder, root)
mapping :: try mem.alloc(ast.NodeId, mem.c_allocator, ast.node_count(&original))
defer mem.free(mem.c_allocator, mapping)
mem.set(mapping, maxval!(ast.NodeId))
cloned :: try ast.clone_subtree(&builder, root, mapping)
tree :: ast.view(&builder, root)
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
roots [2]ast.NodeId :: [root, cloned]
for (roots) |package| {
function :: ast.function_decl(&tree, ast.child(&tree, package, 0))
value :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 0)).initializer
try testing.expect_equal(ast.ExprKind.block_expr, ast.expr_kind(&tree, value))
try testing.expect_equal(ast.NodeClass.expression, ast.node_class(&tree, value))
block :: ast.block_expr(&tree, value)
try testing.expect_equal(3, ast.block_statement_count(&tree, block))
ordinary :: ast.statement_block(&tree, ast.block_statement(&tree, block, 1))
branch :: ast.if_stmt(&tree, ast.block_statement(&tree, ordinary, 0))
first :: ast.block_statement(&tree, branch.then_block, 0)
try testing.expect_equal(ast.StmtKind.yield_stmt, ast.stmt_kind(&tree, first))
try testing.expect_equal(ast.NodeClass.statement, ast.node_class(&tree, first))
try testing.expect_equal("yield local", ast.source_text(&tree, ast.yield_stmt(&tree, first).span))
last :: ast.yield_stmt(&tree, ast.block_statement(&tree, block, 2))
try testing.expect_equal(ast.ExprKind.block_expr, ast.expr_kind(&tree, last.value?))
nested :: ast.block_statement(&tree, ast.block_expr(&tree, last.value?), 0)
try testing.expect_equal(ast.ExprKind.literal_int, ast.expr_kind(&tree, ast.yield_stmt(&tree, nested).value?))
conditional :: ast.return_stmt(&tree, ast.block_statement(&tree, function.body, 1)).value?
for (0..ast.match_arm_count(&tree, conditional)) |index| {
arm :: ast.match_arm_view(&tree, ast.match_arm(&tree, conditional, index))
try testing.expect(arm.expression)
try testing.expect_equal(ast.ExprKind.block_expr, ast.expr_kind(&tree, arm.body))
}
}
first_function :: ast.function_decl(&tree, ast.child(&tree, root, 0))
first_value :: ast.statement_decl(&tree, ast.block_statement(&tree, first_function.body, 0)).initializer
copied_value :: mapping[usize(first_value)]
try testing.expect(copied_value != first_value)
try testing.expect_equal(mapping[usize(ast.block_expr(&tree, first_value))], ast.block_expr(&tree, copied_value))
}
parses_value_blocks_starting_with_named_assignments 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() i16 {
` value ?i16 :: 42
` return if value |payload| { payload = 7
` yield payload
` } else 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(0, diagnostics.count(&diagnostic_store))
function :: ast.function_decl(&tree, ast.package_decl(&tree, 0))
conditional :: ast.return_stmt(&tree, ast.block_statement(&tree, function.body, 1)).value?
arm :: ast.match_arm_view(&tree, ast.match_arm(&tree, conditional, 0))
try testing.expect_equal(ast.ExprKind.block_expr, ast.expr_kind(&tree, arm.body))
block :: ast.block_expr(&tree, arm.body)
try testing.expect_equal(2, ast.block_statement_count(&tree, block))
first :: ast.block_statement(&tree, block, 0)
try testing.expect_equal(ast.StmtKind.assignment_stmt, ast.stmt_kind(&tree, first))
assignment :: ast.assignment_stmt(&tree, first)
try testing.expect_equal("payload", ast.source_text(&tree, ast.node_span(&tree, assignment.target)))
try testing.expect_equal("7", ast.source_text(&tree, ast.node_span(&tree, assignment.value)))
last :: ast.block_statement(&tree, block, 1)
try testing.expect_equal(ast.StmtKind.yield_stmt, ast.stmt_kind(&tree, last))
try testing.expect_equal("payload", ast.source_text(&tree, ast.node_span(&tree, ast.yield_stmt(&tree, last).value?)))
}
preserves_record_literals_alongside_value_blocks test {
strings := strpool.init(mem.c_allocator)
defer strpool.deinit(&strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
source ::
`empty :: {}
`single :: {1}
`pair :: {1, 2}
`keyed :: { value = { yield 3 } }
`keyword :: { yield = 4 }
`choose :: if (true) { value = 7 } else { value = 8 }
`conditional :: {if (true) 1 else 2}
`block :: { if (true) yield 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(0, diagnostics.count(&diagnostic_store))
try testing.expect_equal(8, ast.package_decl_count(&tree))
for (usize(0)..usize(5)) |index| {
value :: ast.statement_decl(&tree, ast.package_decl(&tree, index)).initializer
try testing.expect_equal(ast.ExprKind.record_literal, ast.expr_kind(&tree, value))
}
choose :: ast.statement_decl(&tree, ast.package_decl(&tree, 5)).initializer
for (usize(0)..usize(2)) |index| {
arm :: ast.match_arm_view(&tree, ast.match_arm(&tree, choose, index))
try testing.expect_equal(ast.ExprKind.record_literal, ast.expr_kind(&tree, arm.body))
}
conditional :: ast.statement_decl(&tree, ast.package_decl(&tree, 6)).initializer
try testing.expect_equal(ast.ExprKind.record_literal, ast.expr_kind(&tree, conditional))
block :: ast.statement_decl(&tree, ast.package_decl(&tree, 7)).initializer
try testing.expect_equal(ast.ExprKind.block_expr, ast.expr_kind(&tree, block))
}
requires_yield_values_and_recovers_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 ::
`run proc() int {
` value :: {
` yield
` after :: 2
` if (true) yield else yield 3
` yield 4 5
` yield after
` }
` return value
`}
`tail :: 7
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))
value :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 0)).initializer
block :: ast.block_expr(&tree, value)
try testing.expect_equal(5, ast.block_statement_count(&tree, block))
missing :: ast.yield_stmt(&tree, ast.block_statement(&tree, block, 0))
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, missing.value?))
try testing.expect_equal(ast.StmtKind.declaration, ast.stmt_kind(&tree, ast.block_statement(&tree, block, 1)))
try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, ast.block_statement(&tree, block, 3)))
last :: ast.yield_stmt(&tree, ast.block_statement(&tree, block, 4))
try testing.expect_equal("after", ast.source_text(&tree, ast.node_span(&tree, last.value?)))
}
keeps_yielding_match_and_catch_bodies_as_raw_blocks 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() int {
` selected :: match 1 { 1: { yield 2 }, else: yield 3 }
` recovered :: selected catch |err| { yield err }
` return use({ yield recovered }, $({ yield 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(0, diagnostics.count(&diagnostic_store))
function :: ast.function_decl(&tree, ast.package_decl(&tree, 0))
selected :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 0)).initializer
for (usize(0)..usize(2)) |index| {
arm :: ast.match_arm_view(&tree, ast.match_arm(&tree, selected, index))
try testing.expect(!arm.expression)
try testing.expect_equal(ast.NodeClass.block, ast.node_class(&tree, arm.body))
try testing.expect_equal(ast.StmtKind.yield_stmt, ast.stmt_kind(&tree, ast.block_statement(&tree, arm.body, 0)))
}
recovered :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 1)).initializer
handler :: ast.catch_expr(&tree, recovered)
try testing.expect(handler.handler_is_block)
try testing.expect_equal(ast.NodeClass.block, ast.node_class(&tree, handler.handler))
try testing.expect_equal(ast.StmtKind.yield_stmt, ast.stmt_kind(&tree, ast.block_statement(&tree, handler.handler, 0)))
call :: ast.return_stmt(&tree, ast.block_statement(&tree, function.body, 2)).value?
try testing.expect_equal(ast.ExprKind.block_expr, ast.expr_kind(&tree, ast.call_arg(&tree, call, 0)))
constant :: ast.call_arg(&tree, call, 1)
try testing.expect_equal(ast.ExprKind.comptime_expr, ast.expr_kind(&tree, constant))
try testing.expect_equal(ast.ExprKind.block_expr, ast.expr_kind(&tree, ast.comptime_operand(&tree, constant)))
}
parses_bitwise_precedence_and_left_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 :: "bits :: ~a + b * c << d >> e <<| f & g xor h | i < j == k and l or m\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))
expression := ast.statement_decl(&tree, ast.package_decl(&tree, 0)).initializer
operators [11]ast.BinaryOperator :: [
.logical_or, .logical_and, .equal, .less,
.bit_or, .bit_xor, .bit_and,
.shift_left_saturating, .shift_right, .shift_left, .add,
]
for (operators) |operator| {
binary :: ast.binary_expr(&tree, expression)
try testing.expect_equal(operator, binary.operator)
if (operator == .add) {
product :: ast.binary_expr(&tree, binary.right)
try testing.expect_equal(ast.BinaryOperator.multiply, product.operator)
}
expression = binary.left
}
complement :: ast.unary_expr(&tree, expression)
try testing.expect_equal(ast.UnaryOperator.bit_not, complement.operator)
try testing.expect_equal("a", ast.source_text(&tree, ast.node_span(&tree, complement.operand)))
}
parses_compound_operators_in_statement_contexts 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 {
` items[next()].field += rhs()
` value -= 1
` value *= 2
` value /= 3
` value &= mask
` value |= mask
` value xor= mask
` value <<= count
` value >>= count
` value <<|= count
` value = 4
` match value { 0: value += 1, else: value -= 1 }
` result :: { value |= mask }
`}
scan_state := lexer.init(mem.c_allocator)
defer lexer.deinit(&scan_state)
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
builder := ast.init_builder(mem.c_allocator, source)
defer ast.deinit_builder(&builder)
root :: try parse_source(&builder, scan_state.tokens.items, &diagnostic_store)
tree := ast.view(&builder, root)
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
body :: ast.function_decl(&tree, ast.package_decl(&tree, 0)).body
operators [10]ast.BinaryOperator :: [
.add, .subtract, .multiply, .divide, .bit_and, .bit_or, .bit_xor,
.shift_left, .shift_right, .shift_left_saturating,
]
for (operators) |operator, index| {
assignment :: ast.assignment_stmt(&tree, ast.block_statement(&tree, body, index))
try testing.expect(assignment.compound)
binary :: ast.binary_expr(&tree, assignment.value)
try testing.expect_equal(operator, binary.operator)
}
try testing.expect(!ast.assignment_stmt(&tree, ast.block_statement(&tree, body, 10)).compound)
match_value :: ast.statement_expr(&tree, ast.block_statement(&tree, body, 11))
for (usize(0)..usize(2)) |index| {
arm :: ast.match_arm_view(&tree, ast.match_arm(&tree, match_value, index))
try testing.expect(!arm.expression)
try testing.expect(ast.assignment_stmt(&tree, ast.block_statement(&tree, arm.body, 0)).compound)
}
block_value :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 12)).initializer
try testing.expect_equal(ast.ExprKind.block_expr, ast.expr_kind(&tree, block_value))
}
keeps_type_composition_and_capture_pipes_with_bitwise_or test {
strings := strpool.init(mem.c_allocator)
defer strpool.deinit(&strings)
diagnostic_store := diagnostics.init(mem.c_allocator)
defer diagnostics.deinit(&diagnostic_store)
source ::
`Alias :: A | B
`Error :: A | B
`Fallible :: T ! (A | B)
`run proc(value A | B) T ! (A | B) {
` if (flags | mask) |item| { item }
` while (flags | mask) |item| { break }
` for (flags | mask) |item, index| { continue }
` if value |item| { item }
` while value |item| { break }
` for values |item| { continue }
` if check_flags(flags | mask) { return }
` while values[flags | mask] { break }
`}
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))
for (usize(0)..usize(2)) |index| {
expression :: ast.statement_decl(&tree, ast.package_decl(&tree, index)).initializer
try testing.expect_equal(ast.BinaryOperator.bit_or, ast.binary_expr(&tree, expression).operator)
}
fallible :: ast.statement_decl(&tree, ast.package_decl(&tree, 2)).initializer
try testing.expect_equal(ast.ExprKind.fallible_type, ast.expr_kind(&tree, fallible))
try testing.expect_equal(ast.ExprKind.sum_type, ast.expr_kind(&tree, ast.binary_expr(&tree, fallible).right))
function_id :: ast.package_decl(&tree, 3)
annotation :: ast.param(&tree, ast.function_param(&tree, function_id, 0)).type
try testing.expect_equal(ast.ExprKind.sum_type, ast.expr_kind(&tree, annotation))
body :: ast.function_decl(&tree, function_id).body
condition :: ast.if_stmt(&tree, ast.block_statement(&tree, body, 0))
try testing.expect(condition.capture != null)
try testing.expect_equal(ast.BinaryOperator.bit_or, ast.binary_expr(&tree, condition.condition).operator)
loop :: ast.while_stmt(&tree, ast.block_statement(&tree, body, 1))
try testing.expect(loop.capture != null)
try testing.expect_equal(ast.BinaryOperator.bit_or, ast.binary_expr(&tree, loop.condition).operator)
iteration :: ast.for_stmt(&tree, ast.block_statement(&tree, body, 2))
try testing.expect(iteration.capture != null and iteration.index != null)
try testing.expect_equal(ast.BinaryOperator.bit_or, ast.binary_expr(&tree, iteration.iterable).operator)
try testing.expect(ast.if_stmt(&tree, ast.block_statement(&tree, body, 3)).capture != null)
try testing.expect(ast.while_stmt(&tree, ast.block_statement(&tree, body, 4)).capture != null)
try testing.expect(ast.for_stmt(&tree, ast.block_statement(&tree, body, 5)).capture != null)
}
renders_compound_updates_in_scoped_statement_bodies 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 {
` value := 1
` { value += 2 }
` defer { value -= 3 }
` errdefer |err| { value xor= 4 }
` for (0..2) |item, index| value |= 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))
output := renderer.init(mem.c_allocator)
defer renderer.deinit(&output)
try renderer.render(&output, &tree)
expected :: "package\n function run\n declaration value := 1\n block\n assignment value += 2\n defer\n assignment value -= 3\n errdefer |err|\n assignment value xor= 4\n for 0..2 |item, index|\n assignment value |= 5\n"
try testing.expect(mem.eql(output.bytes.items, expected))
}