feat: execute fixed arrays and indexed access
This commit is contained in:
@@ -49,19 +49,25 @@ function calls are postfix expressions: `f()`, `f(a, b)`, and `factory()(x)`. ca
|
|||||||
|
|
||||||
`name = expression` updates a local `:=` binding. `::` bindings, parameters, named functions, builtin types and globals are not assignment targets. the established binding type is preserved: assignment inputs do not widen or infer that type, though numeric literals can adopt its float context. function-valued locals accept replacement functions with the same parameter and result types.
|
`name = expression` updates a local `:=` binding. `::` bindings, parameters, named functions, builtin types and globals are not assignment targets. the established binding type is preserved: assignment inputs do not widen or infer that type, though numeric literals can adopt its float context. function-valued locals accept replacement functions with the same parameter and result types.
|
||||||
|
|
||||||
|
fixed arrays use `[N]T` for readonly elements and `[N]mut T` for mutable elements; literals use `[a, b]`, including empty, nested and multiline forms with trailing commas. `values [4]mut i64 := [1, 2, 3, 4]` permits `values[2] = 10`. writable elements require a mutable local binding and `mut` at every traversed array level; parameters and globals remain readonly. whole-array assignment only requires a mutable binding, but source and destination types must match recursively, including counts and element mutability. initializations, assignments, arguments and returns copy arrays by value, including nested rows.
|
||||||
|
|
||||||
|
array lengths currently support nonnegative integer literals, unary signs, `+`, `-`, `*` and package-level immutable integer constants; local constants and other compile-time expressions are not implemented. `[_]` infers a binding's array length from its initializer. element annotations require concrete supported value types: use `i64` or `f64` rather than the `int` or `float` constraints. an unannotated array literal has readonly elements.
|
||||||
|
|
||||||
|
indices require `usize`; integer literals and flexible inferred values receive this context, while fixed signed integers do not convert implicitly. out-of-bounds access traps before loading or storing, including for empty arrays. reads evaluate the base before the index; indexed assignment evaluates its right-hand side before the destination, each once. leading nested assignment targets follow Bro's declaration ambiguity: write `(matrix[1])[0] = 9`, not `matrix[1][0] = 9`. [programs/array_sum.hon](programs/array_sum.hon) uses a `usize` range bound to index, mutate and sum an array; `./build/honey run programs/array_sum.hon` prints `17`.
|
||||||
|
|
||||||
`while` repeats a block or single statement while its `bool` condition holds, re-evaluating the condition on every iteration. guards follow the same braced/braceless convention as `if`. loop bodies introduce child scopes; their locals, including locals in nested conditional arms, begin uninitialized on each scope entry.
|
`while` repeats a block or single statement while its `bool` condition holds, re-evaluating the condition on every iteration. guards follow the same braced/braceless convention as `if`. loop bodies introduce child scopes; their locals, including locals in nested conditional arms, begin uninitialized on each scope entry.
|
||||||
|
|
||||||
`for 0..10 |i| { ... }` iterates an ascending, upper-exclusive integer range; `..=` includes the upper bound. a capture is required: `|i|` binds an immutable `int` in the body scope, while `|_|` discards it. bounds resolve in the enclosing scope and are evaluated left-to-right once, so changing a bound variable in the body does not change the range. reversed ranges are empty; equal bounds produce zero iterations with `..` and one with `..=`. inclusive ranges terminate safely at the maximum integer.
|
`for 0..10 |i| { ... }` iterates an ascending, upper-exclusive integer range; `..=` includes the upper bound. a capture is required: `|i|` binds an immutable value with the bounds' integer family (`int` or `usize`) in the body scope, while `|_|` discards it. bounds resolve in the enclosing scope and are evaluated left-to-right once, so changing a bound variable in the body does not change the range. reversed ranges are empty; equal bounds produce zero iterations with `..` and one with `..=`. inclusive ranges terminate safely at the maximum integer.
|
||||||
|
|
||||||
range bounds that are compound or unary expressions require their own parentheses, such as `(-2)..(limit + 1)`. a braceless body also requires parentheses around the entire iterable: `for (0..10) |i| total = total + i`. current execution supports direct integer ranges only; first-class range values, collection iteration, pointer captures and index captures remain unimplemented. [programs/range_loop.hon](programs/range_loop.hon) skips `5` while summing `0..10`; `./build/honey run programs/range_loop.hon` prints `40`.
|
range bounds that are compound or unary expressions require their own parentheses, such as `(-2)..(limit + 1)`. a braceless body also requires parentheses around the entire iterable: `for (0..10) |i| total = total + i`. current execution supports direct integer ranges only; first-class range values, collection iteration, pointer captures and index captures remain unimplemented. [programs/range_loop.hon](programs/range_loop.hon) skips `5` while summing `0..10`; `./build/honey run programs/range_loop.hon` prints `40`.
|
||||||
|
|
||||||
`break` exits the nearest enclosing `for` or `while`; `continue` restarts a `while` condition or advances a `for` range through its guarded update. both are operand-free statements permitted within loop bodies and nested conditional arms; labels and values are unsupported. controls outside loops produce keyword-spanned diagnostics, including in untaken or unreachable code, and trap only if reached. code after a transfer is still resolved and type-checked but does not execute on that path. body locals are reset on re-entry after `continue`; loop transfers do not guarantee a function return. both controls lower to existing IR jumps, without new interpreter instructions or loop frames. [programs/loop_control.hon](programs/loop_control.hon) skips an iteration and exits early; `./build/honey run programs/loop_control.hon` prints `12`.
|
`break` exits the nearest enclosing `for` or `while`; `continue` restarts a `while` condition or advances a `for` range through its guarded update. both are operand-free statements permitted within loop bodies and nested conditional arms; labels and values are unsupported. controls outside loops produce keyword-spanned diagnostics, including in untaken or unreachable code, and trap only if reached. code after a transfer is still resolved and type-checked but does not execute on that path. body locals are reset on re-entry after `continue`; loop transfers do not guarantee a function return. both controls lower to existing IR jumps, without new interpreter instructions or loop frames. [programs/loop_control.hon](programs/loop_control.hon) skips an iteration and exits early; `./build/honey run programs/loop_control.hon` prints `12`.
|
||||||
|
|
||||||
name resolution (`@source/sema`) runs after parsing. it predeclares package names and each block's direct locals, allowing forward references and mutually recursive functions. parameters and top-level function-body locals share a scope; each conditional arm and loop body introduces a child scope whose names do not escape it. signatures resolve names in the enclosing package scope. shadowing is forbidden, including reuse of builtin type names (`int`, `float`, `bool`, `string`, `void`); sibling scopes may independently reuse names.
|
name resolution (`@source/sema`) runs after parsing. it predeclares package names and each block's direct locals, allowing forward references and mutually recursive functions. parameters and top-level function-body locals share a scope; each conditional arm and loop body introduces a child scope whose names do not escape it. signatures resolve names in the enclosing package scope. shadowing is forbidden, including reuse of builtin type names (`int`, `float`, `i64`, `f64`, `usize`, `bool`, `string`, `void`); sibling scopes may independently reuse names.
|
||||||
|
|
||||||
`sema.resolve` returns an owned binding table indexed by AST `NodeId`, without modifying the AST. `sema.binding` identifies a declaration, builtin type, or invalid binding carrying its diagnostic ID; nodes with no binding return `.none`. release the result with `sema.deinit`. unknown names and duplicate declarations add source-spanned diagnostics; invalid syntax retains its existing diagnostic identity. same-scope duplicates retain the first declaration for recovery, while forbidden inner declarations invalidate that name's uses rather than silently binding them to an outer declaration.
|
`sema.resolve` returns an owned binding table indexed by AST `NodeId`, without modifying the AST. `sema.binding` identifies a declaration, builtin type, or invalid binding carrying its diagnostic ID; nodes with no binding return `.none`. release the result with `sema.deinit`. unknown names and duplicate declarations add source-spanned diagnostics; invalid syntax retains its existing diagnostic identity. same-scope duplicates retain the first declaration for recovery, while forbidden inner declarations invalidate that name's uses rather than silently binding them to an outer declaration.
|
||||||
|
|
||||||
type checking (`@source/sema/check`) runs after name resolution. `check.check` borrows the AST and binding table and returns owned per-node types; query with `check.type_of` and release with `check.deinit`. declarations infer their initializer types, including forward references and function aliases; function types identify the declaration supplying the callable signature. annotations must name builtin types; type names are not ordinary values. `void` is valid as a function result, not as a parameter or stored value.
|
type checking (`@source/sema/check`) runs after name resolution. `check.check` borrows the AST and binding table and returns owned per-node types and interned array descriptors; query with `check.type_of` and `check.array_type`, and release with `check.deinit`. declarations infer their initializer types, including forward references and function aliases; function types identify the declaration supplying the callable signature. annotations support builtin types and fixed arrays; type names are not ordinary values. `void` is valid as a function result, not as a parameter or stored value.
|
||||||
|
|
||||||
numeric typing follows contextual rules within Honey's current `int` and `float` families. integer literals can adopt either family; float literals remain float. numeric demands propagate through arithmetic and inferred bindings before undemanded integers default to `int`. conflicting demands are errors; fixed `int` values do not implicitly convert to `float`. `+`, `-`, `*` and unary `-` require compatible numeric operands; `/` requires and supplies float context. `3 / 2`, `3.0 / 2`, and `ratio float :: 3 / 2` are valid; division involving fixed `int` values requires explicit conversion.
|
numeric typing follows contextual rules within Honey's current `int` and `float` families. integer literals can adopt either family; float literals remain float. numeric demands propagate through arithmetic and inferred bindings before undemanded integers default to `int`. conflicting demands are errors; fixed `int` values do not implicitly convert to `float`. `+`, `-`, `*` and unary `-` require compatible numeric operands; `/` requires and supplies float context. `3 / 2`, `3.0 / 2`, and `ratio float :: 3 / 2` are valid; division involving fixed `int` values requires explicit conversion.
|
||||||
|
|
||||||
@@ -69,6 +75,8 @@ type errors carry source spans and diagnostic identities; invalid operands propa
|
|||||||
|
|
||||||
`lower.lower` turns the checked AST and binding table into an owned `@source/ir` module. functions, globals, local slots and instruction-result temporaries have explicit IDs; typed instructions execute in source evaluation order and basic blocks end in jump, conditional-branch, return or diagnostic-trap terminators. conditional arms join only when execution can continue; loops use condition/body/exit blocks and backedges. short-circuit expressions use branches and synthetic boolean local slots to merge results; call arguments are packed in the block reached after evaluating them. local slots and temporaries are function-wide, without SSA or phi nodes. literals are parsed and strings decoded during lowering; the module retains no AST, binding-table or source-buffer references. release it with `ir.deinit`; keep and reuse the diagnostic store used during lowering so diagnostic IDs remain valid. the IR is target-independent and is intended to feed later native backends as well as the current interpreter.
|
`lower.lower` turns the checked AST and binding table into an owned `@source/ir` module. functions, globals, local slots and instruction-result temporaries have explicit IDs; typed instructions execute in source evaluation order and basic blocks end in jump, conditional-branch, return or diagnostic-trap terminators. conditional arms join only when execution can continue; loops use condition/body/exit blocks and backedges. short-circuit expressions use branches and synthetic boolean local slots to merge results; call arguments are packed in the block reached after evaluating them. local slots and temporaries are function-wide, without SSA or phi nodes. literals are parsed and strings decoded during lowering; the module retains no AST, binding-table or source-buffer references. release it with `ir.deinit`; keep and reuse the diagnostic store used during lowering so diagnostic IDs remain valid. the IR is target-independent and is intended to feed later native backends as well as the current interpreter.
|
||||||
|
|
||||||
`interpreter.run` consumes only IR and returns a value or a diagnostic trap. string values carry a module-local `ir.StringId`, not a slice; resolve bytes with `ir.string_bytes(&module, id)` before releasing the module. decoded bytes and their full backing allocations remain module-owned, without copying strings during calls or returns. heap-backed invocation frames track their current block and resume it after calls or global initialization; loop iterations reuse the invocation's storage without allocating new frames. parameters are initialized on entry and local declarations execute in statement order. premature local reads and assignments trap; assignment evaluates its right-hand side before checking the destination's initialization. globals initialize lazily on their first read, cache their result for that run, and trap on initialization cycles. integers use signed 64-bit storage with checked arithmetic; floats use 64-bit IEEE arithmetic and comparisons, including NaN behavior. numeric literal overflow and float underflow to zero are diagnosed; representable subnormals are accepted. strings support `\\`, `\"`, `\n`, `\r`, `\t`, `\0` and `\xNN` escapes.
|
array IR retains recursive element types, counts and mutability. typed aggregate construction, element addresses and load/store instructions use flattened storage reserved per invocation; nested copies and indexed accesses reuse that storage without per-iteration allocation. addresses are internal IR values, not source-language pointers.
|
||||||
|
|
||||||
|
`interpreter.run` consumes only IR and returns an `Outcome` containing a value or diagnostic trap. returned arrays own their escaped cell storage; release every outcome with `interpreter.deinit` before releasing its module. string values carry a module-local `ir.StringId`, not a slice; resolve bytes with `ir.string_bytes(&module, id)` before releasing the module. decoded bytes and their full backing allocations remain module-owned, without copying strings during calls or returns. array descriptors also borrow the module. heap-backed invocation frames track their current block and resume it after calls or global initialization; loop iterations reuse the invocation's storage without allocating new frames. parameters are initialized on entry and local declarations execute in statement order. premature local reads and assignments trap; assignment evaluates its right-hand side before checking the destination's initialization. globals initialize lazily on their first read, cache their result for that run, and trap on initialization cycles. `int`/`i64` use signed 64-bit storage, `usize` uses unsigned 64-bit storage, both with checked arithmetic; `float`/`f64` use 64-bit IEEE arithmetic and comparisons, including NaN behavior. `usize` supports `+`, `-`, `*` and comparisons, but not unary negation or division. numeric literal overflow and float underflow to zero are diagnosed; representable subnormals are accepted. strings support `\\`, `\"`, `\n`, `\r`, `\t`, `\0` and `\xNN` escapes.
|
||||||
|
|
||||||
recoverable source errors do not stop execution of unaffected code. unused invalid functions, globals and nested bodies retain diagnostic traps; reaching invalid code reports its original diagnostic identity. missing-return traps affect only paths that fall through, not paths that already return or trap. diagnostics include the filename, line and byte column. exit status is `0` for clean execution, `1` for source diagnostics or a runtime trap, and `2` for usage or infrastructure failure. native code generation and compound assignment remain unimplemented.
|
recoverable source errors do not stop execution of unaffected code. unused invalid functions, globals and nested bodies retain diagnostic traps; reaching invalid code reports its original diagnostic identity. missing-return traps affect only paths that fall through, not paths that already return or trap. diagnostics include the filename, line and byte column. exit status is `0` for clean execution, `1` for source diagnostics or a runtime trap, and `2` for usage or infrastructure failure. native code generation and compound assignment remain unimplemented.
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
- [ ] consider whether numeric conversion semantics are properly tuned.
|
||||||
|
- e.g. are we disallowing implicit conversion between types where loss of precission or wrapping could/would occur?
|
||||||
|
- should we do that or should we not?
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
main proc() int {
|
||||||
|
values [4]mut i64 := [1, 2, 3, 4]
|
||||||
|
values[2] = 10
|
||||||
|
|
||||||
|
total := 0
|
||||||
|
count usize :: 4
|
||||||
|
for 0..count |index| {
|
||||||
|
total = total + values[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
return total
|
||||||
|
}
|
||||||
+91
-4
@@ -1,15 +1,15 @@
|
|||||||
#! grammar:
|
#! grammar:
|
||||||
#! * type ..... -> identifier
|
#! * type ..... -> identifier | '[' (expr | '_') ']' ['mut'] type
|
||||||
#! * binding ... -> :: | :=
|
#! * binding ... -> :: | :=
|
||||||
#! * literal .. -> int | float | string | 'true' | 'false'
|
#! * literal .. -> int | float | string | 'true' | 'false'
|
||||||
#! * op ....... -> '+' | '-' | '*' | '/' | '==' | '!=' | '<' | '<=' | '>' | '>=' | 'and' | 'or'
|
#! * op ....... -> '+' | '-' | '*' | '/' | '==' | '!=' | '<' | '<=' | '>' | '>=' | 'and' | 'or'
|
||||||
#! * expr ..... -> identifier | literal | '(' expr ')' | ('-' | '!') expr | expr op expr | expr '(' [expr {',' expr} [',']] ')' | expr ('..' | '..=') expr
|
#! * expr ..... -> identifier | literal | type | '[' [expr {',' expr} [',']] ']' | '(' expr ')' | ('-' | '!') expr | expr op expr | expr '(' [expr {',' expr} [',']] ')' | expr '[' expr ']' | expr ('..' | '..=') expr
|
||||||
#! * decl ..... -> identifier [type] binding expr
|
#! * decl ..... -> identifier [type] binding expr
|
||||||
#! * block .... -> '{' {stmt} '}'
|
#! * block .... -> '{' {stmt} '}'
|
||||||
#! * arm ...... -> block | stmt (single statements become canonical blocks)
|
#! * arm ...... -> block | stmt (single statements become canonical blocks)
|
||||||
#! * stmt ..... -> decl | expr | identifier '=' expr | 'return' [expr] | 'break' | 'continue' | 'if' expr arm ['else' arm] | 'while' expr arm | 'for' expr '|' ['@'] identifier [',' identifier] '|' arm
|
#! * stmt ..... -> decl | expr | expr '=' expr | 'return' [expr] | 'break' | 'continue' | 'if' expr arm ['else' arm] | 'while' expr arm | 'for' expr '|' ['@'] identifier [',' identifier] '|' arm
|
||||||
#! braceless guards/iterables require parentheses unless they are calls; else binds to the nearest if.
|
#! braceless guards/iterables require parentheses unless they are calls; else binds to the nearest if.
|
||||||
#! precedence (tightest first): calls, unary -/!, multiplication/division, addition/subtraction, relational comparisons, equality, and, or, ranges. compound range endpoints require their own parentheses.
|
#! precedence (tightest first): calls/indices, unary -/!, multiplication/division, addition/subtraction, relational comparisons, equality, and, or, ranges. compound range endpoints require their own parentheses.
|
||||||
#! binary operators associate left; unary operators associate right.
|
#! binary operators associate left; unary operators associate right.
|
||||||
|
|
||||||
import "@std/debug"
|
import "@std/debug"
|
||||||
@@ -40,6 +40,9 @@ ExprKind :: enum {
|
|||||||
binary
|
binary
|
||||||
call
|
call
|
||||||
range
|
range
|
||||||
|
array_type
|
||||||
|
array_literal
|
||||||
|
index
|
||||||
invalid
|
invalid
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,6 +89,9 @@ NodeKind :: enum {
|
|||||||
expr_binary,
|
expr_binary,
|
||||||
expr_call,
|
expr_call,
|
||||||
expr_range,
|
expr_range,
|
||||||
|
expr_array_type,
|
||||||
|
expr_array_literal,
|
||||||
|
expr_index,
|
||||||
stmt_expr,
|
stmt_expr,
|
||||||
stmt_decl,
|
stmt_decl,
|
||||||
stmt_return,
|
stmt_return,
|
||||||
@@ -181,6 +187,19 @@ CallExprView :: struct {
|
|||||||
callee NodeId
|
callee NodeId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ArrayTypeView :: struct {
|
||||||
|
span Span
|
||||||
|
count ?NodeId
|
||||||
|
element NodeId
|
||||||
|
mutable bool
|
||||||
|
}
|
||||||
|
|
||||||
|
IndexView :: struct {
|
||||||
|
span Span
|
||||||
|
base NodeId
|
||||||
|
index NodeId
|
||||||
|
}
|
||||||
|
|
||||||
ReturnView :: struct {
|
ReturnView :: struct {
|
||||||
span Span
|
span Span
|
||||||
value ?NodeId
|
value ?NodeId
|
||||||
@@ -336,6 +355,37 @@ add_call proc(
|
|||||||
}, span)
|
}, span)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_array_type proc(
|
||||||
|
builder @mut Builder, span Span, count ?NodeId, element NodeId, mutable bool,
|
||||||
|
) NodeId ! mem.AllocError {
|
||||||
|
raw_count :: if (count) |id| id else no_node
|
||||||
|
flag :: if (mutable) u32(1) else u32(0)
|
||||||
|
return try add_node(builder, Node{
|
||||||
|
kind = .expr_array_type,
|
||||||
|
main = NodeData{ extra = flag },
|
||||||
|
data0 = NodeData{ node_id = raw_count },
|
||||||
|
data1 = NodeData{ node_id = element },
|
||||||
|
}, span)
|
||||||
|
}
|
||||||
|
|
||||||
|
add_array_literal proc(builder @mut Builder, span Span, elements []NodeId) NodeId ! mem.AllocError {
|
||||||
|
start :: builder.storage.extra.items.len
|
||||||
|
for (elements) |element| _ = try add_extra(builder, u32(element))
|
||||||
|
return try add_node(builder, Node{
|
||||||
|
kind = .expr_array_literal,
|
||||||
|
data0 = NodeData{ extra = u32(start) },
|
||||||
|
data1 = NodeData{ extra = u32(builder.storage.extra.items.len) },
|
||||||
|
}, span)
|
||||||
|
}
|
||||||
|
|
||||||
|
add_index proc(builder @mut Builder, span Span, base, index NodeId) NodeId ! mem.AllocError {
|
||||||
|
return try add_node(builder, Node{
|
||||||
|
kind = .expr_index,
|
||||||
|
data0 = NodeData{ node_id = base },
|
||||||
|
data1 = NodeData{ node_id = index },
|
||||||
|
}, span)
|
||||||
|
}
|
||||||
|
|
||||||
add_expression_stmt proc(builder @mut Builder, expression NodeId) NodeId ! mem.AllocError {
|
add_expression_stmt proc(builder @mut Builder, expression NodeId) NodeId ! mem.AllocError {
|
||||||
return try add_node(builder, Node{
|
return try add_node(builder, Node{
|
||||||
kind = .stmt_expr,
|
kind = .stmt_expr,
|
||||||
@@ -710,6 +760,9 @@ expression_kind proc(kind NodeKind) ExprKind {
|
|||||||
.expr_binary: .binary
|
.expr_binary: .binary
|
||||||
.expr_call: .call
|
.expr_call: .call
|
||||||
.expr_range: .range
|
.expr_range: .range
|
||||||
|
.expr_array_type: .array_type
|
||||||
|
.expr_array_literal: .array_literal
|
||||||
|
.expr_index: .index
|
||||||
.invalid_expr: .invalid
|
.invalid_expr: .invalid
|
||||||
else: unreachable
|
else: unreachable
|
||||||
}
|
}
|
||||||
@@ -769,6 +822,40 @@ call_arg proc(tree @Tree, id NodeId, index usize) NodeId {
|
|||||||
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index])
|
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
array_type proc(tree @Tree, id NodeId) ArrayTypeView {
|
||||||
|
node :: get_node(&tree.storage, id)
|
||||||
|
debug.assert(node.kind == .expr_array_type)
|
||||||
|
return ArrayTypeView{
|
||||||
|
span = get_span(&tree.storage, id),
|
||||||
|
count = maybe_node_id(u32(node.data0.node_id)),
|
||||||
|
element = node.data1.node_id,
|
||||||
|
mutable = node.main.extra != 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
array_element_count proc(tree @Tree, id NodeId) usize {
|
||||||
|
node :: get_node(&tree.storage, id)
|
||||||
|
debug.assert(node.kind == .expr_array_literal)
|
||||||
|
return usize(node.data1.extra - node.data0.extra)
|
||||||
|
}
|
||||||
|
|
||||||
|
array_element proc(tree @Tree, id NodeId, index usize) NodeId {
|
||||||
|
node :: get_node(&tree.storage, id)
|
||||||
|
debug.assert(node.kind == .expr_array_literal)
|
||||||
|
debug.assert(index < usize(node.data1.extra - node.data0.extra))
|
||||||
|
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index])
|
||||||
|
}
|
||||||
|
|
||||||
|
index_expr proc(tree @Tree, id NodeId) IndexView {
|
||||||
|
node :: get_node(&tree.storage, id)
|
||||||
|
debug.assert(node.kind == .expr_index)
|
||||||
|
return IndexView{
|
||||||
|
span = get_span(&tree.storage, id),
|
||||||
|
base = node.data0.node_id,
|
||||||
|
index = node.data1.node_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
node_span proc(tree @Tree, id NodeId) Span {
|
node_span proc(tree @Tree, id NodeId) Span {
|
||||||
return get_span(&tree.storage, id)
|
return get_span(&tree.storage, id)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,3 +32,35 @@ exposes_typed_declaration_views test {
|
|||||||
try testing.expect_equal(ExprKind.literal_int, expr_kind(&tree, statement.initializer))
|
try testing.expect_equal(ExprKind.literal_int, expr_kind(&tree, statement.initializer))
|
||||||
try testing.expect_equal("value", source_text(&tree, statement.name))
|
try testing.expect_equal("value", source_text(&tree, statement.name))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exposes_array_type_literal_and_index_views test {
|
||||||
|
builder := init_builder(mem.c_allocator, "")
|
||||||
|
defer deinit_builder(&builder)
|
||||||
|
span :: Span{ start = 0, end = 0 }
|
||||||
|
count :: try add_literal(&builder, .int, span)
|
||||||
|
element :: try add_identifier(&builder, span)
|
||||||
|
row :: try add_array_type(&builder, span, count, element, false)
|
||||||
|
matrix :: try add_array_type(&builder, span, null, row, true)
|
||||||
|
elements [2]NodeId :: [count, element]
|
||||||
|
literal :: try add_array_literal(&builder, span, elements[..])
|
||||||
|
empty :: try add_array_literal(&builder, span, &[])
|
||||||
|
indexed :: try add_index(&builder, span, literal, count)
|
||||||
|
package :: try add_package(&builder, span, &[])
|
||||||
|
tree := finish(&builder, package)
|
||||||
|
defer deinit(&tree)
|
||||||
|
try testing.expect_equal(ExprKind.array_type, expr_kind(&tree, matrix))
|
||||||
|
try testing.expect_equal(null, array_type(&tree, matrix).count)
|
||||||
|
try testing.expect(array_type(&tree, matrix).mutable)
|
||||||
|
try testing.expect_equal(row, array_type(&tree, matrix).element)
|
||||||
|
try testing.expect_equal(count, array_type(&tree, row).count?)
|
||||||
|
try testing.expect(!array_type(&tree, row).mutable)
|
||||||
|
try testing.expect_equal(element, array_type(&tree, row).element)
|
||||||
|
try testing.expect_equal(ExprKind.array_literal, expr_kind(&tree, literal))
|
||||||
|
try testing.expect_equal(2, array_element_count(&tree, literal))
|
||||||
|
try testing.expect_equal(count, array_element(&tree, literal, 0))
|
||||||
|
try testing.expect_equal(element, array_element(&tree, literal, 1))
|
||||||
|
try testing.expect_equal(0, array_element_count(&tree, empty))
|
||||||
|
try testing.expect_equal(ExprKind.index, expr_kind(&tree, indexed))
|
||||||
|
try testing.expect_equal(literal, index_expr(&tree, indexed).base)
|
||||||
|
try testing.expect_equal(count, index_expr(&tree, indexed).index)
|
||||||
|
}
|
||||||
|
|||||||
@@ -132,6 +132,12 @@ render_statement proc(
|
|||||||
try append_text(renderer, indent)
|
try append_text(renderer, indent)
|
||||||
try append_text(renderer, "declaration ")
|
try append_text(renderer, "declaration ")
|
||||||
try append_text(renderer, ast.source_text(tree, statement.name))
|
try append_text(renderer, ast.source_text(tree, statement.name))
|
||||||
|
if (statement.type) |type| {
|
||||||
|
try append_text(renderer, " ")
|
||||||
|
try append_text(renderer, ast.source_text(tree, ast.node_span(tree, type)))
|
||||||
|
}
|
||||||
|
try append_text(renderer, if (statement.mutability == .constant) " :: " else " := ")
|
||||||
|
try append_text(renderer, ast.source_text(tree, ast.node_span(tree, statement.initializer)))
|
||||||
try append_text(renderer, "\n")
|
try append_text(renderer, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import "@std/mem"
|
||||||
|
import "@std/testing"
|
||||||
|
import "@source/diagnostics"
|
||||||
|
import "@source/strpool"
|
||||||
|
import "@source/lexer"
|
||||||
|
import "@source/parser"
|
||||||
|
import "@source/ast"
|
||||||
|
|
||||||
|
renders_array_annotations_literals_and_indexed_assignments test {
|
||||||
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.strings)
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
source ::
|
||||||
|
`data [2]mut i64 := [1, 2]
|
||||||
|
`main proc() i64 {
|
||||||
|
` (data)[0] = 9
|
||||||
|
` return data[0]
|
||||||
|
`}
|
||||||
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
|
defer lexer.deinit(&scan_state)
|
||||||
|
try lexer.scan(&scan_state, source, &diagnostic_store)
|
||||||
|
tree := try parser.parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||||
|
defer ast.deinit(&tree)
|
||||||
|
renderer := init(mem.c_allocator)
|
||||||
|
defer deinit(&renderer)
|
||||||
|
try render(&renderer, &tree)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
try testing.expect_equal(
|
||||||
|
"package\n declaration data [2]mut i64 := [1, 2]\n function main\n assignment (data)[0] = 9\n return data[0]\n",
|
||||||
|
renderer.bytes.items,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -37,6 +37,14 @@ Code :: enum {
|
|||||||
range_value_not_supported,
|
range_value_not_supported,
|
||||||
range_pointer_capture,
|
range_pointer_capture,
|
||||||
range_index_capture,
|
range_index_capture,
|
||||||
|
invalid_array_length,
|
||||||
|
array_length_mismatch,
|
||||||
|
invalid_array_element_type,
|
||||||
|
array_size_overflow,
|
||||||
|
invalid_index_type,
|
||||||
|
not_indexable,
|
||||||
|
invalid_index_assignment,
|
||||||
|
index_out_of_bounds,
|
||||||
missing_entry,
|
missing_entry,
|
||||||
invalid_entry,
|
invalid_entry,
|
||||||
literal_out_of_range,
|
literal_out_of_range,
|
||||||
@@ -55,7 +63,7 @@ Diagnostic :: struct {
|
|||||||
#! owns diagnostic storage and retains its allocator; release with deinit.
|
#! owns diagnostic storage and retains its allocator; release with deinit.
|
||||||
Store :: struct { list arraylist.ArrayList(Diagnostic) }
|
Store :: struct { list arraylist.ArrayList(Diagnostic) }
|
||||||
|
|
||||||
CapacityError :: enum { diagnostic_limit_reached }
|
CapacityError :: enum { diagnostic_limit_reached, storage_limit_reached }
|
||||||
|
|
||||||
#! retains `allocator` for added diagnostics; the caller owns the returned Store.
|
#! retains `allocator` for added diagnostics; the caller owns the returned Store.
|
||||||
init proc(allocator mem.Allocator) Store {
|
init proc(allocator mem.Allocator) Store {
|
||||||
@@ -112,6 +120,14 @@ messages :: $enummap.init(Code, []u8, {
|
|||||||
range_value_not_supported = "first-class range values are not implemented",
|
range_value_not_supported = "first-class range values are not implemented",
|
||||||
range_pointer_capture = "range loops do not support pointer captures",
|
range_pointer_capture = "range loops do not support pointer captures",
|
||||||
range_index_capture = "range loops do not support index captures",
|
range_index_capture = "range loops do not support index captures",
|
||||||
|
invalid_array_length = "array length must be a nonnegative supported compile-time integer",
|
||||||
|
array_length_mismatch = "array literal length does not match its type",
|
||||||
|
invalid_array_element_type = "array element annotations require a supported concrete value type",
|
||||||
|
array_size_overflow = "array storage size exceeds the supported address space",
|
||||||
|
invalid_index_type = "array index must have type usize",
|
||||||
|
not_indexable = "indexing is currently implemented for fixed arrays only",
|
||||||
|
invalid_index_assignment = "indexed assignment requires a writable array element",
|
||||||
|
index_out_of_bounds = "index out of bounds",
|
||||||
missing_entry = "program requires a main function",
|
missing_entry = "program requires a main function",
|
||||||
invalid_entry = "main must be a function with no parameters",
|
invalid_entry = "main must be a function with no parameters",
|
||||||
literal_out_of_range = "numeric literal is outside its runtime type's range",
|
literal_out_of_range = "numeric literal is outside its runtime type's range",
|
||||||
|
|||||||
@@ -6,6 +6,17 @@ import "@source/diagnostics"
|
|||||||
Outcome :: struct {
|
Outcome :: struct {
|
||||||
value ir.Value = ir.Value{}
|
value ir.Value = ir.Value{}
|
||||||
trap ?diagnostics.DiagnosticId = null
|
trap ?diagnostics.DiagnosticId = null
|
||||||
|
allocator ?mem.Allocator = null
|
||||||
|
owned_elements ?[]mut ir.Value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
#! release an escaped aggregate with its retained allocator; scalar and trap outcomes need no allocation.
|
||||||
|
#! strings and array descriptors borrow the module, which must outlive this outcome.
|
||||||
|
deinit proc(outcome @mut Outcome) void {
|
||||||
|
if (outcome.owned_elements) |elements| mem.free(outcome.allocator?, elements)
|
||||||
|
outcome.owned_elements = null
|
||||||
|
outcome.allocator = null
|
||||||
|
outcome.value = ir.Value{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
@@ -23,6 +34,15 @@ GlobalState :: struct {
|
|||||||
value ir.Value = ir.Value{}
|
value ir.Value = ir.Value{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
Address :: struct {
|
||||||
|
type ir.Type
|
||||||
|
array_type ir.ArrayId = ir.no_array
|
||||||
|
cells []mut ir.Value
|
||||||
|
scalar ?@mut ir.Value = null
|
||||||
|
initialized ?@mut bool = null
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
Frame :: struct {
|
Frame :: struct {
|
||||||
function ir.FunctionId
|
function ir.FunctionId
|
||||||
@@ -32,10 +52,12 @@ Frame :: struct {
|
|||||||
global ?usize = null
|
global ?usize = null
|
||||||
locals []mut Slot
|
locals []mut Slot
|
||||||
values []mut ir.Value
|
values []mut ir.Value
|
||||||
|
aggregate []mut ir.Value
|
||||||
|
addresses []mut Address
|
||||||
}
|
}
|
||||||
|
|
||||||
#! executes only typed IR; invocation and global state are temporary and released on every exit.
|
#! executes typed IR with invocation/global storage released on every exit.
|
||||||
#! returned string IDs resolve through `module`; runtime source errors are traps, not infrastructure errors.
|
#! array results own flat storage released by `deinit`; string IDs and shapes resolve through `module`.
|
||||||
run proc(
|
run proc(
|
||||||
allocator mem.Allocator,
|
allocator mem.Allocator,
|
||||||
module @ir.Module,
|
module @ir.Module,
|
||||||
@@ -51,6 +73,22 @@ run proc(
|
|||||||
globals :: try mem.alloc(GlobalState, allocator, module.globals.items.len)
|
globals :: try mem.alloc(GlobalState, allocator, module.globals.items.len)
|
||||||
defer mem.free(allocator, globals)
|
defer mem.free(allocator, globals)
|
||||||
mem.set(globals, GlobalState{})
|
mem.set(globals, GlobalState{})
|
||||||
|
global_cells usize := 0
|
||||||
|
for (module.globals.items) |global| {
|
||||||
|
if (global.type != .array) continue
|
||||||
|
cells :: module.arrays.items[usize(global.array_type)].cells
|
||||||
|
if (cells > maxval!(usize) - global_cells) return .storage_limit_reached
|
||||||
|
global_cells += cells
|
||||||
|
}
|
||||||
|
aggregate_globals :: try mem.alloc(ir.Value, allocator, global_cells)
|
||||||
|
defer mem.free(allocator, aggregate_globals)
|
||||||
|
global_offset usize := 0
|
||||||
|
for (module.globals.items) |global, i| {
|
||||||
|
if (global.type != .array) continue
|
||||||
|
cells :: module.arrays.items[usize(global.array_type)].cells
|
||||||
|
globals[i].value = array_view(global.array_type, aggregate_globals[global_offset..global_offset + cells])
|
||||||
|
global_offset += cells
|
||||||
|
}
|
||||||
frames := arraylist.init(Frame, allocator)
|
frames := arraylist.init(Frame, allocator)
|
||||||
defer {
|
defer {
|
||||||
for (frames.items) |frame| free_frame(allocator, frame)
|
for (frames.items) |frame| free_frame(allocator, frame)
|
||||||
@@ -81,16 +119,25 @@ run proc(
|
|||||||
value = frames.items[frame_index].values[usize(terminator.value)]
|
value = frames.items[frame_index].values[usize(terminator.value)]
|
||||||
}
|
}
|
||||||
completed :: frames.items[frame_index]
|
completed :: frames.items[frame_index]
|
||||||
_ = arraylist.pop(&frames)
|
# transfer leaves while the completed frame still owns its backing.
|
||||||
free_frame(allocator, completed)
|
|
||||||
if completed.global |global_index| {
|
if completed.global |global_index| {
|
||||||
globals[global_index].value = value
|
copy_value(&globals[global_index].value, value)
|
||||||
globals[global_index].status = .ready
|
globals[global_index].status = .ready
|
||||||
}
|
}
|
||||||
if (frames.items.len == 0) return Outcome{ value = value }
|
if (frames.items.len == 1) {
|
||||||
if completed.destination != ir.no_value {
|
if (value.type == .array) {
|
||||||
frames.items[frames.items.len - 1].values[usize(completed.destination)] = value
|
escaped :: try mem.alloc(ir.Value, allocator, value.data.elements.len)
|
||||||
|
memcopy!(escaped, value.data.elements)
|
||||||
|
value.data.elements = escaped
|
||||||
|
return Outcome{ value = value, allocator = allocator, owned_elements = escaped }
|
||||||
}
|
}
|
||||||
|
return Outcome{ value = value }
|
||||||
|
}
|
||||||
|
if completed.destination != ir.no_value {
|
||||||
|
copy_value(&frames.items[frame_index - 1].values[usize(completed.destination)], value)
|
||||||
|
}
|
||||||
|
_ = arraylist.pop(&frames)
|
||||||
|
free_frame(allocator, completed)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,10 +154,9 @@ run proc(
|
|||||||
value = local.value
|
value = local.value
|
||||||
}
|
}
|
||||||
.store_local: {
|
.store_local: {
|
||||||
frames.items[frame_index].locals[usize(instruction.slot)] = Slot{
|
local :: &frames.items[frame_index].locals[usize(instruction.slot)]
|
||||||
initialized = true,
|
copy_value(&local.value, frames.items[frame_index].values[usize(instruction.left)])
|
||||||
value = frames.items[frame_index].values[usize(instruction.left)],
|
local.initialized = true
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
.assign_local: {
|
.assign_local: {
|
||||||
@@ -118,7 +164,7 @@ run proc(
|
|||||||
if (!local.initialized) {
|
if (!local.initialized) {
|
||||||
return try runtime_trap(diagnostic_store, .write_before_initialization, instruction.span)
|
return try runtime_trap(diagnostic_store, .write_before_initialization, instruction.span)
|
||||||
}
|
}
|
||||||
local.value = frames.items[frame_index].values[usize(instruction.left)]
|
copy_value(&local.value, frames.items[frame_index].values[usize(instruction.left)])
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
.reset_local: {
|
.reset_local: {
|
||||||
@@ -152,13 +198,107 @@ run proc(
|
|||||||
# frame indices survive stack growth; pointers into frames.items do not.
|
# frame indices survive stack growth; pointers into frames.items do not.
|
||||||
for (0..instruction.arguments_count) |argument_index| {
|
for (0..instruction.arguments_count) |argument_index| {
|
||||||
argument :: block.arguments.items[instruction.arguments_start + argument_index]
|
argument :: block.arguments.items[instruction.arguments_start + argument_index]
|
||||||
frames.items[frame_index + 1].locals[argument_index] = Slot{
|
local :: &frames.items[frame_index + 1].locals[argument_index]
|
||||||
initialized = true,
|
copy_value(&local.value, frames.items[frame_index].values[usize(argument)])
|
||||||
value = frames.items[frame_index].values[usize(argument)],
|
local.initialized = true
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
.array_literal: {
|
||||||
|
destination :: &frames.items[frame_index].values[usize(instruction.destination)]
|
||||||
|
offset usize := 0
|
||||||
|
for (0..instruction.arguments_count) |i| {
|
||||||
|
operand :: block.arguments.items[instruction.arguments_start + i]
|
||||||
|
element :: frames.items[frame_index].values[usize(operand)]
|
||||||
|
if (element.type == .array) {
|
||||||
|
count :: element.data.elements.len
|
||||||
|
memcopy!(destination.data.elements[offset..offset + count], element.data.elements)
|
||||||
|
offset += count
|
||||||
|
} else {
|
||||||
|
destination.data.elements[offset] = element
|
||||||
|
offset += 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
.index: {
|
||||||
|
base :: frames.items[frame_index].values[usize(instruction.left)]
|
||||||
|
index :: frames.items[frame_index].values[usize(instruction.right)].data.unsigned
|
||||||
|
shape :: module.arrays.items[usize(base.array_type)]
|
||||||
|
if (index >= u64(shape.len)) {
|
||||||
|
return try runtime_trap(diagnostic_store, .index_out_of_bounds, instruction.span)
|
||||||
|
}
|
||||||
|
value = indexed_value(module, base.array_type, base.data.elements, usize(index))
|
||||||
|
}
|
||||||
|
.address_local: {
|
||||||
|
local :: &frames.items[frame_index].locals[usize(instruction.slot)]
|
||||||
|
address := Address{
|
||||||
|
type = instruction.pointee, array_type = instruction.array_type,
|
||||||
|
cells = mem.empty(ir.Value), initialized = &local.initialized,
|
||||||
|
}
|
||||||
|
if (address.type == .array) address.cells = local.value.data.elements
|
||||||
|
else address.scalar = &local.value
|
||||||
|
frames.items[frame_index].addresses[usize(instruction.destination)] = address
|
||||||
|
value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } }
|
||||||
|
}
|
||||||
|
.address_global: {
|
||||||
|
global_index :: usize(instruction.slot)
|
||||||
|
match globals[global_index].status {
|
||||||
|
.initializing: return try runtime_trap(diagnostic_store, .initialization_cycle, instruction.span)
|
||||||
|
.uninitialized: {
|
||||||
|
frames.items[frame_index].next_instruction -= 1
|
||||||
|
try push_frame(allocator, module, &frames, module.globals.items[global_index].initializer, ir.no_value, global_index)
|
||||||
|
globals[global_index].status = .initializing
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
.ready: {}
|
||||||
|
}
|
||||||
|
global :: &globals[global_index]
|
||||||
|
address := Address{
|
||||||
|
type = instruction.pointee, array_type = instruction.array_type,
|
||||||
|
cells = mem.empty(ir.Value),
|
||||||
|
}
|
||||||
|
if (address.type == .array) address.cells = global.value.data.elements
|
||||||
|
else address.scalar = &global.value
|
||||||
|
frames.items[frame_index].addresses[usize(instruction.destination)] = address
|
||||||
|
value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } }
|
||||||
|
}
|
||||||
|
.index_address: {
|
||||||
|
base_id :: frames.items[frame_index].values[usize(instruction.left)].data.address
|
||||||
|
base :: frames.items[frame_index].addresses[usize(base_id)]
|
||||||
|
index :: frames.items[frame_index].values[usize(instruction.right)].data.unsigned
|
||||||
|
shape :: module.arrays.items[usize(base.array_type)]
|
||||||
|
if (index >= u64(shape.len)) {
|
||||||
|
return try runtime_trap(diagnostic_store, .index_out_of_bounds, instruction.span)
|
||||||
|
}
|
||||||
|
width :: element_cells(module, shape)
|
||||||
|
start :: usize(index) * width
|
||||||
|
address := Address{
|
||||||
|
type = instruction.pointee, array_type = instruction.array_type,
|
||||||
|
cells = base.cells[start..start + width], initialized = base.initialized,
|
||||||
|
}
|
||||||
|
if (address.type != .array) address.scalar = &address.cells[0]
|
||||||
|
frames.items[frame_index].addresses[usize(instruction.destination)] = address
|
||||||
|
value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } }
|
||||||
|
}
|
||||||
|
.load_address, .store_address: {
|
||||||
|
id :: frames.items[frame_index].values[usize(instruction.left)].data.address
|
||||||
|
address :: frames.items[frame_index].addresses[usize(id)]
|
||||||
|
if address.initialized |initialized| {
|
||||||
|
if (!initialized^) {
|
||||||
|
code :: if (instruction.op == .load_address) diagnostics.Code.read_before_initialization else diagnostics.Code.write_before_initialization
|
||||||
|
return try runtime_trap(diagnostic_store, code, instruction.span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (instruction.op == .load_address) {
|
||||||
|
value = if (address.type == .array) array_view(address.array_type, address.cells) else address.scalar?^
|
||||||
|
} else {
|
||||||
|
source :: frames.items[frame_index].values[usize(instruction.right)]
|
||||||
|
if (address.type == .array) memcopy!(address.cells, source.data.elements)
|
||||||
|
else address.scalar?^ = source
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
.negate: {
|
.negate: {
|
||||||
operand :: frames.items[frame_index].values[usize(instruction.left)]
|
operand :: frames.items[frame_index].values[usize(instruction.left)]
|
||||||
if instruction.type == .float {
|
if instruction.type == .float {
|
||||||
@@ -187,6 +327,13 @@ run proc(
|
|||||||
else: unreachable
|
else: unreachable
|
||||||
}
|
}
|
||||||
value = ir.Value{ type = .float, data = ir.ValueData{ real = result } }
|
value = ir.Value{ type = .float, data = ir.ValueData{ real = result } }
|
||||||
|
} else if instruction.type == .usize {
|
||||||
|
result :: checked_unsigned(instruction.op, left.data.unsigned, right.data.unsigned)
|
||||||
|
if result |unsigned| {
|
||||||
|
value = ir.Value{ type = .usize, data = ir.ValueData{ unsigned = unsigned } }
|
||||||
|
} else {
|
||||||
|
return try runtime_trap(diagnostic_store, .integer_overflow, instruction.span)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
result :: checked_integer(instruction.op, left.data.integer, right.data.integer)
|
result :: checked_integer(instruction.op, left.data.integer, right.data.integer)
|
||||||
if result |result_integer| {
|
if result |result_integer| {
|
||||||
@@ -210,6 +357,15 @@ run proc(
|
|||||||
.greater_equal: left.data.integer >= right.data.integer
|
.greater_equal: left.data.integer >= right.data.integer
|
||||||
else: unreachable
|
else: unreachable
|
||||||
}
|
}
|
||||||
|
.usize: result = match instruction.op {
|
||||||
|
.equal: left.data.unsigned == right.data.unsigned
|
||||||
|
.not_equal: left.data.unsigned != right.data.unsigned
|
||||||
|
.less: left.data.unsigned < right.data.unsigned
|
||||||
|
.less_equal: left.data.unsigned <= right.data.unsigned
|
||||||
|
.greater: left.data.unsigned > right.data.unsigned
|
||||||
|
.greater_equal: left.data.unsigned >= right.data.unsigned
|
||||||
|
else: unreachable
|
||||||
|
}
|
||||||
.float: result = match instruction.op {
|
.float: result = match instruction.op {
|
||||||
.equal: left.data.real == right.data.real
|
.equal: left.data.real == right.data.real
|
||||||
.not_equal: left.data.real != right.data.real
|
.not_equal: left.data.real != right.data.real
|
||||||
@@ -230,7 +386,7 @@ run proc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if instruction.destination != ir.no_value {
|
if instruction.destination != ir.no_value {
|
||||||
frames.items[frame_index].values[usize(instruction.destination)] = value
|
copy_value(&frames.items[frame_index].values[usize(instruction.destination)], value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unreachable
|
unreachable
|
||||||
@@ -251,6 +407,17 @@ push_frame proc(
|
|||||||
mem.set(locals, Slot{})
|
mem.set(locals, Slot{})
|
||||||
values :: try mem.alloc(ir.Value, allocator, signature.value_count)
|
values :: try mem.alloc(ir.Value, allocator, signature.value_count)
|
||||||
errdefer mem.free(allocator, values)
|
errdefer mem.free(allocator, values)
|
||||||
|
aggregate :: try mem.alloc(ir.Value, allocator, signature.aggregate_cells)
|
||||||
|
errdefer mem.free(allocator, aggregate)
|
||||||
|
address_count usize :: if (signature.has_addresses) signature.value_count else 0
|
||||||
|
addresses :: try mem.alloc(Address, allocator, address_count)
|
||||||
|
errdefer mem.free(allocator, addresses)
|
||||||
|
for (signature.array_storage.items) |storage| {
|
||||||
|
cells :: module.arrays.items[usize(storage.array_type)].cells
|
||||||
|
value :: array_view(storage.array_type, aggregate[storage.offset..storage.offset + cells])
|
||||||
|
if (storage.local) locals[usize(storage.index)].value = value
|
||||||
|
else values[usize(storage.index)] = value
|
||||||
|
}
|
||||||
# typed IR assigns each temporary before use; only local slots need initialization flags.
|
# typed IR assigns each temporary before use; only local slots need initialization flags.
|
||||||
try arraylist.append(frames, Frame{
|
try arraylist.append(frames, Frame{
|
||||||
function = function,
|
function = function,
|
||||||
@@ -259,6 +426,8 @@ push_frame proc(
|
|||||||
global = global,
|
global = global,
|
||||||
locals = locals,
|
locals = locals,
|
||||||
values = values,
|
values = values,
|
||||||
|
aggregate = aggregate,
|
||||||
|
addresses = addresses,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,6 +435,8 @@ push_frame proc(
|
|||||||
free_frame proc(allocator mem.Allocator, frame Frame) void {
|
free_frame proc(allocator mem.Allocator, frame Frame) void {
|
||||||
mem.free(allocator, frame.locals)
|
mem.free(allocator, frame.locals)
|
||||||
mem.free(allocator, frame.values)
|
mem.free(allocator, frame.values)
|
||||||
|
mem.free(allocator, frame.aggregate)
|
||||||
|
mem.free(allocator, frame.addresses)
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
@@ -305,3 +476,51 @@ checked_integer proc(op ir.Op, left i64, right i64) ?i64 {
|
|||||||
else: unreachable
|
else: unreachable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
array_view proc(shape ir.ArrayId, cells []mut ir.Value) ir.Value {
|
||||||
|
return ir.Value{ type = .array, array_type = shape, data = ir.ValueData{ elements = cells } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
copy_value proc(destination @mut ir.Value, source ir.Value) void {
|
||||||
|
if (source.type == .array) {
|
||||||
|
# aggregate destinations already own disjoint fixed segments; never install an alias.
|
||||||
|
memcopy!(destination.data.elements, source.data.elements)
|
||||||
|
} else {
|
||||||
|
destination^ = source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
element_cells proc(module @ir.Module, shape ir.ArrayType) usize {
|
||||||
|
return if (shape.element == .array) module.arrays.items[usize(shape.element_array)].cells else 1
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
indexed_value proc(module @ir.Module, shape_id ir.ArrayId, cells []mut ir.Value, index usize) ir.Value {
|
||||||
|
shape :: module.arrays.items[usize(shape_id)]
|
||||||
|
if (shape.element != .array) return cells[index]
|
||||||
|
width :: element_cells(module, shape)
|
||||||
|
start :: index * width
|
||||||
|
return array_view(shape.element_array, cells[start..start + width])
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
checked_unsigned proc(op ir.Op, left u64, right u64) ?u64 {
|
||||||
|
match op {
|
||||||
|
.add: {
|
||||||
|
if (left > maxval!(u64) - right) return null
|
||||||
|
return left + right
|
||||||
|
}
|
||||||
|
.subtract: {
|
||||||
|
if (left < right) return null
|
||||||
|
return left - right
|
||||||
|
}
|
||||||
|
.multiply: {
|
||||||
|
if (right != 0 and left > divtrunc!(maxval!(u64), right)) return null
|
||||||
|
return left * right
|
||||||
|
}
|
||||||
|
else: unreachable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ add_function proc(
|
|||||||
errdefer {
|
errdefer {
|
||||||
arraylist.deinit(&function.parameters)
|
arraylist.deinit(&function.parameters)
|
||||||
arraylist.deinit(&function.locals)
|
arraylist.deinit(&function.locals)
|
||||||
|
arraylist.deinit(&function.array_storage)
|
||||||
for (function.blocks.items) |block| {
|
for (function.blocks.items) |block| {
|
||||||
owned := block
|
owned := block
|
||||||
arraylist.deinit(&owned.instructions)
|
arraylist.deinit(&owned.instructions)
|
||||||
@@ -37,6 +38,7 @@ add_function proc(
|
|||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
emit proc(module @mut ir.Module, function ir.FunctionId, instruction ir.Instruction) void ! mem.AllocError {
|
emit proc(module @mut ir.Module, function ir.FunctionId, instruction ir.Instruction) void ! mem.AllocError {
|
||||||
|
if (instruction.type == .address) module.functions.items[usize(function)].has_addresses = true
|
||||||
try arraylist.append(&module.functions.items[usize(function)].blocks.items[0].instructions, instruction)
|
try arraylist.append(&module.functions.items[usize(function)].blocks.items[0].instructions, instruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +112,8 @@ repeated_calls_have_fresh_parameters_locals_and_temporaries test {
|
|||||||
try emit(&module, main, function_value(6, noop))
|
try emit(&module, main, function_value(6, noop))
|
||||||
try emit(&module, main, ir.Instruction{ op = .call, type = .void, destination = ir.ValueId(7), left = ir.ValueId(6) })
|
try emit(&module, main, ir.Instruction{ op = .call, type = .void, destination = ir.ValueId(7), left = ir.ValueId(6) })
|
||||||
return_value(&module, main, ir.ValueId(5))
|
return_value(&module, main, ir.ValueId(5))
|
||||||
outcome :: try run(mem.c_allocator, &module, &store)
|
outcome := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(ir.Type.int, outcome.value.type)
|
try testing.expect_equal(ir.Type.int, outcome.value.type)
|
||||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||||
@@ -138,11 +141,13 @@ lazy_globals_follow_forward_dependencies_and_reset_between_runs test {
|
|||||||
try emit(&module, main, ir.Instruction{ op = .load_global, type = .int, destination = ir.ValueId(1), slot = 0 })
|
try emit(&module, main, ir.Instruction{ op = .load_global, type = .int, destination = ir.ValueId(1), slot = 0 })
|
||||||
try emit(&module, main, ir.Instruction{ op = .add, type = .int, destination = ir.ValueId(2), left = ir.ValueId(0), right = ir.ValueId(1) })
|
try emit(&module, main, ir.Instruction{ op = .add, type = .int, destination = ir.ValueId(2), left = ir.ValueId(0), right = ir.ValueId(1) })
|
||||||
return_value(&module, main, ir.ValueId(2))
|
return_value(&module, main, ir.ValueId(2))
|
||||||
initial :: try run(mem.c_allocator, &module, &store)
|
initial := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&initial)
|
||||||
try testing.expect(initial.trap == null)
|
try testing.expect(initial.trap == null)
|
||||||
try testing.expect_equal(i64(42), initial.value.data.integer)
|
try testing.expect_equal(i64(42), initial.value.data.integer)
|
||||||
module.functions.items[usize(second)].blocks.items[0].instructions.items[0] = integer(0, 30)
|
module.functions.items[usize(second)].blocks.items[0].instructions.items[0] = integer(0, 30)
|
||||||
repeated :: try run(mem.c_allocator, &module, &store)
|
repeated := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&repeated)
|
||||||
try testing.expect(repeated.trap == null)
|
try testing.expect(repeated.trap == null)
|
||||||
try testing.expect_equal(i64(62), repeated.value.data.integer)
|
try testing.expect_equal(i64(62), repeated.value.data.integer)
|
||||||
}
|
}
|
||||||
@@ -176,7 +181,8 @@ function_aliases_and_borrowed_string_values_execute test {
|
|||||||
try emit(&module, main, ir.Instruction{ op = .load_global, type = .function, destination = ir.ValueId(0), slot = 0 })
|
try emit(&module, main, ir.Instruction{ op = .load_global, type = .function, destination = ir.ValueId(0), slot = 0 })
|
||||||
try emit(&module, main, ir.Instruction{ op = .call, type = .string, destination = ir.ValueId(1), left = ir.ValueId(0) })
|
try emit(&module, main, ir.Instruction{ op = .call, type = .string, destination = ir.ValueId(1), left = ir.ValueId(0) })
|
||||||
return_value(&module, main, ir.ValueId(1))
|
return_value(&module, main, ir.ValueId(1))
|
||||||
outcome :: try run(mem.c_allocator, &module, &store)
|
outcome := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(ir.Type.string, outcome.value.type)
|
try testing.expect_equal(ir.Type.string, outcome.value.type)
|
||||||
try testing.expect_equal([]u8, text, ir.string_bytes(&module, outcome.value.data.string))
|
try testing.expect_equal([]u8, text, ir.string_bytes(&module, outcome.value.data.string))
|
||||||
@@ -193,7 +199,8 @@ premature_local_reads_and_global_cycles_report_instruction_spans test {
|
|||||||
module.entry = main
|
module.entry = main
|
||||||
try emit(&module, main, ir.Instruction{ op = .load_local, type = .int, destination = ir.ValueId(0), slot = 0, span = span })
|
try emit(&module, main, ir.Instruction{ op = .load_local, type = .int, destination = ir.ValueId(0), slot = 0, span = span })
|
||||||
return_value(&module, main, ir.ValueId(0))
|
return_value(&module, main, ir.ValueId(0))
|
||||||
premature :: try run(mem.c_allocator, &module, &store)
|
premature := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&premature)
|
||||||
try expect_trap(premature, &store, .read_before_initialization, span)
|
try expect_trap(premature, &store, .read_before_initialization, span)
|
||||||
|
|
||||||
first :: try add_function(&module, mem.empty(ir.Type), 0, .int, 1)
|
first :: try add_function(&module, mem.empty(ir.Type), 0, .int, 1)
|
||||||
@@ -206,9 +213,11 @@ premature_local_reads_and_global_cycles_report_instruction_spans test {
|
|||||||
try arraylist.append(&module.globals, ir.Global{ type = .int, initializer = first, span = diagnostics.Span{ start = 0, end = 0 } })
|
try arraylist.append(&module.globals, ir.Global{ type = .int, initializer = first, span = diagnostics.Span{ start = 0, end = 0 } })
|
||||||
try arraylist.append(&module.globals, ir.Global{ type = .int, initializer = second, span = diagnostics.Span{ start = 0, end = 0 } })
|
try arraylist.append(&module.globals, ir.Global{ type = .int, initializer = second, span = diagnostics.Span{ start = 0, end = 0 } })
|
||||||
module.functions.items[usize(main)].blocks.items[0].instructions.items[0] = ir.Instruction{ op = .load_global, type = .int, destination = ir.ValueId(0), slot = 0 }
|
module.functions.items[usize(main)].blocks.items[0].instructions.items[0] = ir.Instruction{ op = .load_global, type = .int, destination = ir.ValueId(0), slot = 0 }
|
||||||
cycle :: try run(mem.c_allocator, &module, &store)
|
cycle := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&cycle)
|
||||||
try expect_trap(cycle, &store, .initialization_cycle, cycle_span)
|
try expect_trap(cycle, &store, .initialization_cycle, cycle_span)
|
||||||
retry :: try run(mem.c_allocator, &module, &store)
|
retry := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&retry)
|
||||||
try expect_trap(retry, &store, .initialization_cycle, cycle_span)
|
try expect_trap(retry, &store, .initialization_cycle, cycle_span)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,7 +228,8 @@ existing_trap_identity_entry_failures_and_unused_errors_survive test {
|
|||||||
defer diagnostics.deinit(&store)
|
defer diagnostics.deinit(&store)
|
||||||
entry_id :: try diagnostics.add(&store, .missing_entry, diagnostics.Span{ start = 0, end = 0 })
|
entry_id :: try diagnostics.add(&store, .missing_entry, diagnostics.Span{ start = 0, end = 0 })
|
||||||
module.entry_diagnostic = entry_id
|
module.entry_diagnostic = entry_id
|
||||||
absent :: try run(mem.c_allocator, &module, &store)
|
absent := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&absent)
|
||||||
try testing.expect_equal(entry_id, absent.trap?)
|
try testing.expect_equal(entry_id, absent.trap?)
|
||||||
invalid_id :: try diagnostics.add(&store, .unknown_name, diagnostics.Span{ start = 12, end = 17 })
|
invalid_id :: try diagnostics.add(&store, .unknown_name, diagnostics.Span{ start = 12, end = 17 })
|
||||||
bad :: try add_function(&module, mem.empty(ir.Type), 0, .invalid, 0)
|
bad :: try add_function(&module, mem.empty(ir.Type), 0, .invalid, 0)
|
||||||
@@ -228,12 +238,14 @@ existing_trap_identity_entry_failures_and_unused_errors_survive test {
|
|||||||
module.entry = main
|
module.entry = main
|
||||||
try emit(&module, main, integer(0, 42))
|
try emit(&module, main, integer(0, 42))
|
||||||
return_value(&module, main, ir.ValueId(0))
|
return_value(&module, main, ir.ValueId(0))
|
||||||
success :: try run(mem.c_allocator, &module, &store)
|
success := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&success)
|
||||||
try testing.expect(success.trap == null)
|
try testing.expect(success.trap == null)
|
||||||
try testing.expect_equal(i64(42), success.value.data.integer)
|
try testing.expect_equal(i64(42), success.value.data.integer)
|
||||||
module.functions.items[usize(main)].blocks.items[0].instructions.items[0] = function_value(0, bad)
|
module.functions.items[usize(main)].blocks.items[0].instructions.items[0] = function_value(0, bad)
|
||||||
try emit(&module, main, ir.Instruction{ op = .call, type = .invalid, destination = ir.ValueId(1), left = ir.ValueId(0) })
|
try emit(&module, main, ir.Instruction{ op = .call, type = .invalid, destination = ir.ValueId(1), left = ir.ValueId(0) })
|
||||||
trapped :: try run(mem.c_allocator, &module, &store)
|
trapped := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&trapped)
|
||||||
try testing.expect_equal(invalid_id, trapped.trap?)
|
try testing.expect_equal(invalid_id, trapped.trap?)
|
||||||
try testing.expect_equal(usize(2), diagnostics.count(&store))
|
try testing.expect_equal(usize(2), diagnostics.count(&store))
|
||||||
}
|
}
|
||||||
@@ -280,7 +292,8 @@ integer_arithmetic_checks_boundaries_before_host_operations test {
|
|||||||
try emit(&module, main, integer(1, case.right))
|
try emit(&module, main, integer(1, case.right))
|
||||||
try emit(&module, main, ir.Instruction{ op = case.op, type = .int, destination = ir.ValueId(2), left = ir.ValueId(0), right = ir.ValueId(1), span = span })
|
try emit(&module, main, ir.Instruction{ op = case.op, type = .int, destination = ir.ValueId(2), left = ir.ValueId(0), right = ir.ValueId(1), span = span })
|
||||||
return_value(&module, main, ir.ValueId(2))
|
return_value(&module, main, ir.ValueId(2))
|
||||||
outcome :: try run(mem.c_allocator, &module, &store)
|
outcome := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&outcome)
|
||||||
if case.expected |expected| {
|
if case.expected |expected| {
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(expected, outcome.value.data.integer)
|
try testing.expect_equal(expected, outcome.value.data.integer)
|
||||||
@@ -301,10 +314,12 @@ integer_negation_traps_only_at_minimum test {
|
|||||||
try emit(&module, main, integer(0, minval!(i64)))
|
try emit(&module, main, integer(0, minval!(i64)))
|
||||||
try emit(&module, main, ir.Instruction{ op = .negate, type = .int, destination = ir.ValueId(1), left = ir.ValueId(0), span = span })
|
try emit(&module, main, ir.Instruction{ op = .negate, type = .int, destination = ir.ValueId(1), left = ir.ValueId(0), span = span })
|
||||||
return_value(&module, main, ir.ValueId(1))
|
return_value(&module, main, ir.ValueId(1))
|
||||||
trapped :: try run(mem.c_allocator, &module, &store)
|
trapped := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&trapped)
|
||||||
try expect_trap(trapped, &store, .integer_overflow, span)
|
try expect_trap(trapped, &store, .integer_overflow, span)
|
||||||
module.functions.items[usize(main)].blocks.items[0].instructions.items[0] = integer(0, maxval!(i64))
|
module.functions.items[usize(main)].blocks.items[0].instructions.items[0] = integer(0, maxval!(i64))
|
||||||
success :: try run(mem.c_allocator, &module, &store)
|
success := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&success)
|
||||||
try testing.expect(success.trap == null)
|
try testing.expect(success.trap == null)
|
||||||
try testing.expect_equal(-maxval!(i64), success.value.data.integer)
|
try testing.expect_equal(-maxval!(i64), success.value.data.integer)
|
||||||
}
|
}
|
||||||
@@ -324,18 +339,21 @@ float_arithmetic_and_ieee_zero_division_execute test {
|
|||||||
try emit(&module, main, ir.Instruction{ op = .divide, type = .float, destination = ir.ValueId(5), left = ir.ValueId(4), right = ir.ValueId(1) })
|
try emit(&module, main, ir.Instruction{ op = .divide, type = .float, destination = ir.ValueId(5), left = ir.ValueId(4), right = ir.ValueId(1) })
|
||||||
try emit(&module, main, ir.Instruction{ op = .negate, type = .float, destination = ir.ValueId(6), left = ir.ValueId(5) })
|
try emit(&module, main, ir.Instruction{ op = .negate, type = .float, destination = ir.ValueId(6), left = ir.ValueId(5) })
|
||||||
return_value(&module, main, ir.ValueId(6))
|
return_value(&module, main, ir.ValueId(6))
|
||||||
result :: try run(mem.c_allocator, &module, &store)
|
result := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&result)
|
||||||
try testing.expect(result.trap == null)
|
try testing.expect(result.trap == null)
|
||||||
try testing.expect_equal(ir.Type.float, result.value.type)
|
try testing.expect_equal(ir.Type.float, result.value.type)
|
||||||
try testing.expect_equal(f64(-10.5), result.value.data.real)
|
try testing.expect_equal(f64(-10.5), result.value.data.real)
|
||||||
module.functions.items[usize(main)].blocks.items[0].instructions.items[0].constant.data.real = 0
|
module.functions.items[usize(main)].blocks.items[0].instructions.items[0].constant.data.real = 0
|
||||||
module.functions.items[usize(main)].blocks.items[0].instructions.items[1].constant.data.real = 0
|
module.functions.items[usize(main)].blocks.items[0].instructions.items[1].constant.data.real = 0
|
||||||
nan :: try run(mem.c_allocator, &module, &store)
|
nan := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&nan)
|
||||||
try testing.expect(nan.trap == null)
|
try testing.expect(nan.trap == null)
|
||||||
try testing.expect(nan.value.data.real != nan.value.data.real)
|
try testing.expect(nan.value.data.real != nan.value.data.real)
|
||||||
module.functions.items[usize(main)].blocks.items[0].instructions.items[0].constant.data.real = 1
|
module.functions.items[usize(main)].blocks.items[0].instructions.items[0].constant.data.real = 1
|
||||||
module.functions.items[usize(main)].blocks.items[0].instructions.items[2].op = .add
|
module.functions.items[usize(main)].blocks.items[0].instructions.items[2].op = .add
|
||||||
infinite :: try run(mem.c_allocator, &module, &store)
|
infinite := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&infinite)
|
||||||
try testing.expect(infinite.trap == null)
|
try testing.expect(infinite.trap == null)
|
||||||
try testing.expect(infinite.value.data.real < f64(-1000000000))
|
try testing.expect(infinite.value.data.real < f64(-1000000000))
|
||||||
try testing.expect_equal(infinite.value.data.real, infinite.value.data.real * f64(2))
|
try testing.expect_equal(infinite.value.data.real, infinite.value.data.real * f64(2))
|
||||||
@@ -403,7 +421,7 @@ recursive_invocations_use_heap_frames_and_free_on_allocation_failure test {
|
|||||||
try emit_at(&module, main, recursive_block, ir.Instruction{ op = .call, type = .void, destination = ir.ValueId(1), left = ir.ValueId(0) })
|
try emit_at(&module, main, recursive_block, ir.Instruction{ op = .call, type = .void, destination = ir.ValueId(1), left = ir.ValueId(0) })
|
||||||
state := AllocationState{ remaining = 2048 }
|
state := AllocationState{ remaining = 2048 }
|
||||||
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
||||||
_ = run(allocator, &module, &store) catch |error| {
|
unexpected := run(allocator, &module, &store) catch |error| {
|
||||||
match error {
|
match error {
|
||||||
.out_of_memory: {}
|
.out_of_memory: {}
|
||||||
else: return .expectation_failed
|
else: return .expectation_failed
|
||||||
@@ -412,6 +430,7 @@ recursive_invocations_use_heap_frames_and_free_on_allocation_failure test {
|
|||||||
try testing.expect_equal(usize(0), diagnostics.count(&store))
|
try testing.expect_equal(usize(0), diagnostics.count(&store))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
deinit(&unexpected)
|
||||||
return .expectation_failed
|
return .expectation_failed
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -438,7 +457,7 @@ frames_and_globals_are_freed_at_every_allocation_boundary_and_trap test {
|
|||||||
for (usize(0)..usize(16)) |limit| {
|
for (usize(0)..usize(16)) |limit| {
|
||||||
state := AllocationState{ remaining = limit }
|
state := AllocationState{ remaining = limit }
|
||||||
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
||||||
outcome :: run(allocator, &module, &store) catch |error| {
|
outcome := run(allocator, &module, &store) catch |error| {
|
||||||
match error {
|
match error {
|
||||||
.out_of_memory: {}
|
.out_of_memory: {}
|
||||||
else: return .expectation_failed
|
else: return .expectation_failed
|
||||||
@@ -447,9 +466,11 @@ frames_and_globals_are_freed_at_every_allocation_boundary_and_trap test {
|
|||||||
try testing.expect_equal(usize(0), state.live)
|
try testing.expect_equal(usize(0), state.live)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
defer deinit(&outcome)
|
||||||
succeeded = true
|
succeeded = true
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||||
|
deinit(&outcome)
|
||||||
try testing.expect_equal(usize(0), state.live)
|
try testing.expect_equal(usize(0), state.live)
|
||||||
}
|
}
|
||||||
try testing.expect(succeeded)
|
try testing.expect(succeeded)
|
||||||
@@ -458,8 +479,10 @@ frames_and_globals_are_freed_at_every_allocation_boundary_and_trap test {
|
|||||||
module.functions.items[usize(initializer)].blocks.items[usize(init_block)].terminator = ir.Terminator{ kind = .trap, diagnostic = trap_id }
|
module.functions.items[usize(initializer)].blocks.items[usize(init_block)].terminator = ir.Terminator{ kind = .trap, diagnostic = trap_id }
|
||||||
state := AllocationState{ remaining = 64 }
|
state := AllocationState{ remaining = 64 }
|
||||||
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
||||||
trapped :: try run(allocator, &module, &store)
|
trapped := try run(allocator, &module, &store)
|
||||||
|
defer deinit(&trapped)
|
||||||
try testing.expect_equal(trap_id, trapped.trap?)
|
try testing.expect_equal(trap_id, trapped.trap?)
|
||||||
|
deinit(&trapped)
|
||||||
try testing.expect_equal(usize(0), state.live)
|
try testing.expect_equal(usize(0), state.live)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -477,7 +500,7 @@ runtime_diagnostic_allocation_failure_is_infrastructure_error_and_frees_frames t
|
|||||||
defer diagnostics.deinit(&store)
|
defer diagnostics.deinit(&store)
|
||||||
state := AllocationState{ remaining = 64 }
|
state := AllocationState{ remaining = 64 }
|
||||||
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
||||||
_ = run(allocator, &module, &store) catch |error| {
|
unexpected := run(allocator, &module, &store) catch |error| {
|
||||||
match error {
|
match error {
|
||||||
.out_of_memory: {}
|
.out_of_memory: {}
|
||||||
else: return .expectation_failed
|
else: return .expectation_failed
|
||||||
@@ -487,6 +510,7 @@ runtime_diagnostic_allocation_failure_is_infrastructure_error_and_frees_frames t
|
|||||||
try testing.expect_equal(usize(0), diagnostics.count(&store))
|
try testing.expect_equal(usize(0), diagnostics.count(&store))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
deinit(&unexpected)
|
||||||
return .expectation_failed
|
return .expectation_failed
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -500,6 +524,7 @@ add_block proc(module @mut ir.Module, function ir.FunctionId) ir.BlockId ! mem.A
|
|||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
emit_at proc(module @mut ir.Module, function ir.FunctionId, block ir.BlockId, instruction ir.Instruction) void ! mem.AllocError {
|
emit_at proc(module @mut ir.Module, function ir.FunctionId, block ir.BlockId, instruction ir.Instruction) void ! mem.AllocError {
|
||||||
|
if (instruction.type == .address) module.functions.items[usize(function)].has_addresses = true
|
||||||
try arraylist.append(&module.functions.items[usize(function)].blocks.items[usize(block)].instructions, instruction)
|
try arraylist.append(&module.functions.items[usize(function)].blocks.items[usize(block)].instructions, instruction)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -557,15 +582,19 @@ branching_recursive_calls_and_lazy_initializers_resume_the_current_block test {
|
|||||||
module.functions.items[usize(main)].blocks.items[usize(main_exit)].terminator = ir.Terminator{ value = ir.ValueId(4) }
|
module.functions.items[usize(main)].blocks.items[usize(main_exit)].terminator = ir.Terminator{ value = ir.ValueId(4) }
|
||||||
state := AllocationState{ remaining = 2048 }
|
state := AllocationState{ remaining = 2048 }
|
||||||
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
||||||
outcome :: try run(allocator, &module, &store)
|
outcome := try run(allocator, &module, &store)
|
||||||
|
defer deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(i64(130), outcome.value.data.integer)
|
try testing.expect_equal(i64(130), outcome.value.data.integer)
|
||||||
|
deinit(&outcome)
|
||||||
try testing.expect_equal(usize(0), state.live)
|
try testing.expect_equal(usize(0), state.live)
|
||||||
try testing.expect_equal(usize(1), diagnostics.count(&store))
|
try testing.expect_equal(usize(1), diagnostics.count(&store))
|
||||||
# trapping after returning through many heap frames also releases every live frame.
|
# trapping after returning through many heap frames also releases every live frame.
|
||||||
module.functions.items[usize(main)].blocks.items[usize(main_exit)].terminator = ir.Terminator{ kind = .trap, diagnostic = trap_id }
|
module.functions.items[usize(main)].blocks.items[usize(main_exit)].terminator = ir.Terminator{ kind = .trap, diagnostic = trap_id }
|
||||||
trapped :: try run(allocator, &module, &store)
|
trapped := try run(allocator, &module, &store)
|
||||||
|
defer deinit(&trapped)
|
||||||
try testing.expect_equal(trap_id, trapped.trap?)
|
try testing.expect_equal(trap_id, trapped.trap?)
|
||||||
|
deinit(&trapped)
|
||||||
try testing.expect_equal(usize(0), state.live)
|
try testing.expect_equal(usize(0), state.live)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -603,7 +632,8 @@ all_numeric_comparisons_dispatch_on_operand_type_not_bool_result_type test {
|
|||||||
.greater_equal: left >= right
|
.greater_equal: left >= right
|
||||||
else: unreachable
|
else: unreachable
|
||||||
}
|
}
|
||||||
outcome :: try run(mem.c_allocator, &module, &store)
|
outcome := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(ir.Type.bool, outcome.value.type)
|
try testing.expect_equal(ir.Type.bool, outcome.value.type)
|
||||||
try testing.expect_equal(expected, outcome.value.data.boolean)
|
try testing.expect_equal(expected, outcome.value.data.boolean)
|
||||||
@@ -631,7 +661,8 @@ nan_comparisons_obey_ieee_rules_in_either_operand_position test {
|
|||||||
right ir.ValueId :: if (position == 0) ir.ValueId(0) else ir.ValueId(1)
|
right ir.ValueId :: if (position == 0) ir.ValueId(0) else ir.ValueId(1)
|
||||||
try emit(&module, main, ir.Instruction{ op = op, type = .bool, destination = ir.ValueId(2), left = left, right = right })
|
try emit(&module, main, ir.Instruction{ op = op, type = .bool, destination = ir.ValueId(2), left = left, right = right })
|
||||||
return_value(&module, main, ir.ValueId(2))
|
return_value(&module, main, ir.ValueId(2))
|
||||||
outcome :: try run(mem.c_allocator, &module, &store)
|
outcome := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(ir.Type.bool, outcome.value.type)
|
try testing.expect_equal(ir.Type.bool, outcome.value.type)
|
||||||
try testing.expect_equal(op == .not_equal, outcome.value.data.boolean)
|
try testing.expect_equal(op == .not_equal, outcome.value.data.boolean)
|
||||||
@@ -664,7 +695,8 @@ boolean_equality_and_branching_use_boolean_payloads test {
|
|||||||
equal :: left == right
|
equal :: left == right
|
||||||
expected_true :: if (op == .equal) equal else !equal
|
expected_true :: if (op == .equal) equal else !equal
|
||||||
expected i64 :: if (expected_true) 42 else 7
|
expected i64 :: if (expected_true) 42 else 7
|
||||||
outcome :: try run(mem.c_allocator, &module, &store)
|
outcome := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(expected, outcome.value.data.integer)
|
try testing.expect_equal(expected, outcome.value.data.integer)
|
||||||
}
|
}
|
||||||
@@ -686,7 +718,8 @@ assign_local_replaces_initialized_values_without_producing_a_result test {
|
|||||||
try emit(&module, main, ir.Instruction{ op = .assign_local, slot = 0, left = ir.ValueId(1) })
|
try emit(&module, main, ir.Instruction{ op = .assign_local, slot = 0, left = ir.ValueId(1) })
|
||||||
try emit(&module, main, ir.Instruction{ op = .load_local, type = .int, destination = ir.ValueId(2), slot = 0 })
|
try emit(&module, main, ir.Instruction{ op = .load_local, type = .int, destination = ir.ValueId(2), slot = 0 })
|
||||||
return_value(&module, main, ir.ValueId(2))
|
return_value(&module, main, ir.ValueId(2))
|
||||||
outcome :: try run(mem.c_allocator, &module, &store)
|
outcome := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(ir.Type.int, outcome.value.type)
|
try testing.expect_equal(ir.Type.int, outcome.value.type)
|
||||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||||
@@ -712,7 +745,8 @@ reset_local_invalidates_reads_and_assignments_at_the_instruction_span test {
|
|||||||
try emit(&module, main, ir.Instruction{
|
try emit(&module, main, ir.Instruction{
|
||||||
op = op, type = .int, destination = destination, slot = 0, left = ir.ValueId(0), span = span,
|
op = op, type = .int, destination = destination, slot = 0, left = ir.ValueId(0), span = span,
|
||||||
})
|
})
|
||||||
outcome :: try run(mem.c_allocator, &module, &store)
|
outcome := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&outcome)
|
||||||
try expect_trap(outcome, &store, codes[index], span)
|
try expect_trap(outcome, &store, codes[index], span)
|
||||||
try testing.expect_equal(usize(1), diagnostics.count(&store))
|
try testing.expect_equal(usize(1), diagnostics.count(&store))
|
||||||
}
|
}
|
||||||
@@ -736,7 +770,7 @@ premature_assignment_and_reset_traps_release_frames_at_all_allocation_boundaries
|
|||||||
for (usize(0)..usize(16)) |limit| {
|
for (usize(0)..usize(16)) |limit| {
|
||||||
state := AllocationState{ remaining = limit }
|
state := AllocationState{ remaining = limit }
|
||||||
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
||||||
outcome :: run(allocator, &module, &store) catch |error| {
|
outcome := run(allocator, &module, &store) catch |error| {
|
||||||
match error {
|
match error {
|
||||||
.out_of_memory: {}
|
.out_of_memory: {}
|
||||||
else: return .expectation_failed
|
else: return .expectation_failed
|
||||||
@@ -745,8 +779,10 @@ premature_assignment_and_reset_traps_release_frames_at_all_allocation_boundaries
|
|||||||
try testing.expect_equal(usize(0), state.live)
|
try testing.expect_equal(usize(0), state.live)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
defer deinit(&outcome)
|
||||||
succeeded = true
|
succeeded = true
|
||||||
try expect_trap(outcome, &store, .write_before_initialization, span)
|
try expect_trap(outcome, &store, .write_before_initialization, span)
|
||||||
|
deinit(&outcome)
|
||||||
try testing.expect_equal(usize(0), state.live)
|
try testing.expect_equal(usize(0), state.live)
|
||||||
}
|
}
|
||||||
try testing.expect(succeeded)
|
try testing.expect(succeeded)
|
||||||
@@ -767,7 +803,7 @@ assignment_diagnostic_allocation_failure_releases_runtime_storage test {
|
|||||||
defer diagnostics.deinit(&store)
|
defer diagnostics.deinit(&store)
|
||||||
state := AllocationState{ remaining = 64 }
|
state := AllocationState{ remaining = 64 }
|
||||||
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
||||||
_ = run(allocator, &module, &store) catch |error| {
|
unexpected := run(allocator, &module, &store) catch |error| {
|
||||||
match error {
|
match error {
|
||||||
.out_of_memory: {}
|
.out_of_memory: {}
|
||||||
else: return .expectation_failed
|
else: return .expectation_failed
|
||||||
@@ -777,6 +813,7 @@ assignment_diagnostic_allocation_failure_releases_runtime_storage test {
|
|||||||
try testing.expect_equal(usize(0), diagnostics.count(&store))
|
try testing.expect_equal(usize(0), diagnostics.count(&store))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
deinit(&unexpected)
|
||||||
return .expectation_failed
|
return .expectation_failed
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -799,14 +836,630 @@ logical_not_inverts_boolean_payload_and_composes_in_typed_ir test {
|
|||||||
try emit(&module, main, ir.Instruction{ op = .logical_not, type = .bool, destination = ir.ValueId(1), left = ir.ValueId(0) })
|
try emit(&module, main, ir.Instruction{ op = .logical_not, type = .bool, destination = ir.ValueId(1), left = ir.ValueId(0) })
|
||||||
try emit(&module, main, ir.Instruction{ op = .logical_not, type = .bool, destination = ir.ValueId(2), left = ir.ValueId(1) })
|
try emit(&module, main, ir.Instruction{ op = .logical_not, type = .bool, destination = ir.ValueId(2), left = ir.ValueId(1) })
|
||||||
return_value(&module, main, ir.ValueId(1))
|
return_value(&module, main, ir.ValueId(1))
|
||||||
inverted :: try run(mem.c_allocator, &module, &store)
|
inverted := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&inverted)
|
||||||
try testing.expect(inverted.trap == null)
|
try testing.expect(inverted.trap == null)
|
||||||
try testing.expect_equal(ir.Type.bool, inverted.value.type)
|
try testing.expect_equal(ir.Type.bool, inverted.value.type)
|
||||||
try testing.expect_equal(expected[index], inverted.value.data.boolean)
|
try testing.expect_equal(expected[index], inverted.value.data.boolean)
|
||||||
return_value(&module, main, ir.ValueId(2))
|
return_value(&module, main, ir.ValueId(2))
|
||||||
composed :: try run(mem.c_allocator, &module, &store)
|
composed := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&composed)
|
||||||
try testing.expect(composed.trap == null)
|
try testing.expect(composed.trap == null)
|
||||||
try testing.expect_equal(operand, composed.value.data.boolean)
|
try testing.expect_equal(operand, composed.value.data.boolean)
|
||||||
try testing.expect_equal(usize(0), diagnostics.count(&store))
|
try testing.expect_equal(usize(0), diagnostics.count(&store))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
unsigned proc(destination u32, value u64) ir.Instruction {
|
||||||
|
return ir.Instruction{
|
||||||
|
op = .constant, type = .usize, destination = ir.ValueId(destination),
|
||||||
|
constant = ir.Value{ type = .usize, data = ir.ValueData{ unsigned = value } },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
add_array proc(module @mut ir.Module, element ir.Type, child ir.ArrayId, len usize) ir.ArrayId ! mem.AllocError {
|
||||||
|
width :: if (element == .array) module.arrays.items[usize(child)].cells else 1
|
||||||
|
id :: ir.ArrayId(module.arrays.items.len)
|
||||||
|
try arraylist.append(&module.arrays, ir.ArrayType{
|
||||||
|
element = element, element_array = child, len = len, cells = len * width, mutable = true,
|
||||||
|
})
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
array_storage proc(module @mut ir.Module, function ir.FunctionId, index u32, shape ir.ArrayId, local bool) void ! mem.AllocError {
|
||||||
|
body :: &module.functions.items[usize(function)]
|
||||||
|
try arraylist.append(&body.array_storage, ir.ArrayStorage{
|
||||||
|
index = index, array_type = shape, offset = body.aggregate_cells, local = local,
|
||||||
|
})
|
||||||
|
body.aggregate_cells += module.arrays.items[usize(shape)].cells
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
literal proc(module @mut ir.Module, function ir.FunctionId, destination u32, shape ir.ArrayId, elements []u32) void ! mem.AllocError {
|
||||||
|
try array_storage(module, function, destination, shape, false)
|
||||||
|
block :: &module.functions.items[usize(function)].blocks.items[0]
|
||||||
|
start :: block.arguments.items.len
|
||||||
|
for (elements) |element| try arraylist.append(&block.arguments, ir.ValueId(element))
|
||||||
|
try emit(module, function, ir.Instruction{
|
||||||
|
op = .array_literal, type = .array, array_type = shape, destination = ir.ValueId(destination),
|
||||||
|
arguments_start = start, arguments_count = elements.len,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
nested_array_places_and_rvalues_copy_only_their_typed_leaf_segments test {
|
||||||
|
module := ir.init(mem.c_allocator)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&store)
|
||||||
|
row :: try add_array(&module, .int, ir.no_array, 2)
|
||||||
|
matrix :: try add_array(&module, .array, row, 2)
|
||||||
|
types [2]ir.Type :: [.array, .array]
|
||||||
|
main :: try add_function(&module, types[..], 0, .array, 15)
|
||||||
|
module.entry = main
|
||||||
|
module.functions.items[usize(main)].result_array_type = matrix
|
||||||
|
try array_storage(&module, main, 0, matrix, true)
|
||||||
|
try array_storage(&module, main, 1, matrix, true)
|
||||||
|
try emit(&module, main, integer(0, 1))
|
||||||
|
try emit(&module, main, integer(1, 2))
|
||||||
|
pair [2]u32 :: [0, 1]
|
||||||
|
try literal(&module, main, 2, row, pair[..])
|
||||||
|
rows [2]u32 :: [2, 2]
|
||||||
|
try literal(&module, main, 3, matrix, rows[..])
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .store_local, slot = 0, left = ir.ValueId(3) })
|
||||||
|
try array_storage(&module, main, 4, matrix, false)
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .load_local, type = .array, array_type = matrix, destination = ir.ValueId(4), slot = 0 })
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .store_local, slot = 1, left = ir.ValueId(4) })
|
||||||
|
try emit(&module, main, unsigned(5, 0))
|
||||||
|
try emit(&module, main, unsigned(6, 1))
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .address_local, type = .address, pointee = .array, array_type = matrix, destination = ir.ValueId(7), slot = 0 })
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .index_address, type = .address, pointee = .array, array_type = row, destination = ir.ValueId(8), left = ir.ValueId(7), right = ir.ValueId(5) })
|
||||||
|
try array_storage(&module, main, 9, row, false)
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .load_address, type = .array, array_type = row, destination = ir.ValueId(9), left = ir.ValueId(8) })
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .index_address, type = .address, pointee = .int, destination = ir.ValueId(10), left = ir.ValueId(8), right = ir.ValueId(6) })
|
||||||
|
try emit(&module, main, integer(11, 9))
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .store_address, left = ir.ValueId(10), right = ir.ValueId(11) })
|
||||||
|
# a row load is a private copy, not a view of the subsequently mutated matrix.
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .store_address, left = ir.ValueId(8), right = ir.ValueId(9) })
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .store_address, left = ir.ValueId(10), right = ir.ValueId(11) })
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .index_address, type = .address, pointee = .array, array_type = row, destination = ir.ValueId(8), left = ir.ValueId(7), right = ir.ValueId(6) })
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .store_address, left = ir.ValueId(8), right = ir.ValueId(9) })
|
||||||
|
try array_storage(&module, main, 12, matrix, false)
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .load_local, type = .array, array_type = matrix, destination = ir.ValueId(12), slot = 0 })
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .assign_local, slot = 1, left = ir.ValueId(12) })
|
||||||
|
try emit(&module, main, integer(13, 7))
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .store_address, left = ir.ValueId(10), right = ir.ValueId(13) })
|
||||||
|
try array_storage(&module, main, 14, matrix, false)
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .load_local, type = .array, array_type = matrix, destination = ir.ValueId(14), slot = 1 })
|
||||||
|
return_value(&module, main, ir.ValueId(14))
|
||||||
|
outcome := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&outcome)
|
||||||
|
try testing.expect(outcome.trap == null)
|
||||||
|
try testing.expect_equal(matrix, outcome.value.array_type)
|
||||||
|
expected [4]i64 :: [1, 9, 1, 2]
|
||||||
|
for (expected) |value, i| try testing.expect_equal(value, outcome.value.data.elements[i].data.integer)
|
||||||
|
}
|
||||||
|
|
||||||
|
array_arguments_and_returns_are_private_values_before_frame_release test {
|
||||||
|
module := ir.init(mem.c_allocator)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&store)
|
||||||
|
shape :: try add_array(&module, .int, ir.no_array, 1)
|
||||||
|
pair_shape :: try add_array(&module, .int, ir.no_array, 2)
|
||||||
|
types [2]ir.Type :: [.array, .array]
|
||||||
|
callee :: try add_function(&module, types[..], 1, .array, 6)
|
||||||
|
module.functions.items[usize(callee)].result_array_type = shape
|
||||||
|
try array_storage(&module, callee, 0, shape, true)
|
||||||
|
try array_storage(&module, callee, 1, shape, true)
|
||||||
|
try array_storage(&module, callee, 0, shape, false)
|
||||||
|
try array_storage(&module, callee, 5, shape, false)
|
||||||
|
try emit(&module, callee, ir.Instruction{ op = .load_local, type = .array, array_type = shape, destination = ir.ValueId(0), slot = 0 })
|
||||||
|
try emit(&module, callee, ir.Instruction{ op = .store_local, left = ir.ValueId(0), slot = 1 })
|
||||||
|
try emit(&module, callee, ir.Instruction{ op = .address_local, type = .address, pointee = .array, array_type = shape, destination = ir.ValueId(1), slot = 1 })
|
||||||
|
try emit(&module, callee, unsigned(2, 0))
|
||||||
|
try emit(&module, callee, ir.Instruction{ op = .index_address, type = .address, pointee = .int, destination = ir.ValueId(3), left = ir.ValueId(1), right = ir.ValueId(2) })
|
||||||
|
try emit(&module, callee, integer(4, 99))
|
||||||
|
try emit(&module, callee, ir.Instruction{ op = .store_address, left = ir.ValueId(3), right = ir.ValueId(4) })
|
||||||
|
try emit(&module, callee, ir.Instruction{ op = .load_local, type = .array, array_type = shape, destination = ir.ValueId(5), slot = 1 })
|
||||||
|
# typed IR can address a parameter; even this mutation must not escape into the caller.
|
||||||
|
try emit(&module, callee, ir.Instruction{ op = .address_local, type = .address, pointee = .array, array_type = shape, destination = ir.ValueId(1), slot = 0 })
|
||||||
|
try emit(&module, callee, ir.Instruction{ op = .index_address, type = .address, pointee = .int, destination = ir.ValueId(3), left = ir.ValueId(1), right = ir.ValueId(2) })
|
||||||
|
try emit(&module, callee, integer(4, 7))
|
||||||
|
try emit(&module, callee, ir.Instruction{ op = .store_address, left = ir.ValueId(3), right = ir.ValueId(4) })
|
||||||
|
return_value(&module, callee, ir.ValueId(5))
|
||||||
|
main :: try add_function(&module, mem.empty(ir.Type), 0, .array, 8)
|
||||||
|
module.entry = main
|
||||||
|
module.functions.items[usize(main)].result_array_type = pair_shape
|
||||||
|
try emit(&module, main, integer(0, 42))
|
||||||
|
element [1]u32 :: [0]
|
||||||
|
try literal(&module, main, 1, shape, element[..])
|
||||||
|
try emit(&module, main, function_value(2, callee))
|
||||||
|
try array_storage(&module, main, 3, shape, false)
|
||||||
|
try arraylist.append(&module.functions.items[usize(main)].blocks.items[0].arguments, ir.ValueId(1))
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .call, type = .array, array_type = shape, destination = ir.ValueId(3), left = ir.ValueId(2), arguments_start = 1, arguments_count = 1 })
|
||||||
|
try emit(&module, main, unsigned(4, 0))
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .index, type = .int, destination = ir.ValueId(5), left = ir.ValueId(1), right = ir.ValueId(4) })
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .index, type = .int, destination = ir.ValueId(6), left = ir.ValueId(3), right = ir.ValueId(4) })
|
||||||
|
pair [2]u32 :: [5, 6]
|
||||||
|
try literal(&module, main, 7, pair_shape, pair[..])
|
||||||
|
return_value(&module, main, ir.ValueId(7))
|
||||||
|
outcome := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&outcome)
|
||||||
|
try testing.expect(outcome.trap == null)
|
||||||
|
try testing.expect_equal(i64(42), outcome.value.data.elements[0].data.integer)
|
||||||
|
try testing.expect_equal(i64(99), outcome.value.data.elements[1].data.integer)
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_aggregate_globals_escape_with_owned_results_and_release_at_every_boundary test {
|
||||||
|
module := ir.init(mem.c_allocator)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&store)
|
||||||
|
shape :: try add_array(&module, .int, ir.no_array, 1)
|
||||||
|
first :: try add_function(&module, mem.empty(ir.Type), 0, .array, 1)
|
||||||
|
second :: try add_function(&module, mem.empty(ir.Type), 0, .array, 2)
|
||||||
|
module.functions.items[usize(first)].result_array_type = shape
|
||||||
|
module.functions.items[usize(second)].result_array_type = shape
|
||||||
|
try array_storage(&module, first, 0, shape, false)
|
||||||
|
try emit(&module, first, ir.Instruction{ op = .load_global, type = .array, array_type = shape, destination = ir.ValueId(0), slot = 1 })
|
||||||
|
return_value(&module, first, ir.ValueId(0))
|
||||||
|
try emit(&module, second, integer(0, 42))
|
||||||
|
operand [1]u32 :: [0]
|
||||||
|
try literal(&module, second, 1, shape, operand[..])
|
||||||
|
return_value(&module, second, ir.ValueId(1))
|
||||||
|
initializers [2]ir.FunctionId :: [first, second]
|
||||||
|
for (initializers) |initializer| {
|
||||||
|
try arraylist.append(&module.globals, ir.Global{
|
||||||
|
type = .array, array_type = shape, initializer = initializer,
|
||||||
|
span = diagnostics.Span{ start = 0, end = 0 },
|
||||||
|
})
|
||||||
|
}
|
||||||
|
main :: try add_function(&module, mem.empty(ir.Type), 0, .array, 2)
|
||||||
|
module.entry = main
|
||||||
|
module.functions.items[usize(main)].result_array_type = shape
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .address_global, type = .address, pointee = .array, array_type = shape, destination = ir.ValueId(0), slot = 0 })
|
||||||
|
try array_storage(&module, main, 1, shape, false)
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .load_address, type = .array, array_type = shape, destination = ir.ValueId(1), left = ir.ValueId(0) })
|
||||||
|
return_value(&module, main, ir.ValueId(1))
|
||||||
|
succeeded := false
|
||||||
|
failures usize := 0
|
||||||
|
for (usize(0)..usize(40)) |limit| {
|
||||||
|
state := AllocationState{ remaining = limit }
|
||||||
|
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
||||||
|
outcome := run(allocator, &module, &store) catch |error| {
|
||||||
|
if (error != .out_of_memory) return .expectation_failed
|
||||||
|
failures += 1
|
||||||
|
try testing.expect_equal(usize(0), state.live)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
defer deinit(&outcome)
|
||||||
|
succeeded = true
|
||||||
|
try testing.expect(outcome.trap == null)
|
||||||
|
try testing.expect_equal(i64(42), outcome.value.data.elements[0].data.integer)
|
||||||
|
try testing.expect_equal(usize(1), state.live)
|
||||||
|
deinit(&outcome)
|
||||||
|
try testing.expect_equal(ir.Type.void, outcome.value.type)
|
||||||
|
try testing.expect(outcome.owned_elements == null)
|
||||||
|
try testing.expect_equal(usize(0), state.live)
|
||||||
|
}
|
||||||
|
try testing.expect(succeeded)
|
||||||
|
try testing.expect(failures > 0)
|
||||||
|
# restarting a run clears global readiness, including address-triggered initialization cycles.
|
||||||
|
cycle_span :: diagnostics.Span{ start = 51, end = 59 }
|
||||||
|
module.functions.items[usize(second)].has_addresses = true
|
||||||
|
module.functions.items[usize(second)].blocks.items[0].instructions.items[0] = ir.Instruction{
|
||||||
|
op = .address_global, type = .address, pointee = .array, array_type = shape,
|
||||||
|
destination = ir.ValueId(0), slot = 0, span = cycle_span,
|
||||||
|
}
|
||||||
|
state := AllocationState{ remaining = 64 }
|
||||||
|
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
||||||
|
cycle := try run(allocator, &module, &store)
|
||||||
|
defer deinit(&cycle)
|
||||||
|
try expect_trap(cycle, &store, .initialization_cycle, cycle_span)
|
||||||
|
deinit(&cycle)
|
||||||
|
try testing.expect_equal(usize(0), state.live)
|
||||||
|
trap_id :: try diagnostics.add(&store, .invalid_escape, cycle_span)
|
||||||
|
module.functions.items[usize(second)].blocks.items[0].terminator = ir.Terminator{ kind = .trap, diagnostic = trap_id }
|
||||||
|
module.functions.items[usize(second)].blocks.items[0].instructions.items[0] = integer(0, 42)
|
||||||
|
trapped := try run(allocator, &module, &store)
|
||||||
|
defer deinit(&trapped)
|
||||||
|
try testing.expect_equal(trap_id, trapped.trap?)
|
||||||
|
deinit(&trapped)
|
||||||
|
try testing.expect_equal(usize(0), state.live)
|
||||||
|
}
|
||||||
|
|
||||||
|
array_bounds_check_zero_lengths_and_the_full_unsigned_word_before_addressing test {
|
||||||
|
lengths [2]usize :: [0, 1]
|
||||||
|
indices [3]u64 :: [0, 1, maxval!(u64)]
|
||||||
|
operations [2]ir.Op :: [.index, .index_address]
|
||||||
|
for (lengths) |length| {
|
||||||
|
for (indices) |index| {
|
||||||
|
for (operations) |op| {
|
||||||
|
module := ir.init(mem.c_allocator)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&store)
|
||||||
|
shape :: try add_array(&module, .int, ir.no_array, length)
|
||||||
|
types [1]ir.Type :: [.array]
|
||||||
|
main :: try add_function(&module, types[..], 0, .int, 5)
|
||||||
|
module.entry = main
|
||||||
|
try array_storage(&module, main, 0, shape, true)
|
||||||
|
try emit(&module, main, integer(0, 42))
|
||||||
|
operands [1]u32 :: [0]
|
||||||
|
try literal(&module, main, 1, shape, operands[..length])
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .store_local, slot = 0, left = ir.ValueId(1) })
|
||||||
|
try emit(&module, main, unsigned(2, index))
|
||||||
|
span :: diagnostics.Span{ start = 17, end = 29 }
|
||||||
|
base := ir.ValueId(1)
|
||||||
|
if (op == .index_address) {
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .address_local, type = .address, pointee = .array, array_type = shape, destination = ir.ValueId(3), slot = 0 })
|
||||||
|
base = ir.ValueId(3)
|
||||||
|
}
|
||||||
|
result_type ir.Type :: if (op == .index) .int else .address
|
||||||
|
try emit(&module, main, ir.Instruction{
|
||||||
|
op = op, type = result_type,
|
||||||
|
pointee = .int, destination = ir.ValueId(4), left = base, right = ir.ValueId(2), span = span,
|
||||||
|
})
|
||||||
|
if (op == .index_address) {
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .load_address, type = .int, destination = ir.ValueId(0), left = ir.ValueId(4) })
|
||||||
|
return_value(&module, main, ir.ValueId(0))
|
||||||
|
} else return_value(&module, main, ir.ValueId(4))
|
||||||
|
state := AllocationState{ remaining = 64 }
|
||||||
|
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
||||||
|
outcome := try run(allocator, &module, &store)
|
||||||
|
defer deinit(&outcome)
|
||||||
|
if (length == 1 and index == 0) {
|
||||||
|
try testing.expect(outcome.trap == null)
|
||||||
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||||
|
} else try expect_trap(outcome, &store, .index_out_of_bounds, span)
|
||||||
|
deinit(&outcome)
|
||||||
|
try testing.expect_equal(usize(0), state.live)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
zero_leaf_nested_arrays_return_a_shape_without_an_owned_allocation test {
|
||||||
|
module := ir.init(mem.c_allocator)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&store)
|
||||||
|
empty :: try add_array(&module, .bool, ir.no_array, 0)
|
||||||
|
rows :: try add_array(&module, .array, empty, 3)
|
||||||
|
main :: try add_function(&module, mem.empty(ir.Type), 0, .array, 4)
|
||||||
|
module.entry = main
|
||||||
|
module.functions.items[usize(main)].result_array_type = rows
|
||||||
|
try literal(&module, main, 0, empty, mem.empty(u32))
|
||||||
|
operands [3]u32 :: [0, 0, 0]
|
||||||
|
try literal(&module, main, 1, rows, operands[..])
|
||||||
|
return_value(&module, main, ir.ValueId(1))
|
||||||
|
state := AllocationState{ remaining = 64 }
|
||||||
|
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
||||||
|
outcome := try run(allocator, &module, &store)
|
||||||
|
defer deinit(&outcome)
|
||||||
|
try testing.expect(outcome.trap == null)
|
||||||
|
try testing.expect_equal(rows, outcome.value.array_type)
|
||||||
|
try testing.expect_equal(usize(0), outcome.value.data.elements.len)
|
||||||
|
try testing.expect_equal(usize(0), state.live)
|
||||||
|
deinit(&outcome)
|
||||||
|
deinit(&outcome)
|
||||||
|
try testing.expect_equal(usize(0), state.live)
|
||||||
|
try emit(&module, main, unsigned(2, 2))
|
||||||
|
try array_storage(&module, main, 3, empty, false)
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .index, type = .array, array_type = empty, destination = ir.ValueId(3), left = ir.ValueId(1), right = ir.ValueId(2) })
|
||||||
|
return_value(&module, main, ir.ValueId(3))
|
||||||
|
module.functions.items[usize(main)].result_array_type = empty
|
||||||
|
indexed := try run(allocator, &module, &store)
|
||||||
|
defer deinit(&indexed)
|
||||||
|
try testing.expect(indexed.trap == null)
|
||||||
|
try testing.expect_equal(empty, indexed.value.array_type)
|
||||||
|
try testing.expect_equal(usize(0), indexed.value.data.elements.len)
|
||||||
|
deinit(&indexed)
|
||||||
|
try testing.expect_equal(usize(0), state.live)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
UnsignedCase :: struct {
|
||||||
|
op ir.Op
|
||||||
|
left u64
|
||||||
|
right u64
|
||||||
|
expected ?u64
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned_arithmetic_and_comparisons_preserve_all_sixty_four_bits test {
|
||||||
|
wide_sum u64 :: 9223372036854775815
|
||||||
|
almost_maximum u64 :: 18446744073709551614
|
||||||
|
cases [10]UnsignedCase :: [
|
||||||
|
UnsignedCase{ op = .add, left = maxval!(u64), right = 0, expected = maxval!(u64) },
|
||||||
|
UnsignedCase{ op = .add, left = maxval!(u64), right = 1, expected = null },
|
||||||
|
UnsignedCase{ op = .add, left = 9223372036854775808, right = 7, expected = wide_sum },
|
||||||
|
UnsignedCase{ op = .subtract, left = 0, right = 1, expected = null },
|
||||||
|
UnsignedCase{ op = .subtract, left = maxval!(u64), right = 1, expected = almost_maximum },
|
||||||
|
UnsignedCase{ op = .multiply, left = maxval!(u64), right = 1, expected = maxval!(u64) },
|
||||||
|
UnsignedCase{ op = .multiply, left = 9223372036854775808, right = 2, expected = null },
|
||||||
|
UnsignedCase{ op = .multiply, left = maxval!(u64), right = 0, expected = u64(0) },
|
||||||
|
UnsignedCase{ op = .multiply, left = 0, right = maxval!(u64), expected = u64(0) },
|
||||||
|
UnsignedCase{ op = .multiply, left = 3, right = 14, expected = u64(42) },
|
||||||
|
]
|
||||||
|
for (cases) |case| {
|
||||||
|
module := ir.init(mem.c_allocator)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&store)
|
||||||
|
main :: try add_function(&module, mem.empty(ir.Type), 0, .usize, 3)
|
||||||
|
module.entry = main
|
||||||
|
span :: diagnostics.Span{ start = 7, end = 12 }
|
||||||
|
try emit(&module, main, unsigned(0, case.left))
|
||||||
|
try emit(&module, main, unsigned(1, case.right))
|
||||||
|
try emit(&module, main, ir.Instruction{ op = case.op, type = .usize, destination = ir.ValueId(2), left = ir.ValueId(0), right = ir.ValueId(1), span = span })
|
||||||
|
return_value(&module, main, ir.ValueId(2))
|
||||||
|
outcome := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&outcome)
|
||||||
|
if case.expected |expected| {
|
||||||
|
try testing.expect(outcome.trap == null)
|
||||||
|
try testing.expect_equal(ir.Type.usize, outcome.value.type)
|
||||||
|
try testing.expect_equal(expected, outcome.value.data.unsigned)
|
||||||
|
} else try expect_trap(outcome, &store, .integer_overflow, span)
|
||||||
|
}
|
||||||
|
operations [6]ir.Op :: [.equal, .not_equal, .less, .less_equal, .greater, .greater_equal]
|
||||||
|
expected [6]bool :: [false, true, false, false, true, true]
|
||||||
|
for (operations) |op, i| {
|
||||||
|
module := ir.init(mem.c_allocator)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&store)
|
||||||
|
main :: try add_function(&module, mem.empty(ir.Type), 0, .bool, 3)
|
||||||
|
module.entry = main
|
||||||
|
try emit(&module, main, unsigned(0, maxval!(u64)))
|
||||||
|
try emit(&module, main, unsigned(1, 1))
|
||||||
|
try emit(&module, main, ir.Instruction{ op = op, type = .bool, destination = ir.ValueId(2), left = ir.ValueId(0), right = ir.ValueId(1) })
|
||||||
|
return_value(&module, main, ir.ValueId(2))
|
||||||
|
outcome := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&outcome)
|
||||||
|
try testing.expect(outcome.trap == null)
|
||||||
|
try testing.expect_equal(expected[i], outcome.value.data.boolean)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
all_scalar_leaf_families_survive_aggregate_return_and_function_extraction test {
|
||||||
|
values [5]ir.Value :: [
|
||||||
|
ir.Value{ type = .bool, data = ir.ValueData{ boolean = true } },
|
||||||
|
ir.Value{ type = .float, data = ir.ValueData{ real = 3.25 } },
|
||||||
|
ir.Value{ type = .string, data = ir.ValueData{ string = ir.StringId(0) } },
|
||||||
|
ir.Value{ type = .usize, data = ir.ValueData{ unsigned = maxval!(u64) } },
|
||||||
|
ir.Value{ type = .function, data = ir.ValueData{ function = ir.FunctionId(0) } },
|
||||||
|
]
|
||||||
|
for (values) |scalar| {
|
||||||
|
module := ir.init(mem.c_allocator)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
if (scalar.type == .string) {
|
||||||
|
text :: "borrowed\x00text"
|
||||||
|
owned :: try mem.alloc(u8, module.allocator, text.len)
|
||||||
|
errdefer mem.free(module.allocator, owned)
|
||||||
|
memcopy!(owned, text)
|
||||||
|
try arraylist.append(&module.strings, ir.StringStorage{ allocation = owned, len = text.len })
|
||||||
|
}
|
||||||
|
store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&store)
|
||||||
|
target :: try add_function(&module, mem.empty(ir.Type), 0, .int, 1)
|
||||||
|
try emit(&module, target, integer(0, 42))
|
||||||
|
return_value(&module, target, ir.ValueId(0))
|
||||||
|
shape :: try add_array(&module, scalar.type, ir.no_array, 1)
|
||||||
|
main :: try add_function(&module, mem.empty(ir.Type), 0, .array, 5)
|
||||||
|
module.entry = main
|
||||||
|
module.functions.items[usize(main)].result_array_type = shape
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .constant, type = scalar.type, destination = ir.ValueId(0), constant = scalar })
|
||||||
|
operand [1]u32 :: [0]
|
||||||
|
try literal(&module, main, 1, shape, operand[..])
|
||||||
|
return_value(&module, main, ir.ValueId(1))
|
||||||
|
outcome := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&outcome)
|
||||||
|
try testing.expect(outcome.trap == null)
|
||||||
|
leaf :: outcome.value.data.elements[0]
|
||||||
|
try testing.expect_equal(scalar.type, leaf.type)
|
||||||
|
match scalar.type {
|
||||||
|
.bool: try testing.expect(leaf.data.boolean)
|
||||||
|
.float: try testing.expect_equal(f64(3.25), leaf.data.real)
|
||||||
|
.string: try testing.expect_equal([]u8, "borrowed\x00text", ir.string_bytes(&module, leaf.data.string))
|
||||||
|
.usize: try testing.expect_equal(maxval!(u64), leaf.data.unsigned)
|
||||||
|
.function: {
|
||||||
|
try testing.expect_equal(target, leaf.data.function)
|
||||||
|
try emit(&module, main, unsigned(2, 0))
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .index, type = .function, destination = ir.ValueId(3), left = ir.ValueId(1), right = ir.ValueId(2) })
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .call, type = .int, destination = ir.ValueId(4), left = ir.ValueId(3) })
|
||||||
|
return_value(&module, main, ir.ValueId(4))
|
||||||
|
called := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&called)
|
||||||
|
try testing.expect(called.trap == null)
|
||||||
|
try testing.expect_equal(i64(42), called.value.data.integer)
|
||||||
|
}
|
||||||
|
else: unreachable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
array_address_initialization_flags_survive_reset_and_diagnostic_allocation_failure test {
|
||||||
|
operations [2]ir.Op :: [.load_address, .store_address]
|
||||||
|
codes [2]diagnostics.Code :: [.read_before_initialization, .write_before_initialization]
|
||||||
|
for (operations) |op, i| {
|
||||||
|
module := ir.init(mem.c_allocator)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
shape :: try add_array(&module, .int, ir.no_array, 1)
|
||||||
|
types [1]ir.Type :: [.array]
|
||||||
|
main :: try add_function(&module, types[..], 0, .int, 5)
|
||||||
|
module.entry = main
|
||||||
|
try array_storage(&module, main, 0, shape, true)
|
||||||
|
try emit(&module, main, integer(0, 42))
|
||||||
|
operand [1]u32 :: [0]
|
||||||
|
try literal(&module, main, 1, shape, operand[..])
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .store_local, slot = 0, left = ir.ValueId(1) })
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .address_local, type = .address, pointee = .array, array_type = shape, destination = ir.ValueId(2), slot = 0 })
|
||||||
|
try emit(&module, main, unsigned(3, 0))
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .index_address, type = .address, pointee = .int, destination = ir.ValueId(4), left = ir.ValueId(2), right = ir.ValueId(3) })
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .reset_local, slot = 0 })
|
||||||
|
span :: diagnostics.Span{ start = 23, end = 37 }
|
||||||
|
try emit(&module, main, ir.Instruction{ op = op, type = .int, destination = ir.ValueId(0), left = ir.ValueId(4), right = ir.ValueId(0), span = span })
|
||||||
|
for (usize(0)..usize(2)) |attempt| {
|
||||||
|
state := AllocationState{ remaining = 64 }
|
||||||
|
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
||||||
|
remaining usize :: if (attempt == 0) 0 else 64
|
||||||
|
diagnostic_state := AllocationState{ remaining = remaining }
|
||||||
|
diagnostic_allocator :: mem.Allocator{ context = &diagnostic_state, vtable = &limited_vtable }
|
||||||
|
store := diagnostics.init(diagnostic_allocator)
|
||||||
|
defer diagnostics.deinit(&store)
|
||||||
|
outcome := run(allocator, &module, &store) catch |error| {
|
||||||
|
if (error != .out_of_memory) return .expectation_failed
|
||||||
|
try testing.expect_equal(usize(0), attempt)
|
||||||
|
try testing.expect_equal(usize(0), state.live)
|
||||||
|
try testing.expect_equal(usize(0), diagnostic_state.live)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
defer deinit(&outcome)
|
||||||
|
try testing.expect_equal(usize(1), attempt)
|
||||||
|
try expect_trap(outcome, &store, codes[i], span)
|
||||||
|
deinit(&outcome)
|
||||||
|
try testing.expect_equal(usize(0), state.live)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aggregate_loop_places_reuse_storage_without_allocation_per_iteration test {
|
||||||
|
module := ir.init(mem.c_allocator)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&store)
|
||||||
|
shape :: try add_array(&module, .int, ir.no_array, 1)
|
||||||
|
types [2]ir.Type :: [.array, .usize]
|
||||||
|
main :: try add_function(&module, types[..], 0, .int, 14)
|
||||||
|
module.entry = main
|
||||||
|
try array_storage(&module, main, 0, shape, true)
|
||||||
|
try emit(&module, main, integer(0, 0))
|
||||||
|
operand [1]u32 :: [0]
|
||||||
|
try literal(&module, main, 1, shape, operand[..])
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .store_local, slot = 0, left = ir.ValueId(1) })
|
||||||
|
try emit(&module, main, unsigned(2, 0))
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .store_local, slot = 1, left = ir.ValueId(2) })
|
||||||
|
try emit(&module, main, unsigned(3, 1))
|
||||||
|
header :: try add_block(&module, main)
|
||||||
|
body :: try add_block(&module, main)
|
||||||
|
done :: try add_block(&module, main)
|
||||||
|
module.functions.items[usize(main)].blocks.items[0].terminator = ir.Terminator{ kind = .jump, target = header }
|
||||||
|
try emit_at(&module, main, header, ir.Instruction{ op = .load_local, type = .usize, destination = ir.ValueId(4), slot = 1 })
|
||||||
|
try emit_at(&module, main, header, ir.Instruction{ op = .less, type = .bool, destination = ir.ValueId(5), left = ir.ValueId(4), right = ir.ValueId(3) })
|
||||||
|
module.functions.items[usize(main)].blocks.items[usize(header)].terminator = ir.Terminator{ kind = .branch, value = ir.ValueId(5), target = body, alternative = done }
|
||||||
|
try emit_at(&module, main, body, ir.Instruction{ op = .address_local, type = .address, pointee = .array, array_type = shape, destination = ir.ValueId(6), slot = 0 })
|
||||||
|
try emit_at(&module, main, body, ir.Instruction{ op = .index_address, type = .address, pointee = .int, destination = ir.ValueId(7), left = ir.ValueId(6), right = ir.ValueId(2) })
|
||||||
|
try emit_at(&module, main, body, ir.Instruction{ op = .load_address, type = .int, destination = ir.ValueId(8), left = ir.ValueId(7) })
|
||||||
|
try emit_at(&module, main, body, integer(9, 1))
|
||||||
|
try emit_at(&module, main, body, ir.Instruction{ op = .add, type = .int, destination = ir.ValueId(10), left = ir.ValueId(8), right = ir.ValueId(9) })
|
||||||
|
try emit_at(&module, main, body, ir.Instruction{ op = .store_address, left = ir.ValueId(7), right = ir.ValueId(10) })
|
||||||
|
try arraylist.append(&module.functions.items[usize(main)].blocks.items[usize(body)].arguments, ir.ValueId(10))
|
||||||
|
try emit_at(&module, main, body, ir.Instruction{ op = .array_literal, type = .array, array_type = shape, destination = ir.ValueId(1), arguments_count = 1 })
|
||||||
|
try emit_at(&module, main, body, ir.Instruction{ op = .assign_local, slot = 0, left = ir.ValueId(1) })
|
||||||
|
try emit_at(&module, main, body, unsigned(11, 1))
|
||||||
|
try emit_at(&module, main, body, ir.Instruction{ op = .add, type = .usize, destination = ir.ValueId(12), left = ir.ValueId(4), right = ir.ValueId(11) })
|
||||||
|
try emit_at(&module, main, body, ir.Instruction{ op = .assign_local, slot = 1, left = ir.ValueId(12) })
|
||||||
|
module.functions.items[usize(main)].blocks.items[usize(body)].terminator = ir.Terminator{ kind = .jump, target = header }
|
||||||
|
try emit_at(&module, main, done, ir.Instruction{ op = .load_address, type = .int, destination = ir.ValueId(13), left = ir.ValueId(7) })
|
||||||
|
module.functions.items[usize(main)].blocks.items[usize(done)].terminator = ir.Terminator{ value = ir.ValueId(13) }
|
||||||
|
iterations [2]u64 :: [1, 10000]
|
||||||
|
allocations usize := 0
|
||||||
|
for (iterations) |count, i| {
|
||||||
|
module.functions.items[usize(main)].blocks.items[0].instructions.items[5] = unsigned(3, count)
|
||||||
|
state := AllocationState{ remaining = 64 }
|
||||||
|
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
||||||
|
outcome := try run(allocator, &module, &store)
|
||||||
|
defer deinit(&outcome)
|
||||||
|
try testing.expect(outcome.trap == null)
|
||||||
|
try testing.expect_equal(i64(count), outcome.value.data.integer)
|
||||||
|
deinit(&outcome)
|
||||||
|
try testing.expect_equal(usize(0), state.live)
|
||||||
|
consumed :: usize(64) - state.remaining
|
||||||
|
if (i == 0) allocations = consumed
|
||||||
|
else try testing.expect_equal(allocations, consumed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recursive_array_backings_and_addresses_remain_fresh_across_stack_growth test {
|
||||||
|
module := ir.init(mem.c_allocator)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&store)
|
||||||
|
shape :: try add_array(&module, .int, ir.no_array, 1)
|
||||||
|
types [2]ir.Type :: [.int, .array]
|
||||||
|
recursive :: try add_function(&module, types[..], 1, .int, 12)
|
||||||
|
try array_storage(&module, recursive, 1, shape, true)
|
||||||
|
try emit(&module, recursive, ir.Instruction{ op = .load_local, type = .int, destination = ir.ValueId(0), slot = 0 })
|
||||||
|
operand [1]u32 :: [0]
|
||||||
|
try literal(&module, recursive, 1, shape, operand[..])
|
||||||
|
try emit(&module, recursive, ir.Instruction{ op = .store_local, slot = 1, left = ir.ValueId(1) })
|
||||||
|
try emit(&module, recursive, ir.Instruction{ op = .address_local, type = .address, pointee = .array, array_type = shape, destination = ir.ValueId(2), slot = 1 })
|
||||||
|
try emit(&module, recursive, unsigned(3, 0))
|
||||||
|
try emit(&module, recursive, ir.Instruction{ op = .index_address, type = .address, pointee = .int, destination = ir.ValueId(4), left = ir.ValueId(2), right = ir.ValueId(3) })
|
||||||
|
try emit(&module, recursive, integer(5, 0))
|
||||||
|
try emit(&module, recursive, ir.Instruction{ op = .equal, type = .bool, destination = ir.ValueId(6), left = ir.ValueId(0), right = ir.ValueId(5) })
|
||||||
|
done :: try add_block(&module, recursive)
|
||||||
|
recurse :: try add_block(&module, recursive)
|
||||||
|
module.functions.items[usize(recursive)].blocks.items[0].terminator = ir.Terminator{ kind = .branch, value = ir.ValueId(6), target = done, alternative = recurse }
|
||||||
|
module.functions.items[usize(recursive)].blocks.items[usize(done)].terminator = ir.Terminator{ value = ir.ValueId(5) }
|
||||||
|
try emit_at(&module, recursive, recurse, integer(5, 1))
|
||||||
|
try emit_at(&module, recursive, recurse, ir.Instruction{ op = .subtract, type = .int, destination = ir.ValueId(7), left = ir.ValueId(0), right = ir.ValueId(5) })
|
||||||
|
try emit_at(&module, recursive, recurse, function_value(8, recursive))
|
||||||
|
try arraylist.append(&module.functions.items[usize(recursive)].blocks.items[usize(recurse)].arguments, ir.ValueId(7))
|
||||||
|
try emit_at(&module, recursive, recurse, ir.Instruction{ op = .call, type = .int, destination = ir.ValueId(9), left = ir.ValueId(8), arguments_count = 1 })
|
||||||
|
try emit_at(&module, recursive, recurse, ir.Instruction{ op = .load_address, type = .int, destination = ir.ValueId(10), left = ir.ValueId(4) })
|
||||||
|
try emit_at(&module, recursive, recurse, ir.Instruction{ op = .add, type = .int, destination = ir.ValueId(11), left = ir.ValueId(9), right = ir.ValueId(10) })
|
||||||
|
module.functions.items[usize(recursive)].blocks.items[usize(recurse)].terminator = ir.Terminator{ value = ir.ValueId(11) }
|
||||||
|
main :: try add_function(&module, mem.empty(ir.Type), 0, .int, 3)
|
||||||
|
module.entry = main
|
||||||
|
try emit(&module, main, function_value(0, recursive))
|
||||||
|
try emit(&module, main, integer(1, 64))
|
||||||
|
try arraylist.append(&module.functions.items[usize(main)].blocks.items[0].arguments, ir.ValueId(1))
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .call, type = .int, destination = ir.ValueId(2), left = ir.ValueId(0), arguments_count = 1 })
|
||||||
|
return_value(&module, main, ir.ValueId(2))
|
||||||
|
outcome := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&outcome)
|
||||||
|
try testing.expect(outcome.trap == null)
|
||||||
|
try testing.expect_equal(i64(2080), outcome.value.data.integer)
|
||||||
|
}
|
||||||
|
|
||||||
|
inclusive_unsigned_range_reaches_the_maximum_without_incrementing_past_it test {
|
||||||
|
module := ir.init(mem.c_allocator)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&store)
|
||||||
|
types [2]ir.Type :: [.usize, .usize]
|
||||||
|
main :: try add_function(&module, types[..], 0, .usize, 9)
|
||||||
|
module.entry = main
|
||||||
|
try emit(&module, main, unsigned(0, maxval!(u64) - 1))
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .store_local, slot = 0, left = ir.ValueId(0) })
|
||||||
|
try emit(&module, main, unsigned(1, 0))
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .store_local, slot = 1, left = ir.ValueId(1) })
|
||||||
|
try emit(&module, main, unsigned(2, maxval!(u64)))
|
||||||
|
try emit(&module, main, unsigned(3, 1))
|
||||||
|
header :: try add_block(&module, main)
|
||||||
|
body :: try add_block(&module, main)
|
||||||
|
advance :: try add_block(&module, main)
|
||||||
|
done :: try add_block(&module, main)
|
||||||
|
module.functions.items[usize(main)].blocks.items[0].terminator = ir.Terminator{ kind = .jump, target = header }
|
||||||
|
try emit_at(&module, main, header, ir.Instruction{ op = .load_local, type = .usize, destination = ir.ValueId(4), slot = 0 })
|
||||||
|
try emit_at(&module, main, header, ir.Instruction{ op = .less_equal, type = .bool, destination = ir.ValueId(5), left = ir.ValueId(4), right = ir.ValueId(2) })
|
||||||
|
module.functions.items[usize(main)].blocks.items[usize(header)].terminator = ir.Terminator{ kind = .branch, value = ir.ValueId(5), target = body, alternative = done }
|
||||||
|
try emit_at(&module, main, body, ir.Instruction{ op = .load_local, type = .usize, destination = ir.ValueId(6), slot = 1 })
|
||||||
|
try emit_at(&module, main, body, ir.Instruction{ op = .add, type = .usize, destination = ir.ValueId(7), left = ir.ValueId(6), right = ir.ValueId(3) })
|
||||||
|
try emit_at(&module, main, body, ir.Instruction{ op = .assign_local, slot = 1, left = ir.ValueId(7) })
|
||||||
|
try emit_at(&module, main, body, ir.Instruction{ op = .equal, type = .bool, destination = ir.ValueId(8), left = ir.ValueId(4), right = ir.ValueId(2) })
|
||||||
|
module.functions.items[usize(main)].blocks.items[usize(body)].terminator = ir.Terminator{ kind = .branch, value = ir.ValueId(8), target = done, alternative = advance }
|
||||||
|
try emit_at(&module, main, advance, ir.Instruction{ op = .add, type = .usize, destination = ir.ValueId(0), left = ir.ValueId(4), right = ir.ValueId(3) })
|
||||||
|
try emit_at(&module, main, advance, ir.Instruction{ op = .assign_local, slot = 0, left = ir.ValueId(0) })
|
||||||
|
module.functions.items[usize(main)].blocks.items[usize(advance)].terminator = ir.Terminator{ kind = .jump, target = header }
|
||||||
|
try emit_at(&module, main, done, ir.Instruction{ op = .load_local, type = .usize, destination = ir.ValueId(1), slot = 1 })
|
||||||
|
module.functions.items[usize(main)].blocks.items[usize(done)].terminator = ir.Terminator{ value = ir.ValueId(1) }
|
||||||
|
outcome := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&outcome)
|
||||||
|
try testing.expect(outcome.trap == null)
|
||||||
|
try testing.expect_equal(u64(2), outcome.value.data.unsigned)
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ LocalId :: distinct u32
|
|||||||
ValueId :: distinct u32
|
ValueId :: distinct u32
|
||||||
BlockId :: distinct u32
|
BlockId :: distinct u32
|
||||||
StringId :: distinct u32
|
StringId :: distinct u32
|
||||||
|
ArrayId :: distinct u32
|
||||||
|
no_array :: maxval!(ArrayId)
|
||||||
no_value :: maxval!(ValueId)
|
no_value :: maxval!(ValueId)
|
||||||
|
|
||||||
Type :: enum {
|
Type :: enum {
|
||||||
@@ -18,20 +20,44 @@ Type :: enum {
|
|||||||
string,
|
string,
|
||||||
void,
|
void,
|
||||||
function,
|
function,
|
||||||
|
array,
|
||||||
|
usize,
|
||||||
|
address,
|
||||||
}
|
}
|
||||||
|
|
||||||
ValueData :: union {
|
ValueData :: union {
|
||||||
integer i64
|
integer i64
|
||||||
|
unsigned u64
|
||||||
real f64
|
real f64
|
||||||
boolean bool
|
boolean bool
|
||||||
string StringId
|
string StringId
|
||||||
function FunctionId
|
function FunctionId
|
||||||
|
elements []mut Value
|
||||||
|
address u32
|
||||||
}
|
}
|
||||||
Value :: struct {
|
Value :: struct {
|
||||||
type Type = .void
|
type Type = .void
|
||||||
|
array_type ArrayId = no_array
|
||||||
data ValueData = ValueData{ integer = 0 }
|
data ValueData = ValueData{ integer = 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! fixed recursive shape; `cells` counts flattened scalar leaves, including zero-sized children.
|
||||||
|
ArrayType :: struct {
|
||||||
|
element Type
|
||||||
|
element_array ArrayId = no_array
|
||||||
|
len usize
|
||||||
|
cells usize
|
||||||
|
mutable bool
|
||||||
|
}
|
||||||
|
|
||||||
|
#! maps a local slot or temporary to its preallocated, by-value frame segment.
|
||||||
|
ArrayStorage :: struct {
|
||||||
|
index u32
|
||||||
|
array_type ArrayId
|
||||||
|
offset usize
|
||||||
|
local bool
|
||||||
|
}
|
||||||
|
|
||||||
Op :: enum {
|
Op :: enum {
|
||||||
constant
|
constant
|
||||||
load_local
|
load_local
|
||||||
@@ -39,6 +65,13 @@ Op :: enum {
|
|||||||
assign_local
|
assign_local
|
||||||
reset_local
|
reset_local
|
||||||
load_global
|
load_global
|
||||||
|
array_literal
|
||||||
|
index
|
||||||
|
address_local
|
||||||
|
address_global
|
||||||
|
index_address
|
||||||
|
load_address
|
||||||
|
store_address
|
||||||
negate
|
negate
|
||||||
logical_not
|
logical_not
|
||||||
add
|
add
|
||||||
@@ -57,6 +90,8 @@ Op :: enum {
|
|||||||
Instruction :: struct {
|
Instruction :: struct {
|
||||||
op Op
|
op Op
|
||||||
type Type = .void
|
type Type = .void
|
||||||
|
array_type ArrayId = no_array
|
||||||
|
pointee Type = .invalid
|
||||||
span diagnostics.Span = diagnostics.Span{ start = 0, end = 0 }
|
span diagnostics.Span = diagnostics.Span{ start = 0, end = 0 }
|
||||||
destination ValueId = no_value
|
destination ValueId = no_value
|
||||||
left ValueId = no_value
|
left ValueId = no_value
|
||||||
@@ -93,6 +128,10 @@ Function :: struct {
|
|||||||
parameters arraylist.ArrayList(Type)
|
parameters arraylist.ArrayList(Type)
|
||||||
locals arraylist.ArrayList(Type)
|
locals arraylist.ArrayList(Type)
|
||||||
result Type = .void
|
result Type = .void
|
||||||
|
result_array_type ArrayId = no_array
|
||||||
|
array_storage arraylist.ArrayList(ArrayStorage)
|
||||||
|
aggregate_cells usize = 0
|
||||||
|
has_addresses bool = false
|
||||||
blocks arraylist.ArrayList(Block)
|
blocks arraylist.ArrayList(Block)
|
||||||
entry BlockId = BlockId(0)
|
entry BlockId = BlockId(0)
|
||||||
value_count usize = 0
|
value_count usize = 0
|
||||||
@@ -101,6 +140,7 @@ Function :: struct {
|
|||||||
|
|
||||||
Global :: struct {
|
Global :: struct {
|
||||||
type Type
|
type Type
|
||||||
|
array_type ArrayId = no_array
|
||||||
initializer FunctionId
|
initializer FunctionId
|
||||||
span diagnostics.Span
|
span diagnostics.Span
|
||||||
}
|
}
|
||||||
@@ -118,6 +158,7 @@ Module :: struct {
|
|||||||
functions arraylist.ArrayList(Function)
|
functions arraylist.ArrayList(Function)
|
||||||
globals arraylist.ArrayList(Global)
|
globals arraylist.ArrayList(Global)
|
||||||
strings arraylist.ArrayList(StringStorage)
|
strings arraylist.ArrayList(StringStorage)
|
||||||
|
arrays arraylist.ArrayList(ArrayType)
|
||||||
entry ?FunctionId = null
|
entry ?FunctionId = null
|
||||||
entry_diagnostic diagnostics.DiagnosticId = diagnostics.no_diagnostic
|
entry_diagnostic diagnostics.DiagnosticId = diagnostics.no_diagnostic
|
||||||
}
|
}
|
||||||
@@ -128,6 +169,7 @@ init proc(allocator mem.Allocator) Module {
|
|||||||
functions = arraylist.init(allocator),
|
functions = arraylist.init(allocator),
|
||||||
globals = arraylist.init(allocator),
|
globals = arraylist.init(allocator),
|
||||||
strings = arraylist.init(allocator),
|
strings = arraylist.init(allocator),
|
||||||
|
arrays = arraylist.init(allocator),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,6 +178,7 @@ init_function proc(allocator mem.Allocator) Function {
|
|||||||
parameters = arraylist.init(allocator),
|
parameters = arraylist.init(allocator),
|
||||||
locals = arraylist.init(allocator),
|
locals = arraylist.init(allocator),
|
||||||
blocks = arraylist.init(allocator),
|
blocks = arraylist.init(allocator),
|
||||||
|
array_storage = arraylist.init(allocator),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,6 +207,7 @@ deinit proc(module @mut Module) void {
|
|||||||
arraylist.deinit(&function.parameters)
|
arraylist.deinit(&function.parameters)
|
||||||
arraylist.deinit(&function.locals)
|
arraylist.deinit(&function.locals)
|
||||||
arraylist.deinit(&function.blocks)
|
arraylist.deinit(&function.blocks)
|
||||||
|
arraylist.deinit(&function.array_storage)
|
||||||
}
|
}
|
||||||
for (module.strings.items) |string|
|
for (module.strings.items) |string|
|
||||||
mem.free(module.allocator, string.allocation)
|
mem.free(module.allocator, string.allocation)
|
||||||
@@ -171,4 +215,5 @@ deinit proc(module @mut Module) void {
|
|||||||
arraylist.deinit(&module.functions)
|
arraylist.deinit(&module.functions)
|
||||||
arraylist.deinit(&module.globals)
|
arraylist.deinit(&module.globals)
|
||||||
arraylist.deinit(&module.strings)
|
arraylist.deinit(&module.strings)
|
||||||
|
arraylist.deinit(&module.arrays)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ keywords strmap.StaticStringMap(TokenKind) :: strmap.init([
|
|||||||
{ "false", .false },
|
{ "false", .false },
|
||||||
{ "and", .and },
|
{ "and", .and },
|
||||||
{ "or", .or },
|
{ "or", .or },
|
||||||
|
{ "mut", .mut },
|
||||||
])
|
])
|
||||||
|
|
||||||
#! owns token storage and retains its allocator; release with deinit.
|
#! owns token storage and retains its allocator; release with deinit.
|
||||||
|
|||||||
@@ -122,3 +122,22 @@ scans_ranges_without_consuming_float_dots test {
|
|||||||
try testing.expect_equal(u32(10), state.tokens.items[4].span.end)
|
try testing.expect_equal(u32(10), state.tokens.items[4].span.end)
|
||||||
try testing.expect_equal(2, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(2, diagnostics.count(&diagnostic_store))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
scans_array_delimiters_and_mut_keyword test {
|
||||||
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.strings)
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
state := init(mem.c_allocator)
|
||||||
|
defer deinit(&state)
|
||||||
|
try scan(&state, "[_]mut [2]i64 [1,2] [] mutable mut_1", &diagnostic_store)
|
||||||
|
expected [18]TokenKind :: [
|
||||||
|
.open_bracket, .ident, .close_bracket, .mut,
|
||||||
|
.open_bracket, .int, .close_bracket, .ident,
|
||||||
|
.open_bracket, .int, .comma, .int, .close_bracket,
|
||||||
|
.open_bracket, .close_bracket, .ident, .ident, .eof,
|
||||||
|
]
|
||||||
|
try testing.expect_equal(expected.len, state.tokens.items.len)
|
||||||
|
for (expected) |kind, index| try testing.expect_equal(kind, state.tokens.items[index].kind)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ TokenKind :: enum {
|
|||||||
break, continue
|
break, continue
|
||||||
true, false
|
true, false
|
||||||
and, or
|
and, or
|
||||||
|
mut
|
||||||
|
|
||||||
# literals
|
# literals
|
||||||
ident, string
|
ident, string
|
||||||
|
|||||||
+203
-13
@@ -30,6 +30,7 @@ State :: struct {
|
|||||||
diagnostics @mut diagnostics.Store
|
diagnostics @mut diagnostics.Store
|
||||||
module @mut ir.Module
|
module @mut ir.Module
|
||||||
storage []mut Storage
|
storage []mut Storage
|
||||||
|
arrays []mut ir.ArrayId
|
||||||
arguments arraylist.ArrayList(ir.ValueId)
|
arguments arraylist.ArrayList(ir.ValueId)
|
||||||
function usize = 0
|
function usize = 0
|
||||||
block ir.BlockId = ir.BlockId(0)
|
block ir.BlockId = ir.BlockId(0)
|
||||||
@@ -51,6 +52,9 @@ lower proc(
|
|||||||
storage :: try mem.alloc(Storage, allocator, ast.node_count(tree))
|
storage :: try mem.alloc(Storage, allocator, ast.node_count(tree))
|
||||||
defer mem.free(allocator, storage)
|
defer mem.free(allocator, storage)
|
||||||
mem.set(storage, Storage{})
|
mem.set(storage, Storage{})
|
||||||
|
arrays :: try mem.alloc(ir.ArrayId, allocator, typecheck.array_type_count(checked))
|
||||||
|
defer mem.free(allocator, arrays)
|
||||||
|
mem.set(arrays, ir.no_array)
|
||||||
state := State{
|
state := State{
|
||||||
tree = tree,
|
tree = tree,
|
||||||
resolution = resolution,
|
resolution = resolution,
|
||||||
@@ -58,6 +62,7 @@ lower proc(
|
|||||||
diagnostics = diagnostic_store,
|
diagnostics = diagnostic_store,
|
||||||
module = &module,
|
module = &module,
|
||||||
storage = storage,
|
storage = storage,
|
||||||
|
arrays = arrays,
|
||||||
arguments = arraylist.init(ir.ValueId, allocator),
|
arguments = arraylist.init(ir.ValueId, allocator),
|
||||||
}
|
}
|
||||||
defer arraylist.deinit(&state.arguments)
|
defer arraylist.deinit(&state.arguments)
|
||||||
@@ -75,6 +80,7 @@ lower proc(
|
|||||||
global :: u32(module.globals.items.len)
|
global :: u32(module.globals.items.len)
|
||||||
try arraylist.append(&module.globals, ir.Global{
|
try arraylist.append(&module.globals, ir.Global{
|
||||||
type = lower_type(typecheck.type_of(checked, id)),
|
type = lower_type(typecheck.type_of(checked, id)),
|
||||||
|
array_type = try array_shape(&state, typecheck.type_of(checked, id)),
|
||||||
initializer = initializer,
|
initializer = initializer,
|
||||||
span = ast.node_span(tree, id),
|
span = ast.node_span(tree, id),
|
||||||
})
|
})
|
||||||
@@ -99,6 +105,8 @@ lower proc(
|
|||||||
lower_type proc(type typecheck.Type) ir.Type {
|
lower_type proc(type typecheck.Type) ir.Type {
|
||||||
return match type.kind {
|
return match type.kind {
|
||||||
.int: .int
|
.int: .int
|
||||||
|
.usize: .usize
|
||||||
|
.array: .array
|
||||||
.float: .float
|
.float: .float
|
||||||
.bool: .bool
|
.bool: .bool
|
||||||
.string: .string
|
.string: .string
|
||||||
@@ -108,6 +116,53 @@ lower_type proc(type typecheck.Type) ir.Type {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
array_shape proc(state @mut State, type typecheck.Type) ir.ArrayId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
|
if (type.kind != .array) return ir.no_array
|
||||||
|
# checker history includes unresolved shapes; materialize only final used types.
|
||||||
|
return try prepare_array(state, type.array_type)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
prepare_array proc(state @mut State, id typecheck.ArrayId) ir.ArrayId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
|
if (state.arrays[usize(id)] != ir.no_array) return state.arrays[usize(id)]
|
||||||
|
descriptor :: typecheck.array_type(state.checked, id)
|
||||||
|
child := ir.no_array
|
||||||
|
width := usize(1)
|
||||||
|
if (descriptor.element.kind == .array) {
|
||||||
|
child = try prepare_array(state, descriptor.element.array_type)
|
||||||
|
width = state.module.arrays.items[usize(child)].cells
|
||||||
|
}
|
||||||
|
if (width != 0 and descriptor.len > divtrunc!(maxval!(usize), width)) return .storage_limit_reached
|
||||||
|
if (descriptor.len * width > divtrunc!(maxval!(usize), sizeof!(ir.Value))) return .storage_limit_reached
|
||||||
|
if (state.module.arrays.items.len >= usize(ir.no_array)) return .storage_limit_reached
|
||||||
|
result :: ir.ArrayId(state.module.arrays.items.len)
|
||||||
|
try arraylist.append(&state.module.arrays, ir.ArrayType{
|
||||||
|
element = lower_type(descriptor.element),
|
||||||
|
element_array = child,
|
||||||
|
len = descriptor.len,
|
||||||
|
cells = descriptor.len * width,
|
||||||
|
mutable = descriptor.mutable,
|
||||||
|
})
|
||||||
|
state.arrays[usize(id)] = result
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
prepare_storage proc(state @mut State, index u32, shape ir.ArrayId, local bool) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
|
if (shape == ir.no_array) return
|
||||||
|
function := &state.module.functions.items[state.function]
|
||||||
|
cells :: state.module.arrays.items[usize(shape)].cells
|
||||||
|
if (cells > divtrunc!(maxval!(usize), sizeof!(ir.Value)) - function.aggregate_cells) return .storage_limit_reached
|
||||||
|
try arraylist.append(&function.array_storage, ir.ArrayStorage{
|
||||||
|
index = index,
|
||||||
|
array_type = shape,
|
||||||
|
offset = function.aggregate_cells,
|
||||||
|
local = local,
|
||||||
|
})
|
||||||
|
function.aggregate_cells += cells
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
add_function proc(module @mut ir.Module, span diagnostics.Span) ir.FunctionId ! mem.AllocError {
|
add_function proc(module @mut ir.Module, span diagnostics.Span) ir.FunctionId ! mem.AllocError {
|
||||||
id :: ir.FunctionId(module.functions.items.len)
|
id :: ir.FunctionId(module.functions.items.len)
|
||||||
@@ -182,19 +237,22 @@ finish proc(state @mut State, value ir.ValueId, span diagnostics.Span) void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
emit proc(state @mut State, instruction ir.Instruction) ir.ValueId ! mem.AllocError {
|
emit proc(state @mut State, instruction ir.Instruction) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
function := &state.module.functions.items[state.function]
|
function := &state.module.functions.items[state.function]
|
||||||
operation := instruction
|
operation := instruction
|
||||||
if (operation.op != .store_local and operation.op != .assign_local and operation.op != .reset_local) {
|
if (operation.op != .store_local and operation.op != .assign_local and operation.op != .reset_local and operation.op != .store_address) {
|
||||||
|
if (function.value_count >= usize(ir.no_value)) return .storage_limit_reached
|
||||||
operation.destination = ir.ValueId(function.value_count)
|
operation.destination = ir.ValueId(function.value_count)
|
||||||
function.value_count += 1
|
function.value_count += 1
|
||||||
|
if (operation.type == .array) try prepare_storage(state, u32(operation.destination), operation.array_type, false)
|
||||||
}
|
}
|
||||||
|
if (operation.type == .address) function.has_addresses = true
|
||||||
try arraylist.append(&function.blocks.items[usize(state.block)].instructions, operation)
|
try arraylist.append(&function.blocks.items[usize(state.block)].instructions, operation)
|
||||||
return operation.destination
|
return operation.destination
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
constant proc(state @mut State, value ir.Value, span diagnostics.Span) ir.ValueId ! mem.AllocError {
|
constant proc(state @mut State, value ir.Value, span diagnostics.Span) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
return try emit(state, ir.Instruction{ op = .constant, type = value.type, constant = value, span = span })
|
return try emit(state, ir.Instruction{ op = .constant, type = value.type, constant = value, span = span })
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,6 +263,7 @@ lower_global proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diag
|
|||||||
state.block = ir.BlockId(0)
|
state.block = ir.BlockId(0)
|
||||||
state.terminated = false
|
state.terminated = false
|
||||||
state.module.functions.items[state.function].result = global.type
|
state.module.functions.items[state.function].result = global.type
|
||||||
|
state.module.functions.items[state.function].result_array_type = global.array_type
|
||||||
if (trap_binding(state, id)) return
|
if (trap_binding(state, id)) return
|
||||||
declaration :: ast.statement_decl(state.tree, id)
|
declaration :: ast.statement_decl(state.tree, id)
|
||||||
if (declaration.type) |annotation| if (trap_type(state, annotation)) return
|
if (declaration.type) |annotation| if (trap_type(state, annotation)) return
|
||||||
@@ -222,12 +281,14 @@ lower_function proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | di
|
|||||||
state.terminated = false
|
state.terminated = false
|
||||||
function := &state.module.functions.items[state.function]
|
function := &state.module.functions.items[state.function]
|
||||||
function.result = lower_type(typecheck.type_of(state.checked, declaration.return_type))
|
function.result = lower_type(typecheck.type_of(state.checked, declaration.return_type))
|
||||||
|
function.result_array_type = try array_shape(state, typecheck.type_of(state.checked, declaration.return_type))
|
||||||
for (0..ast.function_param_count(tree, id)) |i| {
|
for (0..ast.function_param_count(tree, id)) |i| {
|
||||||
parameter :: ast.function_param(tree, id, i)
|
parameter :: ast.function_param(tree, id, i)
|
||||||
type :: lower_type(typecheck.type_of(state.checked, parameter))
|
type :: lower_type(typecheck.type_of(state.checked, parameter))
|
||||||
state.storage[usize(parameter)] = Storage{ kind = .local, index = u32(function.locals.items.len) }
|
state.storage[usize(parameter)] = Storage{ kind = .local, index = u32(function.locals.items.len) }
|
||||||
try arraylist.append(&function.parameters, type)
|
try arraylist.append(&function.parameters, type)
|
||||||
try arraylist.append(&function.locals, type)
|
try arraylist.append(&function.locals, type)
|
||||||
|
try prepare_storage(state, state.storage[usize(parameter)].index, try array_shape(state, typecheck.type_of(state.checked, parameter)), true)
|
||||||
}
|
}
|
||||||
try predeclare_locals(state, declaration.body)
|
try predeclare_locals(state, declaration.body)
|
||||||
# signature errors are entry traps; body errors are reached in statement order.
|
# signature errors are entry traps; body errors are reached in statement order.
|
||||||
@@ -252,7 +313,7 @@ lower_function proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | di
|
|||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
predeclare_locals proc(state @mut State, block ast.NodeId) void ! mem.AllocError {
|
predeclare_locals proc(state @mut State, block ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
for (0..ast.block_statement_count(state.tree, block)) |i| {
|
for (0..ast.block_statement_count(state.tree, block)) |i| {
|
||||||
statement :: ast.block_statement(state.tree, block, i)
|
statement :: ast.block_statement(state.tree, block, i)
|
||||||
match ast.stmt_kind(state.tree, statement) {
|
match ast.stmt_kind(state.tree, statement) {
|
||||||
@@ -260,6 +321,7 @@ predeclare_locals proc(state @mut State, block ast.NodeId) void ! mem.AllocError
|
|||||||
function := &state.module.functions.items[state.function]
|
function := &state.module.functions.items[state.function]
|
||||||
state.storage[usize(statement)] = Storage{ kind = .local, index = u32(function.locals.items.len) }
|
state.storage[usize(statement)] = Storage{ kind = .local, index = u32(function.locals.items.len) }
|
||||||
try arraylist.append(&function.locals, lower_type(typecheck.type_of(state.checked, statement)))
|
try arraylist.append(&function.locals, lower_type(typecheck.type_of(state.checked, statement)))
|
||||||
|
try prepare_storage(state, state.storage[usize(statement)].index, try array_shape(state, typecheck.type_of(state.checked, statement)), true)
|
||||||
}
|
}
|
||||||
.if_stmt: {
|
.if_stmt: {
|
||||||
conditional :: ast.if_stmt(state.tree, statement)
|
conditional :: ast.if_stmt(state.tree, statement)
|
||||||
@@ -271,7 +333,9 @@ predeclare_locals proc(state @mut State, block ast.NodeId) void ! mem.AllocError
|
|||||||
loop :: ast.for_stmt(state.tree, statement)
|
loop :: ast.for_stmt(state.tree, statement)
|
||||||
function := &state.module.functions.items[state.function]
|
function := &state.module.functions.items[state.function]
|
||||||
slot :: u32(function.locals.items.len)
|
slot :: u32(function.locals.items.len)
|
||||||
try arraylist.append(&function.locals, ir.Type.int)
|
family :: typecheck.type_of(state.checked, loop.iterable).range_element
|
||||||
|
type ir.Type :: if (family == .usize) .usize else .int
|
||||||
|
try arraylist.append(&function.locals, type)
|
||||||
state.storage[usize(statement)] = Storage{ kind = .local, index = slot }
|
state.storage[usize(statement)] = Storage{ kind = .local, index = slot }
|
||||||
if (loop.capture) |capture| state.storage[usize(capture)] = state.storage[usize(statement)]
|
if (loop.capture) |capture| state.storage[usize(capture)] = state.storage[usize(statement)]
|
||||||
if (loop.index) |index| {
|
if (loop.index) |index| {
|
||||||
@@ -405,7 +469,8 @@ lower_for proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnos
|
|||||||
end :: try lower_expression(state, interval.end)
|
end :: try lower_expression(state, interval.end)
|
||||||
if (state.terminated or trap_type(state, loop.iterable) or trap_type(state, id)) return
|
if (state.terminated or trap_type(state, loop.iterable) or trap_type(state, id)) return
|
||||||
slot :: state.storage[usize(id)].index
|
slot :: state.storage[usize(id)].index
|
||||||
_ = try emit(state, ir.Instruction{ op = .store_local, type = .int, left = start, slot = slot, span = interval.span })
|
family :: lower_type(typecheck.Type{ kind = typecheck.type_of(state.checked, loop.iterable).range_element })
|
||||||
|
_ = try emit(state, ir.Instruction{ op = .store_local, type = family, left = start, slot = slot, span = interval.span })
|
||||||
origin :: state.block
|
origin :: state.block
|
||||||
condition :: try add_block(state)
|
condition :: try add_block(state)
|
||||||
body :: try add_block(state)
|
body :: try add_block(state)
|
||||||
@@ -416,7 +481,7 @@ lower_for proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnos
|
|||||||
kind = .jump, target = condition, span = loop.span,
|
kind = .jump, target = condition, span = loop.span,
|
||||||
}
|
}
|
||||||
state.block = condition
|
state.block = condition
|
||||||
current :: try emit(state, ir.Instruction{ op = .load_local, type = .int, slot = slot, span = interval.span })
|
current :: try emit(state, ir.Instruction{ op = .load_local, type = family, slot = slot, span = interval.span })
|
||||||
comparison ir.Op :: if (interval.inclusive) .less_equal else .less
|
comparison ir.Op :: if (interval.inclusive) .less_equal else .less
|
||||||
condition_value :: try emit(state, ir.Instruction{ op = comparison, type = .bool, left = current, right = end, span = interval.span })
|
condition_value :: try emit(state, ir.Instruction{ op = comparison, type = .bool, left = current, right = end, span = interval.span })
|
||||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||||
@@ -435,16 +500,18 @@ lower_for proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnos
|
|||||||
}
|
}
|
||||||
state.block = update
|
state.block = update
|
||||||
state.terminated = false
|
state.terminated = false
|
||||||
last :: try emit(state, ir.Instruction{ op = .load_local, type = .int, slot = slot, span = interval.span })
|
last :: try emit(state, ir.Instruction{ op = .load_local, type = family, slot = slot, span = interval.span })
|
||||||
finished :: try emit(state, ir.Instruction{ op = .equal, type = .bool, left = last, right = end, span = interval.span })
|
finished :: try emit(state, ir.Instruction{ op = .equal, type = .bool, left = last, right = end, span = interval.span })
|
||||||
# continue reaches this guard too, so an inclusive maximum never adds one.
|
# continue reaches this guard too, so an inclusive maximum never adds one.
|
||||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||||
kind = .branch, value = finished, target = exit, alternative = increment, span = interval.span,
|
kind = .branch, value = finished, target = exit, alternative = increment, span = interval.span,
|
||||||
}
|
}
|
||||||
state.block = increment
|
state.block = increment
|
||||||
one :: try constant(state, ir.Value{ type = .int, data = ir.ValueData{ integer = 1 } }, interval.span)
|
unit := ir.Value{ type = .int, data = ir.ValueData{ integer = 1 } }
|
||||||
next :: try emit(state, ir.Instruction{ op = .add, type = .int, left = last, right = one, span = interval.span })
|
if (family == .usize) unit = ir.Value{ type = .usize, data = ir.ValueData{ unsigned = 1 } }
|
||||||
_ = try emit(state, ir.Instruction{ op = .store_local, type = .int, left = next, slot = slot, span = interval.span })
|
one :: try constant(state, unit, interval.span)
|
||||||
|
next :: try emit(state, ir.Instruction{ op = .add, type = family, left = last, right = one, span = interval.span })
|
||||||
|
_ = try emit(state, ir.Instruction{ op = .store_local, type = family, left = next, slot = slot, span = interval.span })
|
||||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||||
kind = .jump, target = condition, span = loop.span,
|
kind = .jump, target = condition, span = loop.span,
|
||||||
}
|
}
|
||||||
@@ -473,6 +540,25 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
|
|||||||
}
|
}
|
||||||
.assignment_stmt: {
|
.assignment_stmt: {
|
||||||
assignment :: ast.assignment_stmt(tree, id)
|
assignment :: ast.assignment_stmt(tree, id)
|
||||||
|
if (ast.expr_kind(tree, assignment.target) == .index) {
|
||||||
|
value :: try lower_expression(state, assignment.value)
|
||||||
|
if (state.terminated) return
|
||||||
|
if (!is_place(state, assignment.target)) {
|
||||||
|
_ = try lower_expression(state, assignment.target)
|
||||||
|
if (!state.terminated) _ = trap_type(state, id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
address :: try lower_place(state, assignment.target)
|
||||||
|
if (state.terminated or trap_type(state, id)) return
|
||||||
|
_ = try emit(state, ir.Instruction{
|
||||||
|
op = .store_address,
|
||||||
|
type = .void,
|
||||||
|
left = address,
|
||||||
|
right = value,
|
||||||
|
span = ast.node_span(tree, assignment.target),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
if (trap_binding(state, assignment.target) or trap_type(state, id)) return
|
if (trap_binding(state, assignment.target) or trap_type(state, id)) return
|
||||||
value :: try lower_expression(state, assignment.value)
|
value :: try lower_expression(state, assignment.value)
|
||||||
if (state.terminated) return
|
if (state.terminated) return
|
||||||
@@ -480,6 +566,7 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
|
|||||||
_ = try emit(state, ir.Instruction{
|
_ = try emit(state, ir.Instruction{
|
||||||
op = .assign_local,
|
op = .assign_local,
|
||||||
type = lower_type(typecheck.type_of(state.checked, assignment.target)),
|
type = lower_type(typecheck.type_of(state.checked, assignment.target)),
|
||||||
|
array_type = try array_shape(state, typecheck.type_of(state.checked, assignment.target)),
|
||||||
left = value,
|
left = value,
|
||||||
slot = state.storage[usize(binding.data.declaration)].index,
|
slot = state.storage[usize(binding.data.declaration)].index,
|
||||||
span = ast.node_span(tree, assignment.target),
|
span = ast.node_span(tree, assignment.target),
|
||||||
@@ -499,6 +586,7 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
|
|||||||
_ = try emit(state, ir.Instruction{
|
_ = try emit(state, ir.Instruction{
|
||||||
op = .store_local,
|
op = .store_local,
|
||||||
type = lower_type(typecheck.type_of(state.checked, id)),
|
type = lower_type(typecheck.type_of(state.checked, id)),
|
||||||
|
array_type = try array_shape(state, typecheck.type_of(state.checked, id)),
|
||||||
left = value,
|
left = value,
|
||||||
slot = state.storage[usize(id)].index,
|
slot = state.storage[usize(id)].index,
|
||||||
span = declaration.span,
|
span = declaration.span,
|
||||||
@@ -556,11 +644,61 @@ lower_logical proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError
|
|||||||
return try emit(state, ir.Instruction{ op = .load_local, type = .bool, slot = slot, span = span })
|
return try emit(state, ir.Instruction{ op = .load_local, type = .bool, slot = slot, span = span })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
is_place proc(state @State, id ast.NodeId) bool {
|
||||||
|
match ast.expr_kind(state.tree, id) {
|
||||||
|
.index: return is_place(state, ast.index_expr(state.tree, id).base)
|
||||||
|
.identifier: {
|
||||||
|
binding :: sema.binding(state.resolution, id)
|
||||||
|
if (binding.kind == .invalid) return true
|
||||||
|
if (binding.kind != .declaration) return false
|
||||||
|
location :: state.storage[usize(binding.data.declaration)]
|
||||||
|
return location.kind == .local or location.kind == .global
|
||||||
|
}
|
||||||
|
else: return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
lower_place proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
|
span :: ast.node_span(state.tree, id)
|
||||||
|
checked :: typecheck.type_of(state.checked, id)
|
||||||
|
if (ast.expr_kind(state.tree, id) == .index) {
|
||||||
|
expression :: ast.index_expr(state.tree, id)
|
||||||
|
base :: try lower_place(state, expression.base)
|
||||||
|
if (state.terminated) return ir.no_value
|
||||||
|
index :: try lower_expression(state, expression.index)
|
||||||
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
||||||
|
return try emit(state, ir.Instruction{
|
||||||
|
op = .index_address,
|
||||||
|
type = .address,
|
||||||
|
pointee = lower_type(checked),
|
||||||
|
array_type = try array_shape(state, checked),
|
||||||
|
left = base,
|
||||||
|
right = index,
|
||||||
|
span = span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (trap_binding(state, id) or trap_type(state, id)) return ir.no_value
|
||||||
|
binding :: sema.binding(state.resolution, id)
|
||||||
|
location :: state.storage[usize(binding.data.declaration)]
|
||||||
|
operation ir.Op :: if (location.kind == .global) .address_global else .address_local
|
||||||
|
return try emit(state, ir.Instruction{
|
||||||
|
op = operation,
|
||||||
|
type = .address,
|
||||||
|
pointee = lower_type(checked),
|
||||||
|
array_type = try array_shape(state, checked),
|
||||||
|
slot = location.index,
|
||||||
|
span = span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
tree :: state.tree
|
tree :: state.tree
|
||||||
span :: ast.node_span(tree, id)
|
span :: ast.node_span(tree, id)
|
||||||
type :: lower_type(typecheck.type_of(state.checked, id))
|
type :: lower_type(typecheck.type_of(state.checked, id))
|
||||||
|
shape :: try array_shape(state, typecheck.type_of(state.checked, id))
|
||||||
match ast.expr_kind(tree, id) {
|
match ast.expr_kind(tree, id) {
|
||||||
.invalid: {
|
.invalid: {
|
||||||
invalid :: ast.invalid(tree, id)
|
invalid :: ast.invalid(tree, id)
|
||||||
@@ -570,6 +708,53 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
|||||||
# ranges have no physical value; only lower_for decomposes an iterable.
|
# ranges have no physical value; only lower_for decomposes an iterable.
|
||||||
if (!trap_type(state, id)) unreachable
|
if (!trap_type(state, id)) unreachable
|
||||||
}
|
}
|
||||||
|
.array_type: {
|
||||||
|
if (!trap_type(state, id)) {
|
||||||
|
trap(state, try diagnostics.add(state.diagnostics, .type_not_value, span), span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.array_literal: {
|
||||||
|
scratch_start :: state.arguments.items.len
|
||||||
|
defer state.arguments.items = state.arguments.items.ptr[..scratch_start]
|
||||||
|
count :: ast.array_element_count(tree, id)
|
||||||
|
try arraylist.reserve(&state.arguments, scratch_start + count)
|
||||||
|
for (0..count) |_| try arraylist.append(&state.arguments, ir.no_value)
|
||||||
|
for (0..count) |i| {
|
||||||
|
element :: try lower_expression(state, ast.array_element(tree, id, i))
|
||||||
|
if (state.terminated) return ir.no_value
|
||||||
|
state.arguments.items[scratch_start + i] = element
|
||||||
|
}
|
||||||
|
if (trap_type(state, id)) return ir.no_value
|
||||||
|
block := &state.module.functions.items[state.function].blocks.items[usize(state.block)]
|
||||||
|
start :: block.arguments.items.len
|
||||||
|
try arraylist.reserve(&block.arguments, start + count)
|
||||||
|
for (0..count) |i| try arraylist.append(&block.arguments, state.arguments.items[scratch_start + i])
|
||||||
|
return try emit(state, ir.Instruction{
|
||||||
|
op = .array_literal,
|
||||||
|
type = .array,
|
||||||
|
array_type = shape,
|
||||||
|
arguments_start = start,
|
||||||
|
arguments_count = count,
|
||||||
|
span = span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
.index: {
|
||||||
|
if (is_place(state, id)) {
|
||||||
|
address :: try lower_place(state, id)
|
||||||
|
if (state.terminated) return ir.no_value
|
||||||
|
return try emit(state, ir.Instruction{
|
||||||
|
op = .load_address, type = type, array_type = shape, left = address, span = span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
expression :: ast.index_expr(tree, id)
|
||||||
|
base :: try lower_expression(state, expression.base)
|
||||||
|
if (state.terminated) return ir.no_value
|
||||||
|
index :: try lower_expression(state, expression.index)
|
||||||
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
||||||
|
return try emit(state, ir.Instruction{
|
||||||
|
op = .index, type = type, array_type = shape, left = base, right = index, span = span,
|
||||||
|
})
|
||||||
|
}
|
||||||
.literal_int, .literal_float: {
|
.literal_int, .literal_float: {
|
||||||
if (trap_type(state, id)) return ir.no_value
|
if (trap_type(state, id)) return ir.no_value
|
||||||
return try lower_number(state, id)
|
return try lower_number(state, id)
|
||||||
@@ -594,8 +779,8 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
|||||||
type = .function,
|
type = .function,
|
||||||
data = ir.ValueData{ function = ir.FunctionId(location.index) },
|
data = ir.ValueData{ function = ir.FunctionId(location.index) },
|
||||||
}, span)
|
}, span)
|
||||||
.global: return try emit(state, ir.Instruction{ op = .load_global, type = type, slot = location.index, span = span })
|
.global: return try emit(state, ir.Instruction{ op = .load_global, type = type, array_type = shape, slot = location.index, span = span })
|
||||||
.local: return try emit(state, ir.Instruction{ op = .load_local, type = type, slot = location.index, span = span })
|
.local: return try emit(state, ir.Instruction{ op = .load_local, type = type, array_type = shape, slot = location.index, span = span })
|
||||||
.none: unreachable
|
.none: unreachable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -664,6 +849,7 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
|||||||
return try emit(state, ir.Instruction{
|
return try emit(state, ir.Instruction{
|
||||||
op = .call,
|
op = .call,
|
||||||
type = type,
|
type = type,
|
||||||
|
array_type = shape,
|
||||||
left = callee,
|
left = callee,
|
||||||
arguments_start = start,
|
arguments_start = start,
|
||||||
arguments_count = count,
|
arguments_count = count,
|
||||||
@@ -695,6 +881,10 @@ lower_number proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError
|
|||||||
if (integer_magnitude(text, u64(maxval!(i64)))) |magnitude| {
|
if (integer_magnitude(text, u64(maxval!(i64)))) |magnitude| {
|
||||||
return try constant(state, ir.Value{ type = .int, data = ir.ValueData{ integer = i64(magnitude) } }, span)
|
return try constant(state, ir.Value{ type = .int, data = ir.ValueData{ integer = i64(magnitude) } }, span)
|
||||||
}
|
}
|
||||||
|
} else if (type == .usize) {
|
||||||
|
if (integer_magnitude(text, maxval!(u64))) |magnitude| {
|
||||||
|
return try constant(state, ir.Value{ type = .usize, data = ir.ValueData{ unsigned = magnitude } }, span)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
buffer :: try mem.alloc(u8, state.module.allocator, text.len + 1)
|
buffer :: try mem.alloc(u8, state.module.allocator, text.len + 1)
|
||||||
defer mem.free(state.module.allocator, buffer)
|
defer mem.free(state.module.allocator, buffer)
|
||||||
|
|||||||
+409
-57
@@ -68,7 +68,8 @@ executes_calls_locals_arithmetic_and_nested_argument_slices test {
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(ir.Type.int, outcome.value.type)
|
try testing.expect_equal(ir.Type.int, outcome.value.type)
|
||||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||||
@@ -88,7 +89,8 @@ owns_decoded_strings_after_source_and_frontend_are_freed test {
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(ir.Type.string, outcome.value.type)
|
try testing.expect_equal(ir.Type.string, outcome.value.type)
|
||||||
try testing.expect(mem.eql(ir.string_bytes(&module, outcome.value.data.string), "a\\\"\n\r\t\x00Az"))
|
try testing.expect(mem.eql(ir.string_bytes(&module, outcome.value.data.string), "a\\\"\n\r\t\x00Az"))
|
||||||
@@ -118,7 +120,8 @@ honors_float_context_and_float_division test {
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(ir.Type.float, outcome.value.type)
|
try testing.expect_equal(ir.Type.float, outcome.value.type)
|
||||||
try testing.expect_equal(f64(0.5), outcome.value.data.real)
|
try testing.expect_equal(f64(0.5), outcome.value.data.real)
|
||||||
@@ -142,10 +145,12 @@ executes_forward_lazy_globals_and_function_aliases test {
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||||
again :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
again := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&again)
|
||||||
try testing.expect(again.trap == null)
|
try testing.expect(again.trap == null)
|
||||||
try testing.expect_equal(i64(42), again.value.data.integer)
|
try testing.expect_equal(i64(42), again.value.data.integer)
|
||||||
}
|
}
|
||||||
@@ -162,7 +167,8 @@ supports_bare_void_value_returns_and_fallthrough test {
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(ir.Type.void, outcome.value.type)
|
try testing.expect_equal(ir.Type.void, outcome.value.type)
|
||||||
}
|
}
|
||||||
@@ -185,7 +191,8 @@ preserves_demanded_diagnostic_identity_and_source_location test {
|
|||||||
diagnostic :: diagnostics.get(&diagnostic_store, diagnostics.DiagnosticId(0))?
|
diagnostic :: diagnostics.get(&diagnostic_store, diagnostics.DiagnosticId(0))?
|
||||||
try testing.expect_equal(diagnostics.Code.unknown_name, diagnostic.code)
|
try testing.expect_equal(diagnostics.Code.unknown_name, diagnostic.code)
|
||||||
try testing.expect(mem.eql(source[usize(diagnostic.span.start)..usize(diagnostic.span.end)], "unknown"))
|
try testing.expect(mem.eql(source[usize(diagnostic.span.start)..usize(diagnostic.span.end)], "unknown"))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect_equal(diagnostics.DiagnosticId(0), outcome.trap?)
|
try testing.expect_equal(diagnostics.DiagnosticId(0), outcome.trap?)
|
||||||
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
||||||
}
|
}
|
||||||
@@ -207,7 +214,8 @@ skips_unreachable_and_unused_invalid_code test {
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect(diagnostics.count(&diagnostic_store) > 0)
|
try testing.expect(diagnostics.count(&diagnostic_store) > 0)
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||||
}
|
}
|
||||||
@@ -224,7 +232,8 @@ traps_local_forward_loads_and_global_initialization_cycles test {
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
code diagnostics.Code :: if (i == 0) .read_before_initialization else .initialization_cycle
|
code diagnostics.Code :: if (i == 0) .read_before_initialization else .initialization_cycle
|
||||||
try expect_trap(outcome, &diagnostic_store, code)
|
try expect_trap(outcome, &diagnostic_store, code)
|
||||||
}
|
}
|
||||||
@@ -243,7 +252,8 @@ handles_integer_boundaries_without_host_overflow test {
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(expected[i], outcome.value.data.integer)
|
try testing.expect_equal(expected[i], outcome.value.data.integer)
|
||||||
}
|
}
|
||||||
@@ -265,7 +275,8 @@ traps_invalid_literals_escapes_and_checked_integer_arithmetic test {
|
|||||||
defer diagnostics.deinit(&diagnostic_store)
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
code := diagnostics.Code.integer_overflow
|
code := diagnostics.Code.integer_overflow
|
||||||
if (i < 2) code = .literal_out_of_range
|
if (i < 2) code = .literal_out_of_range
|
||||||
if (i >= 6) code = .invalid_escape
|
if (i >= 6) code = .invalid_escape
|
||||||
@@ -285,7 +296,8 @@ validates_main_and_preserves_first_declaration test {
|
|||||||
defer diagnostics.deinit(&diagnostic_store)
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
if (i == 3) {
|
if (i == 3) {
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||||
@@ -308,7 +320,8 @@ invalid_signatures_trap_without_poisoning_unrelated_main test {
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect(diagnostics.count(&diagnostic_store) > 0)
|
try testing.expect(diagnostics.count(&diagnostic_store) > 0)
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||||
@@ -327,7 +340,8 @@ parses_float_context_without_integer_range_limits_and_rejects_float_overflow tes
|
|||||||
&diagnostic_store,
|
&diagnostic_store,
|
||||||
)
|
)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(ir.Type.float, outcome.value.type)
|
try testing.expect_equal(ir.Type.float, outcome.value.type)
|
||||||
try testing.expect_equal(f64(18446744073709551616.0), outcome.value.data.real)
|
try testing.expect_equal(f64(18446744073709551616.0), outcome.value.data.real)
|
||||||
@@ -338,7 +352,8 @@ parses_float_context_without_integer_range_limits_and_rejects_float_overflow tes
|
|||||||
for (".0\n}\n") |byte| try arraylist.append(&source, byte)
|
for (".0\n}\n") |byte| try arraylist.append(&source, byte)
|
||||||
oversized := try compile_owned(source.items, &diagnostic_store)
|
oversized := try compile_owned(source.items, &diagnostic_store)
|
||||||
defer ir.deinit(&oversized)
|
defer ir.deinit(&oversized)
|
||||||
trapped :: try interpreter.run(mem.c_allocator, &oversized, &diagnostic_store)
|
trapped := try interpreter.run(mem.c_allocator, &oversized, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&trapped)
|
||||||
try expect_trap(trapped, &diagnostic_store, .literal_out_of_range)
|
try expect_trap(trapped, &diagnostic_store, .literal_out_of_range)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,7 +369,8 @@ accepts_representable_subnormals_and_rejects_underflow_to_zero test {
|
|||||||
defer diagnostics.deinit(&diagnostic_store)
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
module := try compile_owned(source.items, &diagnostic_store)
|
module := try compile_owned(source.items, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
if (count == 307) {
|
if (count == 307) {
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
@@ -436,8 +452,11 @@ releases_temporaries_and_partially_owned_modules_on_allocation_failure test {
|
|||||||
` local :: value
|
` local :: value
|
||||||
` return local
|
` return local
|
||||||
`}
|
`}
|
||||||
|
`rows proc(value [2][2]string) [2][2]string { return value }
|
||||||
`main proc() string {
|
`main proc() string {
|
||||||
` computed float :: factor * 2
|
` computed float :: factor * 2
|
||||||
|
` aggregate :: rows([[text, "second"], ["third", text]])
|
||||||
|
` (aggregate[1])[1]
|
||||||
` remaining := 2
|
` remaining := 2
|
||||||
` for 0..=2 |item| {
|
` for 0..=2 |item| {
|
||||||
` saved :: identity(text)
|
` saved :: identity(text)
|
||||||
@@ -491,9 +510,11 @@ releases_temporaries_and_partially_owned_modules_on_allocation_failure test {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
errdefer ir.deinit(&module)
|
errdefer ir.deinit(&module)
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect(mem.eql(outcome.value.data.string, "owned\nstring"))
|
try testing.expect(mem.eql(ir.string_bytes(&module, outcome.value.data.string), "owned\nstring"))
|
||||||
|
interpreter.deinit(&outcome)
|
||||||
ir.deinit(&module)
|
ir.deinit(&module)
|
||||||
try testing.expect_equal(usize(0), state.live)
|
try testing.expect_equal(usize(0), state.live)
|
||||||
succeeded = true
|
succeeded = true
|
||||||
@@ -520,7 +541,8 @@ recursive_factorial_and_conditional_returns_execute_after_frontend_cleanup test
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(i64(120), outcome.value.data.integer)
|
try testing.expect_equal(i64(120), outcome.value.data.integer)
|
||||||
}
|
}
|
||||||
@@ -564,7 +586,8 @@ nested_branches_joins_calls_and_lazy_globals_preserve_execution_position test {
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||||
}
|
}
|
||||||
@@ -594,7 +617,8 @@ bool_storage_results_and_numeric_comparisons_execute test {
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(ir.Type.bool, outcome.value.type)
|
try testing.expect_equal(ir.Type.bool, outcome.value.type)
|
||||||
try testing.expect(outcome.value.data.boolean)
|
try testing.expect(outcome.value.data.boolean)
|
||||||
@@ -618,7 +642,8 @@ conditional_errors_trap_only_when_demanded_and_preserve_original_identity test {
|
|||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
||||||
original :: diagnostics.get(&diagnostic_store, diagnostics.DiagnosticId(0))?
|
original :: diagnostics.get(&diagnostic_store, diagnostics.DiagnosticId(0))?
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
if (index == 0 or index == 2 or index == 4) {
|
if (index == 0 or index == 2 or index == 4) {
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
if (index == 4) try testing.expect_equal(ir.Type.void, outcome.value.type)
|
if (index == 4) try testing.expect_equal(ir.Type.void, outcome.value.type)
|
||||||
@@ -646,7 +671,8 @@ missing_return_does_not_replace_reached_arm_error test {
|
|||||||
defer diagnostics.deinit(&diagnostic_store)
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
if (index == 2) {
|
if (index == 2) {
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||||
@@ -678,7 +704,8 @@ nested_locals_are_predeclared_before_any_arm_executes test {
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try expect_trap(outcome, &diagnostic_store, .read_before_initialization)
|
try expect_trap(outcome, &diagnostic_store, .read_before_initialization)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -701,7 +728,8 @@ iterative_factorial_executes_from_owned_ir_after_frontend_cleanup test {
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(ir.Type.int, outcome.value.type)
|
try testing.expect_equal(ir.Type.int, outcome.value.type)
|
||||||
try testing.expect_equal(i64(120), outcome.value.data.integer)
|
try testing.expect_equal(i64(120), outcome.value.data.integer)
|
||||||
@@ -722,7 +750,8 @@ zero_nested_early_return_and_predicate_call_loops_execute test {
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(ir.Type.int, outcome.value.type)
|
try testing.expect_equal(ir.Type.int, outcome.value.type)
|
||||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||||
@@ -743,7 +772,8 @@ float_bool_string_and_compatible_function_updates_execute test {
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(expected_types[index], outcome.value.type)
|
try testing.expect_equal(expected_types[index], outcome.value.type)
|
||||||
match index {
|
match index {
|
||||||
@@ -768,7 +798,8 @@ forward_assignments_check_initialization_after_evaluating_the_rhs test {
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try expect_trap(outcome, &diagnostic_store, codes[index])
|
try expect_trap(outcome, &diagnostic_store, codes[index])
|
||||||
diagnostic :: diagnostics.get(&diagnostic_store, outcome.trap?)?
|
diagnostic :: diagnostics.get(&diagnostic_store, outcome.trap?)?
|
||||||
try testing.expect(mem.eql(source[usize(diagnostic.span.start)..usize(diagnostic.span.end)], "later"))
|
try testing.expect(mem.eql(source[usize(diagnostic.span.start)..usize(diagnostic.span.end)], "later"))
|
||||||
@@ -803,7 +834,8 @@ loop_and_reentered_arm_locals_are_uninitialized_before_later_declarations test {
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try expect_trap(outcome, &diagnostic_store, codes[index])
|
try expect_trap(outcome, &diagnostic_store, codes[index])
|
||||||
diagnostic :: diagnostics.get(&diagnostic_store, outcome.trap?)?
|
diagnostic :: diagnostics.get(&diagnostic_store, outcome.trap?)?
|
||||||
try testing.expect(mem.eql(source[usize(diagnostic.span.start)..usize(diagnostic.span.end)], "later"))
|
try testing.expect(mem.eql(source[usize(diagnostic.span.start)..usize(diagnostic.span.end)], "later"))
|
||||||
@@ -832,7 +864,8 @@ invalid_assignments_and_loop_conditions_preserve_original_diagnostics test {
|
|||||||
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
||||||
original :: diagnostics.get(&diagnostic_store, diagnostics.DiagnosticId(0))?
|
original :: diagnostics.get(&diagnostic_store, diagnostics.DiagnosticId(0))?
|
||||||
try testing.expect_equal(codes[index], original.code)
|
try testing.expect_equal(codes[index], original.code)
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect_equal(diagnostics.DiagnosticId(0), outcome.trap?)
|
try testing.expect_equal(diagnostics.DiagnosticId(0), outcome.trap?)
|
||||||
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
||||||
}
|
}
|
||||||
@@ -853,7 +886,8 @@ zero_iteration_body_errors_are_checked_but_not_executed test {
|
|||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect(diagnostics.count(&diagnostic_store) > 0)
|
try testing.expect(diagnostics.count(&diagnostic_store) > 0)
|
||||||
count :: diagnostics.count(&diagnostic_store)
|
count :: diagnostics.count(&diagnostic_store)
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||||
try testing.expect_equal(count, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(count, diagnostics.count(&diagnostic_store))
|
||||||
@@ -878,7 +912,8 @@ loop_returns_do_not_guarantee_return_and_assignment_arithmetic_still_checks_over
|
|||||||
diagnostic :: diagnostics.get(&diagnostic_store, diagnostics.DiagnosticId(0))?
|
diagnostic :: diagnostics.get(&diagnostic_store, diagnostics.DiagnosticId(0))?
|
||||||
try testing.expect_equal(diagnostics.Code.missing_return, diagnostic.code)
|
try testing.expect_equal(diagnostics.Code.missing_return, diagnostic.code)
|
||||||
} else try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
} else try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
if (index == 0) {
|
if (index == 0) {
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||||
@@ -905,9 +940,11 @@ bounded_pure_loops_do_not_allocate_per_iteration_and_release_runtime_storage tes
|
|||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
state := AllocationState{ remaining = 64 }
|
state := AllocationState{ remaining = 64 }
|
||||||
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
||||||
outcome :: try interpreter.run(allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(expected[index], outcome.value.data.integer)
|
try testing.expect_equal(expected[index], outcome.value.data.integer)
|
||||||
|
interpreter.deinit(&outcome)
|
||||||
try testing.expect_equal(usize(0), state.live)
|
try testing.expect_equal(usize(0), state.live)
|
||||||
if (index == 0) allocations = usize(64) - state.remaining
|
if (index == 0) allocations = usize(64) - state.remaining
|
||||||
else try testing.expect_equal(allocations, usize(64) - state.remaining)
|
else try testing.expect_equal(allocations, usize(64) - state.remaining)
|
||||||
@@ -945,7 +982,8 @@ boolean_truth_tables_nesting_negation_and_precedence_execute test {
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(ir.Type.bool, outcome.value.type)
|
try testing.expect_equal(ir.Type.bool, outcome.value.type)
|
||||||
try testing.expect_equal(expected[index], outcome.value.data.boolean)
|
try testing.expect_equal(expected[index], outcome.value.data.boolean)
|
||||||
@@ -970,7 +1008,8 @@ branchful_calls_preserve_earlier_temps_nested_arguments_and_function_callees tes
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||||
}
|
}
|
||||||
@@ -995,7 +1034,8 @@ short_circuit_source_errors_remain_checked_but_skipped_in_every_consumer test {
|
|||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
count :: diagnostics.count(&diagnostic_store)
|
count :: diagnostics.count(&diagnostic_store)
|
||||||
try testing.expect(count > 0)
|
try testing.expect(count > 0)
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(ir.Type.bool, outcome.value.type)
|
try testing.expect_equal(ir.Type.bool, outcome.value.type)
|
||||||
try testing.expect(outcome.value.data.boolean)
|
try testing.expect(outcome.value.data.boolean)
|
||||||
@@ -1025,7 +1065,8 @@ demanded_boolean_source_errors_keep_the_original_diagnostic_and_operand_span tes
|
|||||||
operand []u8 :: if (index >= 2 and index <= 4) "1" else "absent"
|
operand []u8 :: if (index >= 2 and index <= 4) "1" else "absent"
|
||||||
try testing.expect_equal(code, original.code)
|
try testing.expect_equal(code, original.code)
|
||||||
try testing.expect(mem.eql(source[usize(original.span.start)..usize(original.span.end)], operand))
|
try testing.expect(mem.eql(source[usize(original.span.start)..usize(original.span.end)], operand))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect_equal(diagnostics.DiagnosticId(0), outcome.trap?)
|
try testing.expect_equal(diagnostics.DiagnosticId(0), outcome.trap?)
|
||||||
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
||||||
}
|
}
|
||||||
@@ -1042,7 +1083,8 @@ branchful_call_arguments_evaluate_left_to_right_before_later_errors test {
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(2, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(2, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
original :: diagnostics.get(&diagnostic_store, outcome.trap?)?
|
original :: diagnostics.get(&diagnostic_store, outcome.trap?)?
|
||||||
try testing.expect_equal(diagnostics.Code.unknown_name, original.code)
|
try testing.expect_equal(diagnostics.Code.unknown_name, original.code)
|
||||||
try testing.expect(mem.eql(source[usize(original.span.start)..usize(original.span.end)], "first_absent"))
|
try testing.expect(mem.eql(source[usize(original.span.start)..usize(original.span.end)], "first_absent"))
|
||||||
@@ -1064,7 +1106,8 @@ short_circuit_protects_uninitialized_reads_global_cycles_and_overflow test {
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect(outcome.value.data.boolean)
|
try testing.expect(outcome.value.data.boolean)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
@@ -1086,7 +1129,8 @@ demanded_boolean_runtime_errors_keep_the_instruction_span test {
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try expect_trap(outcome, &diagnostic_store, codes[index])
|
try expect_trap(outcome, &diagnostic_store, codes[index])
|
||||||
diagnostic :: diagnostics.get(&diagnostic_store, outcome.trap?)?
|
diagnostic :: diagnostics.get(&diagnostic_store, outcome.trap?)?
|
||||||
try testing.expect(mem.eql(source[usize(diagnostic.span.start)..usize(diagnostic.span.end)], spans[index]))
|
try testing.expect(mem.eql(source[usize(diagnostic.span.start)..usize(diagnostic.span.end)], spans[index]))
|
||||||
@@ -1114,7 +1158,8 @@ composed_loop_guards_recompute_and_overwrite_synthetic_results test {
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||||
}
|
}
|
||||||
@@ -1150,14 +1195,16 @@ lazy_branchful_global_initializers_and_calls_release_every_runtime_allocation te
|
|||||||
for (usize(0)..usize(64)) |limit| {
|
for (usize(0)..usize(64)) |limit| {
|
||||||
state := AllocationState{ remaining = limit }
|
state := AllocationState{ remaining = limit }
|
||||||
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
||||||
outcome :: interpreter.run(allocator, &module, &diagnostic_store) catch |error| {
|
outcome := interpreter.run(allocator, &module, &diagnostic_store) catch |error| {
|
||||||
try testing.expect(error == .out_of_memory)
|
try testing.expect(error == .out_of_memory)
|
||||||
failures += 1
|
failures += 1
|
||||||
try testing.expect_equal(usize(0), state.live)
|
try testing.expect_equal(usize(0), state.live)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect(outcome.value.data.boolean)
|
try testing.expect(outcome.value.data.boolean)
|
||||||
|
interpreter.deinit(&outcome)
|
||||||
try testing.expect_equal(usize(0), state.live)
|
try testing.expect_equal(usize(0), state.live)
|
||||||
succeeded = true
|
succeeded = true
|
||||||
break
|
break
|
||||||
@@ -1181,7 +1228,8 @@ skipped_rhs_does_not_hide_a_demanded_outer_type_mismatch test {
|
|||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
count :: diagnostics.count(&diagnostic_store)
|
count :: diagnostics.count(&diagnostic_store)
|
||||||
try testing.expect_equal(2, count)
|
try testing.expect_equal(2, count)
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try expect_trap(outcome, &diagnostic_store, codes[index])
|
try expect_trap(outcome, &diagnostic_store, codes[index])
|
||||||
try testing.expect_equal(count, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(count, diagnostics.count(&diagnostic_store))
|
||||||
}
|
}
|
||||||
@@ -1199,7 +1247,8 @@ break_and_continue_skip_iterations_exit_early_and_restore_outer_targets test {
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||||
}
|
}
|
||||||
@@ -1223,7 +1272,8 @@ continue_restarts_composed_calling_guards_after_branchful_body_calls test {
|
|||||||
module := try compile_owned(source.items, &diagnostic_store)
|
module := try compile_owned(source.items, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||||
}
|
}
|
||||||
@@ -1247,7 +1297,8 @@ reached_outside_controls_preserve_diagnostic_identity_and_keyword_span test {
|
|||||||
original :: diagnostics.get(&diagnostic_store, diagnostics.DiagnosticId(0))?
|
original :: diagnostics.get(&diagnostic_store, diagnostics.DiagnosticId(0))?
|
||||||
try testing.expect_equal(codes[index], original.code)
|
try testing.expect_equal(codes[index], original.code)
|
||||||
try testing.expect(mem.eql(source[usize(original.span.start)..usize(original.span.end)], keywords[index]))
|
try testing.expect(mem.eql(source[usize(original.span.start)..usize(original.span.end)], keywords[index]))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect_equal(diagnostics.DiagnosticId(0), outcome.trap?)
|
try testing.expect_equal(diagnostics.DiagnosticId(0), outcome.trap?)
|
||||||
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
||||||
}
|
}
|
||||||
@@ -1270,7 +1321,8 @@ untaken_outside_controls_remain_checked_without_trapping test {
|
|||||||
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
||||||
original :: diagnostics.get(&diagnostic_store, diagnostics.DiagnosticId(0))?
|
original :: diagnostics.get(&diagnostic_store, diagnostics.DiagnosticId(0))?
|
||||||
try testing.expect_equal(codes[index], original.code)
|
try testing.expect_equal(codes[index], original.code)
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||||
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
||||||
@@ -1292,7 +1344,8 @@ source_errors_after_loop_transfers_stay_checked_but_unreached test {
|
|||||||
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
||||||
original :: diagnostics.get(&diagnostic_store, diagnostics.DiagnosticId(0))?
|
original :: diagnostics.get(&diagnostic_store, diagnostics.DiagnosticId(0))?
|
||||||
try testing.expect_equal(diagnostics.Code.unknown_name, original.code)
|
try testing.expect_equal(diagnostics.Code.unknown_name, original.code)
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||||
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
||||||
@@ -1310,7 +1363,8 @@ traps_before_loop_controls_keep_the_original_error test {
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try expect_trap(outcome, &diagnostic_store, .unknown_name)
|
try expect_trap(outcome, &diagnostic_store, .unknown_name)
|
||||||
try testing.expect_equal(diagnostics.DiagnosticId(0), outcome.trap?)
|
try testing.expect_equal(diagnostics.DiagnosticId(0), outcome.trap?)
|
||||||
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
||||||
@@ -1340,7 +1394,8 @@ owned_integer_ranges_execute_exclusive_inclusive_empty_signed_and_discard_forms
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(ir.Type.int, outcome.value.type)
|
try testing.expect_equal(ir.Type.int, outcome.value.type)
|
||||||
try testing.expect_equal(expected[index], outcome.value.data.integer)
|
try testing.expect_equal(expected[index], outcome.value.data.integer)
|
||||||
@@ -1372,7 +1427,8 @@ range_bounds_calls_and_globals_are_frozen_before_branchful_body_calls test {
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||||
}
|
}
|
||||||
@@ -1389,7 +1445,8 @@ range_break_continue_and_nested_for_while_restore_lexical_targets test {
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||||
}
|
}
|
||||||
@@ -1415,7 +1472,8 @@ range_body_and_arm_locals_reset_before_continued_later_declarations test {
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try expect_trap(outcome, &diagnostic_store, codes[index])
|
try expect_trap(outcome, &diagnostic_store, codes[index])
|
||||||
diagnostic :: diagnostics.get(&diagnostic_store, outcome.trap?)?
|
diagnostic :: diagnostics.get(&diagnostic_store, outcome.trap?)?
|
||||||
try testing.expect(mem.eql(source[usize(diagnostic.span.start)..usize(diagnostic.span.end)], "later"))
|
try testing.expect(mem.eql(source[usize(diagnostic.span.start)..usize(diagnostic.span.end)], "later"))
|
||||||
@@ -1443,7 +1501,8 @@ untaken_range_errors_and_errors_after_controls_remain_diagnostic_only test {
|
|||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
count :: diagnostics.count(&diagnostic_store)
|
count :: diagnostics.count(&diagnostic_store)
|
||||||
try testing.expect(count > 0)
|
try testing.expect(count > 0)
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
try testing.expect_equal(i64(42), outcome.value.data.integer)
|
||||||
try testing.expect_equal(count, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(count, diagnostics.count(&diagnostic_store))
|
||||||
@@ -1476,7 +1535,8 @@ demanded_range_header_capture_and_body_errors_keep_original_diagnostic_identity
|
|||||||
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
||||||
original :: diagnostics.get(&diagnostic_store, diagnostics.DiagnosticId(0))?
|
original :: diagnostics.get(&diagnostic_store, diagnostics.DiagnosticId(0))?
|
||||||
try testing.expect_equal(codes[index], original.code)
|
try testing.expect_equal(codes[index], original.code)
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect_equal(diagnostics.DiagnosticId(0), outcome.trap?)
|
try testing.expect_equal(diagnostics.DiagnosticId(0), outcome.trap?)
|
||||||
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
||||||
}
|
}
|
||||||
@@ -1495,7 +1555,8 @@ range_bounds_evaluate_left_to_right_before_header_errors_and_body_overflow_keeps
|
|||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
before :: diagnostics.count(&diagnostic_store)
|
before :: diagnostics.count(&diagnostic_store)
|
||||||
outcome :: try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
code diagnostics.Code :: if (index == 3) .unknown_name else .integer_overflow
|
code diagnostics.Code :: if (index == 3) .unknown_name else .integer_overflow
|
||||||
try expect_trap(outcome, &diagnostic_store, code)
|
try expect_trap(outcome, &diagnostic_store, code)
|
||||||
diagnostic :: diagnostics.get(&diagnostic_store, outcome.trap?)?
|
diagnostic :: diagnostics.get(&diagnostic_store, outcome.trap?)?
|
||||||
@@ -1526,11 +1587,302 @@ range_runtime_allocations_do_not_grow_with_iterations test {
|
|||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
state := AllocationState{ remaining = 64 }
|
state := AllocationState{ remaining = 64 }
|
||||||
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
||||||
outcome :: try interpreter.run(allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(expected[index], outcome.value.data.integer)
|
try testing.expect_equal(expected[index], outcome.value.data.integer)
|
||||||
|
interpreter.deinit(&outcome)
|
||||||
try testing.expect_equal(usize(0), state.live)
|
try testing.expect_equal(usize(0), state.live)
|
||||||
if (index == 0) allocations = usize(64) - state.remaining
|
if (index == 0) allocations = usize(64) - state.remaining
|
||||||
else try testing.expect_equal(allocations, usize(64) - state.remaining)
|
else try testing.expect_equal(allocations, usize(64) - state.remaining)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
array_mutation_copies_rows_wholes_and_by_value_calls test {
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
source ::
|
||||||
|
`seed :: later
|
||||||
|
`later [2]mut [2]mut i64 :: [[1, 2], [3, 4]]
|
||||||
|
`identity proc(value [2]mut [2]mut i64) [2]mut [2]mut i64 { return value }
|
||||||
|
`changed proc(value [2]mut [2]mut i64) [2]mut [2]mut i64 {
|
||||||
|
` copy := value
|
||||||
|
` (copy[0])[0] = 20
|
||||||
|
` return copy
|
||||||
|
`}
|
||||||
|
`main proc() bool {
|
||||||
|
` a := identity(identity(seed))
|
||||||
|
` row := a[0]
|
||||||
|
` row[0] = 9
|
||||||
|
` b := changed(identity(a))
|
||||||
|
` (b[1])[1] = 30
|
||||||
|
` c := a
|
||||||
|
` c = b
|
||||||
|
` (b[0])[1] = 40
|
||||||
|
` a[1] = row
|
||||||
|
` row[1] = 50
|
||||||
|
` count usize :: 2
|
||||||
|
` sum := 0
|
||||||
|
` for 0..count |i| {
|
||||||
|
` for 0..count |j| { sum = sum + a[i][j] }
|
||||||
|
` }
|
||||||
|
` return sum == 14 and seed[0][0] == 1 and seed[1][1] == 4 and c[0][0] == 20 and c[0][1] == 2 and c[1][1] == 30
|
||||||
|
`}
|
||||||
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
|
try testing.expect(outcome.trap == null)
|
||||||
|
try testing.expect(outcome.value.data.boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
array_context_inference_empty_shapes_and_scalar_leaf_families test {
|
||||||
|
sources [8][]u8 :: [
|
||||||
|
"main proc() bool {\n a [_]mut i64 := [1, 2, 3]\n a[1] = 9\n return a[1] == 9\n}\n",
|
||||||
|
"main proc() bool {\n a [0]bool :: []\n b :: []\n return true\n}\n",
|
||||||
|
"main proc() bool {\n a [2]mut f64 := [1, 2.5]\n a[0] = 3.5\n return a[0] + a[1] == 6.0\n}\n",
|
||||||
|
"main proc() bool {\n a [2]mut bool := [false or true, true and false]\n a[1] = true\n return a[0] and a[1]\n}\n",
|
||||||
|
"main proc() string {\n a [2]mut string := [\"one\", \"two\"]\n b := a\n a[1] = \"three\"\n return b[1]\n}\n",
|
||||||
|
"plus proc(n int) int { return n + 1 }\nminus proc(n int) int { return n - 1 }\nmain proc() bool {\n a :: [plus, minus]\n return a[0](41) == 42 and a[1](43) == 42\n}\n",
|
||||||
|
"make proc() [2]i64 { return [20, 22] }\nmain proc() bool {\n return make()[0] + ([20, 22])[1] == 42\n}\n",
|
||||||
|
"count :: alias + 1\nalias :: 1\nmain proc() bool {\n a [count][0]bool :: [[], []]\n return true\n}\n",
|
||||||
|
]
|
||||||
|
for (sources) |source| {
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
|
try testing.expect(outcome.trap == null)
|
||||||
|
if (outcome.value.type == .string) {
|
||||||
|
try testing.expect(mem.eql(ir.string_bytes(&module, outcome.value.data.string), "two"))
|
||||||
|
} else {
|
||||||
|
try testing.expect(outcome.value.data.boolean)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
array_literal_scratch_handles_nested_calls_and_branchful_elements test {
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
source ::
|
||||||
|
`identity proc(value [2]bool) [2]bool { return value }
|
||||||
|
`pair proc(left [2]bool, right [2]bool) [2][2]bool { return [left, right] }
|
||||||
|
`main proc() bool {
|
||||||
|
` value :: pair(identity([true or false, false and true]), identity(identity([false or true, true and !false])))
|
||||||
|
` return value[0][0] and !value[0][1] and value[1][0] and value[1][1]
|
||||||
|
`}
|
||||||
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
|
try testing.expect(outcome.trap == null)
|
||||||
|
try testing.expect(outcome.value.data.boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
array_bounds_roots_and_mutability_trap_at_demanded_positions test {
|
||||||
|
sources [12][]u8 :: [
|
||||||
|
"main proc() int {\n a :: [1]\n return a[1]\n}\n",
|
||||||
|
"main proc() int {\n a [0]i64 :: []\n return a[0]\n}\n",
|
||||||
|
"main proc() int {\n a [1]mut i64 := [1]\n a[18446744073709551615] = 2\n return 0\n}\n",
|
||||||
|
"main proc() int {\n a [1]mut i64 :: [1]\n a[0] = 2\n return 0\n}\n",
|
||||||
|
"main proc() int {\n a := [1]\n a[0] = 2\n return 0\n}\n",
|
||||||
|
"a [1]mut i64 :: [1]\nmain proc() int {\n a[0] = 2\n return 0\n}\n",
|
||||||
|
"bad proc(a [1]mut i64) int {\n a[0] = 2\n return 0\n}\nmain proc() int {\n return bad([1])\n}\n",
|
||||||
|
"main proc() int {\n a [1]mut [1]i64 := [[1]]\n (a[0])[0] = 2\n return 0\n}\n",
|
||||||
|
"main proc() int {\n a [1][1]mut i64 := [[1]]\n (a[0])[0] = 2\n return 0\n}\n",
|
||||||
|
"a [1]i64 :: b\nb [1]i64 :: a\nmain proc() int {\n return a[0]\n}\n",
|
||||||
|
"main proc() int {\n before :: after[0]\n after [1]i64 :: [1]\n return before\n}\n",
|
||||||
|
"main proc() int {\n [1]i64\n return 0\n}\n",
|
||||||
|
]
|
||||||
|
codes [12]diagnostics.Code :: [
|
||||||
|
.index_out_of_bounds, .index_out_of_bounds, .index_out_of_bounds,
|
||||||
|
.invalid_index_assignment, .invalid_index_assignment, .invalid_index_assignment,
|
||||||
|
.invalid_index_assignment, .invalid_index_assignment, .invalid_index_assignment,
|
||||||
|
.initialization_cycle, .read_before_initialization, .type_not_value,
|
||||||
|
]
|
||||||
|
for (sources) |source, i| {
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
|
try expect_trap(outcome, &diagnostic_store, codes[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
array_assignment_rhs_and_nested_bounds_preserve_error_order_and_identity test {
|
||||||
|
sources [5][]u8 :: [
|
||||||
|
"rhs proc() i64 { return absent_rhs }\nmain proc() int {\n a [0]mut i64 := []\n a[absent_index] = rhs()\n return 0\n}\n",
|
||||||
|
"index proc() usize { return absent_inner }\nmain proc() int {\n a [1][1]i64 :: [[1]]\n return a[2][index()]\n}\n",
|
||||||
|
"rhs proc() i64 { return absent_rhs }\nmain proc() int {\n a :: [1]\n a[0] = rhs()\n return 0\n}\n",
|
||||||
|
"main proc() int {\n a [0]mut i64 := []\n a[0] = 9223372036854775807 + 1\n return 0\n}\n",
|
||||||
|
"main proc() int {\n a [1]mut i64 := [1]\n a[absent_index] = absent_rhs\n return 0\n}\n",
|
||||||
|
]
|
||||||
|
codes [5]diagnostics.Code :: [.unknown_name, .index_out_of_bounds, .unknown_name, .integer_overflow, .unknown_name]
|
||||||
|
for (sources) |source, i| {
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
before :: diagnostics.count(&diagnostic_store)
|
||||||
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
|
try expect_trap(outcome, &diagnostic_store, codes[i])
|
||||||
|
if (i == 0 or i == 2 or i == 4) {
|
||||||
|
diagnostic :: diagnostics.get(&diagnostic_store, outcome.trap?)?
|
||||||
|
try testing.expect(mem.eql(source[usize(diagnostic.span.start)..usize(diagnostic.span.end)], "absent_rhs"))
|
||||||
|
try testing.expect_equal(before, diagnostics.count(&diagnostic_store))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
usize_arithmetic_and_guarded_ranges_use_unsigned_full_width test {
|
||||||
|
sources [5][]u8 :: [
|
||||||
|
"main proc() bool {\n max usize :: 18446744073709551615\n n := 0\n for max..=max |i| {\n if (i != max) return false\n n = n + 1\n continue\n }\n return n == 1\n}\n",
|
||||||
|
"main proc() bool {\n max usize :: 18446744073709551615\n zero usize :: 0\n n := 0\n for max..max |_| { n = n + 1 }\n for zero..=zero |_| { n = n + 1 }\n return n == 1\n}\n",
|
||||||
|
"main proc() bool {\n start usize :: 18446744073709551614\n end usize :: 18446744073709551615\n n := 0\n for start..=end |i| {\n n = n + 1\n continue\n }\n return n == 2\n}\n",
|
||||||
|
"main proc() bool {\n a usize :: 9223372036854775808\n return a > 9223372036854775807 and a * 1 + 1 - 1 == a\n}\n",
|
||||||
|
"main proc() bool {\n end usize :: 3\n sum usize := 0\n for 0..end |i| { sum = sum + i }\n return sum == 3\n}\n",
|
||||||
|
]
|
||||||
|
for (sources) |source| {
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
|
try testing.expect(outcome.trap == null)
|
||||||
|
try testing.expect(outcome.value.data.boolean)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aggregate_loop_frames_allocate_once_and_reset_local_freshness test {
|
||||||
|
sources [2][]u8 :: [
|
||||||
|
"main proc() int {\n end usize :: 1\n count := 0\n a [1]mut i64 := [0]\n for 0..end |i| {\n row [1]mut i64 := [a[0]]\n row[0] = row[0] + 1\n a = row\n count = count + 1\n }\n return count + a[0]\n}\n",
|
||||||
|
"main proc() int {\n end usize :: 10000\n count := 0\n a [1]mut i64 := [0]\n for 0..end |i| {\n row [1]mut i64 := [a[0]]\n row[0] = row[0] + 1\n a = row\n count = count + 1\n }\n return count + a[0]\n}\n",
|
||||||
|
]
|
||||||
|
expected [2]i64 :: [2, 20000]
|
||||||
|
allocations := usize(0)
|
||||||
|
for (sources) |source, i| {
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
state := AllocationState{ remaining = 64 }
|
||||||
|
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
||||||
|
outcome := try interpreter.run(allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
|
try testing.expect(outcome.trap == null)
|
||||||
|
try testing.expect_equal(expected[i], outcome.value.data.integer)
|
||||||
|
interpreter.deinit(&outcome)
|
||||||
|
try testing.expect_equal(usize(0), state.live)
|
||||||
|
if (i == 0) allocations = usize(64) - state.remaining
|
||||||
|
else try testing.expect_equal(allocations, usize(64) - state.remaining)
|
||||||
|
}
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
module := try compile_owned(
|
||||||
|
"main proc() int {\n end usize :: 2\n for 0..end |i| {\n if (i == 1) return later[0]\n later [1]i64 :: [42]\n }\n return 0\n}\n",
|
||||||
|
&diagnostic_store,
|
||||||
|
)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
|
try expect_trap(outcome, &diagnostic_store, .read_before_initialization)
|
||||||
|
}
|
||||||
|
|
||||||
|
escaped_nested_array_returns_survive_frontend_and_frames_and_release_on_oom test {
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
source ::
|
||||||
|
`later [2][2]string :: [["owned\none", "two"], ["three", "four"]]
|
||||||
|
`first :: identity(later)
|
||||||
|
`identity proc(value [2][2]string) [2][2]string { return value }
|
||||||
|
`main proc() [2][2]string { return identity(identity(first)) }
|
||||||
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
succeeded := false
|
||||||
|
failures := usize(0)
|
||||||
|
for (usize(0)..usize(512)) |limit| {
|
||||||
|
state := AllocationState{ remaining = limit }
|
||||||
|
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
||||||
|
outcome := interpreter.run(allocator, &module, &diagnostic_store) catch |error| {
|
||||||
|
try testing.expect(error == .out_of_memory)
|
||||||
|
failures += 1
|
||||||
|
try testing.expect_equal(usize(0), state.live)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
|
try testing.expect(outcome.trap == null)
|
||||||
|
try testing.expect_equal(ir.Type.array, outcome.value.type)
|
||||||
|
try testing.expect_equal(usize(4), outcome.value.data.elements.len)
|
||||||
|
try testing.expect(mem.eql(ir.string_bytes(&module, outcome.value.data.elements[0].data.string), "owned\none"))
|
||||||
|
try testing.expect(mem.eql(ir.string_bytes(&module, outcome.value.data.elements[3].data.string), "four"))
|
||||||
|
try testing.expect(state.live > 0)
|
||||||
|
interpreter.deinit(&outcome)
|
||||||
|
try testing.expect_equal(usize(0), state.live)
|
||||||
|
succeeded = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
try testing.expect(succeeded)
|
||||||
|
try testing.expect(failures > 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned_arithmetic_reports_overflow_without_signed_narrowing test {
|
||||||
|
sources [3][]u8 :: [
|
||||||
|
"main proc() usize {\n return 18446744073709551615 + 1\n}\n",
|
||||||
|
"main proc() usize {\n return 0 - 1\n}\n",
|
||||||
|
"main proc() usize {\n return 9223372036854775808 * 2\n}\n",
|
||||||
|
]
|
||||||
|
for (sources) |source| {
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
|
try expect_trap(outcome, &diagnostic_store, .integer_overflow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
array_index_write_checks_initialization_after_rhs test {
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
module := try compile_owned(
|
||||||
|
"main proc() int {\n a[0] = 42\n a [1]mut i64 := [0]\n return 0\n}\n",
|
||||||
|
&diagnostic_store,
|
||||||
|
)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
|
try expect_trap(outcome, &diagnostic_store, .write_before_initialization)
|
||||||
|
}
|
||||||
|
|
||||||
|
empty_array_entry_results_need_no_escaped_allocation test {
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
module := try compile_owned("main proc() [0]bool { return [] }\n", &diagnostic_store)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
state := AllocationState{ remaining = 64 }
|
||||||
|
allocator :: mem.Allocator{ context = &state, vtable = &limited_vtable }
|
||||||
|
outcome := try interpreter.run(allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
|
try testing.expect(outcome.trap == null)
|
||||||
|
try testing.expect_equal(ir.Type.array, outcome.value.type)
|
||||||
|
try testing.expect_equal(usize(0), outcome.value.data.elements.len)
|
||||||
|
try testing.expect_equal(usize(0), state.live)
|
||||||
|
interpreter.deinit(&outcome)
|
||||||
|
try testing.expect_equal(usize(0), state.live)
|
||||||
|
}
|
||||||
|
|||||||
+40
-6
@@ -13,6 +13,7 @@ import "@source/sema"
|
|||||||
import "@source/ir"
|
import "@source/ir"
|
||||||
import "@source/lower"
|
import "@source/lower"
|
||||||
import "@source/interpreter"
|
import "@source/interpreter"
|
||||||
|
|
||||||
typecheck :: import "@source/sema/check"
|
typecheck :: import "@source/sema/check"
|
||||||
|
|
||||||
test import "@std/mem"
|
test import "@std/mem"
|
||||||
@@ -65,6 +66,7 @@ infrastructure_message proc(error DriverError) []u8 {
|
|||||||
return match error {
|
return match error {
|
||||||
.out_of_memory: "out of memory"
|
.out_of_memory: "out of memory"
|
||||||
.diagnostic_limit_reached: "diagnostic limit reached"
|
.diagnostic_limit_reached: "diagnostic limit reached"
|
||||||
|
.storage_limit_reached: "aggregate storage limit reached"
|
||||||
.source_too_large: "source file is too large"
|
.source_too_large: "source file is too large"
|
||||||
.out_of_space: "identifier storage is full"
|
.out_of_space: "identifier storage is full"
|
||||||
.open_failed: "cannot open source file"
|
.open_failed: "cannot open source file"
|
||||||
@@ -133,7 +135,8 @@ run_file proc(allocator mem.Allocator, runtime io.Io, path [;0]u8) i32 ! DriverE
|
|||||||
defer diagnostics.deinit(&diagnostic_store)
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
module := try compile(allocator, source.items, &diagnostic_store)
|
module := try compile(allocator, source.items, &diagnostic_store)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
outcome :: try interpreter.run(allocator, &module, &diagnostic_store)
|
outcome := try interpreter.run(allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
if (outcome.trap == null) try print_result(io.stdout(runtime), &module, outcome.value)
|
if (outcome.trap == null) try print_result(io.stdout(runtime), &module, outcome.value)
|
||||||
try report_diagnostics(allocator, io.stderr(runtime), path, source.items, &diagnostic_store, outcome.trap)
|
try report_diagnostics(allocator, io.stderr(runtime), path, source.items, &diagnostic_store, outcome.trap)
|
||||||
if (outcome.trap != null or diagnostics.count(&diagnostic_store) != 0) return 1
|
if (outcome.trap != null or diagnostics.count(&diagnostic_store) != 0) return 1
|
||||||
@@ -142,12 +145,43 @@ run_file proc(allocator mem.Allocator, runtime io.Io, path [;0]u8) i32 ! DriverE
|
|||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
print_result proc(output io.Writer, module @ir.Module, value ir.Value) void ! io.WriteError {
|
print_result proc(output io.Writer, module @ir.Module, value ir.Value) void ! io.WriteError {
|
||||||
|
if (value.type == .void) return
|
||||||
|
try print_value(output, module, value)
|
||||||
|
try io.print("\n", output, {})
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
print_value proc(output io.Writer, module @ir.Module, value ir.Value) void ! io.WriteError {
|
||||||
match value.type {
|
match value.type {
|
||||||
.int: try io.print("{}\n", output, {value.data.integer})
|
.int: try io.print("{}", output, {value.data.integer})
|
||||||
.float: try io.print("{d}\n", output, {value.data.real})
|
.usize: try io.print("{}", output, {value.data.unsigned})
|
||||||
.bool: try io.print("{}\n", output, {value.data.boolean})
|
.float: try io.print("{d}", output, {value.data.real})
|
||||||
.string: try io.print("{s}\n", output, {ir.string_bytes(module, value.data.string)})
|
.bool: try io.print("{}", output, {value.data.boolean})
|
||||||
.void: {}
|
.string: try io.print("{s}", output, {ir.string_bytes(module, value.data.string)})
|
||||||
|
.array: {
|
||||||
|
shape :: module.arrays.items[usize(value.array_type)]
|
||||||
|
stride usize :: if (shape.element == .array)
|
||||||
|
module.arrays.items[usize(shape.element_array)].cells
|
||||||
|
else
|
||||||
|
1
|
||||||
|
try io.print("[", output, {})
|
||||||
|
for 0..(shape.len) |index| {
|
||||||
|
if (index != 0) try io.print(", ", output, {})
|
||||||
|
element := ir.Value{}
|
||||||
|
if shape.element == .array {
|
||||||
|
start :: index * stride
|
||||||
|
element = ir.Value{
|
||||||
|
type = .array,
|
||||||
|
array_type = shape.element_array,
|
||||||
|
data = ir.ValueData{ elements = value.data.elements[start..(start + stride)] },
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
element = value.data.elements[index]
|
||||||
|
}
|
||||||
|
try print_value(output, module, element)
|
||||||
|
}
|
||||||
|
try io.print("]", output, {})
|
||||||
|
}
|
||||||
else: unreachable
|
else: unreachable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+213
-53
@@ -103,7 +103,7 @@ parse_func_decl proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
while true {
|
while true {
|
||||||
token :: try consume(state, .ident)
|
token :: try consume(state, .ident)
|
||||||
try arraylist.append(&names, token.span)
|
try arraylist.append(&names, token.span)
|
||||||
if next_is(state, .ident) break
|
if (next_is(state, .ident) or next_is(state, .open_bracket)) break
|
||||||
_ = try consume(state, .comma)
|
_ = try consume(state, .comma)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -169,13 +169,11 @@ parse_block proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
||||||
if (next_is(state, .ident) and next_n_is(state, 1, .equal)) {
|
|
||||||
return try parse_assignment(state, allow_else)
|
|
||||||
}
|
|
||||||
if (
|
if (
|
||||||
next_is(state, .ident)
|
next_is(state, .ident)
|
||||||
and (
|
and (
|
||||||
next_n_is(state, 1, .ident)
|
next_n_is(state, 1, .ident)
|
||||||
|
or (next_n_is(state, 1, .open_bracket) and array_starts_type(state))
|
||||||
or next_n_is(state, 1, .double_colon)
|
or next_n_is(state, 1, .double_colon)
|
||||||
or next_n_is(state, 1, .colon_equal)
|
or next_n_is(state, 1, .colon_equal)
|
||||||
)
|
)
|
||||||
@@ -188,8 +186,9 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
|
|||||||
.for: return try parse_for(state, allow_else)
|
.for: return try parse_for(state, allow_else)
|
||||||
.return: return try parse_return(state, allow_else)
|
.return: return try parse_return(state, allow_else)
|
||||||
.break, .continue: return try parse_loop_control(state, allow_else)
|
.break, .continue: return try parse_loop_control(state, allow_else)
|
||||||
.ident, .int, .float, .string, .true, .false, .minus, .bang, .open_paren: {
|
.ident, .int, .float, .string, .true, .false, .minus, .bang, .open_paren, .open_bracket: {
|
||||||
expression :: try parse_expr(state)
|
expression :: try parse_expr(state)
|
||||||
|
if next_is(state, .equal) return try parse_assignment(state, expression, allow_else)
|
||||||
return try ast.add_expression_stmt(&state.builder, expression)
|
return try ast.add_expression_stmt(&state.builder, expression)
|
||||||
}
|
}
|
||||||
else: return .unexpected_token
|
else: return .unexpected_token
|
||||||
@@ -239,9 +238,7 @@ parse_loop_control proc(state @mut State, allow_else bool) ast.NodeId ! ParseErr
|
|||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
parse_assignment proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
parse_assignment proc(state @mut State, target ast.NodeId, allow_else bool) ast.NodeId ! ParseError {
|
||||||
name :: try consume(state, .ident)
|
|
||||||
target :: try ast.add_identifier(&state.builder, name.span)
|
|
||||||
_ = try consume(state, .equal)
|
_ = try consume(state, .equal)
|
||||||
value :: try parse_expr(state)
|
value :: try parse_expr(state)
|
||||||
if (
|
if (
|
||||||
@@ -255,7 +252,7 @@ parse_assignment proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
|
|||||||
end :: state.tokens[state.next_token - 1].span.end
|
end :: state.tokens[state.next_token - 1].span.end
|
||||||
return try ast.add_assignment_stmt(
|
return try ast.add_assignment_stmt(
|
||||||
&state.builder,
|
&state.builder,
|
||||||
ast.Span{ start = name.span.start, end = end },
|
ast.Span{ start = ast.builder_span(&state.builder, target).start, end = end },
|
||||||
target,
|
target,
|
||||||
value,
|
value,
|
||||||
)
|
)
|
||||||
@@ -454,8 +451,8 @@ parse_arm proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
|||||||
.unexpected_token: {}
|
.unexpected_token: {}
|
||||||
else: return err
|
else: return err
|
||||||
}
|
}
|
||||||
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, current(state).span)
|
diagnostic :: try unexpected_diagnostic(state)
|
||||||
span :: synchronize_arm(state, start)
|
span :: synchronize(state, start, true, true, false)
|
||||||
yield try ast.add_invalid_stmt(&state.builder, span, diagnostic)
|
yield try ast.add_invalid_stmt(&state.builder, span, diagnostic)
|
||||||
}
|
}
|
||||||
statements [1]ast.NodeId :: [statement]
|
statements [1]ast.NodeId :: [statement]
|
||||||
@@ -466,28 +463,11 @@ parse_arm proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
|
||||||
synchronize_arm proc(state @mut State, start usize) ast.Span {
|
|
||||||
first :: state.tokens[start].span
|
|
||||||
end := first.end
|
|
||||||
depth usize := 0
|
|
||||||
while !next_is(state, .eof) {
|
|
||||||
if (
|
|
||||||
depth == 0
|
|
||||||
and (next_is(state, .newline) or next_is(state, .close_curly) or next_is(state, .else))
|
|
||||||
) break
|
|
||||||
if next_is(state, .open_curly) depth += 1
|
|
||||||
if (next_is(state, .close_curly) and depth > 0) depth -= 1
|
|
||||||
end = current(state).span.end
|
|
||||||
state.next_token += 1
|
|
||||||
}
|
|
||||||
return ast.Span{ start = first.start, end = end }
|
|
||||||
}
|
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
parse_statement_decl proc(state @mut State) ast.NodeId ! ParseError {
|
parse_statement_decl proc(state @mut State) ast.NodeId ! ParseError {
|
||||||
name :: try consume(state, .ident)
|
name :: try consume(state, .ident)
|
||||||
type_id ?ast.NodeId :: if next_is(state, .ident)
|
type_id ?ast.NodeId :: if (next_is(state, .ident) or next_is(state, .open_bracket))
|
||||||
try parse_type(state)
|
try parse_type(state)
|
||||||
else
|
else
|
||||||
null
|
null
|
||||||
@@ -508,9 +488,29 @@ parse_statement_decl proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
parse_type proc(state @mut State) ast.NodeId ! ParseError {
|
parse_type proc(state @mut State) ast.NodeId ! ParseError {
|
||||||
if (!next_is(state, .ident)) return .unexpected_token
|
if next_is(state, .open_bracket) {
|
||||||
token :: current(state)
|
open :: try consume(state, .open_bracket)
|
||||||
|
count ?ast.NodeId := null
|
||||||
|
if (
|
||||||
|
next_is(state, .ident)
|
||||||
|
and current(state).span.end == current(state).span.start + 1
|
||||||
|
and state.builder.source[usize(current(state).span.start)] == '_'
|
||||||
|
) {
|
||||||
state.next_token += 1
|
state.next_token += 1
|
||||||
|
} else {
|
||||||
|
count = try parse_expr(state)
|
||||||
|
}
|
||||||
|
_ = try consume(state, .close_bracket)
|
||||||
|
mutable :: next_is(state, .mut)
|
||||||
|
if (mutable) state.next_token += 1
|
||||||
|
element :: try parse_type(state)
|
||||||
|
return try ast.add_array_type(
|
||||||
|
&state.builder,
|
||||||
|
ast.Span{ start = open.span.start, end = ast.builder_span(&state.builder, element).end },
|
||||||
|
count, element, mutable,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
token :: try consume(state, .ident)
|
||||||
return try ast.add_identifier(&state.builder, token.span)
|
return try ast.add_identifier(&state.builder, token.span)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -544,7 +544,12 @@ parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! Par
|
|||||||
skip_continuation_newlines(state)
|
skip_continuation_newlines(state)
|
||||||
token = current(state)
|
token = current(state)
|
||||||
if (token.kind == .open_paren) {
|
if (token.kind == .open_paren) {
|
||||||
left = try parse_call(state, left)
|
left = try parse_call(state, left, state.tokens[expression_start].span.start)
|
||||||
|
left_grouped = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (token.kind == .open_bracket) {
|
||||||
|
left = try parse_index(state, left, state.tokens[expression_start].span.start)
|
||||||
left_grouped = false
|
left_grouped = false
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -603,7 +608,7 @@ validate_range_bound proc(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
parse_call proc(state @mut State, callee ast.NodeId) ast.NodeId ! ParseError {
|
parse_call proc(state @mut State, callee ast.NodeId, start u32) ast.NodeId ! ParseError {
|
||||||
_ = try consume(state, .open_paren)
|
_ = try consume(state, .open_paren)
|
||||||
state.paren_depth += 1
|
state.paren_depth += 1
|
||||||
defer state.paren_depth -= 1
|
defer state.paren_depth -= 1
|
||||||
@@ -622,7 +627,7 @@ parse_call proc(state @mut State, callee ast.NodeId) ast.NodeId ! ParseError {
|
|||||||
return try ast.add_call(
|
return try ast.add_call(
|
||||||
&state.builder,
|
&state.builder,
|
||||||
ast.Span{
|
ast.Span{
|
||||||
start = ast.builder_span(&state.builder, callee).start,
|
start = start,
|
||||||
end = close.span.end,
|
end = close.span.end,
|
||||||
},
|
},
|
||||||
callee,
|
callee,
|
||||||
@@ -630,10 +635,108 @@ parse_call proc(state @mut State, callee ast.NodeId) ast.NodeId ! ParseError {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
parse_index proc(state @mut State, base ast.NodeId, start u32) ast.NodeId ! ParseError {
|
||||||
|
_ = try consume(state, .open_bracket)
|
||||||
|
index :: try parse_expr(state)
|
||||||
|
if !next_is(state, .close_bracket) {
|
||||||
|
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, current(state).span)
|
||||||
|
return try ast.add_invalid_expr(
|
||||||
|
&state.builder,
|
||||||
|
ast.Span{
|
||||||
|
start = start,
|
||||||
|
end = ast.builder_span(&state.builder, index).end,
|
||||||
|
},
|
||||||
|
diagnostic,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
close :: try consume(state, .close_bracket)
|
||||||
|
return try ast.add_index(
|
||||||
|
&state.builder,
|
||||||
|
ast.Span{ start = start, end = close.span.end },
|
||||||
|
base, index,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
array_starts_type proc(state @State) bool {
|
||||||
|
depth usize := 0
|
||||||
|
for (state.next_token)..(state.tokens.len) |i| {
|
||||||
|
kind :: state.tokens[i].kind
|
||||||
|
if (kind == .open_bracket) depth += 1
|
||||||
|
if (kind == .close_bracket) {
|
||||||
|
depth -= 1
|
||||||
|
if (depth == 0) {
|
||||||
|
if (i + 1 >= state.tokens.len) return false
|
||||||
|
next :: state.tokens[i + 1].kind
|
||||||
|
return next == .ident or next == .mut or next == .open_bracket
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (kind == .open_curly or kind == .close_curly or kind == .eof) return false
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
parse_array_literal proc(state @mut State) ast.NodeId ! ParseError {
|
||||||
|
open :: try consume(state, .open_bracket)
|
||||||
|
elements arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
||||||
|
defer arraylist.deinit(&elements)
|
||||||
|
after_open :: state.next_token
|
||||||
|
skip_newlines(state)
|
||||||
|
if named_binding_starts(state) {
|
||||||
|
state.next_token = after_open
|
||||||
|
return try invalid_array_literal(state, open.span.start, open.span.end)
|
||||||
|
}
|
||||||
|
while !next_is(state, .close_bracket) {
|
||||||
|
element :: try parse_expr(state)
|
||||||
|
try arraylist.append(&elements, element)
|
||||||
|
after_element :: state.next_token
|
||||||
|
skip_newlines(state)
|
||||||
|
if next_is(state, .close_bracket) break
|
||||||
|
if !next_is(state, .comma) {
|
||||||
|
state.next_token = after_element
|
||||||
|
return try invalid_array_literal(state, open.span.start, ast.builder_span(&state.builder, element).end)
|
||||||
|
}
|
||||||
|
state.next_token += 1
|
||||||
|
after_comma :: state.next_token
|
||||||
|
skip_newlines(state)
|
||||||
|
if named_binding_starts(state) {
|
||||||
|
state.next_token = after_comma
|
||||||
|
return try invalid_array_literal(state, open.span.start, ast.builder_span(&state.builder, element).end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close :: try consume(state, .close_bracket)
|
||||||
|
return try ast.add_array_literal(
|
||||||
|
&state.builder, ast.Span{ start = open.span.start, end = close.span.end }, elements.items,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
invalid_array_literal proc(state @mut State, start, end u32) ast.NodeId ! ParseError {
|
||||||
|
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, current(state).span)
|
||||||
|
return try ast.add_invalid_expr(&state.builder, ast.Span{ start = start, end = end }, diagnostic)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
named_binding_starts proc(state @State) bool {
|
||||||
|
return (
|
||||||
|
next_is(state, .ident)
|
||||||
|
and (
|
||||||
|
next_n_is(state, 1, .double_colon) or next_n_is(state, 1, .colon_equal)
|
||||||
|
or next_n_is(state, 1, .ident) or next_n_is(state, 1, .proc)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
||||||
token :: current(state)
|
token :: current(state)
|
||||||
match token.kind {
|
match token.kind {
|
||||||
|
.open_bracket: {
|
||||||
|
if array_starts_type(state) return try parse_type(state)
|
||||||
|
return try parse_array_literal(state)
|
||||||
|
}
|
||||||
.open_paren: {
|
.open_paren: {
|
||||||
state.next_token += 1
|
state.next_token += 1
|
||||||
state.paren_depth += 1
|
state.paren_depth += 1
|
||||||
@@ -709,6 +812,7 @@ is_call_boundary proc(kind lexer.TokenKind) bool {
|
|||||||
is_expression_recovery_boundary proc(kind lexer.TokenKind) bool {
|
is_expression_recovery_boundary proc(kind lexer.TokenKind) bool {
|
||||||
return (
|
return (
|
||||||
kind == .comma
|
kind == .comma
|
||||||
|
or kind == .close_bracket
|
||||||
or kind == .and
|
or kind == .and
|
||||||
or kind == .or
|
or kind == .or
|
||||||
or kind == .pipe
|
or kind == .pipe
|
||||||
@@ -725,14 +829,8 @@ skip_operand_newlines proc(state @mut State) void {
|
|||||||
if (kind != .close_paren and is_expression_boundary(kind)) state.next_token = start
|
if (kind != .close_paren and is_expression_boundary(kind)) state.next_token = start
|
||||||
# a missing operand must not consume the declaration or assignment on the next line.
|
# a missing operand must not consume the declaration or assignment on the next line.
|
||||||
if (
|
if (
|
||||||
next_is(state, .ident)
|
named_binding_starts(state)
|
||||||
and (
|
or (next_is(state, .ident) and next_n_is(state, 1, .equal))
|
||||||
next_n_is(state, 1, .double_colon)
|
|
||||||
or next_n_is(state, 1, .colon_equal)
|
|
||||||
or next_n_is(state, 1, .equal)
|
|
||||||
or next_n_is(state, 1, .ident)
|
|
||||||
or next_n_is(state, 1, .proc)
|
|
||||||
)
|
|
||||||
) state.next_token = start
|
) state.next_token = start
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -744,6 +842,8 @@ skip_continuation_newlines proc(state @mut State) void {
|
|||||||
kind :: current(state).kind
|
kind :: current(state).kind
|
||||||
if (
|
if (
|
||||||
kind != .close_paren and kind != .comma and kind != .open_paren
|
kind != .close_paren and kind != .comma and kind != .open_paren
|
||||||
|
and kind != .close_bracket and kind != .open_bracket
|
||||||
|
and kind != .range and kind != .range_inclusive
|
||||||
and infix_binding_power(kind) == null
|
and infix_binding_power(kind) == null
|
||||||
) state.next_token = start
|
) state.next_token = start
|
||||||
}
|
}
|
||||||
@@ -753,8 +853,8 @@ recover_decl proc(
|
|||||||
state @mut State,
|
state @mut State,
|
||||||
start usize,
|
start usize,
|
||||||
) ast.NodeId ! (mem.AllocError | diagnostics.CapacityError) {
|
) ast.NodeId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, current(state).span)
|
diagnostic :: try unexpected_diagnostic(state)
|
||||||
span :: synchronize(state, start, false)
|
span :: synchronize(state, start, false, false, true)
|
||||||
return try ast.add_invalid_decl(&state.builder, span, diagnostic)
|
return try ast.add_invalid_decl(&state.builder, span, diagnostic)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -763,31 +863,91 @@ recover_statement proc(
|
|||||||
state @mut State,
|
state @mut State,
|
||||||
start usize,
|
start usize,
|
||||||
) ast.NodeId ! (mem.AllocError | diagnostics.CapacityError) {
|
) ast.NodeId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, current(state).span)
|
diagnostic :: try unexpected_diagnostic(state)
|
||||||
span :: synchronize(state, start, true)
|
span :: synchronize(state, start, true, false, true)
|
||||||
return try ast.add_invalid_stmt(&state.builder, span, diagnostic)
|
return try ast.add_invalid_stmt(&state.builder, span, diagnostic)
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
synchronize proc(state @mut State, start usize, stop_at_curly bool) ast.Span {
|
synchronize proc(
|
||||||
|
state @mut State, start usize, stop_at_curly, allow_else, consume_newline bool,
|
||||||
|
) ast.Span {
|
||||||
first :: state.tokens[start].span
|
first :: state.tokens[start].span
|
||||||
end := first.end
|
end := first.end
|
||||||
depth usize := 0
|
curly usize := 0
|
||||||
|
paren usize := 0
|
||||||
|
square usize := 0
|
||||||
|
for (start..state.next_token) |i| {
|
||||||
|
kind :: state.tokens[i].kind
|
||||||
|
if (kind == .open_paren) paren += 1
|
||||||
|
if (kind == .close_paren and paren > 0) paren -= 1
|
||||||
|
if (kind == .open_bracket) square += 1
|
||||||
|
if (kind == .close_bracket and square > 0) square -= 1
|
||||||
|
}
|
||||||
while !next_is(state, .eof) {
|
while !next_is(state, .eof) {
|
||||||
if (depth == 0 and next_is(state, .newline)) break
|
if (curly == 0) {
|
||||||
if (stop_at_curly and depth == 0 and next_is(state, .close_curly)) break
|
if (stop_at_curly and next_is(state, .close_curly)) break
|
||||||
if (stop_at_curly and next_is(state, .open_curly)) depth += 1
|
if (allow_else and next_is(state, .else)) break
|
||||||
if (stop_at_curly and next_is(state, .close_curly) and depth > 0) depth -= 1
|
if (
|
||||||
|
next_is(state, .newline)
|
||||||
|
and !recovery_closes_delimiters(state, paren, square)
|
||||||
|
) break
|
||||||
|
}
|
||||||
|
kind :: current(state).kind
|
||||||
|
if (kind == .open_curly) curly += 1
|
||||||
|
if (kind == .close_curly and curly > 0) curly -= 1
|
||||||
|
if (kind == .open_paren) paren += 1
|
||||||
|
if (kind == .close_paren and paren > 0) paren -= 1
|
||||||
|
if (kind == .open_bracket) square += 1
|
||||||
|
if (kind == .close_bracket and square > 0) square -= 1
|
||||||
end = current(state).span.end
|
end = current(state).span.end
|
||||||
state.next_token += 1
|
state.next_token += 1
|
||||||
}
|
}
|
||||||
if next_is(state, .newline) {
|
if (consume_newline and next_is(state, .newline)) {
|
||||||
end = current(state).span.end
|
end = current(state).span.end
|
||||||
state.next_token += 1
|
state.next_token += 1
|
||||||
}
|
}
|
||||||
return ast.Span{ start = first.start, end = end }
|
return ast.Span{ start = first.start, end = end }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
recovery_closes_delimiters proc(state @State, paren_depth, square_depth usize) bool {
|
||||||
|
if (paren_depth == 0 and square_depth == 0) return false
|
||||||
|
paren := paren_depth
|
||||||
|
square := square_depth
|
||||||
|
for (state.next_token)..(state.tokens.len) |i| {
|
||||||
|
kind :: state.tokens[i].kind
|
||||||
|
match kind {
|
||||||
|
.open_curly, .close_curly, .eof, .if, .else, .while, .for,
|
||||||
|
.return, .break, .continue: return false
|
||||||
|
.ident: {
|
||||||
|
if (i + 1 < state.tokens.len) {
|
||||||
|
next :: state.tokens[i + 1].kind
|
||||||
|
if (next == .double_colon or next == .colon_equal or next == .ident or next == .proc) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else: {}
|
||||||
|
}
|
||||||
|
if (kind == .open_paren) paren += 1
|
||||||
|
if (kind == .close_paren and paren > 0) paren -= 1
|
||||||
|
if (kind == .open_bracket) square += 1
|
||||||
|
if (kind == .close_bracket and square > 0) square -= 1
|
||||||
|
if (paren == 0 and square == 0) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
unexpected_diagnostic proc(state @mut State) diagnostics.DiagnosticId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
|
token :: current(state)
|
||||||
|
if (token.kind == .invalid and token.data.diagnostic != diagnostics.no_diagnostic) {
|
||||||
|
return token.data.diagnostic
|
||||||
|
}
|
||||||
|
return try diagnostics.add(state.diagnostics, .unexpected_token, token.span)
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
skip_newlines proc(state @mut State) void {
|
skip_newlines proc(state @mut State) void {
|
||||||
while next_is(state, .newline) state.next_token += 1
|
while next_is(state, .newline) state.next_token += 1
|
||||||
|
|||||||
@@ -1200,3 +1200,201 @@ range_loop_preserves_nearest_else_and_canonical_body test {
|
|||||||
try testing.expect_equal(ast.ExprKind.call, ast.expr_kind(&tree, call_loop.iterable))
|
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))
|
try testing.expect_equal(1, ast.block_statement_count(&tree, call_loop.body))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parses_recursive_array_annotations_literals_and_grouped_parameters test {
|
||||||
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.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, &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(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 {
|
||||||
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.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, &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 {
|
||||||
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.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, &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 {
|
||||||
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.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, &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 {
|
||||||
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.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, &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))
|
||||||
|
}
|
||||||
|
|||||||
+569
-66
@@ -15,21 +15,35 @@ TypeKind :: enum {
|
|||||||
void,
|
void,
|
||||||
function,
|
function,
|
||||||
range,
|
range,
|
||||||
|
array,
|
||||||
|
usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayId :: distinct u32
|
||||||
|
no_array :: maxval!(ArrayId)
|
||||||
|
|
||||||
|
ArrayType :: struct {
|
||||||
|
element Type
|
||||||
|
len usize
|
||||||
|
mutable bool
|
||||||
}
|
}
|
||||||
|
|
||||||
Type :: struct {
|
Type :: struct {
|
||||||
kind TypeKind = .none
|
kind TypeKind = .none
|
||||||
function ast.NodeId = ast.NodeId(maxval!(u32))
|
function ast.NodeId = ast.NodeId(maxval!(u32))
|
||||||
diagnostic diagnostics.DiagnosticId = diagnostics.no_diagnostic
|
diagnostic diagnostics.DiagnosticId = diagnostics.no_diagnostic
|
||||||
|
array_type ArrayId = no_array
|
||||||
|
range_element TypeKind = .none
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
Storage :: struct {
|
Storage :: struct {
|
||||||
types []mut Type
|
types []mut Type
|
||||||
|
arrays arraylist.ArrayList(ArrayType)
|
||||||
allocator mem.Allocator
|
allocator mem.Allocator
|
||||||
}
|
}
|
||||||
|
|
||||||
#! owns node-indexed types and retains its allocator; release with deinit.
|
#! owns node-indexed types and interned array descriptors; release with deinit.
|
||||||
#! function IDs refer to the caller-owned AST, not checker-owned signatures.
|
#! function IDs refer to the caller-owned AST, not checker-owned signatures.
|
||||||
Result :: struct { storage Storage }
|
Result :: struct { storage Storage }
|
||||||
|
|
||||||
@@ -68,6 +82,13 @@ Cell :: struct {
|
|||||||
local bool = false
|
local bool = false
|
||||||
assignment_value bool = false
|
assignment_value bool = false
|
||||||
for_iterable bool = false
|
for_iterable bool = false
|
||||||
|
expected Type = Type{}
|
||||||
|
visiting bool = false
|
||||||
|
constant_visiting bool = false
|
||||||
|
count_declaration bool = false
|
||||||
|
literal ast.NodeId = ast.NodeId(maxval!(u32))
|
||||||
|
producer ast.NodeId = ast.NodeId(maxval!(u32))
|
||||||
|
capture ast.NodeId = ast.NodeId(maxval!(u32))
|
||||||
first_edge usize = no_index
|
first_edge usize = no_index
|
||||||
return_annotation ast.NodeId = ast.NodeId(maxval!(u32))
|
return_annotation ast.NodeId = ast.NodeId(maxval!(u32))
|
||||||
}
|
}
|
||||||
@@ -86,12 +107,13 @@ State :: struct {
|
|||||||
types []mut Type
|
types []mut Type
|
||||||
cells []mut Cell
|
cells []mut Cell
|
||||||
edges arraylist.ArrayList(Edge)
|
edges arraylist.ArrayList(Edge)
|
||||||
|
arrays arraylist.ArrayList(ArrayType)
|
||||||
collecting_assignment bool = false
|
collecting_assignment bool = false
|
||||||
has_assignments bool = false
|
has_assignments bool = false
|
||||||
}
|
}
|
||||||
|
|
||||||
#! borrows the AST, resolution, and diagnostic store only during checking.
|
#! borrows the AST, resolution, and diagnostic store only during checking.
|
||||||
#! temporary constraints are freed on every exit; Result owns only its type table.
|
#! temporary constraints are freed on every exit; Result owns types and array descriptors.
|
||||||
check proc(
|
check proc(
|
||||||
allocator mem.Allocator,
|
allocator mem.Allocator,
|
||||||
tree @ast.Tree,
|
tree @ast.Tree,
|
||||||
@@ -115,9 +137,15 @@ check proc(
|
|||||||
types = types,
|
types = types,
|
||||||
cells = cells,
|
cells = cells,
|
||||||
edges = arraylist.init(allocator),
|
edges = arraylist.init(allocator),
|
||||||
|
arrays = arraylist.init(allocator),
|
||||||
}
|
}
|
||||||
defer arraylist.deinit(&state.edges)
|
defer arraylist.deinit(&state.edges)
|
||||||
|
errdefer arraylist.deinit(&state.arrays)
|
||||||
|
|
||||||
|
for 0..ast.package_decl_count(tree) |i| {
|
||||||
|
id :: ast.package_decl(tree, i)
|
||||||
|
if (ast.decl_kind(tree, id) == .statement) cells[usize(id)].count_declaration = true
|
||||||
|
}
|
||||||
for (0..ast.package_decl_count(tree)) |i| {
|
for (0..ast.package_decl_count(tree)) |i| {
|
||||||
id :: ast.package_decl(tree, i)
|
id :: ast.package_decl(tree, i)
|
||||||
match ast.decl_kind(tree, id) {
|
match ast.decl_kind(tree, id) {
|
||||||
@@ -126,19 +154,21 @@ check proc(
|
|||||||
.invalid: collect_invalid(&state, id)
|
.invalid: collect_invalid(&state, id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (0..count) |i| {
|
# Preserve binding errors before RHS errors propagate. Indexed writability
|
||||||
|
# waits for shapes; neither check poisons the target's type.
|
||||||
|
for 0..count |i| {
|
||||||
if (cells[i].role != .assignment_stmt) continue
|
if (cells[i].role != .assignment_stmt) continue
|
||||||
target :: ast.assignment_stmt(tree, ast.NodeId(i)).target
|
target :: ast.assignment_stmt(tree, ast.NodeId(i)).target
|
||||||
|
if (ast.expr_kind(tree, target) != .identifier) continue
|
||||||
binding :: sema.binding(resolution, target)
|
binding :: sema.binding(resolution, target)
|
||||||
if (binding.kind == .invalid) {
|
if (binding.kind == .invalid) {
|
||||||
cells[i].type = invalid_type(binding.data.diagnostic)
|
cells[i].type = invalid_type(binding.data.diagnostic)
|
||||||
continue
|
} else if (binding.kind == .declaration and !mutable_target(&state, target)) {
|
||||||
}
|
|
||||||
if (binding.kind != .declaration or mutable_target(&state, target)) continue
|
|
||||||
cells[i].type = invalid_type(try diagnostics.add(
|
cells[i].type = invalid_type(try diagnostics.add(
|
||||||
diagnostic_store, .invalid_assignment_target, ast.node_span(tree, target),
|
diagnostic_store, .invalid_assignment_target, ast.node_span(tree, target),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# aliases share a type variable; explicit annotations and parameters are boundaries.
|
# aliases share a type variable; explicit annotations and parameters are boundaries.
|
||||||
for (0..count) |i| {
|
for (0..count) |i| {
|
||||||
@@ -175,13 +205,12 @@ check proc(
|
|||||||
if (binding.kind == .declaration) cells[i].type = types[usize(binding.data.declaration)]
|
if (binding.kind == .declaration) cells[i].type = types[usize(binding.data.declaration)]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
# function aliases are known before calls; scalar return annotations
|
# Calls use the signature result before contextual literal inference.
|
||||||
# mean a call cannot introduce a function type.
|
|
||||||
for (0..count) |i| {
|
for (0..count) |i| {
|
||||||
id :: ast.NodeId(i)
|
id :: ast.NodeId(i)
|
||||||
if (cells[i].role != .expression or cells[i].assignment_value != assignment_values or ast.expr_kind(tree, id) != .call) continue
|
if (cells[i].role != .expression or cells[i].assignment_value != assignment_values or ast.expr_kind(tree, id) != .call) continue
|
||||||
callee :: ast.call_expr(tree, id).callee
|
callee :: ast.call_expr(tree, id).callee
|
||||||
signature :: group_type(&state, callee)
|
signature :: try infer_shape(&state, callee, false)
|
||||||
if (signature.function != ast.NodeId(maxval!(u32))) {
|
if (signature.function != ast.NodeId(maxval!(u32))) {
|
||||||
result :: ast.function_decl(tree, signature.function).return_type
|
result :: ast.function_decl(tree, signature.function).return_type
|
||||||
seed(&state, id, types[usize(result)])
|
seed(&state, id, types[usize(result)])
|
||||||
@@ -209,6 +238,9 @@ check proc(
|
|||||||
types[i] = cells[root(&state, i)].type
|
types[i] = cells[root(&state, i)].type
|
||||||
}
|
}
|
||||||
# reject nonnumeric operations before connecting otherwise valid inputs.
|
# reject nonnumeric operations before connecting otherwise valid inputs.
|
||||||
|
try apply_contexts(&state, assignment_values, true)
|
||||||
|
try prepare_shapes(&state, assignment_values, false)
|
||||||
|
try prepare_ranges(&state, assignment_values, false)
|
||||||
for (0..count) |i| {
|
for (0..count) |i| {
|
||||||
id :: ast.NodeId(i)
|
id :: ast.NodeId(i)
|
||||||
if (cells[i].role != .expression or cells[i].assignment_value != assignment_values) continue
|
if (cells[i].role != .expression or cells[i].assignment_value != assignment_values) continue
|
||||||
@@ -253,42 +285,13 @@ check proc(
|
|||||||
else: {}
|
else: {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (0..count) |i| {
|
try apply_contexts(&state, assignment_values, false)
|
||||||
|
try prepare_shapes(&state, assignment_values, false)
|
||||||
|
try prepare_ranges(&state, assignment_values, true)
|
||||||
|
for 0..count |i| {
|
||||||
id :: ast.NodeId(i)
|
id :: ast.NodeId(i)
|
||||||
if (!assignment_values and cells[i].role == .declaration) {
|
if (cells[i].role != .expression or cells[i].assignment_value != assignment_values or ast.expr_kind(tree, id) != .index) continue
|
||||||
declaration :: ast.statement_decl(tree, id)
|
try demand(&state, ast.index_expr(tree, id).index, Type{ kind = .usize }, .invalid_index_type)
|
||||||
if (declaration.type) |annotation| {
|
|
||||||
try demand(&state, declaration.initializer, types[usize(annotation)], .type_mismatch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!assignment_values and cells[i].role == .return_stmt) {
|
|
||||||
if (ast.return_stmt(tree, id).value) |value| {
|
|
||||||
try demand(&state, value, types[usize(cells[i].return_annotation)], .return_type_mismatch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (assignment_values and cells[i].role == .assignment_stmt) {
|
|
||||||
assignment :: ast.assignment_stmt(tree, id)
|
|
||||||
if (mutable_target(&state, assignment.target)) {
|
|
||||||
try demand(&state, assignment.value, types[usize(assignment.target)], .type_mismatch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (cells[i].role != .expression or cells[i].assignment_value != assignment_values or ast.expr_kind(tree, id) != .call) continue
|
|
||||||
signature :: group_type(&state, ast.call_expr(tree, id).callee)
|
|
||||||
if (signature.function == ast.NodeId(maxval!(u32))) continue
|
|
||||||
parameter_count :: ast.function_param_count(tree, signature.function)
|
|
||||||
for (0..ast.call_arg_count(tree, id)) |j| {
|
|
||||||
if (j >= parameter_count) continue
|
|
||||||
parameter :: ast.function_param(tree, signature.function, j)
|
|
||||||
try demand(&state, ast.call_arg(tree, id, j), cells[usize(parameter)].type, .type_mismatch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# integer range demands cannot poison an endpoint's existing float aliases.
|
|
||||||
for (0..count) |i| {
|
|
||||||
id :: ast.NodeId(i)
|
|
||||||
if (cells[i].role != .expression or cells[i].assignment_value != assignment_values or ast.expr_kind(tree, id) != .range) continue
|
|
||||||
expression :: ast.range_expr(tree, id)
|
|
||||||
try range_bound_context(&state, expression.start)
|
|
||||||
try range_bound_context(&state, expression.end)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# defaults follow ordinary program demands, then isolated assignment demands.
|
# defaults follow ordinary program demands, then isolated assignment demands.
|
||||||
@@ -296,10 +299,12 @@ check proc(
|
|||||||
if (root(&state, i) != i or cells[i].assignment_value != assignment_values) continue
|
if (root(&state, i) != i or cells[i].assignment_value != assignment_values) continue
|
||||||
if (cells[i].type.kind == .invalid) continue
|
if (cells[i].type.kind == .invalid) continue
|
||||||
if (cells[i].numeric and cells[i].anchored) {
|
if (cells[i].numeric and cells[i].anchored) {
|
||||||
kind TypeKind :: if (cells[i].demand == .float) .float else .int
|
kind TypeKind :: if (cells[i].demand != .none) cells[i].demand else .int
|
||||||
cells[i].type = Type{ kind = kind }
|
cells[i].type = Type{ kind = kind }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
try prepare_shapes(&state, assignment_values, true)
|
||||||
|
if (!assignment_values) try specialize_annotations(&state)
|
||||||
for (0..count) |i| {
|
for (0..count) |i| {
|
||||||
id :: ast.NodeId(i)
|
id :: ast.NodeId(i)
|
||||||
if (cells[i].role != .expression or cells[i].assignment_value != assignment_values or ast.expr_kind(tree, id) != .call) continue
|
if (cells[i].role != .expression or cells[i].assignment_value != assignment_values or ast.expr_kind(tree, id) != .call) continue
|
||||||
@@ -358,7 +363,7 @@ check proc(
|
|||||||
types[usize(function.body)] = invalid_type(diagnostic)
|
types[usize(function.body)] = invalid_type(diagnostic)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Result{ storage = Storage{ types = types, allocator = allocator } }
|
return Result{ storage = Storage{ types = types, arrays = state.arrays, allocator = allocator } }
|
||||||
}
|
}
|
||||||
|
|
||||||
#! returns a type without retaining a reference to checker temporaries.
|
#! returns a type without retaining a reference to checker temporaries.
|
||||||
@@ -367,9 +372,19 @@ type_of proc(result @Result, id ast.NodeId) Type {
|
|||||||
return result.storage.types[usize(id)]
|
return result.storage.types[usize(id)]
|
||||||
}
|
}
|
||||||
|
|
||||||
#! releases type storage using the allocator retained by check.
|
array_type_count proc(result @Result) usize {
|
||||||
|
return result.storage.arrays.items.len
|
||||||
|
}
|
||||||
|
|
||||||
|
array_type proc(result @Result, id ArrayId) ArrayType {
|
||||||
|
debug.assert(usize(id) < result.storage.arrays.items.len)
|
||||||
|
return result.storage.arrays.items[usize(id)]
|
||||||
|
}
|
||||||
|
|
||||||
|
#! releases type and descriptor storage using the allocator retained by check.
|
||||||
deinit proc(result @mut Result) void {
|
deinit proc(result @mut Result) void {
|
||||||
mem.free(result.storage.allocator, result.storage.types)
|
mem.free(result.storage.allocator, result.storage.types)
|
||||||
|
arraylist.deinit(&result.storage.arrays)
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
@@ -456,6 +471,8 @@ join proc(state @mut State, left, right ast.NodeId) void {
|
|||||||
state.cells[a].numeric = state.cells[a].numeric or state.cells[b].numeric
|
state.cells[a].numeric = state.cells[a].numeric or state.cells[b].numeric
|
||||||
state.cells[a].anchored = state.cells[a].anchored or state.cells[b].anchored
|
state.cells[a].anchored = state.cells[a].anchored or state.cells[b].anchored
|
||||||
if (state.cells[a].demand == .none) state.cells[a].demand = state.cells[b].demand
|
if (state.cells[a].demand == .none) state.cells[a].demand = state.cells[b].demand
|
||||||
|
if (state.cells[a].literal == ast.NodeId(maxval!(u32))) state.cells[a].literal = state.cells[b].literal
|
||||||
|
if (state.cells[a].producer == ast.NodeId(maxval!(u32))) state.cells[a].producer = state.cells[b].producer
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
@@ -475,14 +492,47 @@ collect_invalid proc(state @mut State, id ast.NodeId) void {
|
|||||||
check_annotation proc(state @mut State, id ast.NodeId, allow_void bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
|
check_annotation proc(state @mut State, id ast.NodeId, allow_void bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
if (state.cells[usize(id)].role == .annotation) return state.types[usize(id)]
|
if (state.cells[usize(id)].role == .annotation) return state.types[usize(id)]
|
||||||
state.cells[usize(id)].role = .annotation
|
state.cells[usize(id)].role = .annotation
|
||||||
|
if (ast.expr_kind(state.tree, id) == .array_type) {
|
||||||
|
view :: ast.array_type(state.tree, id)
|
||||||
|
element := try check_annotation(state, view.element, true)
|
||||||
|
element_binding :: sema.binding(state.resolution, view.element)
|
||||||
|
if ((element_binding.kind == .builtin
|
||||||
|
and (element_binding.data.builtin == .int or element_binding.data.builtin == .float))
|
||||||
|
or element.kind == .void or element.kind == .range) {
|
||||||
|
element = invalid_type(try diagnostics.add(state.diagnostics, .invalid_array_element_type, ast.node_span(state.tree, view.element)))
|
||||||
|
state.types[usize(view.element)] = element
|
||||||
|
state.cells[usize(view.element)].type = element
|
||||||
|
}
|
||||||
|
len := no_index
|
||||||
|
type := element
|
||||||
|
if (view.count) |count| {
|
||||||
|
try collect_expression(state, count)
|
||||||
|
if (constant_integer(state, count)) |value| {
|
||||||
|
if (!value.negative and value.magnitude <= u64(maxval!(usize))) {
|
||||||
|
len = usize(value.magnitude)
|
||||||
|
if (len == no_index) type = invalid_type(try diagnostics.add(state.diagnostics, .array_size_overflow, ast.node_span(state.tree, count)))
|
||||||
|
} else {
|
||||||
|
type = invalid_type(try diagnostics.add(state.diagnostics, .invalid_array_length, ast.node_span(state.tree, count)))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
actual :: group_type(state, count)
|
||||||
|
type = if (actual.kind == .invalid) actual else invalid_type(try diagnostics.add(state.diagnostics, .invalid_array_length, ast.node_span(state.tree, count)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (type.kind != .invalid) type = try intern_array(state, ArrayType{ element = element, len = len, mutable = view.mutable })
|
||||||
|
state.types[usize(id)] = type
|
||||||
|
state.cells[usize(id)].type = type
|
||||||
|
return type
|
||||||
|
}
|
||||||
binding :: sema.binding(state.resolution, id)
|
binding :: sema.binding(state.resolution, id)
|
||||||
type := Type{}
|
type := Type{}
|
||||||
match binding.kind {
|
match binding.kind {
|
||||||
.invalid: type = invalid_type(binding.data.diagnostic)
|
.invalid: type = invalid_type(binding.data.diagnostic)
|
||||||
.builtin: {
|
.builtin: {
|
||||||
type.kind = match binding.data.builtin {
|
type.kind = match binding.data.builtin {
|
||||||
.int: .int
|
.int, .i64: .int
|
||||||
.float: .float
|
.float, .f64: .float
|
||||||
|
.usize: .usize
|
||||||
.bool: .bool
|
.bool: .bool
|
||||||
.string: .string
|
.string: .string
|
||||||
.void: .void
|
.void: .void
|
||||||
@@ -637,7 +687,10 @@ collect_block proc(state @mut State, id, return_annotation ast.NodeId, in_loop b
|
|||||||
state.cells[usize(statement)].type = invalid_type(diagnostic)
|
state.cells[usize(statement)].type = invalid_type(diagnostic)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (loop.capture) |capture| try collect_capture(state, capture, loop.body)
|
if (loop.capture) |capture| {
|
||||||
|
try collect_capture(state, capture, loop.body)
|
||||||
|
state.cells[usize(loop.iterable)].capture = capture
|
||||||
|
}
|
||||||
if (loop.index) |index| try collect_capture(state, index, loop.body)
|
if (loop.index) |index| try collect_capture(state, index, loop.body)
|
||||||
try collect_block(state, loop.body, return_annotation, true)
|
try collect_block(state, loop.body, return_annotation, true)
|
||||||
try depend(state, loop.body, id)
|
try depend(state, loop.body, id)
|
||||||
@@ -686,6 +739,7 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
|
|||||||
}
|
}
|
||||||
.unary: {
|
.unary: {
|
||||||
expression :: ast.unary_expr(tree, id)
|
expression :: ast.unary_expr(tree, id)
|
||||||
|
if (expression.operator == .negate) state.cells[usize(id)].producer = id
|
||||||
if (expression.operator == .logical_not) {
|
if (expression.operator == .logical_not) {
|
||||||
state.cells[usize(id)].type = Type{ kind = .bool }
|
state.cells[usize(id)].type = Type{ kind = .bool }
|
||||||
} else {
|
} else {
|
||||||
@@ -697,6 +751,7 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
|
|||||||
}
|
}
|
||||||
.binary: {
|
.binary: {
|
||||||
expression :: ast.binary_expr(tree, id)
|
expression :: ast.binary_expr(tree, id)
|
||||||
|
if (!comparison(expression.operator) and !logical(expression.operator)) state.cells[usize(id)].producer = id
|
||||||
if (comparison(expression.operator) or logical(expression.operator)) {
|
if (comparison(expression.operator) or logical(expression.operator)) {
|
||||||
state.cells[usize(id)].type = Type{ kind = .bool }
|
state.cells[usize(id)].type = Type{ kind = .bool }
|
||||||
} else {
|
} else {
|
||||||
@@ -731,13 +786,31 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
|
|||||||
try depend(state, argument, id)
|
try depend(state, argument, id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.array_type: {
|
||||||
|
state.cells[usize(id)].type = invalid_type(try diagnostics.add(state.diagnostics, .type_not_value, ast.node_span(tree, id)))
|
||||||
|
}
|
||||||
|
.array_literal: {
|
||||||
|
state.cells[usize(id)].literal = id
|
||||||
|
for 0..ast.array_element_count(tree, id) |i| {
|
||||||
|
element :: ast.array_element(tree, id, i)
|
||||||
|
try collect_expression(state, element)
|
||||||
|
try depend(state, element, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.index: {
|
||||||
|
expression :: ast.index_expr(tree, id)
|
||||||
|
try collect_expression(state, expression.base)
|
||||||
|
try collect_expression(state, expression.index)
|
||||||
|
try depend(state, expression.base, id)
|
||||||
|
try depend(state, expression.index, id)
|
||||||
|
}
|
||||||
.invalid: collect_invalid(state, id)
|
.invalid: collect_invalid(state, id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
numeric_kind proc(kind TypeKind) bool {
|
numeric_kind proc(kind TypeKind) bool {
|
||||||
return kind == .int or kind == .float
|
return kind == .int or kind == .float or kind == .usize
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
@@ -819,23 +892,43 @@ numeric_constraint proc(state @mut State, expression, operand, operation ast.Nod
|
|||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Code) void ! (mem.AllocError | diagnostics.CapacityError) {
|
demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Code) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
if (!numeric_kind(expected.kind) or state.types[usize(id)].kind == .invalid) return
|
if (state.types[usize(id)].kind == .invalid) return
|
||||||
index :: root(state, usize(id))
|
index :: root(state, usize(id))
|
||||||
|
if (expected.kind == .array) {
|
||||||
|
literal :: state.cells[index].literal
|
||||||
|
if (literal == ast.NodeId(maxval!(u32))) return
|
||||||
|
previous :: state.cells[index].expected
|
||||||
|
if (previous.kind == .array and !same_type(state, previous, expected)) {
|
||||||
|
if (!has_inferred_count(state, previous) and !has_inferred_count(state, expected)) {
|
||||||
|
state.cells[index].type = invalid_type(try diagnostics.add(state.diagnostics, code, ast.node_span(state.tree, id)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.cells[index].expected = expected
|
||||||
|
descriptor :: state.arrays.items[usize(expected.array_type)]
|
||||||
|
len :: ast.array_element_count(state.tree, literal)
|
||||||
|
if (descriptor.len != no_index and descriptor.len != len) {
|
||||||
|
try fail(state, literal, .array_length_mismatch, ast.node_span(state.tree, literal))
|
||||||
|
}
|
||||||
|
for (0..len) |i| try demand(state, ast.array_element(state.tree, literal, i), descriptor.element, code)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!numeric_kind(expected.kind)) return
|
||||||
if (!state.cells[index].numeric and state.cells[index].type.kind != .none) return
|
if (!state.cells[index].numeric and state.cells[index].type.kind != .none) return
|
||||||
state.cells[index].numeric = true
|
state.cells[index].numeric = true
|
||||||
try impose(state, id, expected.kind, code, ast.node_span(state.tree, id))
|
try impose(state, id, expected.kind, code, ast.node_span(state.tree, id))
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
range_bound_context proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
range_bound_context proc(state @mut State, id ast.NodeId, family TypeKind) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
if (state.types[usize(id)].kind == .invalid) return
|
if (state.types[usize(id)].kind == .invalid) return
|
||||||
index :: root(state, usize(id))
|
index :: root(state, usize(id))
|
||||||
if (state.cells[index].type.kind == .invalid) return
|
if (state.cells[index].type.kind == .invalid) return
|
||||||
if (state.cells[index].demand == .float) {
|
if (state.cells[index].demand != .none and state.cells[index].demand != family) {
|
||||||
try fail(state, id, .invalid_range_bound, ast.node_span(state.tree, id))
|
try fail(state, id, .invalid_range_bound, ast.node_span(state.tree, id))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try demand(state, id, Type{ kind = .int }, .invalid_range_bound)
|
try demand(state, id, Type{ kind = family }, .invalid_range_bound)
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
@@ -854,6 +947,11 @@ require_bool proc(state @mut State, operand ast.NodeId) void ! (mem.AllocError |
|
|||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
mutable_target proc(state @State, id ast.NodeId) bool {
|
mutable_target proc(state @State, id ast.NodeId) bool {
|
||||||
|
if (ast.expr_kind(state.tree, id) == .index) {
|
||||||
|
view :: ast.index_expr(state.tree, id)
|
||||||
|
base :: state.types[usize(view.base)]
|
||||||
|
return base.kind == .array and state.arrays.items[usize(base.array_type)].mutable and mutable_target(state, view.base)
|
||||||
|
}
|
||||||
binding :: sema.binding(state.resolution, id)
|
binding :: sema.binding(state.resolution, id)
|
||||||
if (binding.kind != .declaration) return false
|
if (binding.kind != .declaration) return false
|
||||||
target :: binding.data.declaration
|
target :: binding.data.declaration
|
||||||
@@ -867,11 +965,11 @@ same_signature proc(state @State, left, right ast.NodeId) bool {
|
|||||||
if (ast.function_param_count(tree, left) != ast.function_param_count(tree, right)) return false
|
if (ast.function_param_count(tree, left) != ast.function_param_count(tree, right)) return false
|
||||||
left_result :: ast.function_decl(tree, left).return_type
|
left_result :: ast.function_decl(tree, left).return_type
|
||||||
right_result :: ast.function_decl(tree, right).return_type
|
right_result :: ast.function_decl(tree, right).return_type
|
||||||
if (state.types[usize(left_result)].kind != state.types[usize(right_result)].kind) return false
|
if (!same_type(state, state.types[usize(left_result)], state.types[usize(right_result)])) return false
|
||||||
for (0..ast.function_param_count(tree, left)) |i| {
|
for (0..ast.function_param_count(tree, left)) |i| {
|
||||||
left_param :: ast.function_param(tree, left, i)
|
left_param :: ast.function_param(tree, left, i)
|
||||||
right_param :: ast.function_param(tree, right, i)
|
right_param :: ast.function_param(tree, right, i)
|
||||||
if (state.types[usize(left_param)].kind != state.types[usize(right_param)].kind) return false
|
if (!same_type(state, state.types[usize(left_param)], state.types[usize(right_param)])) return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -880,9 +978,7 @@ same_signature proc(state @State, left, right ast.NodeId) bool {
|
|||||||
compatible proc(state @mut State, value ast.NodeId, expected Type) void ! (mem.AllocError | diagnostics.CapacityError) {
|
compatible proc(state @mut State, value ast.NodeId, expected Type) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
actual :: state.types[usize(value)]
|
actual :: state.types[usize(value)]
|
||||||
if (actual.kind == .invalid or expected.kind == .invalid) return
|
if (actual.kind == .invalid or expected.kind == .invalid) return
|
||||||
if (actual.kind != expected.kind) {
|
if (!same_type(state, actual, expected)) {
|
||||||
try fail(state, value, .type_mismatch, ast.node_span(state.tree, value))
|
|
||||||
} else if (actual.kind == .function and !same_signature(state, actual.function, expected.function)) {
|
|
||||||
try fail(state, value, .type_mismatch, ast.node_span(state.tree, value))
|
try fail(state, value, .type_mismatch, ast.node_span(state.tree, value))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -900,8 +996,14 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
|
|||||||
}
|
}
|
||||||
.assignment_stmt: {
|
.assignment_stmt: {
|
||||||
assignment :: ast.assignment_stmt(tree, id)
|
assignment :: ast.assignment_stmt(tree, id)
|
||||||
|
target_type :: state.types[usize(assignment.target)]
|
||||||
|
if (target_type.kind == .invalid) return
|
||||||
if (mutable_target(state, assignment.target)) {
|
if (mutable_target(state, assignment.target)) {
|
||||||
try compatible(state, assignment.value, state.types[usize(assignment.target)])
|
try compatible(state, assignment.value, target_type)
|
||||||
|
} else {
|
||||||
|
code :: if (ast.expr_kind(tree, assignment.target) == .index)
|
||||||
|
diagnostics.Code.invalid_index_assignment else diagnostics.Code.invalid_assignment_target
|
||||||
|
try fail(state, id, code, ast.node_span(tree, assignment.target))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.return_stmt: {
|
.return_stmt: {
|
||||||
@@ -912,7 +1014,7 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
|
|||||||
actual :: state.types[usize(value)]
|
actual :: state.types[usize(value)]
|
||||||
if (actual.kind == .invalid) {
|
if (actual.kind == .invalid) {
|
||||||
state.types[usize(id)] = actual
|
state.types[usize(id)] = actual
|
||||||
} else if (actual.kind != expected.kind) {
|
} else if (!same_type(state, actual, expected)) {
|
||||||
try fail(state, value, .return_type_mismatch, ast.node_span(tree, value))
|
try fail(state, value, .return_type_mismatch, ast.node_span(tree, value))
|
||||||
state.types[usize(id)] = state.types[usize(value)]
|
state.types[usize(id)] = state.types[usize(value)]
|
||||||
}
|
}
|
||||||
@@ -929,7 +1031,7 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
operand :: state.types[usize(expression.operand)]
|
operand :: state.types[usize(expression.operand)]
|
||||||
if (operand.kind != .invalid and !numeric_kind(operand.kind)) {
|
if (operand.kind != .invalid and (!numeric_kind(operand.kind) or operand.kind == .usize)) {
|
||||||
try fail(state, id, .invalid_operand, ast.node_span(tree, id))
|
try fail(state, id, .invalid_operand, ast.node_span(tree, id))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -947,7 +1049,7 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
|
|||||||
and left.kind == .bool and right.kind == .bool) return
|
and left.kind == .bool and right.kind == .bool) return
|
||||||
if (!numeric_kind(left.kind) or !numeric_kind(right.kind) or left.kind != right.kind) {
|
if (!numeric_kind(left.kind) or !numeric_kind(right.kind) or left.kind != right.kind) {
|
||||||
try fail(state, id, .invalid_operand, expression.span)
|
try fail(state, id, .invalid_operand, expression.span)
|
||||||
} else if (expression.operator == .divide and left.kind == .int) {
|
} else if (expression.operator == .divide and left.kind != .float) {
|
||||||
try fail(state, id, .integer_division, expression.span)
|
try fail(state, id, .integer_division, expression.span)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -955,13 +1057,39 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
|
|||||||
expression :: ast.range_expr(tree, id)
|
expression :: ast.range_expr(tree, id)
|
||||||
start_type :: state.types[usize(expression.start)]
|
start_type :: state.types[usize(expression.start)]
|
||||||
end_type :: state.types[usize(expression.end)]
|
end_type :: state.types[usize(expression.end)]
|
||||||
if (start_type.kind != .invalid and start_type.kind != .int) {
|
family TypeKind :: if (start_type.kind == .usize or end_type.kind == .usize) .usize else .int
|
||||||
|
if (start_type.kind != .invalid and start_type.kind != family) {
|
||||||
try fail(state, expression.start, .invalid_range_bound, ast.node_span(tree, expression.start))
|
try fail(state, expression.start, .invalid_range_bound, ast.node_span(tree, expression.start))
|
||||||
}
|
}
|
||||||
if (end_type.kind != .invalid and end_type.kind != .int) {
|
if (end_type.kind != .invalid and end_type.kind != family) {
|
||||||
try fail(state, expression.end, .invalid_range_bound, ast.node_span(tree, expression.end))
|
try fail(state, expression.end, .invalid_range_bound, ast.node_span(tree, expression.end))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.array_literal: {
|
||||||
|
actual :: state.types[usize(id)]
|
||||||
|
if (actual.kind != .array) return
|
||||||
|
element :: state.arrays.items[usize(actual.array_type)].element
|
||||||
|
for 0..ast.array_element_count(tree, id) |i| {
|
||||||
|
child :: ast.array_element(tree, id, i)
|
||||||
|
if (element.kind == .void or element.kind == .range) {
|
||||||
|
try fail(state, child, .invalid_array_element_type, ast.node_span(tree, child))
|
||||||
|
} else {
|
||||||
|
try compatible(state, child, element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (array_cells(state, actual) == null) try fail(state, id, .array_size_overflow, ast.node_span(tree, id))
|
||||||
|
}
|
||||||
|
.index: {
|
||||||
|
view :: ast.index_expr(tree, id)
|
||||||
|
base :: state.types[usize(view.base)]
|
||||||
|
index :: state.types[usize(view.index)]
|
||||||
|
if (index.kind != .invalid and index.kind != .usize) {
|
||||||
|
try fail(state, view.index, .invalid_index_type, ast.node_span(tree, view.index))
|
||||||
|
}
|
||||||
|
if (base.kind != .invalid and base.kind != .array) {
|
||||||
|
try fail(state, id, .not_indexable, ast.node_span(tree, view.base))
|
||||||
|
}
|
||||||
|
}
|
||||||
.call: try validate_call(state, id)
|
.call: try validate_call(state, id)
|
||||||
else: {}
|
else: {}
|
||||||
}
|
}
|
||||||
@@ -1017,3 +1145,378 @@ block_falls_through proc(state @State, id ast.NodeId) bool {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
ConstantInteger :: struct {
|
||||||
|
magnitude u64
|
||||||
|
negative bool = false
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
constant_integer proc(state @mut State, id ast.NodeId) ?ConstantInteger {
|
||||||
|
if (state.cells[usize(id)].constant_visiting) return null
|
||||||
|
state.cells[usize(id)].constant_visiting = true
|
||||||
|
defer state.cells[usize(id)].constant_visiting = false
|
||||||
|
match ast.expr_kind(state.tree, id) {
|
||||||
|
.literal_int: {
|
||||||
|
text :: ast.source_text(state.tree, ast.node_span(state.tree, id))
|
||||||
|
value := u64(0)
|
||||||
|
for text |byte| {
|
||||||
|
if (byte < '0' or byte > '9') return null
|
||||||
|
digit :: u64(byte - '0')
|
||||||
|
if (value > divtrunc!(maxval!(u64) - digit, u64(10))) return null
|
||||||
|
value = value * 10 + digit
|
||||||
|
}
|
||||||
|
return ConstantInteger{ magnitude = value }
|
||||||
|
}
|
||||||
|
.identifier: {
|
||||||
|
binding :: sema.binding(state.resolution, id)
|
||||||
|
if (binding.kind != .declaration) return null
|
||||||
|
target :: binding.data.declaration
|
||||||
|
if (!state.cells[usize(target)].count_declaration) return null
|
||||||
|
declaration :: ast.statement_decl(state.tree, target)
|
||||||
|
if (declaration.mutability != .constant) return null
|
||||||
|
if (declaration.type) |annotation| {
|
||||||
|
type_binding :: sema.binding(state.resolution, annotation)
|
||||||
|
if (type_binding.kind != .builtin) return null
|
||||||
|
kind :: type_binding.data.builtin
|
||||||
|
if (kind != .int and kind != .i64 and kind != .usize) return null
|
||||||
|
}
|
||||||
|
return constant_integer(state, declaration.initializer)
|
||||||
|
}
|
||||||
|
.unary: {
|
||||||
|
view :: ast.unary_expr(state.tree, id)
|
||||||
|
if (view.operator != .negate) return null
|
||||||
|
if (constant_integer(state, view.operand)) |value| {
|
||||||
|
return ConstantInteger{ magnitude = value.magnitude, negative = value.magnitude != 0 and !value.negative }
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
.binary: {
|
||||||
|
view :: ast.binary_expr(state.tree, id)
|
||||||
|
left := ConstantInteger{ magnitude = 0 }
|
||||||
|
right := ConstantInteger{ magnitude = 0 }
|
||||||
|
if (constant_integer(state, view.left)) |value| left = value else return null
|
||||||
|
if (constant_integer(state, view.right)) |value| right = value else return null
|
||||||
|
if (view.operator == .multiply) {
|
||||||
|
if (right.magnitude != 0 and left.magnitude > divtrunc!(maxval!(u64), right.magnitude)) return null
|
||||||
|
magnitude :: left.magnitude * right.magnitude
|
||||||
|
return ConstantInteger{ magnitude = magnitude, negative = magnitude != 0 and left.negative != right.negative }
|
||||||
|
}
|
||||||
|
if (view.operator != .add and view.operator != .subtract) return null
|
||||||
|
if (view.operator == .subtract and right.magnitude != 0) right.negative = !right.negative
|
||||||
|
if (left.negative == right.negative) {
|
||||||
|
if (left.magnitude > maxval!(u64) - right.magnitude) return null
|
||||||
|
return ConstantInteger{ magnitude = left.magnitude + right.magnitude, negative = left.negative }
|
||||||
|
}
|
||||||
|
if (left.magnitude < right.magnitude) {
|
||||||
|
return ConstantInteger{ magnitude = right.magnitude - left.magnitude, negative = right.negative }
|
||||||
|
}
|
||||||
|
return ConstantInteger{ magnitude = left.magnitude - right.magnitude, negative = left.magnitude != right.magnitude and left.negative }
|
||||||
|
}
|
||||||
|
else: return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
intern_array proc(state @mut State, descriptor ArrayType) Type ! mem.AllocError {
|
||||||
|
for state.arrays.items |existing, i| {
|
||||||
|
if (existing.len == descriptor.len and existing.mutable == descriptor.mutable
|
||||||
|
and existing.element.kind == descriptor.element.kind
|
||||||
|
and existing.element.function == descriptor.element.function
|
||||||
|
and existing.element.array_type == descriptor.element.array_type) {
|
||||||
|
return Type{ kind = .array, array_type = ArrayId(i) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try arraylist.append(&state.arrays, descriptor)
|
||||||
|
return Type{ kind = .array, array_type = ArrayId(state.arrays.items.len - 1) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
same_type proc(state @State, left, right Type) bool {
|
||||||
|
if (left.kind != right.kind) return false
|
||||||
|
if (left.kind == .function) return same_signature(state, left.function, right.function)
|
||||||
|
if (left.kind != .array) return true
|
||||||
|
a :: state.arrays.items[usize(left.array_type)]
|
||||||
|
b :: state.arrays.items[usize(right.array_type)]
|
||||||
|
return a.len == b.len and a.mutable == b.mutable and same_type(state, a.element, b.element)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
array_cells proc(state @State, type Type) ?usize {
|
||||||
|
if (type.kind != .array) return usize(1)
|
||||||
|
view :: state.arrays.items[usize(type.array_type)]
|
||||||
|
if (view.len == no_index) return null
|
||||||
|
child := usize(0)
|
||||||
|
if (array_cells(state, view.element)) |value| child = value else return null
|
||||||
|
if (child != 0 and view.len > divtrunc!(maxval!(usize), child)) return null
|
||||||
|
return view.len * child
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
array_origin proc(state @mut State, id ast.NodeId) ast.NodeId {
|
||||||
|
origin :: state.cells[root(state, usize(id))].literal
|
||||||
|
if (origin != ast.NodeId(maxval!(u32))) return origin
|
||||||
|
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .index) {
|
||||||
|
base :: array_origin(state, ast.index_expr(state.tree, id).base)
|
||||||
|
if (base != ast.NodeId(maxval!(u32)) and ast.array_element_count(state.tree, base) != 0) {
|
||||||
|
return array_origin(state, ast.array_element(state.tree, base, 0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ast.NodeId(maxval!(u32))
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
unify_elements proc(state @mut State, left, right ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
|
if (state.types[usize(left)].kind == .invalid or state.types[usize(right)].kind == .invalid) return
|
||||||
|
a :: array_origin(state, left)
|
||||||
|
b :: array_origin(state, right)
|
||||||
|
if (a != ast.NodeId(maxval!(u32)) and b != ast.NodeId(maxval!(u32))) {
|
||||||
|
count :: ast.array_element_count(state.tree, a)
|
||||||
|
if (count != ast.array_element_count(state.tree, b)) return
|
||||||
|
for (0..count) |i| try unify_elements(state, ast.array_element(state.tree, a, i), ast.array_element(state.tree, b, i))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
left_type :: group_type(state, left)
|
||||||
|
right_type :: group_type(state, right)
|
||||||
|
if (numeric_kind(left_type.kind)) try demand(state, right, left_type, .type_mismatch)
|
||||||
|
if (numeric_kind(right_type.kind)) try demand(state, left, right_type, .type_mismatch)
|
||||||
|
left_root :: root(state, usize(left))
|
||||||
|
right_root :: root(state, usize(right))
|
||||||
|
if (state.cells[left_root].numeric and state.cells[right_root].numeric) {
|
||||||
|
try numeric_constraint(state, left, right, right)
|
||||||
|
}
|
||||||
|
if (left_type.kind == .array) try demand(state, right, left_type, .type_mismatch)
|
||||||
|
if (right_type.kind == .array) try demand(state, left, right_type, .type_mismatch)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
|
index :: root(state, usize(id))
|
||||||
|
if (state.cells[index].visiting) return state.cells[index].type
|
||||||
|
if (state.cells[index].type.kind == .invalid) return state.cells[index].type
|
||||||
|
state.cells[index].visiting = true
|
||||||
|
defer state.cells[index].visiting = false
|
||||||
|
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .identifier) {
|
||||||
|
binding :: sema.binding(state.resolution, id)
|
||||||
|
if (binding.kind == .declaration) {
|
||||||
|
target :: binding.data.declaration
|
||||||
|
if (state.cells[usize(target)].role == .declaration and state.cells[usize(target)].fixed
|
||||||
|
and has_inferred_count(state, state.cells[usize(target)].type)) {
|
||||||
|
declaration :: ast.statement_decl(state.tree, target)
|
||||||
|
actual :: try infer_shape(state, declaration.initializer, defaults)
|
||||||
|
if (actual.kind == .array and !has_inferred_count(state, actual)) {
|
||||||
|
try specialize_annotation(state, declaration.type?, actual)
|
||||||
|
state.cells[usize(target)].type = state.types[usize(declaration.type?)]
|
||||||
|
seed(state, id, state.cells[usize(target)].type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
literal :: state.cells[index].literal
|
||||||
|
if (literal != ast.NodeId(maxval!(u32))) {
|
||||||
|
len :: ast.array_element_count(state.tree, literal)
|
||||||
|
expected :: state.cells[index].expected
|
||||||
|
element := Type{}
|
||||||
|
mutable := false
|
||||||
|
if (expected.kind == .array) {
|
||||||
|
view :: state.arrays.items[usize(expected.array_type)]
|
||||||
|
element = view.element
|
||||||
|
mutable = view.mutable
|
||||||
|
}
|
||||||
|
for 0..len |i| {
|
||||||
|
child :: ast.array_element(state.tree, literal, i)
|
||||||
|
actual :: try infer_shape(state, child, defaults)
|
||||||
|
if (element.kind == .none and actual.kind != .none and actual.kind != .invalid) element = actual
|
||||||
|
if (element.kind == .array and actual.kind == .array) element = try specialize_type(state, element, actual)
|
||||||
|
if (element.kind != .none) try demand(state, child, element, .type_mismatch)
|
||||||
|
}
|
||||||
|
if (element.kind == .none and len == 0 and defaults) element = Type{ kind = .int }
|
||||||
|
if (element.kind != .none) {
|
||||||
|
type :: try intern_array(state, ArrayType{ element = element, len = len, mutable = mutable })
|
||||||
|
state.cells[index].type = type
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
return Type{}
|
||||||
|
}
|
||||||
|
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .index) {
|
||||||
|
view :: ast.index_expr(state.tree, id)
|
||||||
|
base :: try infer_shape(state, view.base, defaults)
|
||||||
|
if (base.kind == .array) {
|
||||||
|
element :: state.arrays.items[usize(base.array_type)].element
|
||||||
|
seed(state, id, element)
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
origin :: array_origin(state, view.base)
|
||||||
|
if (origin != ast.NodeId(maxval!(u32)) and ast.array_element_count(state.tree, origin) != 0) {
|
||||||
|
child :: ast.array_element(state.tree, origin, 0)
|
||||||
|
child_root :: root(state, usize(child))
|
||||||
|
if (state.cells[child_root].numeric) {
|
||||||
|
try numeric_constraint(state, id, child, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return group_type(state, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
prepare_shapes proc(state @mut State, assignment_values, defaults bool) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
|
for 0..(state.cells.len) |i| {
|
||||||
|
if (state.cells[i].role != .expression or state.cells[i].assignment_value != assignment_values) continue
|
||||||
|
id :: ast.NodeId(i)
|
||||||
|
if (
|
||||||
|
ast.expr_kind(state.tree, id) == .array_literal
|
||||||
|
and state.cells[root(state, i)].expected.kind != .array
|
||||||
|
) {
|
||||||
|
len :: ast.array_element_count(state.tree, id)
|
||||||
|
for (usize(1)..len) |j| try unify_elements(state, ast.array_element(state.tree, id, 0), ast.array_element(state.tree, id, j))
|
||||||
|
}
|
||||||
|
_ = try infer_shape(state, id, defaults)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
apply_contexts proc(state @mut State, assignment_values, early bool) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
|
tree :: state.tree
|
||||||
|
for 0..(state.cells.len) |i| {
|
||||||
|
id :: ast.NodeId(i)
|
||||||
|
if (!assignment_values and state.cells[i].role == .declaration) {
|
||||||
|
view :: ast.statement_decl(tree, id)
|
||||||
|
if (view.type) |annotation| try context_demand(state, view.initializer, state.types[usize(annotation)], .type_mismatch, early)
|
||||||
|
}
|
||||||
|
if (!assignment_values and state.cells[i].role == .return_stmt) {
|
||||||
|
if (ast.return_stmt(tree, id).value) |value| {
|
||||||
|
try context_demand(state, value, state.types[usize(state.cells[i].return_annotation)], .return_type_mismatch, early)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (assignment_values and state.cells[i].role == .assignment_stmt) {
|
||||||
|
view :: ast.assignment_stmt(tree, id)
|
||||||
|
if (mutable_target(state, view.target)) try context_demand(state, view.value, state.types[usize(view.target)], .type_mismatch, early)
|
||||||
|
}
|
||||||
|
if (state.cells[i].role != .expression or state.cells[i].assignment_value != assignment_values or ast.expr_kind(tree, id) != .call) continue
|
||||||
|
signature :: group_type(state, ast.call_expr(tree, id).callee)
|
||||||
|
if (signature.function == ast.NodeId(maxval!(u32))) continue
|
||||||
|
count :: ast.function_param_count(tree, signature.function)
|
||||||
|
for 0..ast.call_arg_count(tree, id) |j| {
|
||||||
|
if (j >= count) continue
|
||||||
|
parameter :: ast.function_param(tree, signature.function, j)
|
||||||
|
try context_demand(state, ast.call_arg(tree, id, j), state.cells[usize(parameter)].type, .type_mismatch, early)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
context_demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Code, early bool) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
|
if (early and expected.kind != .array and expected.kind != .usize) return
|
||||||
|
try demand(state, id, expected, code)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
bound_family proc(state @mut State, id ast.NodeId) TypeKind {
|
||||||
|
index :: root(state, usize(id))
|
||||||
|
if (state.cells[index].type.kind != .none) return state.cells[index].type.kind
|
||||||
|
if (state.cells[index].demand != .none) return state.cells[index].demand
|
||||||
|
producer :: state.cells[index].producer
|
||||||
|
if (producer == ast.NodeId(maxval!(u32)) or state.cells[index].visiting) return .none
|
||||||
|
state.cells[index].visiting = true
|
||||||
|
defer state.cells[index].visiting = false
|
||||||
|
if (ast.expr_kind(state.tree, producer) == .unary) return bound_family(state, ast.unary_expr(state.tree, producer).operand)
|
||||||
|
view :: ast.binary_expr(state.tree, producer)
|
||||||
|
left :: bound_family(state, view.left)
|
||||||
|
right :: bound_family(state, view.right)
|
||||||
|
if (left == .float or right == .float) return .float
|
||||||
|
if (left == .usize or right == .usize) return .usize
|
||||||
|
if (left == .int or right == .int) return .int
|
||||||
|
return .none
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
prepare_ranges proc(state @mut State, assignment_values, constrain bool) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
|
tree :: state.tree
|
||||||
|
for 0..(state.cells.len) |i| {
|
||||||
|
id :: ast.NodeId(i)
|
||||||
|
if (state.cells[i].role != .expression or state.cells[i].assignment_value != assignment_values or ast.expr_kind(tree, id) != .range) continue
|
||||||
|
view :: ast.range_expr(tree, id)
|
||||||
|
left :: bound_family(state, view.start)
|
||||||
|
right :: bound_family(state, view.end)
|
||||||
|
family TypeKind :: if (left == .usize or right == .usize) .usize else .int
|
||||||
|
if (constrain) {
|
||||||
|
try range_bound_context(state, view.start, family)
|
||||||
|
try range_bound_context(state, view.end, family)
|
||||||
|
}
|
||||||
|
if (group_type(state, id).kind != .invalid) {
|
||||||
|
seed(state, id, Type{ kind = .range, range_element = family })
|
||||||
|
state.types[i] = group_type(state, id)
|
||||||
|
}
|
||||||
|
capture :: state.cells[i].capture
|
||||||
|
if (capture != ast.NodeId(maxval!(u32)) and state.cells[usize(capture)].type.kind != .invalid) {
|
||||||
|
seed(state, capture, Type{ kind = family })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for 0..(state.cells.len) |i| {
|
||||||
|
id :: ast.NodeId(i)
|
||||||
|
if (state.cells[i].role != .expression or state.cells[i].assignment_value or ast.expr_kind(tree, id) != .identifier) continue
|
||||||
|
binding :: sema.binding(state.resolution, id)
|
||||||
|
if (binding.kind == .declaration and state.cells[usize(binding.data.declaration)].role == .loop_capture) {
|
||||||
|
seed(state, id, group_type(state, binding.data.declaration))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
has_inferred_count proc(state @State, type Type) bool {
|
||||||
|
if (type.kind != .array) return false
|
||||||
|
view :: state.arrays.items[usize(type.array_type)]
|
||||||
|
return view.len == no_index or has_inferred_count(state, view.element)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
specialize_type proc(state @mut State, expected, actual Type) Type ! mem.AllocError {
|
||||||
|
if (expected.kind != .array or actual.kind != .array) return expected
|
||||||
|
view := state.arrays.items[usize(expected.array_type)]
|
||||||
|
source :: state.arrays.items[usize(actual.array_type)]
|
||||||
|
if (view.len == no_index) view.len = source.len
|
||||||
|
view.element = try specialize_type(state, view.element, source.element)
|
||||||
|
return try intern_array(state, view)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
specialize_annotation proc(state @mut State, id ast.NodeId, actual Type) void ! mem.AllocError {
|
||||||
|
expected :: try specialize_type(state, state.types[usize(id)], actual)
|
||||||
|
state.types[usize(id)] = expected
|
||||||
|
state.cells[usize(id)].type = expected
|
||||||
|
if (expected.kind == .array and ast.expr_kind(state.tree, id) == .array_type) {
|
||||||
|
try specialize_annotation(state, ast.array_type(state.tree, id).element, state.arrays.items[usize(expected.array_type)].element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
specialize_annotations proc(state @mut State) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
|
for 0..(state.cells.len) |i| {
|
||||||
|
if (state.cells[i].role != .declaration) continue
|
||||||
|
view :: ast.statement_decl(state.tree, ast.NodeId(i))
|
||||||
|
if (view.type) |annotation| {
|
||||||
|
actual :: try infer_shape(state, view.initializer, true)
|
||||||
|
try specialize_annotation(state, annotation, actual)
|
||||||
|
if (state.cells[i].type.kind != .invalid) state.cells[i].type = state.types[usize(annotation)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for 0..(state.cells.len) |i| {
|
||||||
|
if (state.cells[i].role != .annotation or state.types[i].kind != .array) continue
|
||||||
|
if (has_inferred_count(state, state.types[i])) {
|
||||||
|
try fail(state, ast.NodeId(i), .invalid_array_length, ast.node_span(state.tree, ast.NodeId(i)))
|
||||||
|
state.cells[i].type = state.types[i]
|
||||||
|
} else if (array_cells(state, state.types[i]) == null) {
|
||||||
|
try fail(state, ast.NodeId(i), .array_size_overflow, ast.node_span(state.tree, ast.NodeId(i)))
|
||||||
|
state.cells[i].type = state.types[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for 0..(state.cells.len) |i| {
|
||||||
|
id :: ast.NodeId(i)
|
||||||
|
if (state.cells[i].role != .expression or state.cells[i].assignment_value or ast.expr_kind(state.tree, id) != .identifier) continue
|
||||||
|
binding :: sema.binding(state.resolution, id)
|
||||||
|
if (binding.kind == .declaration and state.cells[usize(binding.data.declaration)].fixed) {
|
||||||
|
seed(state, id, state.cells[usize(binding.data.declaration)].type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2129,3 +2129,474 @@ invalid_range_capture_bindings_keep_resolver_diagnostic_ids_without_erasing_head
|
|||||||
try expect_kind(&result, ast.block_statement(&tree, sibling_loop.body, 0), .int)
|
try expect_kind(&result, ast.block_statement(&tree, sibling_loop.body, 0), .int)
|
||||||
try testing.expect_equal(previous_count, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(previous_count, diagnostics.count(&diagnostic_store))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
ArrayFixture :: struct {
|
||||||
|
tree ast.Tree
|
||||||
|
resolution sema.Resolution
|
||||||
|
diagnostics diagnostics.Store
|
||||||
|
result Result
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
array_fixture proc(source []u8) ArrayFixture ! (mem.AllocError | strpool.InternError | diagnostics.CapacityError | lexer.SourceError) {
|
||||||
|
store := diagnostics.init(mem.c_allocator)
|
||||||
|
errdefer diagnostics.deinit(&store)
|
||||||
|
scan := lexer.init(mem.c_allocator)
|
||||||
|
defer lexer.deinit(&scan)
|
||||||
|
try lexer.scan(&scan, source, &store)
|
||||||
|
tree := try parser.parse(mem.c_allocator, scan.tokens.items, source, &store)
|
||||||
|
errdefer ast.deinit(&tree)
|
||||||
|
resolution := try sema.resolve(mem.c_allocator, &tree, &store)
|
||||||
|
errdefer sema.deinit(&resolution)
|
||||||
|
result :: try check(mem.c_allocator, &tree, &resolution, &store)
|
||||||
|
return ArrayFixture{ tree = tree, resolution = resolution, diagnostics = store, result = result }
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
deinit_array_fixture proc(fixture @mut ArrayFixture) void {
|
||||||
|
deinit(&fixture.result)
|
||||||
|
sema.deinit(&fixture.resolution)
|
||||||
|
ast.deinit(&fixture.tree)
|
||||||
|
diagnostics.deinit(&fixture.diagnostics)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
expect_array proc(result @Result, id ast.NodeId, len usize, mutable bool, element TypeKind) ArrayType ! testing.Error {
|
||||||
|
try expect_kind(result, id, .array)
|
||||||
|
actual :: array_type(result, type_of(result, id).array_type)
|
||||||
|
try testing.expect_equal(len, actual.len)
|
||||||
|
try testing.expect_equal(mutable, actual.mutable)
|
||||||
|
try testing.expect_equal(element, actual.element.kind)
|
||||||
|
return actual
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed_arrays_contextualize_forward_aliases_counts_nested_literals_and_signatures test {
|
||||||
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.strings)
|
||||||
|
source ::
|
||||||
|
`context [2]f64 :: alias
|
||||||
|
`alias :: seed
|
||||||
|
`seed := [1, 2]
|
||||||
|
`count :: later * 2 - 1
|
||||||
|
`later :: 2
|
||||||
|
`nested [_]mut [2]mut i64 := [[1, 2], [3, 4]]
|
||||||
|
`empty [_]bool :: []
|
||||||
|
`plain :: []
|
||||||
|
`copy :: nested
|
||||||
|
`index usize :: 0
|
||||||
|
`child float :: seed[index]
|
||||||
|
`produce proc(value [2]f64) [2]f64 { return value }
|
||||||
|
`called :: produce([5, 6])
|
||||||
|
`from_result [_]f64 :: called
|
||||||
|
`counted [count]i64 :: [1, 2, 3]
|
||||||
|
fixture := try array_fixture(source)
|
||||||
|
defer deinit_array_fixture(&fixture)
|
||||||
|
tree :: &fixture.tree
|
||||||
|
result :: &fixture.result
|
||||||
|
for (0..usize(3)) |i| _ = try expect_array(result, ast.package_decl(tree, i), 2, false, .float)
|
||||||
|
seed :: ast.statement_decl(tree, ast.package_decl(tree, 2)).initializer
|
||||||
|
for (0..ast.array_element_count(tree, seed)) |i| try expect_kind(result, ast.array_element(tree, seed, i), .float)
|
||||||
|
nested :: ast.package_decl(tree, 5)
|
||||||
|
shape :: try expect_array(result, nested, 2, true, .array)
|
||||||
|
row :: array_type(result, shape.element.array_type)
|
||||||
|
try testing.expect_equal(2, row.len)
|
||||||
|
try testing.expect(row.mutable)
|
||||||
|
try testing.expect_equal(TypeKind.int, row.element.kind)
|
||||||
|
annotation :: ast.statement_decl(tree, nested).type?
|
||||||
|
_ = try expect_array(result, annotation, 2, true, .array)
|
||||||
|
_ = try expect_array(result, ast.package_decl(tree, 6), 0, false, .bool)
|
||||||
|
_ = try expect_array(result, ast.package_decl(tree, 7), 0, false, .int)
|
||||||
|
try testing.expect_equal(type_of(result, nested).array_type, type_of(result, ast.package_decl(tree, 8)).array_type)
|
||||||
|
indexed :: ast.index_expr(tree, ast.statement_decl(tree, ast.package_decl(tree, 10)).initializer)
|
||||||
|
try expect_kind(result, indexed.index, .usize)
|
||||||
|
try expect_kind(result, ast.statement_decl(tree, ast.package_decl(tree, 10)).initializer, .float)
|
||||||
|
function :: ast.package_decl(tree, 11)
|
||||||
|
_ = try expect_array(result, ast.function_param(tree, function, 0), 2, false, .float)
|
||||||
|
_ = try expect_array(result, ast.function_decl(tree, function).return_type, 2, false, .float)
|
||||||
|
call :: ast.statement_decl(tree, ast.package_decl(tree, 12)).initializer
|
||||||
|
argument :: ast.call_arg(tree, call, 0)
|
||||||
|
_ = try expect_array(result, argument, 2, false, .float)
|
||||||
|
try expect_kind(result, ast.array_element(tree, argument, 0), .float)
|
||||||
|
_ = try expect_array(result, ast.package_decl(tree, 13), 2, false, .float)
|
||||||
|
_ = try expect_array(result, ast.package_decl(tree, 14), 3, false, .int)
|
||||||
|
try testing.expect(array_type_count(result) > 0)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&fixture.diagnostics))
|
||||||
|
}
|
||||||
|
|
||||||
|
nested_array_numeric_context_flows_through_index_children_before_defaults test {
|
||||||
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.strings)
|
||||||
|
source ::
|
||||||
|
`first float :: later[0][0]
|
||||||
|
`later := [[1, 2], [3, 4]]
|
||||||
|
`row :: later[0]
|
||||||
|
`mixed :: [[1, 2], [3.5, 4]]
|
||||||
|
`identity proc(value int) int { return value }
|
||||||
|
`another proc(value int) int { return value + 1 }
|
||||||
|
`functions :: [identity, another]
|
||||||
|
fixture := try array_fixture(source)
|
||||||
|
defer deinit_array_fixture(&fixture)
|
||||||
|
tree :: &fixture.tree
|
||||||
|
result :: &fixture.result
|
||||||
|
for usize(1)..usize(4) |i| {
|
||||||
|
element_kind TypeKind :: if (i == 2) .float else .array
|
||||||
|
type :: try expect_array(result, ast.package_decl(tree, i), 2, false, element_kind)
|
||||||
|
if (type.element.kind == .array) {
|
||||||
|
try testing.expect_equal(TypeKind.float, array_type(result, type.element.array_type).element.kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
matrix :: ast.statement_decl(tree, ast.package_decl(tree, 1)).initializer
|
||||||
|
for 0..ast.array_element_count(tree, matrix) |i| {
|
||||||
|
row :: ast.array_element(tree, matrix, i)
|
||||||
|
for (0..ast.array_element_count(tree, row)) |j| try expect_kind(result, ast.array_element(tree, row, j), .float)
|
||||||
|
}
|
||||||
|
outer :: ast.statement_decl(tree, ast.package_decl(tree, 0)).initializer
|
||||||
|
inner :: ast.index_expr(tree, outer).base
|
||||||
|
_ = try expect_array(result, inner, 2, false, .float)
|
||||||
|
try expect_kind(result, outer, .float)
|
||||||
|
try expect_kind(result, ast.index_expr(tree, outer).index, .usize)
|
||||||
|
try expect_kind(result, ast.index_expr(tree, inner).index, .usize)
|
||||||
|
_ = try expect_array(result, ast.package_decl(tree, 6), 2, false, .function)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&fixture.diagnostics))
|
||||||
|
}
|
||||||
|
|
||||||
|
fixed_index_errors_are_occurrence_local_and_preserve_sources_and_siblings test {
|
||||||
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.strings)
|
||||||
|
source ::
|
||||||
|
`fixed i64 :: 0
|
||||||
|
`inferred := 0
|
||||||
|
`source [2]i64 :: [10, 20]
|
||||||
|
`bad :: source[fixed]
|
||||||
|
`good :: source[inferred]
|
||||||
|
`boolean :: source[true]
|
||||||
|
`missing :: source[unknown]
|
||||||
|
`tail :: source[1]
|
||||||
|
fixture := try array_fixture(source)
|
||||||
|
defer deinit_array_fixture(&fixture)
|
||||||
|
tree :: &fixture.tree
|
||||||
|
result :: &fixture.result
|
||||||
|
try expect_kind(result, ast.package_decl(tree, 0), .int)
|
||||||
|
try expect_kind(result, ast.package_decl(tree, 1), .usize)
|
||||||
|
_ = try expect_array(result, ast.package_decl(tree, 2), 2, false, .int)
|
||||||
|
bad_id :: ast.statement_decl(tree, ast.package_decl(tree, 3)).initializer
|
||||||
|
bad :: ast.index_expr(tree, bad_id)
|
||||||
|
original :: try expect_error(result, &fixture.diagnostics, bad.index, .invalid_index_type, ast.node_span(tree, bad.index))
|
||||||
|
try testing.expect_equal(original, type_of(result, bad_id).diagnostic)
|
||||||
|
try testing.expect_equal(original, type_of(result, ast.package_decl(tree, 3)).diagnostic)
|
||||||
|
_ = try expect_array(result, bad.base, 2, false, .int)
|
||||||
|
try expect_kind(result, ast.package_decl(tree, 4), .int)
|
||||||
|
boolean :: ast.index_expr(tree, ast.statement_decl(tree, ast.package_decl(tree, 5)).initializer).index
|
||||||
|
_ = try expect_error(result, &fixture.diagnostics, boolean, .invalid_index_type, ast.node_span(tree, boolean))
|
||||||
|
missing :: ast.index_expr(tree, ast.statement_decl(tree, ast.package_decl(tree, 6)).initializer).index
|
||||||
|
try testing.expect_equal(sema.binding(&fixture.resolution, missing).data.diagnostic, type_of(result, missing).diagnostic)
|
||||||
|
try expect_kind(result, ast.package_decl(tree, 7), .int)
|
||||||
|
try testing.expect_equal(2, code_count(&fixture.diagnostics, .invalid_index_type))
|
||||||
|
try testing.expect_equal(1, code_count(&fixture.diagnostics, .unknown_name))
|
||||||
|
try testing.expect_equal(3, diagnostics.count(&fixture.diagnostics))
|
||||||
|
}
|
||||||
|
|
||||||
|
array_lengths_require_supported_constexpr_and_finalize_nested_inferred_counts test {
|
||||||
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.strings)
|
||||||
|
source ::
|
||||||
|
`two :: tail
|
||||||
|
`tail :: 2
|
||||||
|
`valid [two * 3 - 4]i64 :: [1, 2]
|
||||||
|
`zero [3 - 3]i64 :: []
|
||||||
|
`nested [_]mut [_]mut i64 := [[1, 2], [3, 4]]
|
||||||
|
`negative [-1]i64 :: []
|
||||||
|
`fraction [1.5]i64 :: []
|
||||||
|
`runtime_count := 2
|
||||||
|
`runtime_array [runtime_count]i64 :: [1, 2]
|
||||||
|
`count proc() int { return 2 }
|
||||||
|
`called [count()]i64 :: [1, 2]
|
||||||
|
`a :: b
|
||||||
|
`b :: a
|
||||||
|
`cycle [a]i64 :: []
|
||||||
|
`overflow [18446744073709551615 + 1]i64 :: []
|
||||||
|
`wrong [2]i64 :: [1]
|
||||||
|
`constraints [2]mut int := [1, 2]
|
||||||
|
`floats [0]float :: []
|
||||||
|
`voids [0]void :: []
|
||||||
|
`missing [absent]i64 :: []
|
||||||
|
fixture := try array_fixture(source)
|
||||||
|
defer deinit_array_fixture(&fixture)
|
||||||
|
tree :: &fixture.tree
|
||||||
|
result :: &fixture.result
|
||||||
|
_ = try expect_array(result, ast.package_decl(tree, 2), 2, false, .int)
|
||||||
|
_ = try expect_array(result, ast.package_decl(tree, 3), 0, false, .int)
|
||||||
|
nested :: ast.statement_decl(tree, ast.package_decl(tree, 4))
|
||||||
|
shape :: try expect_array(result, nested.type?, 2, true, .array)
|
||||||
|
child :: array_type(result, shape.element.array_type)
|
||||||
|
try testing.expect_equal(2, child.len)
|
||||||
|
try testing.expect(child.mutable)
|
||||||
|
_ = try expect_array(result, ast.array_type(tree, nested.type?).element, 2, true, .int)
|
||||||
|
invalid_lengths [6]usize :: [5, 6, 8, 10, 13, 14]
|
||||||
|
for invalid_lengths |i| {
|
||||||
|
annotation :: ast.statement_decl(tree, ast.package_decl(tree, i)).type?
|
||||||
|
count :: ast.array_type(tree, annotation).count?
|
||||||
|
_ = try expect_error(result, &fixture.diagnostics, annotation, .invalid_array_length, ast.node_span(tree, count))
|
||||||
|
}
|
||||||
|
wrong :: ast.statement_decl(tree, ast.package_decl(tree, 15)).initializer
|
||||||
|
_ = try expect_error(result, &fixture.diagnostics, wrong, .array_length_mismatch, ast.node_span(tree, wrong))
|
||||||
|
for usize(16)..usize(19) |i| {
|
||||||
|
annotation :: ast.statement_decl(tree, ast.package_decl(tree, i)).type?
|
||||||
|
child_id :: ast.array_type(tree, annotation).element
|
||||||
|
_ = try expect_error(result, &fixture.diagnostics, annotation, .invalid_array_element_type, ast.node_span(tree, child_id))
|
||||||
|
}
|
||||||
|
missing :: ast.statement_decl(tree, ast.package_decl(tree, 19)).type?
|
||||||
|
count :: ast.array_type(tree, missing).count?
|
||||||
|
try testing.expect_equal(sema.binding(&fixture.resolution, count).data.diagnostic, type_of(result, missing).diagnostic)
|
||||||
|
try testing.expect_equal(6, code_count(&fixture.diagnostics, .invalid_array_length))
|
||||||
|
try testing.expect_equal(1, code_count(&fixture.diagnostics, .array_length_mismatch))
|
||||||
|
try testing.expect_equal(3, code_count(&fixture.diagnostics, .invalid_array_element_type))
|
||||||
|
}
|
||||||
|
|
||||||
|
indexed_writes_require_mutable_local_root_and_each_array_level_but_copies_are_values test {
|
||||||
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.strings)
|
||||||
|
source ::
|
||||||
|
`global [1]mut i64 := [1]
|
||||||
|
`main proc(parameter [1]mut i64) void {
|
||||||
|
` mutable [1]mut i64 := [1]
|
||||||
|
` mutable[0] = 2
|
||||||
|
` copied := mutable
|
||||||
|
` copied[0] = 3
|
||||||
|
` inferred := [1]
|
||||||
|
` inferred[0] = 4
|
||||||
|
` inferred = [5]
|
||||||
|
` constant [1]mut i64 :: [1]
|
||||||
|
` constant[0] = 6
|
||||||
|
` parameter[0] = 7
|
||||||
|
` global[0] = 8
|
||||||
|
` outer_readonly [1][1]mut i64 := [[1]]
|
||||||
|
` (outer_readonly[0])[0] = 9
|
||||||
|
` inner_readonly [1]mut [1]i64 := [[1]]
|
||||||
|
` (inner_readonly[0])[0] = 10
|
||||||
|
` both [1]mut [1]mut i64 := [[1]]
|
||||||
|
` (both[0])[0] = 11
|
||||||
|
` both[0] = [12]
|
||||||
|
` mismatch [1]mut i64 := inner_readonly[0]
|
||||||
|
`}
|
||||||
|
fixture := try array_fixture(source)
|
||||||
|
defer deinit_array_fixture(&fixture)
|
||||||
|
tree :: &fixture.tree
|
||||||
|
result :: &fixture.result
|
||||||
|
body :: ast.function_decl(tree, ast.package_decl(tree, 1)).body
|
||||||
|
valid [6]usize :: [1, 3, 6, 16, 17, 2]
|
||||||
|
for valid |i| {
|
||||||
|
id :: ast.block_statement(tree, body, i)
|
||||||
|
kind TypeKind :: if (i == 2) .array else .void
|
||||||
|
try expect_kind(result, id, kind)
|
||||||
|
}
|
||||||
|
invalid [6]usize :: [5, 8, 9, 10, 12, 14]
|
||||||
|
for invalid |i| {
|
||||||
|
id :: ast.block_statement(tree, body, i)
|
||||||
|
target :: ast.assignment_stmt(tree, id).target
|
||||||
|
_ = try expect_error(result, &fixture.diagnostics, id, .invalid_index_assignment, ast.node_span(tree, target))
|
||||||
|
try expect_kind(result, target, .int)
|
||||||
|
try expect_kind(result, ast.index_expr(tree, target).index, .usize)
|
||||||
|
}
|
||||||
|
mismatch :: ast.statement_decl(tree, ast.block_statement(tree, body, 18)).initializer
|
||||||
|
_ = try expect_error(result, &fixture.diagnostics, mismatch, .type_mismatch, ast.node_span(tree, mismatch))
|
||||||
|
_ = try expect_array(result, ast.block_statement(tree, body, 4), 1, false, .int)
|
||||||
|
_ = try expect_array(result, ast.function_param(tree, ast.package_decl(tree, 1), 0), 1, true, .int)
|
||||||
|
try testing.expect_equal(6, code_count(&fixture.diagnostics, .invalid_index_assignment))
|
||||||
|
try testing.expect_equal(1, code_count(&fixture.diagnostics, .type_mismatch))
|
||||||
|
}
|
||||||
|
|
||||||
|
usize_arithmetic_ranges_and_readonly_captures_keep_the_bound_family test {
|
||||||
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.strings)
|
||||||
|
source ::
|
||||||
|
`limit usize :: 18446744073709551615
|
||||||
|
`arithmetic usize :: (limit - 2) * 1 + 1
|
||||||
|
`ordered :: 0 < limit
|
||||||
|
`equal :: limit == 18446744073709551615
|
||||||
|
`negated :: -limit
|
||||||
|
`divided :: limit / 2
|
||||||
|
`fixed i64 :: 1
|
||||||
|
`mixed :: fixed + limit
|
||||||
|
`main proc() void {
|
||||||
|
` source [2]i64 :: [1, 2]
|
||||||
|
` for 0..limit |index| {
|
||||||
|
` source[index]
|
||||||
|
` index = 1
|
||||||
|
` }
|
||||||
|
` for 0..2 |index| { source[index] }
|
||||||
|
` for limit..=limit |_| {}
|
||||||
|
`}
|
||||||
|
fixture := try array_fixture(source)
|
||||||
|
defer deinit_array_fixture(&fixture)
|
||||||
|
tree :: &fixture.tree
|
||||||
|
result :: &fixture.result
|
||||||
|
try expect_kind(result, ast.package_decl(tree, 0), .usize)
|
||||||
|
try expect_kind(result, ast.package_decl(tree, 1), .usize)
|
||||||
|
try expect_kind(result, ast.package_decl(tree, 2), .bool)
|
||||||
|
try expect_kind(result, ast.package_decl(tree, 3), .bool)
|
||||||
|
for usize(4)..usize(8) |i| {
|
||||||
|
if (i == 6) continue
|
||||||
|
value :: ast.statement_decl(tree, ast.package_decl(tree, i)).initializer
|
||||||
|
code diagnostics.Code :: if (i == 5) .integer_division else .invalid_operand
|
||||||
|
_ = try expect_error(result, &fixture.diagnostics, value, code, ast.node_span(tree, value))
|
||||||
|
}
|
||||||
|
body :: ast.function_decl(tree, ast.package_decl(tree, 8)).body
|
||||||
|
unsigned_loop :: ast.for_stmt(tree, ast.block_statement(tree, body, 1))
|
||||||
|
try expect_kind(result, unsigned_loop.capture?, .usize)
|
||||||
|
try testing.expect_equal(TypeKind.usize, type_of(result, unsigned_loop.iterable).range_element)
|
||||||
|
interval :: ast.range_expr(tree, unsigned_loop.iterable)
|
||||||
|
try expect_kind(result, interval.start, .usize)
|
||||||
|
try expect_kind(result, interval.end, .usize)
|
||||||
|
read :: ast.statement_expr(tree, ast.block_statement(tree, unsigned_loop.body, 0))
|
||||||
|
try expect_kind(result, read, .int)
|
||||||
|
try expect_kind(result, ast.index_expr(tree, read).index, .usize)
|
||||||
|
readonly :: ast.block_statement(tree, unsigned_loop.body, 1)
|
||||||
|
target :: ast.assignment_stmt(tree, readonly).target
|
||||||
|
_ = try expect_error(result, &fixture.diagnostics, readonly, .invalid_assignment_target, ast.node_span(tree, target))
|
||||||
|
try expect_kind(result, target, .usize)
|
||||||
|
signed_loop :: ast.for_stmt(tree, ast.block_statement(tree, body, 2))
|
||||||
|
try expect_kind(result, signed_loop.capture?, .int)
|
||||||
|
signed_read :: ast.statement_expr(tree, ast.block_statement(tree, signed_loop.body, 0))
|
||||||
|
bad_index :: ast.index_expr(tree, signed_read).index
|
||||||
|
_ = try expect_error(result, &fixture.diagnostics, bad_index, .invalid_index_type, ast.node_span(tree, bad_index))
|
||||||
|
try testing.expect_equal(TypeKind.int, type_of(result, signed_loop.iterable).range_element)
|
||||||
|
_ = try expect_array(result, ast.block_statement(tree, body, 0), 2, false, .int)
|
||||||
|
ignored :: ast.for_stmt(tree, ast.block_statement(tree, body, 3))
|
||||||
|
try testing.expect_equal(TypeKind.usize, type_of(result, ignored.iterable).range_element)
|
||||||
|
try testing.expect_equal(1, code_count(&fixture.diagnostics, .invalid_index_type))
|
||||||
|
try testing.expect_equal(0, code_count(&fixture.diagnostics, .invalid_range_bound))
|
||||||
|
}
|
||||||
|
|
||||||
|
array_assignment_rhs_is_contextual_but_does_not_retarget_established_sources test {
|
||||||
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.strings)
|
||||||
|
source ::
|
||||||
|
`main proc() void {
|
||||||
|
` target [2]mut f64 := [1, 2]
|
||||||
|
` seed := 3
|
||||||
|
` source [2]i64 :: [4, 5]
|
||||||
|
` target = [seed, 6]
|
||||||
|
` target = source
|
||||||
|
` target = [1]
|
||||||
|
` matrix [1]mut [2]mut i64 := [[1, 2]]
|
||||||
|
` matrix[0] = [7, 8]
|
||||||
|
` matrix[0] = [true, false]
|
||||||
|
` unknown [1]mut i64 := [absent]
|
||||||
|
` unknown[0] = 9
|
||||||
|
` good := 10
|
||||||
|
`}
|
||||||
|
fixture := try array_fixture(source)
|
||||||
|
defer deinit_array_fixture(&fixture)
|
||||||
|
tree :: &fixture.tree
|
||||||
|
result :: &fixture.result
|
||||||
|
body :: ast.function_decl(tree, ast.package_decl(tree, 0)).body
|
||||||
|
_ = try expect_array(result, ast.block_statement(tree, body, 0), 2, true, .float)
|
||||||
|
try expect_kind(result, ast.block_statement(tree, body, 1), .int)
|
||||||
|
_ = try expect_array(result, ast.block_statement(tree, body, 2), 2, false, .int)
|
||||||
|
contextual :: ast.assignment_stmt(tree, ast.block_statement(tree, body, 3)).value
|
||||||
|
_ = try expect_error(result, &fixture.diagnostics, ast.array_element(tree, contextual, 0), .type_mismatch, ast.node_span(tree, ast.array_element(tree, contextual, 0)))
|
||||||
|
try expect_kind(result, ast.array_element(tree, contextual, 1), .float)
|
||||||
|
copied :: ast.assignment_stmt(tree, ast.block_statement(tree, body, 4)).value
|
||||||
|
_ = try expect_error(result, &fixture.diagnostics, copied, .type_mismatch, ast.node_span(tree, copied))
|
||||||
|
wrong_len :: ast.assignment_stmt(tree, ast.block_statement(tree, body, 5)).value
|
||||||
|
_ = try expect_error(result, &fixture.diagnostics, wrong_len, .array_length_mismatch, ast.node_span(tree, wrong_len))
|
||||||
|
try expect_kind(result, ast.block_statement(tree, body, 7), .void)
|
||||||
|
literal :: ast.statement_decl(tree, ast.block_statement(tree, body, 9)).initializer
|
||||||
|
missing :: ast.array_element(tree, literal, 0)
|
||||||
|
original :: sema.binding(&fixture.resolution, missing).data.diagnostic
|
||||||
|
try testing.expect_equal(original, type_of(result, literal).diagnostic)
|
||||||
|
try testing.expect_equal(original, type_of(result, ast.block_statement(tree, body, 9)).diagnostic)
|
||||||
|
try testing.expect_equal(original, type_of(result, ast.block_statement(tree, body, 10)).diagnostic)
|
||||||
|
try expect_kind(result, ast.block_statement(tree, body, 11), .int)
|
||||||
|
}
|
||||||
|
|
||||||
|
array_signature_identity_includes_recursive_counts_and_mutability test {
|
||||||
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.strings)
|
||||||
|
source ::
|
||||||
|
`accept proc(value [1]mut i64) [1]mut i64 { return value }
|
||||||
|
`different_count proc(value [2]mut i64) [1]mut i64 { return [1] }
|
||||||
|
`different_mutability proc(value [1]i64) [1]mut i64 { return [1] }
|
||||||
|
`functions :: [accept, different_count, different_mutability]
|
||||||
|
`wrong_return proc() [1]mut i64 {
|
||||||
|
` value [1]i64 :: [1]
|
||||||
|
` return value
|
||||||
|
`}
|
||||||
|
`take proc(value [2]i64) void {}
|
||||||
|
`caller proc() void {
|
||||||
|
` take([1])
|
||||||
|
` take([true, false])
|
||||||
|
`}
|
||||||
|
fixture := try array_fixture(source)
|
||||||
|
defer deinit_array_fixture(&fixture)
|
||||||
|
tree :: &fixture.tree
|
||||||
|
result :: &fixture.result
|
||||||
|
functions :: ast.statement_decl(tree, ast.package_decl(tree, 3)).initializer
|
||||||
|
for usize(1)..usize(3) |i| {
|
||||||
|
value :: ast.array_element(tree, functions, i)
|
||||||
|
_ = try expect_error(result, &fixture.diagnostics, value, .type_mismatch, ast.node_span(tree, value))
|
||||||
|
try expect_kind(result, ast.package_decl(tree, i), .function)
|
||||||
|
}
|
||||||
|
wrong :: ast.package_decl(tree, 4)
|
||||||
|
wrong_body :: ast.function_decl(tree, wrong).body
|
||||||
|
value :: ast.return_stmt(tree, ast.block_statement(tree, wrong_body, 1)).value?
|
||||||
|
_ = try expect_error(result, &fixture.diagnostics, value, .return_type_mismatch, ast.node_span(tree, value))
|
||||||
|
_ = try expect_array(result, ast.block_statement(tree, wrong_body, 0), 1, false, .int)
|
||||||
|
try expect_kind(result, wrong, .function)
|
||||||
|
caller :: ast.function_decl(tree, ast.package_decl(tree, 6)).body
|
||||||
|
call :: ast.statement_expr(tree, ast.block_statement(tree, caller, 0))
|
||||||
|
argument :: ast.call_arg(tree, call, 0)
|
||||||
|
_ = try expect_error(result, &fixture.diagnostics, argument, .array_length_mismatch, ast.node_span(tree, argument))
|
||||||
|
second :: ast.statement_expr(tree, ast.block_statement(tree, caller, 1))
|
||||||
|
literal :: ast.call_arg(tree, second, 0)
|
||||||
|
for 0..ast.array_element_count(tree, literal) |i| {
|
||||||
|
child :: ast.array_element(tree, literal, i)
|
||||||
|
_ = try expect_error(result, &fixture.diagnostics, child, .type_mismatch, ast.node_span(tree, child))
|
||||||
|
}
|
||||||
|
try expect_kind(result, ast.package_decl(tree, 5), .function)
|
||||||
|
}
|
||||||
|
|
||||||
|
inferred_counts_follow_forward_fixed_aliases_empty_shapes_and_reject_local_counts test {
|
||||||
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.strings)
|
||||||
|
source ::
|
||||||
|
`forward [_]i64 :: later
|
||||||
|
`later [_]i64 :: [1, 2]
|
||||||
|
`zero [_]mut bool := []
|
||||||
|
`nested [_][_]bool :: [[], []]
|
||||||
|
`unsigned usize :: 5
|
||||||
|
`main proc() void {
|
||||||
|
` local_count :: 2
|
||||||
|
` unsupported [local_count]i64 :: [1, 2]
|
||||||
|
` values [2]i64 :: [1, 2]
|
||||||
|
` for 0..(unsigned + 1) |index| { values[index] }
|
||||||
|
`}
|
||||||
|
fixture := try array_fixture(source)
|
||||||
|
defer deinit_array_fixture(&fixture)
|
||||||
|
tree :: &fixture.tree
|
||||||
|
result :: &fixture.result
|
||||||
|
for 0..usize(2) |i| {
|
||||||
|
declaration :: ast.statement_decl(tree, ast.package_decl(tree, i))
|
||||||
|
_ = try expect_array(result, declaration.type?, 2, false, .int)
|
||||||
|
_ = try expect_array(result, ast.package_decl(tree, i), 2, false, .int)
|
||||||
|
}
|
||||||
|
_ = try expect_array(result, ast.package_decl(tree, 2), 0, true, .bool)
|
||||||
|
shape :: try expect_array(result, ast.package_decl(tree, 3), 2, false, .array)
|
||||||
|
child :: array_type(result, shape.element.array_type)
|
||||||
|
try testing.expect_equal(0, child.len)
|
||||||
|
try testing.expect_equal(TypeKind.bool, child.element.kind)
|
||||||
|
body :: ast.function_decl(tree, ast.package_decl(tree, 5)).body
|
||||||
|
unsupported :: ast.statement_decl(tree, ast.block_statement(tree, body, 1)).type?
|
||||||
|
count :: ast.array_type(tree, unsupported).count?
|
||||||
|
_ = try expect_error(result, &fixture.diagnostics, unsupported, .invalid_array_length, ast.node_span(tree, count))
|
||||||
|
try expect_kind(result, ast.block_statement(tree, body, 0), .int)
|
||||||
|
loop :: ast.for_stmt(tree, ast.block_statement(tree, body, 3))
|
||||||
|
try expect_kind(result, loop.capture?, .usize)
|
||||||
|
try testing.expect_equal(TypeKind.usize, type_of(result, loop.iterable).range_element)
|
||||||
|
read :: ast.statement_expr(tree, ast.block_statement(tree, loop.body, 0))
|
||||||
|
try expect_kind(result, read, .int)
|
||||||
|
try testing.expect_equal(1, code_count(&fixture.diagnostics, .invalid_array_length))
|
||||||
|
try testing.expect_equal(1, diagnostics.count(&fixture.diagnostics))
|
||||||
|
}
|
||||||
|
|||||||
+24
-1
@@ -6,7 +6,12 @@ import "@std/strmap"
|
|||||||
import "@source/ast"
|
import "@source/ast"
|
||||||
import "@source/diagnostics"
|
import "@source/diagnostics"
|
||||||
|
|
||||||
BuiltinType :: enum { int, float, bool, string, void }
|
BuiltinType :: enum {
|
||||||
|
int, float,
|
||||||
|
bool, string,
|
||||||
|
void, i64,
|
||||||
|
f64, usize,
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
builtins strmap.StaticStringMap(BuiltinType) :: strmap.init([
|
builtins strmap.StaticStringMap(BuiltinType) :: strmap.init([
|
||||||
@@ -15,6 +20,9 @@ builtins strmap.StaticStringMap(BuiltinType) :: strmap.init([
|
|||||||
{ "bool", .bool },
|
{ "bool", .bool },
|
||||||
{ "string", .string },
|
{ "string", .string },
|
||||||
{ "void", .void },
|
{ "void", .void },
|
||||||
|
{ "i64", .i64 },
|
||||||
|
{ "f64", .f64 },
|
||||||
|
{ "usize", .usize },
|
||||||
])
|
])
|
||||||
|
|
||||||
BindingKind :: enum { none, declaration, builtin, invalid }
|
BindingKind :: enum { none, declaration, builtin, invalid }
|
||||||
@@ -299,6 +307,21 @@ resolve_expr proc(
|
|||||||
try resolve_expr(state, scope, ast.call_arg(tree, id, i))
|
try resolve_expr(state, scope, ast.call_arg(tree, id, i))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.array_type: {
|
||||||
|
type :: ast.array_type(tree, id)
|
||||||
|
if (type.count) |count| try resolve_expr(state, scope, count)
|
||||||
|
try resolve_expr(state, scope, type.element)
|
||||||
|
}
|
||||||
|
.array_literal: {
|
||||||
|
for (0..ast.array_element_count(tree, id)) |i| {
|
||||||
|
try resolve_expr(state, scope, ast.array_element(tree, id, i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.index: {
|
||||||
|
expression :: ast.index_expr(tree, id)
|
||||||
|
try resolve_expr(state, scope, expression.base)
|
||||||
|
try resolve_expr(state, scope, expression.index)
|
||||||
|
}
|
||||||
.invalid: resolve_invalid(state, id)
|
.invalid: resolve_invalid(state, id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -603,3 +603,155 @@ range_captures_preserve_duplicate_builtin_and_discard_errors test {
|
|||||||
try testing.expect_equal(BindingKind.invalid, binding(&resolution, bounds.end).kind)
|
try testing.expect_equal(BindingKind.invalid, binding(&resolution, bounds.end).kind)
|
||||||
try testing.expect_equal(8, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(8, diagnostics.count(&diagnostic_store))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resolves_array_counts_elements_indices_and_unreachable_targets test {
|
||||||
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.strings)
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
source ::
|
||||||
|
`size usize :: 2
|
||||||
|
`make proc(first, second [size]mut [2]i64) [size]i64 {
|
||||||
|
` return first[0]
|
||||||
|
` dead [local_size]mut [size - 1]f64 := [[later]]
|
||||||
|
` local_size :: size
|
||||||
|
` later f64 :: 1.0
|
||||||
|
` (dead[index])[0] = later
|
||||||
|
` index usize :: 0
|
||||||
|
`}
|
||||||
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
|
defer lexer.deinit(&scan_state)
|
||||||
|
try lexer.scan(&scan_state, source, &diagnostic_store)
|
||||||
|
tree := try parser.parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||||
|
defer ast.deinit(&tree)
|
||||||
|
resolution := try resolve(mem.c_allocator, &tree, &diagnostic_store)
|
||||||
|
defer deinit(&resolution)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
size :: ast.package_decl(&tree, 0)
|
||||||
|
function :: ast.package_decl(&tree, 1)
|
||||||
|
view :: ast.function_decl(&tree, function)
|
||||||
|
return_type :: ast.array_type(&tree, view.return_type)
|
||||||
|
try testing.expect_equal(size, binding(&resolution, return_type.count?).data.declaration)
|
||||||
|
try testing.expect_equal(BuiltinType.i64, binding(&resolution, return_type.element).data.builtin)
|
||||||
|
first :: ast.function_param(&tree, function, 0)
|
||||||
|
second :: ast.function_param(&tree, function, 1)
|
||||||
|
first_type :: ast.param(&tree, first).type
|
||||||
|
try testing.expect_equal(first_type, ast.param(&tree, second).type)
|
||||||
|
parameter_array :: ast.array_type(&tree, first_type)
|
||||||
|
try testing.expect_equal(size, binding(&resolution, parameter_array.count?).data.declaration)
|
||||||
|
row :: ast.array_type(&tree, parameter_array.element)
|
||||||
|
try testing.expect_equal(BuiltinType.i64, binding(&resolution, row.element).data.builtin)
|
||||||
|
returned :: ast.return_stmt(&tree, ast.block_statement(&tree, view.body, 0)).value?
|
||||||
|
try testing.expect_equal(first, binding(&resolution, ast.index_expr(&tree, returned).base).data.declaration)
|
||||||
|
dead :: ast.block_statement(&tree, view.body, 1)
|
||||||
|
local_size :: ast.block_statement(&tree, view.body, 2)
|
||||||
|
later :: ast.block_statement(&tree, view.body, 3)
|
||||||
|
index :: ast.block_statement(&tree, view.body, 5)
|
||||||
|
declaration :: ast.statement_decl(&tree, dead)
|
||||||
|
array :: ast.array_type(&tree, declaration.type?)
|
||||||
|
try testing.expect_equal(local_size, binding(&resolution, array.count?).data.declaration)
|
||||||
|
inner :: ast.array_type(&tree, array.element)
|
||||||
|
count_expression :: ast.binary_expr(&tree, inner.count?)
|
||||||
|
try testing.expect_equal(size, binding(&resolution, count_expression.left).data.declaration)
|
||||||
|
try testing.expect_equal(BuiltinType.f64, binding(&resolution, inner.element).data.builtin)
|
||||||
|
literal_row :: ast.array_element(&tree, declaration.initializer, 0)
|
||||||
|
try testing.expect_equal(later, binding(
|
||||||
|
&resolution, ast.array_element(&tree, literal_row, 0),
|
||||||
|
).data.declaration)
|
||||||
|
assignment :: ast.assignment_stmt(&tree, ast.block_statement(&tree, view.body, 4))
|
||||||
|
target :: ast.index_expr(&tree, ast.index_expr(&tree, assignment.target).base)
|
||||||
|
try testing.expect_equal(dead, binding(&resolution, target.base).data.declaration)
|
||||||
|
try testing.expect_equal(index, binding(&resolution, target.index).data.declaration)
|
||||||
|
try testing.expect_equal(later, binding(&resolution, assignment.value).data.declaration)
|
||||||
|
try testing.expect_equal(BuiltinType.usize, binding(
|
||||||
|
&resolution, ast.statement_decl(&tree, size).type?,
|
||||||
|
).data.builtin)
|
||||||
|
}
|
||||||
|
|
||||||
|
array_signature_counts_use_package_scope_and_local_counts_see_parameters test {
|
||||||
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.strings)
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
source ::
|
||||||
|
`size :: 2
|
||||||
|
`inspect proc(n usize, a, b [n]i64) [body_size]i64 {
|
||||||
|
` body_size :: size
|
||||||
|
` local [n]i64 :: []
|
||||||
|
` return []
|
||||||
|
`}
|
||||||
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
|
defer lexer.deinit(&scan_state)
|
||||||
|
try lexer.scan(&scan_state, source, &diagnostic_store)
|
||||||
|
tree := try parser.parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||||
|
defer ast.deinit(&tree)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
resolution := try resolve(mem.c_allocator, &tree, &diagnostic_store)
|
||||||
|
defer deinit(&resolution)
|
||||||
|
try testing.expect_equal(2, diagnostics.count(&diagnostic_store))
|
||||||
|
function :: ast.package_decl(&tree, 1)
|
||||||
|
view :: ast.function_decl(&tree, function)
|
||||||
|
parameter :: ast.function_param(&tree, function, 0)
|
||||||
|
grouped_type :: ast.param(&tree, ast.function_param(&tree, function, 1)).type
|
||||||
|
try testing.expect_equal(grouped_type, ast.param(
|
||||||
|
&tree, ast.function_param(&tree, function, 2),
|
||||||
|
).type)
|
||||||
|
signature_count :: ast.array_type(&tree, grouped_type).count?
|
||||||
|
try testing.expect_equal(BindingKind.invalid, binding(&resolution, signature_count).kind)
|
||||||
|
result_count :: ast.array_type(&tree, view.return_type).count?
|
||||||
|
try testing.expect_equal(BindingKind.invalid, binding(&resolution, result_count).kind)
|
||||||
|
local :: ast.statement_decl(&tree, ast.block_statement(&tree, view.body, 1))
|
||||||
|
local_count :: ast.array_type(&tree, local.type?).count?
|
||||||
|
try testing.expect_equal(BindingKind.declaration, binding(&resolution, local_count).kind)
|
||||||
|
try testing.expect_equal(parameter, binding(&resolution, local_count).data.declaration)
|
||||||
|
try testing.expect_equal(BuiltinType.i64, binding(
|
||||||
|
&resolution, ast.array_type(&tree, local.type?).element,
|
||||||
|
).data.builtin)
|
||||||
|
}
|
||||||
|
|
||||||
|
resolves_every_array_child_error_and_preserves_lexer_diagnostics test {
|
||||||
|
strpool.strings = strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strpool.strings)
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
source ::
|
||||||
|
`main proc() void {
|
||||||
|
` return
|
||||||
|
` data [missing_count]MissingElement :: [absent, missing_element]
|
||||||
|
` (missing_base[missing_index])[missing_outer] = missing_rhs
|
||||||
|
` malformed [$]i64 :: [$]
|
||||||
|
`}
|
||||||
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
|
defer lexer.deinit(&scan_state)
|
||||||
|
try lexer.scan(&scan_state, source, &diagnostic_store)
|
||||||
|
tree := try parser.parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||||
|
defer ast.deinit(&tree)
|
||||||
|
try testing.expect_equal(2, diagnostics.count(&diagnostic_store))
|
||||||
|
resolution := try resolve(mem.c_allocator, &tree, &diagnostic_store)
|
||||||
|
defer deinit(&resolution)
|
||||||
|
try testing.expect_equal(10, diagnostics.count(&diagnostic_store))
|
||||||
|
body :: ast.function_decl(&tree, ast.package_decl(&tree, 0)).body
|
||||||
|
declaration :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 1))
|
||||||
|
type :: ast.array_type(&tree, declaration.type?)
|
||||||
|
assignment :: ast.assignment_stmt(&tree, ast.block_statement(&tree, body, 2))
|
||||||
|
outer :: ast.index_expr(&tree, assignment.target)
|
||||||
|
inner :: ast.index_expr(&tree, outer.base)
|
||||||
|
unknowns [8]ast.NodeId :: [
|
||||||
|
type.count?, type.element,
|
||||||
|
ast.array_element(&tree, declaration.initializer, 0),
|
||||||
|
ast.array_element(&tree, declaration.initializer, 1),
|
||||||
|
inner.base, inner.index, outer.index, assignment.value,
|
||||||
|
]
|
||||||
|
for (unknowns) |id| {
|
||||||
|
result :: binding(&resolution, id)
|
||||||
|
try testing.expect_equal(BindingKind.invalid, result.kind)
|
||||||
|
try testing.expect_equal(diagnostics.Code.unknown_name, diagnostics.get(
|
||||||
|
&diagnostic_store, result.data.diagnostic,
|
||||||
|
)?.code)
|
||||||
|
}
|
||||||
|
malformed :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 3))
|
||||||
|
malformed_count :: ast.array_type(&tree, malformed.type?).count?
|
||||||
|
malformed_element :: ast.array_element(&tree, malformed.initializer, 0)
|
||||||
|
try testing.expect_equal(diagnostics.DiagnosticId(0), binding(&resolution, malformed_count).data.diagnostic)
|
||||||
|
try testing.expect_equal(diagnostics.DiagnosticId(1), binding(&resolution, malformed_element).data.diagnostic)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user