feat: support enums, distinct types, unions and matching
This commit is contained in:
@@ -97,6 +97,14 @@ records use `Point :: struct { x i16, y i16 = 2 }`, with comma- or newline-separ
|
|||||||
|
|
||||||
tuple types use `struct { i64, bool }`; tuple values use `{42, true}`, `{42,}` and `{}`. anonymous keyed records, such as `{ x = 40, ready = true }`, infer structural types. fields use `.x` or canonical tuple ordinals such as `.0`, including chained selections. mutable local roots permit field updates; constants, parameters and globals remain readonly, and traversed arrays still require mutable elements. construction, binding, assignment, arguments and returns copy nested records/tuples and arrays by value. fields can be read from returned temporaries; records containing no scalar leaves retain their logical shapes. CLI results print declaration-order values as `{value, value}`.
|
tuple types use `struct { i64, bool }`; tuple values use `{42, true}`, `{42,}` and `{}`. anonymous keyed records, such as `{ x = 40, ready = true }`, infer structural types. fields use `.x` or canonical tuple ordinals such as `.0`, including chained selections. mutable local roots permit field updates; constants, parameters and globals remain readonly, and traversed arrays still require mutable elements. construction, binding, assignment, arguments and returns copy nested records/tuples and arrays by value. fields can be read from returned temporaries; records containing no scalar leaves retain their logical shapes. CLI results print declaration-order values as `{value, value}`.
|
||||||
|
|
||||||
|
enums use `Kind :: enum { number, empty }`, qualified members such as `Kind.number`, and contextual `.number` literals. unbacked enums use compilation-wide `u16` variant identities; explicit integer values require a backing, as in `Code :: enum(i16) { missing = -1, ready = 42 }`, and must increase within that backing's range. enum identities remain nominal across packages. equality/inequality and explicit backing extraction are supported; enum arithmetic and ordering are rejected. reserved keywords are valid enum and tagged-union member names.
|
||||||
|
|
||||||
|
`Id :: distinct u32` creates a nominal type with its backing's runtime representation. integer/float literals adopt distinct numeric context; arithmetic preserves the identity and checked-overflow rules. `Id(value)` explicitly constructs a value, applying checked conversion for numeric backing. nested distinct and aggregate backing require the immediate backing type; explicit extraction removes one layer. `Name :: alias declaration` preserves the original function/global/type identity, including imported declarations; aliases have no independent runtime storage.
|
||||||
|
|
||||||
|
untagged `Bits :: union { integer u64, real f64 }` overlays its members in shared storage. keyed construction selects exactly one member; reading another member reinterprets the same bytes. tagged unions use `union(enum)` or `union(Kind)`; an explicit enum must contain every union variant. construct them with `Choice { number = 42 }`, contextual `.number{42}`, `.point{x = 40, y = 2}` for record payloads, or `.empty` for `void` payloads. field reads/writes validate the active tag. nested aggregates copy by value, including across calls. CLI union results print `{payload}` (or `{}` for a void payload); untagged results print the first member.
|
||||||
|
|
||||||
|
`match value { .number |n|: n + 2, .empty: 0 }` selects one arm after evaluating its subject once. matches support enums, tagged unions, numeric/boolean scalars, comma-separated patterns, numeric ranges, final `else` arms, arm-local payload captures, statement bodies and expression results. checking rejects missing coverage, duplicate/overlapping patterns, unreachable arms and incompatible captures/results. block arms can return or transfer loop control; yielding value blocks, pointer captures, inline specialization and composed sum types belong to later gates.
|
||||||
|
|
||||||
`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 value with the bounds' exact integer type 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 of each integer type.
|
`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' exact integer type 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 of each integer type.
|
||||||
@@ -117,7 +125,7 @@ resolver scopes use `@std/hashmap.AutoHashMap(K, V)`, which shares `init`, `put`
|
|||||||
|
|
||||||
`sema.resolve` returns an owned binding table indexed by AST `NodeId`, without modifying the AST. `sema.binding` identifies a declaration, package namespace, builtin type, or invalid binding carrying its diagnostic ID; nodes with no binding return `.none`. `sema.resolve_packages` additionally borrows package roots and import links, predeclares every package before resolving qualified uses, and keeps import aliases in their originating source file. selected declarations have the same binding identity as unqualified declarations. release the result with `sema.deinit`. unknown names and duplicate declarations add source-spanned diagnostics; invalid syntax and failed imports retain their 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, package namespace, builtin type, or invalid binding carrying its diagnostic ID; nodes with no binding return `.none`. `sema.resolve_packages` additionally borrows package roots and import links, predeclares every package before resolving qualified uses, and keeps import aliases in their originating source file. selected declarations have the same binding identity as unqualified declarations. release the result with `sema.deinit`. unknown names and duplicate declarations add source-spanned diagnostics; invalid syntax and failed imports retain their 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, array/record descriptors and construction mappings; query with `check.type_of`, `check.array_type`, `check.record_type`, `check.field_index` and `check.initializer_field`, 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, fixed arrays and local/imported record types; type names are not ordinary runtime 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, array/record/enum descriptors and construction mappings; query with `check.type_of`, `check.array_type`, `check.record_type`, `check.enum_type`, `check.field_index` and `check.initializer_field`, 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, fixed arrays and local/imported records, enums, unions and distinct/alias types; type names are not ordinary runtime values. `void` is valid as a function result or tagged-union payload, not as a parameter or standalone stored value.
|
||||||
|
|
||||||
function fallthrough obligations are separate from body types. `check.fallthrough_diagnostic(&result, function)` accepts a function declaration's `NodeId` and returns its original missing-return diagnostic ID, or `null` when no obligation exists. nested body errors retain their original identities. lowering consumes this fact without interpreting diagnostic codes and places its trap only on a surviving fallthrough path; `check.deinit` releases the obligation storage.
|
function fallthrough obligations are separate from body types. `check.fallthrough_diagnostic(&result, function)` accepts a function declaration's `NodeId` and returns its original missing-return diagnostic ID, or `null` when no obligation exists. nested body errors retain their original identities. lowering consumes this fact without interpreting diagnostic codes and places its trap only on a surviving fallthrough path; `check.deinit` releases the obligation storage.
|
||||||
|
|
||||||
@@ -135,9 +143,9 @@ 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.
|
||||||
|
|
||||||
aggregate IR retains recursive array element types/counts/mutability and declaration-order record fields with flattened offsets. typed construction, field/element addresses and load/store instructions use storage reserved per invocation; nested copies and accesses reuse that storage without per-iteration allocation. addresses are internal IR values, not source-language pointers.
|
aggregate IR retains recursive array element types/counts/mutability and declaration-order record fields with flattened offsets. union descriptors additionally retain overlapping byte layouts and per-variant discriminants. typed construction, field/element addresses and load/store instructions use storage reserved per invocation; nested copies and accesses reuse that storage without per-iteration allocation. addresses are internal IR values, not source-language pointers. match lowering uses ordinary branches and local result slots; enum and distinct identities are checked before erasure to their runtime backing.
|
||||||
|
|
||||||
IR construction owns derived layout metadata. `ir.add_array` and `ir.add_record` check shape capacity and compute flattened leaf counts; record shapes own copied field metadata. both aggregate kinds use `ArrayId` shapes and the existing backing segments. `ir.add_parameter` and `ir.add_local` create slots with disjoint aggregate backing. parameters precede other locals. `ir.emit` assigns fresh result IDs by default and maintains temporary counts, aggregate-result segments and address presence. hand-authored non-SSA IR may supply explicit result IDs; repeated aggregate results retain their shape and reuse the same segment. producers do not register backing separately or write derived counts, offsets or flags. construction failures leave logical contents unchanged, and no finalization pass or extra runtime allocation is required.
|
IR construction owns derived layout metadata. `ir.add_array`, `ir.add_record` and `ir.add_union` check shape capacity and compute their interpreter storage and native layouts; record/union shapes own copied field metadata. all aggregate kinds use `ArrayId` shapes and the existing backing segments. `ir.add_parameter` and `ir.add_local` create slots with disjoint aggregate backing. parameters precede other locals. `ir.emit` assigns fresh result IDs by default and maintains temporary counts, aggregate-result segments and address presence. hand-authored non-SSA IR may supply explicit result IDs; repeated aggregate results retain their shape and reuse the same segment. producers do not register backing separately or write derived counts, offsets or flags. construction failures leave logical contents unchanged, and no finalization pass or extra runtime allocation is required.
|
||||||
|
|
||||||
`interpreter.run` consumes only IR and returns an `Outcome` containing a value or diagnostic trap. returned aggregates 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. aggregate descriptors also borrow the module; `ir.aggregate_item` projects a borrowed field/element while preserving zero-cell child shapes. 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.
|
`interpreter.run` consumes only IR and returns an `Outcome` containing a value or diagnostic trap. returned aggregates 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. aggregate descriptors also borrow the module; `ir.aggregate_item` projects a borrowed field/element while preserving zero-cell child shapes. 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.
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
|
|
||||||
directory packages support file-local import namespaces and postfix declaration
|
directory packages support file-local import namespaces and postfix declaration
|
||||||
selection through the interpreter and native backend. concrete 8/16/32/64-bit
|
selection through the interpreter and native backend. concrete 8/16/32/64-bit
|
||||||
integers, checked numeric conversions, records/tuples, construction/defaults and
|
integers, checked numeric conversions, records/tuples, construction/defaults,
|
||||||
field reads/writes work through both execution paths. enums, distinct/alias types,
|
enums, distinct/alias types, overlapping untagged/tagged unions, field reads/writes
|
||||||
unions and matching remain unsupported.
|
and exhaustive statement/value matching work through both execution paths.
|
||||||
the driver retains frontend storage
|
the driver retains frontend storage
|
||||||
and source provenance through `@source/compilation`, with compilation-wide AST node identities.
|
and source provenance through `@source/compilation`, with compilation-wide AST node identities.
|
||||||
file commands retain single-file behavior; directory commands compile the
|
file commands retain single-file behavior; directory commands compile the
|
||||||
@@ -35,10 +35,10 @@ test discovery and FFI remain outside these completed slices.
|
|||||||
|
|
||||||
## subsequent dependency gates
|
## subsequent dependency gates
|
||||||
|
|
||||||
- [ ] concrete data models: required integer widths/conversions, records/tuples and construction, fields, enums, distinct/alias types, unions and matching. examples: `source/lexer/token.hon`, `source/sema/resolve.hon`, `std/mem/mem.hon`.
|
- [x] concrete data models: required integer widths/conversions, records/tuples and construction, fields, enums, distinct/alias types, unions and matching. examples: `source/lexer/token.hon`, `source/sema/resolve.hon`, `std/mem/mem.hon`.
|
||||||
- [x] native integer widths (`i8`–`i64`, `u8`–`u64`, `isize`/`usize`) and checked explicit numeric conversions, including float truncation/range checks and integer-to-float rounding. preserve exact types through arrays, calls and range captures; retain strict implicit conversion rules.
|
- [x] native integer widths (`i8`–`i64`, `u8`–`u64`, `isize`/`usize`) and checked explicit numeric conversions, including float truncation/range checks and integer-to-float rounding. preserve exact types through arrays, calls and range captures; retain strict implicit conversion rules.
|
||||||
- [x] records/tuples, typed/contextual/anonymous construction, field defaults and reads/writes, nested aggregate value copies, imported record types and constructors through interpreter/native execution.
|
- [x] records/tuples, typed/contextual/anonymous construction, field defaults and reads/writes, nested aggregate value copies, imported record types and constructors through interpreter/native execution.
|
||||||
- [ ] enums, distinct/alias types, unions and matching.
|
- [x] nominal enums and distinct types, transparent declaration/type aliases, overlapping untagged/tagged unions, contextual payload construction, active-field checks and exhaustive scalar/enum/tagged-union matching through interpreter/native execution. pointer captures, inline specialization, sum composition and yielding value blocks remain with their respective later gates.
|
||||||
- [ ] memory representation: source pointers/references, mutable and sentinel slices, optionals, layout/cast builtins and allocator-required libc calls. internal IR array addresses are not source pointers. examples: `std/mem/mem.hon`, `std/arraylist/arraylist.hon`.
|
- [ ] memory representation: source pointers/references, mutable and sentinel slices, optionals, layout/cast builtins and allocator-required libc calls. internal IR array addresses are not source pointers. examples: `std/mem/mem.hon`, `std/arraylist/arraylist.hon`.
|
||||||
- [ ] fallible results and cleanup: error-set composition, `try`/`catch`, `defer`/`errdefer`; infrastructure errors remain distinct from diagnostic traps. examples: `source/main.hon:41`, `source/sema/check/check.hon:117`.
|
- [ ] fallible results and cleanup: error-set composition, `try`/`catch`, `defer`/`errdefer`; infrastructure errors remain distinct from diagnostic traps. examples: `source/main.hon:41`, `source/sema/check/check.hon:117`.
|
||||||
- [ ] generic specialization and compile-time execution/reflection sufficient for existing `ArrayList`, hash maps, `enummap`, `meta` and `io.print`; include type/value parameters and compile-time generated storage.
|
- [ ] generic specialization and compile-time execution/reflection sufficient for existing `ArrayList`, hash maps, `enummap`, `meta` and `io.print`; include type/value parameters and compile-time generated storage.
|
||||||
|
|||||||
+189
-4
@@ -1,12 +1,14 @@
|
|||||||
#! grammar:
|
#! grammar:
|
||||||
#! * type ..... -> identifier {'.' identifier} | '[' (expr | '_') ']' ['mut'] type | 'struct' '{' [field {(',' | newline) field}] '}'
|
#! * type ..... -> identifier {'.' member} | '[' (expr | '_') ']' ['mut'] type | 'struct' '{' fields '}' | 'enum' ['(' type ')'] '{' members '}' | 'union' ['(' (type | 'enum') ')'] '{' fields '}' | ('distinct' | 'alias') type
|
||||||
#! * binding ... -> :: | :=
|
#! * binding ... -> :: | :=
|
||||||
#! * literal .. -> int | float | string | 'true' | 'false'
|
#! * literal .. -> int | float | string | 'true' | 'false'
|
||||||
#! * op ....... -> '+' | '-' | '*' | '/' | '==' | '!=' | '<' | '<=' | '>' | '>=' | 'and' | 'or'
|
#! * op ....... -> '+' | '-' | '*' | '/' | '==' | '!=' | '<' | '<=' | '>' | '>=' | 'and' | 'or'
|
||||||
#! * expr ..... -> identifier | literal | type | '[' [expr {',' expr} [',']] ']' | [type] '{' [initializer {',' initializer} [',']] '}' | '(' expr ')' | ('-' | '!') expr | expr op expr | expr '(' [expr {',' expr} [',']] ')' | expr '[' expr ']' | expr '.' (identifier | int) | expr ('..' | '..=') expr
|
#! * expr ..... -> identifier | literal | type | '.' member | '[' [expr {',' expr} [',']] ']' | [type] '{' [initializer {',' initializer} [',']] '}' | '(' expr ')' | ('-' | '!') expr | expr op expr | expr '(' [expr {',' expr} [',']] ')' | expr '[' expr ']' | expr '.' (member | int) | expr ('..' | '..=') expr | 'match' expr '{' match_arms '}'
|
||||||
#! * decl ..... -> identifier [type] binding expr | ['test'] [identifier '::'] 'import' string
|
#! * decl ..... -> identifier [type] binding expr | ['test'] [identifier '::'] 'import' string
|
||||||
#! * block .... -> '{' {stmt} '}'
|
#! * block .... -> '{' {stmt} '}'
|
||||||
#! * arm ...... -> block | stmt (single statements become canonical blocks)
|
#! * arm ...... -> block | stmt (single statements become canonical blocks)
|
||||||
|
#! * match_arm -> (expr {',' expr} ['|' identifier '|'] | 'else') ':' (block | stmt | expr); arms are comma/newline separated.
|
||||||
|
#! enum members and tagged-union variants accept keyword names; member names do not enter lexical scopes.
|
||||||
#! * stmt ..... -> decl | expr | expr '=' 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/indices/selections, unary -/!, multiplication/division, addition/subtraction, relational comparisons, equality, and, or, ranges. compound range endpoints require their own parentheses.
|
#! precedence (tightest first): calls/indices/selections, unary -/!, multiplication/division, addition/subtraction, relational comparisons, equality, and, or, ranges. compound range endpoints require their own parentheses.
|
||||||
@@ -45,6 +47,12 @@ ExprKind :: enum {
|
|||||||
array_literal
|
array_literal
|
||||||
record_type
|
record_type
|
||||||
record_literal
|
record_literal
|
||||||
|
enum_type
|
||||||
|
union_type
|
||||||
|
distinct_type
|
||||||
|
alias_expr
|
||||||
|
enum_literal
|
||||||
|
match_expr
|
||||||
index
|
index
|
||||||
selection
|
selection
|
||||||
invalid
|
invalid
|
||||||
@@ -101,6 +109,13 @@ NodeKind :: enum {
|
|||||||
expr_record_type
|
expr_record_type
|
||||||
expr_record_literal
|
expr_record_literal
|
||||||
record_field
|
record_field
|
||||||
|
expr_enum_type
|
||||||
|
expr_union_type
|
||||||
|
expr_distinct_type
|
||||||
|
expr_alias
|
||||||
|
expr_enum_literal
|
||||||
|
expr_match
|
||||||
|
match_arm
|
||||||
expr_index
|
expr_index
|
||||||
expr_selection
|
expr_selection
|
||||||
stmt_expr
|
stmt_expr
|
||||||
@@ -228,6 +243,16 @@ FieldView :: struct {
|
|||||||
value ?NodeId
|
value ?NodeId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EnumTypeView :: struct { backing ?NodeId }
|
||||||
|
UnionTypeView :: struct { tag ?NodeId, auto_tag bool }
|
||||||
|
MatchView :: struct { subject NodeId }
|
||||||
|
MatchArmView :: struct {
|
||||||
|
capture ?NodeId
|
||||||
|
body NodeId
|
||||||
|
expression bool
|
||||||
|
catch_all bool
|
||||||
|
}
|
||||||
|
|
||||||
IndexView :: struct {
|
IndexView :: struct {
|
||||||
span Span
|
span Span
|
||||||
base NodeId
|
base NodeId
|
||||||
@@ -477,6 +502,78 @@ add_record_type proc(builder @mut Builder, span Span, fields []NodeId) NodeId !
|
|||||||
}, span)
|
}, span)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_enum_type proc(builder @mut Builder, span Span, backing ?NodeId, members []NodeId) NodeId ! mem.AllocError {
|
||||||
|
start :: builder.storage.extra.items.len
|
||||||
|
for (members) |member| _ = try add_extra(builder, u32(member))
|
||||||
|
raw_backing :: if (backing) |id| id else no_node
|
||||||
|
return try add_node(builder, Node{
|
||||||
|
kind = .expr_enum_type,
|
||||||
|
main = NodeData{ node_id = raw_backing },
|
||||||
|
data0 = NodeData{ extra = u32(start) },
|
||||||
|
data1 = NodeData{ extra = u32(builder.storage.extra.items.len) },
|
||||||
|
}, span)
|
||||||
|
}
|
||||||
|
|
||||||
|
add_union_type proc(builder @mut Builder, span Span, tag ?NodeId, auto_tag bool, fields []NodeId) NodeId ! mem.AllocError {
|
||||||
|
raw_tag :: if (tag) |id| u32(id) else u32(no_node)
|
||||||
|
tag_flag :: if (auto_tag) u32(1) else u32(0)
|
||||||
|
metadata :: try add_extra(builder, raw_tag)
|
||||||
|
_ = try add_extra(builder, tag_flag)
|
||||||
|
start :: builder.storage.extra.items.len
|
||||||
|
for (fields) |field_id| _ = try add_extra(builder, u32(field_id))
|
||||||
|
return try add_node(builder, Node{
|
||||||
|
kind = .expr_union_type,
|
||||||
|
main = NodeData{ extra = metadata },
|
||||||
|
data0 = NodeData{ extra = u32(start) },
|
||||||
|
data1 = NodeData{ extra = u32(builder.storage.extra.items.len) },
|
||||||
|
}, span)
|
||||||
|
}
|
||||||
|
|
||||||
|
add_type_operand proc(builder @mut Builder, span Span, operand NodeId, transparent bool) NodeId ! mem.AllocError {
|
||||||
|
kind :: if (transparent) NodeKind.expr_alias else NodeKind.expr_distinct_type
|
||||||
|
return try add_node(builder, Node{
|
||||||
|
kind = kind,
|
||||||
|
main = NodeData{ node_id = operand },
|
||||||
|
}, span)
|
||||||
|
}
|
||||||
|
|
||||||
|
add_enum_literal proc(builder @mut Builder, span Span, member NodeId) NodeId ! mem.AllocError {
|
||||||
|
return try add_node(builder, Node{
|
||||||
|
kind = .expr_enum_literal,
|
||||||
|
main = NodeData{ node_id = member },
|
||||||
|
}, span)
|
||||||
|
}
|
||||||
|
|
||||||
|
add_match proc(builder @mut Builder, span Span, subject NodeId, arms []NodeId) NodeId ! mem.AllocError {
|
||||||
|
start :: builder.storage.extra.items.len
|
||||||
|
for (arms) |arm| _ = try add_extra(builder, u32(arm))
|
||||||
|
return try add_node(builder, Node{
|
||||||
|
kind = .expr_match,
|
||||||
|
main = NodeData{ node_id = subject },
|
||||||
|
data0 = NodeData{ extra = u32(start) },
|
||||||
|
data1 = NodeData{ extra = u32(builder.storage.extra.items.len) },
|
||||||
|
}, span)
|
||||||
|
}
|
||||||
|
|
||||||
|
add_match_arm proc(
|
||||||
|
builder @mut Builder, span Span, patterns []NodeId, capture ?NodeId,
|
||||||
|
body NodeId, expression, catch_all bool,
|
||||||
|
) NodeId ! mem.AllocError {
|
||||||
|
raw_capture :: if (capture) |id| u32(id) else u32(no_node)
|
||||||
|
expression_flag :: if (expression) u32(1) else u32(0)
|
||||||
|
catch_all_flag :: if (catch_all) u32(1) else u32(0)
|
||||||
|
start :: try add_extra(builder, raw_capture)
|
||||||
|
_ = try add_extra(builder, expression_flag)
|
||||||
|
_ = try add_extra(builder, catch_all_flag)
|
||||||
|
for (patterns) |pattern| _ = try add_extra(builder, u32(pattern))
|
||||||
|
return try add_node(builder, Node{
|
||||||
|
kind = .match_arm,
|
||||||
|
main = NodeData{ node_id = body },
|
||||||
|
data0 = NodeData{ extra = start },
|
||||||
|
data1 = NodeData{ extra = u32(builder.storage.extra.items.len) },
|
||||||
|
}, span)
|
||||||
|
}
|
||||||
|
|
||||||
add_record_literal proc(
|
add_record_literal proc(
|
||||||
builder @mut Builder, span Span, type ?NodeId, fields []NodeId,
|
builder @mut Builder, span Span, type ?NodeId, fields []NodeId,
|
||||||
) NodeId ! mem.AllocError {
|
) NodeId ! mem.AllocError {
|
||||||
@@ -979,6 +1076,12 @@ expression_kind proc(kind NodeKind) ExprKind {
|
|||||||
.expr_array_literal: .array_literal
|
.expr_array_literal: .array_literal
|
||||||
.expr_record_type: .record_type
|
.expr_record_type: .record_type
|
||||||
.expr_record_literal: .record_literal
|
.expr_record_literal: .record_literal
|
||||||
|
.expr_enum_type: .enum_type
|
||||||
|
.expr_union_type: .union_type
|
||||||
|
.expr_distinct_type: .distinct_type
|
||||||
|
.expr_alias: .alias_expr
|
||||||
|
.expr_enum_literal: .enum_literal
|
||||||
|
.expr_match: .match_expr
|
||||||
.expr_index: .index
|
.expr_index: .index
|
||||||
.expr_selection: .selection
|
.expr_selection: .selection
|
||||||
.invalid_expr: .invalid
|
.invalid_expr: .invalid
|
||||||
@@ -1084,15 +1187,97 @@ field proc(tree @Tree, id NodeId) FieldView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum_type proc(tree @Tree, id NodeId) EnumTypeView {
|
||||||
|
node :: get_node(&tree.storage, id)
|
||||||
|
debug.assert(node.kind == .expr_enum_type)
|
||||||
|
return EnumTypeView{ backing = maybe_node_id(u32(node.main.node_id)) }
|
||||||
|
}
|
||||||
|
|
||||||
|
enum_member_count proc(tree @Tree, id NodeId) usize {
|
||||||
|
node :: get_node(&tree.storage, id)
|
||||||
|
debug.assert(node.kind == .expr_enum_type)
|
||||||
|
return usize(node.data1.extra - node.data0.extra)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum_member proc(tree @Tree, id NodeId, index usize) NodeId {
|
||||||
|
node :: get_node(&tree.storage, id)
|
||||||
|
debug.assert(index < enum_member_count(tree, id))
|
||||||
|
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index])
|
||||||
|
}
|
||||||
|
|
||||||
|
union_type proc(tree @Tree, id NodeId) UnionTypeView {
|
||||||
|
node :: get_node(&tree.storage, id)
|
||||||
|
debug.assert(node.kind == .expr_union_type)
|
||||||
|
extra :: usize(node.main.extra)
|
||||||
|
return UnionTypeView{
|
||||||
|
tag = maybe_node_id(tree.storage.extra.items[extra]),
|
||||||
|
auto_tag = tree.storage.extra.items[extra + 1] != 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type_operand proc(tree @Tree, id NodeId) NodeId {
|
||||||
|
node :: get_node(&tree.storage, id)
|
||||||
|
debug.assert(node.kind == .expr_distinct_type or node.kind == .expr_alias)
|
||||||
|
return node.main.node_id
|
||||||
|
}
|
||||||
|
|
||||||
|
enum_literal proc(tree @Tree, id NodeId) NodeId {
|
||||||
|
node :: get_node(&tree.storage, id)
|
||||||
|
debug.assert(node.kind == .expr_enum_literal)
|
||||||
|
return node.main.node_id
|
||||||
|
}
|
||||||
|
|
||||||
|
match_expr proc(tree @Tree, id NodeId) MatchView {
|
||||||
|
node :: get_node(&tree.storage, id)
|
||||||
|
debug.assert(node.kind == .expr_match)
|
||||||
|
return MatchView{ subject = node.main.node_id }
|
||||||
|
}
|
||||||
|
|
||||||
|
match_arm_count proc(tree @Tree, id NodeId) usize {
|
||||||
|
node :: get_node(&tree.storage, id)
|
||||||
|
debug.assert(node.kind == .expr_match)
|
||||||
|
return usize(node.data1.extra - node.data0.extra)
|
||||||
|
}
|
||||||
|
|
||||||
|
match_arm proc(tree @Tree, id NodeId, index usize) NodeId {
|
||||||
|
node :: get_node(&tree.storage, id)
|
||||||
|
debug.assert(index < match_arm_count(tree, id))
|
||||||
|
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index])
|
||||||
|
}
|
||||||
|
|
||||||
|
match_arm_view proc(tree @Tree, id NodeId) MatchArmView {
|
||||||
|
node :: get_node(&tree.storage, id)
|
||||||
|
debug.assert(node.kind == .match_arm)
|
||||||
|
extra :: usize(node.data0.extra)
|
||||||
|
return MatchArmView{
|
||||||
|
capture = maybe_node_id(tree.storage.extra.items[extra]),
|
||||||
|
body = node.main.node_id,
|
||||||
|
expression = tree.storage.extra.items[extra + 1] != 0,
|
||||||
|
catch_all = tree.storage.extra.items[extra + 2] != 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match_pattern_count proc(tree @Tree, id NodeId) usize {
|
||||||
|
node :: get_node(&tree.storage, id)
|
||||||
|
debug.assert(node.kind == .match_arm)
|
||||||
|
return usize(node.data1.extra - node.data0.extra - 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
match_pattern proc(tree @Tree, id NodeId, index usize) NodeId {
|
||||||
|
node :: get_node(&tree.storage, id)
|
||||||
|
debug.assert(index < match_pattern_count(tree, id))
|
||||||
|
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + 3 + index])
|
||||||
|
}
|
||||||
|
|
||||||
record_field_count proc(tree @Tree, id NodeId) usize {
|
record_field_count proc(tree @Tree, id NodeId) usize {
|
||||||
node :: get_node(&tree.storage, id)
|
node :: get_node(&tree.storage, id)
|
||||||
debug.assert(node.kind == .expr_record_type or node.kind == .expr_record_literal)
|
debug.assert(node.kind == .expr_record_type or node.kind == .expr_record_literal or node.kind == .expr_union_type)
|
||||||
return usize(node.data1.extra - node.data0.extra)
|
return usize(node.data1.extra - node.data0.extra)
|
||||||
}
|
}
|
||||||
|
|
||||||
record_field proc(tree @Tree, id NodeId, index usize) NodeId {
|
record_field proc(tree @Tree, id NodeId, index usize) NodeId {
|
||||||
node :: get_node(&tree.storage, id)
|
node :: get_node(&tree.storage, id)
|
||||||
debug.assert(node.kind == .expr_record_type or node.kind == .expr_record_literal)
|
debug.assert(node.kind == .expr_record_type or node.kind == .expr_record_literal or node.kind == .expr_union_type)
|
||||||
debug.assert(index < usize(node.data1.extra - node.data0.extra))
|
debug.assert(index < usize(node.data1.extra - node.data0.extra))
|
||||||
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index])
|
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,6 +76,19 @@ Code :: enum {
|
|||||||
namespace_not_value
|
namespace_not_value
|
||||||
not_namespace
|
not_namespace
|
||||||
invalid_import_alias
|
invalid_import_alias
|
||||||
|
invalid_enum_backing
|
||||||
|
invalid_enum_value
|
||||||
|
enum_context_required
|
||||||
|
invalid_distinct_backing
|
||||||
|
invalid_union_tag
|
||||||
|
union_initializer_count
|
||||||
|
invalid_match_subject
|
||||||
|
invalid_match_pattern
|
||||||
|
duplicate_match_pattern
|
||||||
|
non_exhaustive_match
|
||||||
|
unreachable_match_arm
|
||||||
|
invalid_match_capture
|
||||||
|
inactive_union_field
|
||||||
}
|
}
|
||||||
|
|
||||||
Diagnostic :: struct {
|
Diagnostic :: struct {
|
||||||
@@ -173,5 +186,18 @@ messages :: $enummap.init(Code, []u8, {
|
|||||||
namespace_not_value = "package namespace cannot be used as a value",
|
namespace_not_value = "package namespace cannot be used as a value",
|
||||||
not_namespace = "member selection requires a package namespace",
|
not_namespace = "member selection requires a package namespace",
|
||||||
invalid_import_alias = "import requires an explicit valid identifier alias",
|
invalid_import_alias = "import requires an explicit valid identifier alias",
|
||||||
|
invalid_enum_backing = "enum backing must be a concrete integer type",
|
||||||
|
invalid_enum_value = "enum values require explicit backing and increasing representable constant integers",
|
||||||
|
enum_context_required = "enum literal requires an enum or tagged union context",
|
||||||
|
invalid_distinct_backing = "distinct requires a concrete runtime backing type",
|
||||||
|
invalid_union_tag = "union tag must be an enum containing every union variant",
|
||||||
|
union_initializer_count = "union construction requires exactly one member",
|
||||||
|
invalid_match_subject = "match subject must be an enum, tagged union, or scalar",
|
||||||
|
invalid_match_pattern = "invalid match pattern",
|
||||||
|
duplicate_match_pattern = "duplicate or overlapping match pattern",
|
||||||
|
non_exhaustive_match = "match is not exhaustive",
|
||||||
|
unreachable_match_arm = "match arm is unreachable",
|
||||||
|
invalid_match_capture = "payload capture requires compatible nonvoid tagged union variants",
|
||||||
|
inactive_union_field = "tagged union field is not active",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ Address :: struct {
|
|||||||
cells []mut ir.Value
|
cells []mut ir.Value
|
||||||
scalar ?@mut ir.Value = null
|
scalar ?@mut ir.Value = null
|
||||||
initialized ?@mut bool = null
|
initialized ?@mut bool = null
|
||||||
|
raw bool = false
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
@@ -104,6 +105,7 @@ run proc(
|
|||||||
if instruction_index == block.instructions.items.len {
|
if instruction_index == block.instructions.items.len {
|
||||||
terminator :: block.terminator
|
terminator :: block.terminator
|
||||||
if (terminator.kind == .trap) return Outcome{ trap = terminator.diagnostic }
|
if (terminator.kind == .trap) return Outcome{ trap = terminator.diagnostic }
|
||||||
|
if (terminator.kind == .unreachable) unreachable
|
||||||
if (terminator.kind == .jump or terminator.kind == .branch) {
|
if (terminator.kind == .jump or terminator.kind == .branch) {
|
||||||
target := terminator.target
|
target := terminator.target
|
||||||
if (terminator.kind == .branch) {
|
if (terminator.kind == .branch) {
|
||||||
@@ -121,7 +123,7 @@ run proc(
|
|||||||
completed :: frames.items[frame_index]
|
completed :: frames.items[frame_index]
|
||||||
# transfer leaves while the completed frame still owns its backing.
|
# transfer leaves while the completed frame still owns its backing.
|
||||||
if completed.global |global_index| {
|
if completed.global |global_index| {
|
||||||
copy_value(&globals[global_index].value, value)
|
copy_value(module, &globals[global_index].value, value)
|
||||||
globals[global_index].status = .ready
|
globals[global_index].status = .ready
|
||||||
}
|
}
|
||||||
if (frames.items.len == 1) {
|
if (frames.items.len == 1) {
|
||||||
@@ -134,7 +136,7 @@ run proc(
|
|||||||
return Outcome{ value = value }
|
return Outcome{ value = value }
|
||||||
}
|
}
|
||||||
if completed.destination != ir.no_value {
|
if completed.destination != ir.no_value {
|
||||||
copy_value(&frames.items[frame_index - 1].values[usize(completed.destination)], value)
|
copy_value(module, &frames.items[frame_index - 1].values[usize(completed.destination)], value)
|
||||||
}
|
}
|
||||||
_ = arraylist.pop(&frames)
|
_ = arraylist.pop(&frames)
|
||||||
free_frame(allocator, completed)
|
free_frame(allocator, completed)
|
||||||
@@ -155,7 +157,7 @@ run proc(
|
|||||||
}
|
}
|
||||||
.store_local: {
|
.store_local: {
|
||||||
local :: &frames.items[frame_index].locals[usize(instruction.slot)]
|
local :: &frames.items[frame_index].locals[usize(instruction.slot)]
|
||||||
copy_value(&local.value, frames.items[frame_index].values[usize(instruction.left)])
|
copy_value(module, &local.value, frames.items[frame_index].values[usize(instruction.left)])
|
||||||
local.initialized = true
|
local.initialized = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -164,7 +166,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)
|
||||||
}
|
}
|
||||||
copy_value(&local.value, frames.items[frame_index].values[usize(instruction.left)])
|
copy_value(module, &local.value, frames.items[frame_index].values[usize(instruction.left)])
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
.reset_local: {
|
.reset_local: {
|
||||||
@@ -199,7 +201,7 @@ run proc(
|
|||||||
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]
|
||||||
local :: &frames.items[frame_index + 1].locals[argument_index]
|
local :: &frames.items[frame_index + 1].locals[argument_index]
|
||||||
copy_value(&local.value, frames.items[frame_index].values[usize(argument)])
|
copy_value(module, &local.value, frames.items[frame_index].values[usize(argument)])
|
||||||
local.initialized = true
|
local.initialized = true
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
@@ -212,7 +214,8 @@ run proc(
|
|||||||
element :: frames.items[frame_index].values[usize(operand)]
|
element :: frames.items[frame_index].values[usize(operand)]
|
||||||
if (ir.aggregate(element.type)) {
|
if (ir.aggregate(element.type)) {
|
||||||
count :: element.data.elements.len
|
count :: element.data.elements.len
|
||||||
memcopy!(destination.data.elements[offset..offset + count], element.data.elements)
|
target :: aggregate_view(element.type, element.array_type, destination.data.elements[offset..offset + count])
|
||||||
|
ir.copy_aggregate(module, target, element)
|
||||||
offset += count
|
offset += count
|
||||||
} else {
|
} else {
|
||||||
destination.data.elements[offset] = element
|
destination.data.elements[offset] = element
|
||||||
@@ -221,6 +224,18 @@ run proc(
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
.union_literal: {
|
||||||
|
destination :: frames.items[frame_index].values[usize(instruction.destination)]
|
||||||
|
shape :: module.arrays.items[usize(instruction.array_type)]
|
||||||
|
mem.set(destination.data.elements, ir.Value{ type = .u8, data = ir.ValueData{ unsigned = 0 } })
|
||||||
|
if (shape.tag_type != .void) ir.write_storage(module, destination.data.elements, instruction.constant)
|
||||||
|
if (instruction.left != ir.no_value) {
|
||||||
|
payload :: frames.items[frame_index].values[usize(instruction.left)]
|
||||||
|
ir.write_storage(module, destination.data.elements[shape.payload_offset..], payload)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
.union_tag: value = ir.union_tag(module, frames.items[frame_index].values[usize(instruction.left)])
|
||||||
.index: {
|
.index: {
|
||||||
base :: frames.items[frame_index].values[usize(instruction.left)]
|
base :: frames.items[frame_index].values[usize(instruction.left)]
|
||||||
index :: frames.items[frame_index].values[usize(instruction.right)].data.unsigned
|
index :: frames.items[frame_index].values[usize(instruction.right)].data.unsigned
|
||||||
@@ -232,6 +247,10 @@ run proc(
|
|||||||
}
|
}
|
||||||
.field: {
|
.field: {
|
||||||
base :: frames.items[frame_index].values[usize(instruction.left)]
|
base :: frames.items[frame_index].values[usize(instruction.left)]
|
||||||
|
shape :: module.arrays.items[usize(base.array_type)]
|
||||||
|
if (shape.is_union and !ir.union_active(module, base, usize(instruction.slot))) {
|
||||||
|
return try runtime_trap(diagnostic_store, .inactive_union_field, instruction.span)
|
||||||
|
}
|
||||||
value = ir.aggregate_item(module, base, usize(instruction.slot))
|
value = ir.aggregate_item(module, base, usize(instruction.slot))
|
||||||
}
|
}
|
||||||
.address_local: {
|
.address_local: {
|
||||||
@@ -284,26 +303,37 @@ run proc(
|
|||||||
if (index >= u64(shape.len)) {
|
if (index >= u64(shape.len)) {
|
||||||
return try runtime_trap(diagnostic_store, .index_out_of_bounds, instruction.span)
|
return try runtime_trap(diagnostic_store, .index_out_of_bounds, instruction.span)
|
||||||
}
|
}
|
||||||
width :: element_cells(module, shape)
|
width :: if (base.raw) ir.storage_size(module, shape.element, shape.element_array) else element_cells(module, shape)
|
||||||
start :: usize(index) * width
|
start :: usize(index) * width
|
||||||
address := Address{
|
address := Address{
|
||||||
type = instruction.pointee, array_type = instruction.array_type,
|
type = instruction.pointee, array_type = instruction.array_type,
|
||||||
cells = base.cells[start..start + width], initialized = base.initialized,
|
cells = base.cells[start..start + width], initialized = base.initialized, raw = base.raw,
|
||||||
}
|
}
|
||||||
if (!ir.aggregate(address.type)) address.scalar = &address.cells[0]
|
if (!address.raw and !ir.aggregate(address.type)) address.scalar = &address.cells[0]
|
||||||
frames.items[frame_index].addresses[usize(instruction.destination)] = address
|
frames.items[frame_index].addresses[usize(instruction.destination)] = address
|
||||||
value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } }
|
value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } }
|
||||||
}
|
}
|
||||||
.field_address: {
|
.field_address: {
|
||||||
base_id :: frames.items[frame_index].values[usize(instruction.left)].data.address
|
base_id :: frames.items[frame_index].values[usize(instruction.left)].data.address
|
||||||
base :: frames.items[frame_index].addresses[usize(base_id)]
|
base :: frames.items[frame_index].addresses[usize(base_id)]
|
||||||
field :: module.arrays.items[usize(base.array_type)].fields[usize(instruction.slot)]
|
shape :: module.arrays.items[usize(base.array_type)]
|
||||||
width :: if (ir.aggregate(field.type)) module.arrays.items[usize(field.array_type)].cells else 1
|
field :: shape.fields[usize(instruction.slot)]
|
||||||
|
if (shape.is_union and shape.tag_type != .void) {
|
||||||
|
if base.initialized |initialized| {
|
||||||
|
if (!initialized^) return try runtime_trap(diagnostic_store, .read_before_initialization, instruction.span)
|
||||||
|
}
|
||||||
|
if (!ir.union_active(module, aggregate_view(.record, base.array_type, base.cells), usize(instruction.slot))) {
|
||||||
|
return try runtime_trap(diagnostic_store, .inactive_union_field, instruction.span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
raw :: base.raw or shape.is_union
|
||||||
|
width :: if (raw) ir.storage_size(module, field.type, field.array_type) else if (ir.aggregate(field.type)) module.arrays.items[usize(field.array_type)].cells else 1
|
||||||
|
start :: if (raw) field.byte_offset else field.offset
|
||||||
address := Address{
|
address := Address{
|
||||||
type = instruction.pointee, array_type = instruction.array_type,
|
type = instruction.pointee, array_type = instruction.array_type,
|
||||||
cells = base.cells[field.offset..field.offset + width], initialized = base.initialized,
|
cells = base.cells[start..start + width], initialized = base.initialized, raw = raw,
|
||||||
}
|
}
|
||||||
if (!ir.aggregate(address.type)) address.scalar = &address.cells[0]
|
if (!raw and !ir.aggregate(address.type)) address.scalar = &address.cells[0]
|
||||||
frames.items[frame_index].addresses[usize(instruction.destination)] = address
|
frames.items[frame_index].addresses[usize(instruction.destination)] = address
|
||||||
value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } }
|
value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } }
|
||||||
}
|
}
|
||||||
@@ -317,10 +347,12 @@ run proc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (instruction.op == .load_address) {
|
if (instruction.op == .load_address) {
|
||||||
value = if (ir.aggregate(address.type)) aggregate_view(address.type, address.array_type, address.cells) else address.scalar?^
|
if (address.raw) value = ir.read_storage(module, address.type, address.array_type, address.cells, true)
|
||||||
|
else value = if (ir.aggregate(address.type)) aggregate_view(address.type, address.array_type, address.cells) else address.scalar?^
|
||||||
} else {
|
} else {
|
||||||
source :: frames.items[frame_index].values[usize(instruction.right)]
|
source :: frames.items[frame_index].values[usize(instruction.right)]
|
||||||
if (ir.aggregate(address.type)) memcopy!(address.cells, source.data.elements)
|
if (address.raw) ir.write_storage(module, address.cells, source)
|
||||||
|
else if (ir.aggregate(address.type)) ir.copy_aggregate(module, aggregate_view(address.type, address.array_type, address.cells), source)
|
||||||
else address.scalar?^ = source
|
else address.scalar?^ = source
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -421,7 +453,7 @@ run proc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if instruction.destination != ir.no_value {
|
if instruction.destination != ir.no_value {
|
||||||
copy_value(&frames.items[frame_index].values[usize(instruction.destination)], value)
|
copy_value(module, &frames.items[frame_index].values[usize(instruction.destination)], value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unreachable
|
unreachable
|
||||||
@@ -518,10 +550,10 @@ aggregate_view proc(type ir.Type, shape ir.ArrayId, cells []mut ir.Value) ir.Val
|
|||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
copy_value proc(destination @mut ir.Value, source ir.Value) void {
|
copy_value proc(module @ir.Module, destination @mut ir.Value, source ir.Value) void {
|
||||||
if (ir.aggregate(source.type)) {
|
if (ir.aggregate(source.type)) {
|
||||||
# aggregate destinations already own disjoint fixed segments; never install an alias.
|
# aggregate destinations already own disjoint fixed segments; never install an alias.
|
||||||
memcopy!(destination.data.elements, source.data.elements)
|
ir.copy_aggregate(module, destination^, source)
|
||||||
} else {
|
} else {
|
||||||
destination^ = source
|
destination^ = source
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1463,3 +1463,126 @@ inclusive_unsigned_range_reaches_the_maximum_without_incrementing_past_it test {
|
|||||||
try testing.expect(outcome.trap == null)
|
try testing.expect(outcome.trap == null)
|
||||||
try testing.expect_equal(u64(2), outcome.value.data.unsigned)
|
try testing.expect_equal(u64(2), outcome.value.data.unsigned)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
union_storage_overlays_nested_record_and_array_payloads_and_copies_by_value test {
|
||||||
|
module := ir.init(mem.c_allocator)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&store)
|
||||||
|
record_fields [2]ir.RecordField :: [ir.RecordField{ type = .u8 }, ir.RecordField{ type = .u32 }]
|
||||||
|
record :: try ir.add_record(&module, record_fields[..])
|
||||||
|
bytes :: try add_array(&module, .u8, ir.no_array, 8)
|
||||||
|
variants [2]ir.RecordField :: [
|
||||||
|
ir.RecordField{ type = .record, array_type = record },
|
||||||
|
ir.RecordField{ type = .array, array_type = bytes },
|
||||||
|
]
|
||||||
|
shape :: try ir.add_union(&module, variants[..], false)
|
||||||
|
identity :: try add_function(&module, &[], 0, .record)
|
||||||
|
_ = try ir.add_parameter(&module, identity, .record, shape)
|
||||||
|
module.functions.items[usize(identity)].result_array_type = shape
|
||||||
|
try emit(&module, identity, ir.Instruction{ op = .load_local, type = .record, array_type = shape, destination = ir.ValueId(0) })
|
||||||
|
return_value(&module, identity, ir.ValueId(0))
|
||||||
|
main :: try add_function(&module, &[], 0, .u32)
|
||||||
|
_ = try ir.add_local(&module, main, .record, shape)
|
||||||
|
module.entry = main
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .constant, type = .u8, destination = ir.ValueId(0), constant = ir.Value{ type = .u8, data = ir.ValueData{ unsigned = 1 } } })
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .constant, type = .u32, destination = ir.ValueId(1), constant = ir.Value{ type = .u32, data = ir.ValueData{ unsigned = 287454020 } } })
|
||||||
|
block :: &module.functions.items[usize(main)].blocks.items[0]
|
||||||
|
try arraylist.append(&block.arguments, ir.ValueId(0))
|
||||||
|
try arraylist.append(&block.arguments, ir.ValueId(1))
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .record_literal, type = .record, array_type = record, destination = ir.ValueId(2), arguments_count = 2 })
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .union_literal, type = .record, array_type = shape, destination = ir.ValueId(3), left = ir.ValueId(2) })
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .store_local, left = ir.ValueId(3) })
|
||||||
|
try emit(&module, main, function_value(4, identity))
|
||||||
|
try arraylist.append(&block.arguments, ir.ValueId(3))
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .call, type = .record, array_type = shape, destination = ir.ValueId(5), left = ir.ValueId(4), arguments_start = 2, arguments_count = 1 })
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .address_local, type = .address, pointee = .record, array_type = shape, destination = ir.ValueId(6) })
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .field_address, type = .address, pointee = .array, array_type = bytes, destination = ir.ValueId(7), left = ir.ValueId(6), slot = 1 })
|
||||||
|
try emit(&module, main, unsigned(8, 4))
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .index_address, type = .address, pointee = .u8, destination = ir.ValueId(9), left = ir.ValueId(7), right = ir.ValueId(8) })
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .constant, type = .u8, destination = ir.ValueId(10), constant = ir.Value{ type = .u8, data = ir.ValueData{ unsigned = 153 } } })
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .store_address, left = ir.ValueId(9), right = ir.ValueId(10) })
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .load_local, type = .record, array_type = shape, destination = ir.ValueId(11) })
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .field, type = .record, array_type = record, destination = ir.ValueId(12), left = ir.ValueId(11) })
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .field, type = .u32, destination = ir.ValueId(13), left = ir.ValueId(12), slot = 1 })
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .field, type = .record, array_type = record, destination = ir.ValueId(14), left = ir.ValueId(5) })
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .field, type = .u32, destination = ir.ValueId(15), left = ir.ValueId(14), slot = 1 })
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .subtract, type = .u32, destination = ir.ValueId(16), left = ir.ValueId(13), right = ir.ValueId(15) })
|
||||||
|
return_value(&module, main, ir.ValueId(16))
|
||||||
|
outcome := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&outcome)
|
||||||
|
try testing.expect(outcome.trap == null)
|
||||||
|
try testing.expect_equal(u64(85), outcome.value.data.unsigned)
|
||||||
|
}
|
||||||
|
|
||||||
|
tagged_unions_preserve_actual_tags_and_trap_inactive_value_and_address_projections test {
|
||||||
|
operations [2]ir.Op :: [.field, .field_address]
|
||||||
|
for (operations) |operation| {
|
||||||
|
module := ir.init(mem.c_allocator)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&store)
|
||||||
|
tag :: ir.Value{ type = .i8, data = ir.ValueData{ integer = -1 } }
|
||||||
|
variants [2]ir.RecordField :: [
|
||||||
|
ir.RecordField{ type = .int, tag = tag },
|
||||||
|
ir.RecordField{ type = .int, tag = ir.Value{ type = .i8, data = ir.ValueData{ integer = 37 } } },
|
||||||
|
]
|
||||||
|
shape :: try ir.add_union(&module, variants[..], true)
|
||||||
|
main :: try add_function(&module, &[], 0, .i8)
|
||||||
|
_ = try ir.add_local(&module, main, .record, shape)
|
||||||
|
module.entry = main
|
||||||
|
try emit(&module, main, integer(0, 42))
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .union_literal, type = .record, array_type = shape, destination = ir.ValueId(1), left = ir.ValueId(0), constant = tag })
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .union_tag, type = .i8, destination = ir.ValueId(2), left = 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(i64(-1), outcome.value.data.integer)
|
||||||
|
base := ir.ValueId(1)
|
||||||
|
if (operation == .field_address) {
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .store_local, left = base })
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .address_local, type = .address, pointee = .record, array_type = shape, destination = ir.ValueId(3) })
|
||||||
|
base = ir.ValueId(3)
|
||||||
|
}
|
||||||
|
span :: diagnostics.Span{ start = 11, end = 15 }
|
||||||
|
type :: if (operation == .field) ir.Type.int else ir.Type.address
|
||||||
|
try emit(&module, main, ir.Instruction{ op = operation, type = type, pointee = .int, destination = ir.ValueId(4), left = base, slot = 1, span = span })
|
||||||
|
trapped := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&trapped)
|
||||||
|
try expect_trap(trapped, &store, .inactive_union_field, span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
union_scalar_reinterpretation_and_zero_leaf_payloads_keep_their_shapes test {
|
||||||
|
module := ir.init(mem.c_allocator)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&store)
|
||||||
|
empty :: try ir.add_record(&module, &[])
|
||||||
|
variants [3]ir.RecordField :: [
|
||||||
|
ir.RecordField{ type = .float },
|
||||||
|
ir.RecordField{ type = .u64 },
|
||||||
|
ir.RecordField{ type = .record, array_type = empty },
|
||||||
|
]
|
||||||
|
shape :: try ir.add_union(&module, variants[..], false)
|
||||||
|
main :: try add_function(&module, &[], 0, .u64)
|
||||||
|
module.entry = main
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .constant, type = .float, destination = ir.ValueId(0), constant = ir.Value{ type = .float, data = ir.ValueData{ real = 1.0 } } })
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .union_literal, type = .record, array_type = shape, destination = ir.ValueId(1), left = ir.ValueId(0) })
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .field, type = .u64, destination = ir.ValueId(2), left = ir.ValueId(1), slot = 1 })
|
||||||
|
return_value(&module, main, ir.ValueId(2))
|
||||||
|
bits := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&bits)
|
||||||
|
try testing.expect(bits.trap == null)
|
||||||
|
try testing.expect_equal(u64(4607182418800017408), bits.value.data.unsigned)
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .field, type = .record, array_type = empty, destination = ir.ValueId(3), left = ir.ValueId(1), slot = 2 })
|
||||||
|
module.functions.items[usize(main)].result = .record
|
||||||
|
module.functions.items[usize(main)].result_array_type = empty
|
||||||
|
return_value(&module, main, ir.ValueId(3))
|
||||||
|
zero := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&zero)
|
||||||
|
try testing.expect(zero.trap == null)
|
||||||
|
try testing.expect_equal(empty, zero.value.array_type)
|
||||||
|
try testing.expect_equal(usize(0), zero.value.data.elements.len)
|
||||||
|
}
|
||||||
|
|||||||
+189
-9
@@ -85,6 +85,7 @@ ValueData :: union {
|
|||||||
Value :: struct {
|
Value :: struct {
|
||||||
type Type = .void
|
type Type = .void
|
||||||
array_type ArrayId = no_array
|
array_type ArrayId = no_array
|
||||||
|
raw bool = false
|
||||||
data ValueData = ValueData{ integer = 0 }
|
data ValueData = ValueData{ integer = 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,10 +93,12 @@ RecordField :: struct {
|
|||||||
type Type
|
type Type
|
||||||
array_type ArrayId = no_array
|
array_type ArrayId = no_array
|
||||||
offset usize = 0
|
offset usize = 0
|
||||||
|
byte_offset usize = 0
|
||||||
|
tag Value = Value{}
|
||||||
}
|
}
|
||||||
|
|
||||||
#! fixed recursive aggregate shape; constructors derive the flattened scalar-leaf count.
|
#! fixed recursive aggregate shape; cells count interpreter slots, byte_size describes native layout.
|
||||||
#! record fields are owned by the module; array metadata retains its existing representation.
|
#! unions retain overlapping byte cells; ordinary aggregates retain flattened scalar leaves.
|
||||||
ArrayType :: struct {
|
ArrayType :: struct {
|
||||||
kind Type = .array
|
kind Type = .array
|
||||||
element Type = .invalid
|
element Type = .invalid
|
||||||
@@ -104,6 +107,11 @@ ArrayType :: struct {
|
|||||||
cells usize = 0
|
cells usize = 0
|
||||||
mutable bool = false
|
mutable bool = false
|
||||||
fields []mut RecordField = &[]
|
fields []mut RecordField = &[]
|
||||||
|
is_union bool = false
|
||||||
|
tag_type Type = .void
|
||||||
|
payload_offset usize = 0
|
||||||
|
byte_size usize = 0
|
||||||
|
alignment usize = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
#! maps a local slot or temporary to its preallocated, by-value frame segment.
|
#! maps a local slot or temporary to its preallocated, by-value frame segment.
|
||||||
@@ -123,6 +131,8 @@ Op :: enum {
|
|||||||
load_global
|
load_global
|
||||||
array_literal
|
array_literal
|
||||||
record_literal
|
record_literal
|
||||||
|
union_literal
|
||||||
|
union_tag
|
||||||
field
|
field
|
||||||
index
|
index
|
||||||
address_local
|
address_local
|
||||||
@@ -167,6 +177,7 @@ TerminatorKind :: enum {
|
|||||||
trap,
|
trap,
|
||||||
jump,
|
jump,
|
||||||
branch,
|
branch,
|
||||||
|
unreachable,
|
||||||
}
|
}
|
||||||
|
|
||||||
Terminator :: struct {
|
Terminator :: struct {
|
||||||
@@ -266,6 +277,8 @@ add_array proc(
|
|||||||
cells :: len * width
|
cells :: len * width
|
||||||
if (cells > divtrunc!(maxval!(usize), sizeof!(Value))) return .storage_limit_reached
|
if (cells > divtrunc!(maxval!(usize), sizeof!(Value))) return .storage_limit_reached
|
||||||
if (module.arrays.items.len >= usize(no_array)) return .storage_limit_reached
|
if (module.arrays.items.len >= usize(no_array)) return .storage_limit_reached
|
||||||
|
stride :: storage_size(module, element, element_array)
|
||||||
|
if (stride != 0 and len > divtrunc!(maxval!(usize), stride)) return .storage_limit_reached
|
||||||
id :: ArrayId(module.arrays.items.len)
|
id :: ArrayId(module.arrays.items.len)
|
||||||
try arraylist.append(&module.arrays, ArrayType{
|
try arraylist.append(&module.arrays, ArrayType{
|
||||||
element = element,
|
element = element,
|
||||||
@@ -273,6 +286,8 @@ add_array proc(
|
|||||||
len = len,
|
len = len,
|
||||||
cells = cells,
|
cells = cells,
|
||||||
mutable = mutable,
|
mutable = mutable,
|
||||||
|
byte_size = len * stride,
|
||||||
|
alignment = storage_alignment(module, element, element_array),
|
||||||
})
|
})
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
@@ -286,19 +301,35 @@ add_record proc(
|
|||||||
owned :: try mem.alloc(RecordField, module.allocator, fields.len)
|
owned :: try mem.alloc(RecordField, module.allocator, fields.len)
|
||||||
errdefer mem.free(module.allocator, owned)
|
errdefer mem.free(module.allocator, owned)
|
||||||
cells usize := 0
|
cells usize := 0
|
||||||
|
bytes usize := 0
|
||||||
|
alignment usize := 1
|
||||||
for (fields) |field, index| {
|
for (fields) |field, index| {
|
||||||
debug.assert(aggregate(field.type) == (field.array_type != no_array))
|
debug.assert(aggregate(field.type) == (field.array_type != no_array))
|
||||||
width :: if (aggregate(field.type)) module.arrays.items[usize(field.array_type)].cells else 1
|
width :: if (aggregate(field.type)) module.arrays.items[usize(field.array_type)].cells else 1
|
||||||
if (width > divtrunc!(maxval!(usize), sizeof!(Value)) - cells) return .storage_limit_reached
|
if (width > divtrunc!(maxval!(usize), sizeof!(Value)) - cells) return .storage_limit_reached
|
||||||
owned[index] = RecordField{ type = field.type, array_type = field.array_type, offset = cells }
|
field_alignment :: storage_alignment(module, field.type, field.array_type)
|
||||||
|
if (field_alignment > alignment) alignment = field_alignment
|
||||||
|
if (bytes > maxval!(usize) - (field_alignment - 1)) return .storage_limit_reached
|
||||||
|
bytes = aligned_size(bytes, field_alignment)
|
||||||
|
size :: storage_size(module, field.type, field.array_type)
|
||||||
|
if (size > maxval!(usize) - bytes) return .storage_limit_reached
|
||||||
|
owned[index] = RecordField{
|
||||||
|
type = field.type, array_type = field.array_type, offset = cells,
|
||||||
|
byte_offset = bytes, tag = field.tag,
|
||||||
|
}
|
||||||
|
bytes += size
|
||||||
cells += width
|
cells += width
|
||||||
}
|
}
|
||||||
id :: ArrayId(module.arrays.items.len)
|
id :: ArrayId(module.arrays.items.len)
|
||||||
try arraylist.append(&module.arrays, ArrayType{ kind = .record, fields = owned, cells = cells })
|
if (bytes > maxval!(usize) - (alignment - 1)) return .storage_limit_reached
|
||||||
|
try arraylist.append(&module.arrays, ArrayType{
|
||||||
|
kind = .record, fields = owned, cells = cells,
|
||||||
|
byte_size = aligned_size(bytes, alignment), alignment = alignment,
|
||||||
|
})
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
#! returns a borrowed child value from flattened aggregate storage, preserving zero-cell shapes.
|
#! returns a borrowed child, including a raw view when projecting overlapping union storage.
|
||||||
aggregate_item proc(module @Module, value Value, index usize) Value {
|
aggregate_item proc(module @Module, value Value, index usize) Value {
|
||||||
shape :: module.arrays.items[usize(value.array_type)]
|
shape :: module.arrays.items[usize(value.array_type)]
|
||||||
type := shape.element
|
type := shape.element
|
||||||
@@ -310,13 +341,162 @@ aggregate_item proc(module @Module, value Value, index usize) Value {
|
|||||||
type = field.type
|
type = field.type
|
||||||
child = field.array_type
|
child = field.array_type
|
||||||
start = field.offset
|
start = field.offset
|
||||||
|
if (value.raw) start = field.byte_offset
|
||||||
|
if (shape.is_union) start = shape.payload_offset
|
||||||
} else {
|
} else {
|
||||||
if (aggregate(type)) width = module.arrays.items[usize(child)].cells
|
if (value.raw) width = storage_size(module, type, child)
|
||||||
|
else if (aggregate(type)) width = module.arrays.items[usize(child)].cells
|
||||||
start = index * width
|
start = index * width
|
||||||
}
|
}
|
||||||
if (!aggregate(type)) return value.data.elements[start]
|
raw :: value.raw or shape.is_union
|
||||||
width = module.arrays.items[usize(child)].cells
|
if (!aggregate(type)) {
|
||||||
return Value{ type = type, array_type = child, data = ValueData{ elements = value.data.elements[start..start + width] } }
|
if (raw) return read_storage(module, type, child, value.data.elements[start..], true)
|
||||||
|
return value.data.elements[start]
|
||||||
|
}
|
||||||
|
width = if (raw) storage_size(module, type, child) else module.arrays.items[usize(child)].cells
|
||||||
|
return Value{
|
||||||
|
type = type, array_type = child, raw = raw,
|
||||||
|
data = ValueData{ elements = value.data.elements[start..start + width] },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#! copies variant descriptors; all payloads share one eight-byte-aligned storage region.
|
||||||
|
add_union proc(module @mut Module, fields []RecordField, tagged bool) ArrayId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
|
if (module.arrays.items.len >= usize(no_array)) return .storage_limit_reached
|
||||||
|
owned :: try mem.alloc(RecordField, module.allocator, fields.len)
|
||||||
|
errdefer mem.free(module.allocator, owned)
|
||||||
|
size usize := 0
|
||||||
|
for (fields) |field, index| {
|
||||||
|
width :: storage_size(module, field.type, field.array_type)
|
||||||
|
if (width > size) size = width
|
||||||
|
owned[index] = field
|
||||||
|
owned[index].offset = if (tagged) 8 else 0
|
||||||
|
owned[index].byte_offset = owned[index].offset
|
||||||
|
}
|
||||||
|
prefix usize :: if (tagged) 8 else 0
|
||||||
|
if (size > divtrunc!(maxval!(usize), sizeof!(Value)) - 15) return .storage_limit_reached
|
||||||
|
size = aligned_size(size, 8) + prefix
|
||||||
|
id :: ArrayId(module.arrays.items.len)
|
||||||
|
tag_type :: if (tagged and fields.len != 0) fields[0].tag.type else Type.void
|
||||||
|
try arraylist.append(&module.arrays, ArrayType{
|
||||||
|
kind = .record, fields = owned, is_union = true,
|
||||||
|
tag_type = tag_type,
|
||||||
|
payload_offset = prefix, cells = size, byte_size = size, alignment = 8,
|
||||||
|
})
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
aligned_size proc(size, alignment usize) usize {
|
||||||
|
return divtrunc!(size + alignment - 1, alignment) * alignment
|
||||||
|
}
|
||||||
|
|
||||||
|
storage_alignment proc(module @Module, type Type, shape ArrayId) usize {
|
||||||
|
if (aggregate(type)) return module.arrays.items[usize(shape)].alignment
|
||||||
|
bits :: integer_bits(type)
|
||||||
|
if (bits != 0) return divtrunc!(bits, 8)
|
||||||
|
return if (type == .float or type == .string or type == .function) 8 else 1
|
||||||
|
}
|
||||||
|
|
||||||
|
storage_size proc(module @Module, type Type, shape ArrayId) usize {
|
||||||
|
if (aggregate(type)) return module.arrays.items[usize(shape)].byte_size
|
||||||
|
bits :: integer_bits(type)
|
||||||
|
if (bits != 0) return divtrunc!(bits, 8)
|
||||||
|
return match type {
|
||||||
|
.float, .function: 8
|
||||||
|
.string: 16
|
||||||
|
.bool: 1
|
||||||
|
.void: 0
|
||||||
|
else: unreachable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#! loads a scalar or borrows an aggregate from byte cells without allocating.
|
||||||
|
read_storage proc(module @Module, type Type, shape ArrayId, cells []mut Value, raw bool) Value {
|
||||||
|
if (aggregate(type)) {
|
||||||
|
return Value{ type = type, array_type = shape, raw = raw, data = ValueData{ elements = cells[..storage_size(module, type, shape)] } }
|
||||||
|
}
|
||||||
|
bits u64 := 0
|
||||||
|
size :: storage_size(module, type, shape)
|
||||||
|
count :: if (size > 8) usize(8) else size
|
||||||
|
for (0..count) |i| bits += cells[i].data.unsigned << u64(i * 8)
|
||||||
|
data := ValueData{ unsigned = bits }
|
||||||
|
match type {
|
||||||
|
.i8: data = ValueData{ integer = i64(ptrcast!(@i8, &bits)^) }
|
||||||
|
.i16: data = ValueData{ integer = i64(ptrcast!(@i16, &bits)^) }
|
||||||
|
.i32: data = ValueData{ integer = i64(ptrcast!(@i32, &bits)^) }
|
||||||
|
.int, .isize: data = ValueData{ integer = ptrcast!(@i64, &bits)^ }
|
||||||
|
.float: data = ValueData{ real = ptrcast!(@f64, &bits)^ }
|
||||||
|
.bool: data = ValueData{ boolean = (bits & 1) != 0 }
|
||||||
|
.string: data = ValueData{ string = StringId(bits) }
|
||||||
|
.function: data = ValueData{ function = FunctionId(bits) }
|
||||||
|
.void: return Value{}
|
||||||
|
else: {}
|
||||||
|
}
|
||||||
|
return Value{ type = type, data = data }
|
||||||
|
}
|
||||||
|
|
||||||
|
#! stores a value in shared byte cells; recursive aggregates preserve their native field padding.
|
||||||
|
write_storage proc(module @Module, cells []mut Value, value Value) void {
|
||||||
|
if (aggregate(value.type)) {
|
||||||
|
shape :: module.arrays.items[usize(value.array_type)]
|
||||||
|
if (value.raw or shape.is_union) {
|
||||||
|
memcopy!(cells[..shape.byte_size], value.data.elements)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
count :: if (shape.kind == .record) shape.fields.len else shape.len
|
||||||
|
for (0..count) |i| {
|
||||||
|
offset :: if (shape.kind == .record) shape.fields[i].byte_offset else i * storage_size(module, shape.element, shape.element_array)
|
||||||
|
write_storage(module, cells[offset..], aggregate_item(module, value, i))
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bits u64 := 0
|
||||||
|
match value.type {
|
||||||
|
.float: bits = ptrcast!(@u64, &value.data.real)^
|
||||||
|
.bool: bits = if (value.data.boolean) 1 else 0
|
||||||
|
.string: bits = u64(value.data.string)
|
||||||
|
.function: bits = u64(value.data.function)
|
||||||
|
.void: return
|
||||||
|
else: bits = ptrcast!(@u64, &value.data.integer)^
|
||||||
|
}
|
||||||
|
size :: storage_size(module, value.type, value.array_type)
|
||||||
|
for (0..size) |i| {
|
||||||
|
byte u64 := 0
|
||||||
|
if (i < 8) byte = (bits >> u64(i * 8)) & 255
|
||||||
|
else byte = (u64(string_bytes(module, value.data.string).len) >> u64((i - 8) * 8)) & 255
|
||||||
|
cells[i] = Value{ type = .u8, data = ValueData{ unsigned = byte } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#! converts raw aggregate projections into the caller's preallocated flattened segment.
|
||||||
|
copy_aggregate proc(module @Module, destination Value, source Value) void {
|
||||||
|
shape :: module.arrays.items[usize(source.array_type)]
|
||||||
|
if (!source.raw or shape.is_union) {
|
||||||
|
memcopy!(destination.data.elements, source.data.elements)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
count :: if (shape.kind == .record) shape.fields.len else shape.len
|
||||||
|
for (0..count) |i| {
|
||||||
|
child :: aggregate_item(module, source, i)
|
||||||
|
if (aggregate(child.type)) copy_aggregate(module, aggregate_item(module, destination, i), child)
|
||||||
|
else {
|
||||||
|
offset :: if (shape.kind == .record) shape.fields[i].offset else i
|
||||||
|
destination.data.elements[offset] = child
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
union_tag proc(module @Module, value Value) Value {
|
||||||
|
shape :: module.arrays.items[usize(value.array_type)]
|
||||||
|
return read_storage(module, shape.tag_type, no_array, value.data.elements, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
union_active proc(module @Module, value Value, field usize) bool {
|
||||||
|
shape :: module.arrays.items[usize(value.array_type)]
|
||||||
|
if (shape.tag_type == .void) return true
|
||||||
|
tag :: union_tag(module, value)
|
||||||
|
return tag.data.unsigned == shape.fields[field].tag.data.unsigned
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ keywords strmap.StaticStringMap(TokenKind) :: strmap.init([
|
|||||||
{ "or", .or },
|
{ "or", .or },
|
||||||
{ "mut", .mut },
|
{ "mut", .mut },
|
||||||
{ "struct", .struct },
|
{ "struct", .struct },
|
||||||
|
{ "enum", .enum },
|
||||||
|
{ "union", .union },
|
||||||
|
{ "distinct", .distinct },
|
||||||
|
{ "alias", .alias },
|
||||||
|
{ "match", .match },
|
||||||
{ "import", .import },
|
{ "import", .import },
|
||||||
{ "test", .test },
|
{ "test", .test },
|
||||||
])
|
])
|
||||||
@@ -85,7 +90,6 @@ scan proc(
|
|||||||
|
|
||||||
if char == '#' {
|
if char == '#' {
|
||||||
while (cursor < program.len and program[cursor] != '\n') cursor += 1
|
while (cursor < program.len and program[cursor] != '\n') cursor += 1
|
||||||
if (cursor < program.len) cursor += 1
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,12 +356,9 @@ scan_ident proc(
|
|||||||
cursor += 1
|
cursor += 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# don't intern keywords (already O(1) lookup via token kind)
|
|
||||||
kind :: strmap.get(&keywords, program[start..cursor]) orelse .ident
|
kind :: strmap.get(&keywords, program[start..cursor]) orelse .ident
|
||||||
str_id :: if (kind == .ident)
|
# member contexts accept keywords and therefore need their interned spelling too.
|
||||||
try strpool.intern(pool, program[start..cursor])
|
str_id :: try strpool.intern(pool, program[start..cursor])
|
||||||
else
|
|
||||||
strpool.no_str
|
|
||||||
return ScanIdentResult{ end = cursor, kind = kind, str_id = str_id }
|
return ScanIdentResult{ end = cursor, kind = kind, str_id = str_id }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,15 +13,16 @@ reports_invalid_input_and_keeps_scanning test {
|
|||||||
|
|
||||||
try scan(&state, "if name # comment\n$1. 2 3.5 \"hi\"", &pool, &diagnostic_store)
|
try scan(&state, "if name # comment\n$1. 2 3.5 \"hi\"", &pool, &diagnostic_store)
|
||||||
|
|
||||||
try testing.expect_equal(8, state.tokens.items.len)
|
try testing.expect_equal(9, state.tokens.items.len)
|
||||||
try testing.expect_equal(TokenKind.if, state.tokens.items[0].kind)
|
try testing.expect_equal(TokenKind.if, state.tokens.items[0].kind)
|
||||||
try testing.expect_equal(TokenKind.ident, state.tokens.items[1].kind)
|
try testing.expect_equal(TokenKind.ident, state.tokens.items[1].kind)
|
||||||
try testing.expect_equal(TokenKind.invalid, state.tokens.items[2].kind)
|
try testing.expect_equal(TokenKind.newline, state.tokens.items[2].kind)
|
||||||
try testing.expect_equal(TokenKind.invalid, state.tokens.items[3].kind)
|
try testing.expect_equal(TokenKind.invalid, state.tokens.items[3].kind)
|
||||||
try testing.expect_equal(TokenKind.int, state.tokens.items[4].kind)
|
try testing.expect_equal(TokenKind.invalid, state.tokens.items[4].kind)
|
||||||
try testing.expect_equal(TokenKind.float, state.tokens.items[5].kind)
|
try testing.expect_equal(TokenKind.int, state.tokens.items[5].kind)
|
||||||
try testing.expect_equal(TokenKind.string, state.tokens.items[6].kind)
|
try testing.expect_equal(TokenKind.float, state.tokens.items[6].kind)
|
||||||
try testing.expect_equal(TokenKind.eof, state.tokens.items[7].kind)
|
try testing.expect_equal(TokenKind.string, state.tokens.items[7].kind)
|
||||||
|
try testing.expect_equal(TokenKind.eof, state.tokens.items[8].kind)
|
||||||
try testing.expect_equal(2, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(2, diagnostics.count(&diagnostic_store))
|
||||||
try testing.expect_equal(
|
try testing.expect_equal(
|
||||||
"name",
|
"name",
|
||||||
@@ -142,7 +143,7 @@ scans_array_delimiters_and_mut_keyword test {
|
|||||||
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
}
|
}
|
||||||
|
|
||||||
reuses_identifiers_without_interning_keywords test {
|
reuses_identifier_spellings test {
|
||||||
pool := strpool.init(mem.c_allocator)
|
pool := strpool.init(mem.c_allocator)
|
||||||
defer strpool.deinit(&pool)
|
defer strpool.deinit(&pool)
|
||||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
@@ -155,8 +156,6 @@ reuses_identifiers_without_interning_keywords test {
|
|||||||
try testing.expect(first != second)
|
try testing.expect(first != second)
|
||||||
try testing.expect_equal(first, state.tokens.items[2].data.str_id)
|
try testing.expect_equal(first, state.tokens.items[2].data.str_id)
|
||||||
try testing.expect_equal(second, state.tokens.items[4].data.str_id)
|
try testing.expect_equal(second, state.tokens.items[4].data.str_id)
|
||||||
try testing.expect_equal(null, strpool.get_id(&pool, "if"))
|
|
||||||
try testing.expect_equal(usize(2), pool.strings.items.len)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
@@ -230,8 +229,6 @@ scans_import_keywords_and_preserves_source_in_tokens_and_diagnostics test {
|
|||||||
try scan(&state, "import test $\n1.\n\"unfinished", &pool, &diagnostic_store)
|
try scan(&state, "import test $\n1.\n\"unfinished", &pool, &diagnostic_store)
|
||||||
try testing.expect_equal(TokenKind.import, state.tokens.items[0].kind)
|
try testing.expect_equal(TokenKind.import, state.tokens.items[0].kind)
|
||||||
try testing.expect_equal(TokenKind.test, state.tokens.items[1].kind)
|
try testing.expect_equal(TokenKind.test, state.tokens.items[1].kind)
|
||||||
try testing.expect_equal(null, strpool.get_id(&pool, "import"))
|
|
||||||
try testing.expect_equal(null, strpool.get_id(&pool, "test"))
|
|
||||||
for (state.tokens.items) |token| try testing.expect_equal(state.source_id, token.span.source)
|
for (state.tokens.items) |token| try testing.expect_equal(state.source_id, token.span.source)
|
||||||
try testing.expect_equal(3, diagnostics.count(&diagnostic_store))
|
try testing.expect_equal(3, diagnostics.count(&diagnostic_store))
|
||||||
for (diagnostic_store.list.items) |diagnostic| {
|
for (diagnostic_store.list.items) |diagnostic| {
|
||||||
|
|||||||
@@ -20,13 +20,15 @@ TokenKind :: enum {
|
|||||||
# keywords
|
# keywords
|
||||||
if, else
|
if, else
|
||||||
for, while
|
for, while
|
||||||
|
match
|
||||||
proc, return
|
proc, return
|
||||||
break, continue
|
break, continue
|
||||||
true, false
|
true, false
|
||||||
and, or
|
and, or
|
||||||
mut
|
struct, enum, union
|
||||||
struct
|
distinct, alias
|
||||||
import, test
|
import, test
|
||||||
|
mut
|
||||||
|
|
||||||
# literals
|
# literals
|
||||||
ident, string
|
ident, string
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import "@std/io"
|
import "@std/io"
|
||||||
import "@source/ir"
|
import "@source/ir"
|
||||||
|
import "@source/diagnostics"
|
||||||
|
|
||||||
@hide
|
@hide
|
||||||
emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruction) void ! io.WriteError {
|
emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruction) void ! io.WriteError {
|
||||||
@@ -57,12 +58,50 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
.union_literal: {
|
||||||
|
shape :: state.module.arrays.items[usize(instruction.array_type)]
|
||||||
|
try io.write_all(output, " store ")
|
||||||
|
try write_type(output, state.module, type)
|
||||||
|
try io.print(" zeroinitializer, ptr %v{}\n", output, {u32(instruction.destination)})
|
||||||
|
if (shape.tag_type != .void) {
|
||||||
|
try io.write_all(output, " store ")
|
||||||
|
try write_type(output, state.module, Type{ kind = shape.tag_type })
|
||||||
|
try io.write_all(output, " ")
|
||||||
|
try write_constant(output, state.module, instruction.constant)
|
||||||
|
try io.print(", ptr %v{}\n", output, {u32(instruction.destination)})
|
||||||
|
}
|
||||||
|
if (instruction.left != ir.no_value) {
|
||||||
|
payload :: try load(state, instruction.left)
|
||||||
|
pointer :: fresh(state)
|
||||||
|
try io.print(" %r{} = getelementptr i8, ptr %v{}, i64 {}\n", output, {pointer, u32(instruction.destination), shape.payload_offset})
|
||||||
|
try store_payload(state, state.values[usize(instruction.left)], payload, pointer)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
.union_tag: {
|
||||||
|
try io.print(" %r{} = load ", output, {result})
|
||||||
|
try write_type(output, state.module, type)
|
||||||
|
try io.print(", ptr %v{}\n", output, {u32(instruction.left)})
|
||||||
|
}
|
||||||
.field: {
|
.field: {
|
||||||
|
base_type :: state.values[usize(instruction.left)]
|
||||||
|
shape :: state.module.arrays.items[usize(base_type.shape)]
|
||||||
|
if (shape.is_union) {
|
||||||
|
pointer :: fresh(state)
|
||||||
|
try io.print(" %r{} = getelementptr i8, ptr %v{}, i64 0\n", output, {pointer, u32(instruction.left)})
|
||||||
|
try check_union(state, pointer, shape, usize(instruction.slot), instruction.span)
|
||||||
|
if (instruction.type == .void) return
|
||||||
|
payload :: fresh(state)
|
||||||
|
try io.print(" %r{} = getelementptr i8, ptr %r{}, i64 {}\n %r{} = load ", output, {payload, pointer, shape.payload_offset, result})
|
||||||
|
try write_type(output, state.module, type)
|
||||||
|
try io.print(", ptr %r{}\n", output, {payload})
|
||||||
|
} else {
|
||||||
base :: try load(state, instruction.left)
|
base :: try load(state, instruction.left)
|
||||||
try io.print(" %r{} = extractvalue ", output, {result})
|
try io.print(" %r{} = extractvalue ", output, {result})
|
||||||
try write_type(output, state.module, state.values[usize(instruction.left)])
|
try write_type(output, state.module, base_type)
|
||||||
try io.print(" %r{}, {}\n", output, {base, instruction.slot})
|
try io.print(" %r{}, {}\n", output, {base, instruction.slot})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.field_address: {
|
.field_address: {
|
||||||
base_type :: state.values[usize(instruction.left)]
|
base_type :: state.values[usize(instruction.left)]
|
||||||
base :: try load(state, instruction.left)
|
base :: try load(state, instruction.left)
|
||||||
@@ -72,9 +111,19 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
|
|||||||
address :: fresh(state)
|
address :: fresh(state)
|
||||||
try io.print(" %r{} = extractvalue %address %r{}, 0\n", output, {data, base})
|
try io.print(" %r{} = extractvalue %address %r{}, 0\n", output, {data, base})
|
||||||
try io.print(" %r{} = extractvalue %address %r{}, 1\n", output, {flag, base})
|
try io.print(" %r{} = extractvalue %address %r{}, 1\n", output, {flag, base})
|
||||||
|
shape :: state.module.arrays.items[usize(base_type.shape)]
|
||||||
|
if (shape.is_union) {
|
||||||
|
if (shape.tag_type != .void) {
|
||||||
|
position :: location(state.line_starts, instruction.span)
|
||||||
|
try io.print(" call void @check_init(ptr %r{}, i1 true, i64 {}, i64 {}, i64 {})\n", output, {flag, usize(instruction.span.source), position.line, position.column})
|
||||||
|
try check_union(state, data, shape, usize(instruction.slot), instruction.span)
|
||||||
|
}
|
||||||
|
try io.print(" %r{} = getelementptr i8, ptr %r{}, i64 {}\n", output, {pointer, data, shape.payload_offset})
|
||||||
|
} else {
|
||||||
try io.print(" %r{} = getelementptr ", output, {pointer})
|
try io.print(" %r{} = getelementptr ", output, {pointer})
|
||||||
try write_type(output, state.module, Type{ kind = .record, shape = base_type.shape })
|
try write_type(output, state.module, Type{ kind = .record, shape = base_type.shape })
|
||||||
try io.print(", ptr %r{}, i64 0, i32 {}\n", output, {data, instruction.slot})
|
try io.print(", ptr %r{}, i64 0, i32 {}\n", output, {data, instruction.slot})
|
||||||
|
}
|
||||||
try io.print(" %r{} = insertvalue %address poison, ptr %r{}, 0\n", output, {address, pointer})
|
try io.print(" %r{} = insertvalue %address poison, ptr %r{}, 0\n", output, {address, pointer})
|
||||||
try io.print(" %r{} = insertvalue %address %r{}, ptr %r{}, 1\n", output, {result, address, flag})
|
try io.print(" %r{} = insertvalue %address %r{}, ptr %r{}, 1\n", output, {result, address, flag})
|
||||||
}
|
}
|
||||||
@@ -129,9 +178,7 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
|
|||||||
try io.print(" call void @check_init(ptr %r{}, i1 {}, i64 {}, i64 {}, i64 {})\n", output, {flag, instruction.op == .load_address, usize(instruction.span.source), position.line, position.column})
|
try io.print(" call void @check_init(ptr %r{}, i1 {}, i64 {}, i64 {}, i64 {})\n", output, {flag, instruction.op == .load_address, usize(instruction.span.source), position.line, position.column})
|
||||||
if (instruction.op == .store_address) {
|
if (instruction.op == .store_address) {
|
||||||
value :: try load(state, instruction.right)
|
value :: try load(state, instruction.right)
|
||||||
try io.write_all(output, " store ")
|
try store_payload(state, state.values[usize(instruction.right)], value, pointer)
|
||||||
try write_type(output, state.module, state.values[usize(instruction.right)])
|
|
||||||
try io.print(" %r{}, ptr %r{}\n", output, {value, pointer})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try io.print(" %r{} = load ", output, {result})
|
try io.print(" %r{} = load ", output, {result})
|
||||||
@@ -246,6 +293,46 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
|
|||||||
try store(state, instruction.destination, result)
|
try store(state, instruction.destination, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
check_union proc(state @mut State, pointer usize, shape ir.ArrayType, field usize, span diagnostics.Span) void ! io.WriteError {
|
||||||
|
if (shape.tag_type == .void) return
|
||||||
|
tag :: fresh(state)
|
||||||
|
failed :: fresh(state)
|
||||||
|
try io.print(" %r{} = load ", state.output, {tag})
|
||||||
|
try write_type(state.output, state.module, Type{ kind = shape.tag_type })
|
||||||
|
try io.print(", ptr %r{}\n %r{} = icmp ne ", state.output, {pointer, failed})
|
||||||
|
try write_type(state.output, state.module, Type{ kind = shape.tag_type })
|
||||||
|
try io.print(" %r{}, ", state.output, {tag})
|
||||||
|
try write_constant(state.output, state.module, shape.fields[field].tag)
|
||||||
|
try io.write_all(state.output, "\n")
|
||||||
|
try check_failure(state, failed, .inactive_union_field, span)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
store_payload proc(state @mut State, type Type, value, pointer usize) void ! io.WriteError {
|
||||||
|
if (type.kind == .void) return
|
||||||
|
if (ir.aggregate(type.kind)) {
|
||||||
|
shape :: state.module.arrays.items[usize(type.shape)]
|
||||||
|
if (!shape.is_union) {
|
||||||
|
count usize :: if (type.kind == .record) shape.fields.len else shape.len
|
||||||
|
for (0..count) |i| {
|
||||||
|
child :: if (type.kind == .record) Type{ kind = shape.fields[i].type, shape = shape.fields[i].array_type } else Type{ kind = shape.element, shape = shape.element_array }
|
||||||
|
offset usize :: if (type.kind == .record) shape.fields[i].byte_offset else i * ir.storage_size(state.module, shape.element, shape.element_array)
|
||||||
|
element :: fresh(state)
|
||||||
|
address :: fresh(state)
|
||||||
|
try io.print(" %r{} = extractvalue ", state.output, {element})
|
||||||
|
try write_type(state.output, state.module, type)
|
||||||
|
try io.print(" %r{}, {}\n %r{} = getelementptr i8, ptr %r{}, i64 {}\n", state.output, {value, i, address, pointer, offset})
|
||||||
|
try store_payload(state, child, element, address)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try io.write_all(state.output, " store ")
|
||||||
|
try write_type(state.output, state.module, type)
|
||||||
|
try io.print(" %r{}, ptr %r{}\n", state.output, {value, pointer})
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
emit_conversion proc(state @mut State, instruction ir.Instruction) usize ! io.WriteError {
|
emit_conversion proc(state @mut State, instruction ir.Instruction) usize ! io.WriteError {
|
||||||
output :: state.output
|
output :: state.output
|
||||||
|
|||||||
@@ -143,6 +143,11 @@ write_type proc(output io.Writer, module @ir.Module, type Type) void ! io.WriteE
|
|||||||
}
|
}
|
||||||
.record: {
|
.record: {
|
||||||
shape :: module.arrays.items[usize(type.shape)]
|
shape :: module.arrays.items[usize(type.shape)]
|
||||||
|
if (shape.is_union) {
|
||||||
|
words usize :: divtrunc!(shape.byte_size, 8)
|
||||||
|
try io.print("{{ [{} x i64] }}", output, {words})
|
||||||
|
return
|
||||||
|
}
|
||||||
try io.write_all(output, "{ ")
|
try io.write_all(output, "{ ")
|
||||||
for (shape.fields) |field, index| {
|
for (shape.fields) |field, index| {
|
||||||
if (index != 0) try io.write_all(output, ", ")
|
if (index != 0) try io.write_all(output, ", ")
|
||||||
@@ -307,6 +312,10 @@ emit_array_printer proc(output io.Writer, module @ir.Module, id ir.ArrayId) void
|
|||||||
emit_record_printer proc(output io.Writer, module @ir.Module, id ir.ArrayId) void ! io.WriteError {
|
emit_record_printer proc(output io.Writer, module @ir.Module, id ir.ArrayId) void ! io.WriteError {
|
||||||
shape :: module.arrays.items[usize(id)]
|
shape :: module.arrays.items[usize(id)]
|
||||||
type :: Type{ kind = .record, shape = id }
|
type :: Type{ kind = .record, shape = id }
|
||||||
|
if (shape.is_union) {
|
||||||
|
try emit_union_printer(output, module, id)
|
||||||
|
return
|
||||||
|
}
|
||||||
try io.print("define internal void @print_array{}(", output, {u32(id)})
|
try io.print("define internal void @print_array{}(", output, {u32(id)})
|
||||||
try write_type(output, module, type)
|
try write_type(output, module, type)
|
||||||
try io.write_all(output, " %value) {\nentry:\n call void @out(i32 1, ptr @record_open, i64 1)\n")
|
try io.write_all(output, " %value) {\nentry:\n call void @out(i32 1, ptr @record_open, i64 1)\n")
|
||||||
@@ -321,6 +330,51 @@ emit_record_printer proc(output io.Writer, module @ir.Module, id ir.ArrayId) voi
|
|||||||
try io.write_all(output, " call void @out(i32 1, ptr @record_close, i64 1)\n ret void\n}\n")
|
try io.write_all(output, " call void @out(i32 1, ptr @record_close, i64 1)\n ret void\n}\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
emit_union_printer proc(output io.Writer, module @ir.Module, id ir.ArrayId) void ! io.WriteError {
|
||||||
|
shape :: module.arrays.items[usize(id)]
|
||||||
|
type :: Type{ kind = .record, shape = id }
|
||||||
|
try io.print("define internal void @print_array{}(", output, {u32(id)})
|
||||||
|
try write_type(output, module, type)
|
||||||
|
try io.write_all(output, " %value) {\nentry:\n %storage = alloca ")
|
||||||
|
try write_type(output, module, type)
|
||||||
|
try io.write_all(output, "\n store ")
|
||||||
|
try write_type(output, module, type)
|
||||||
|
try io.write_all(output, " %value, ptr %storage\n call void @out(i32 1, ptr @record_open, i64 1)\n")
|
||||||
|
if (shape.tag_type != .void) {
|
||||||
|
try io.write_all(output, " %tag = load ")
|
||||||
|
try write_type(output, module, Type{ kind = shape.tag_type })
|
||||||
|
try io.write_all(output, ", ptr %storage\n")
|
||||||
|
target :: if (shape.fields.len == 0) " br label %end\n" else " br label %test0\n"
|
||||||
|
try io.write_all(output, target)
|
||||||
|
for (shape.fields) |field, index| {
|
||||||
|
try io.print("test{}:\n %tag_match{} = icmp eq ", output, {index, index})
|
||||||
|
try write_type(output, module, Type{ kind = shape.tag_type })
|
||||||
|
try io.write_all(output, " %tag, ")
|
||||||
|
try write_constant(output, module, field.tag)
|
||||||
|
try io.print("\n br i1 %tag_match{}, label %case{}, label %", output, {index, index})
|
||||||
|
if (index + 1 == shape.fields.len) try io.write_all(output, "end\n")
|
||||||
|
else try io.print("test{}\n", output, {index + 1})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
target :: if (shape.fields.len == 0) " br label %end\n" else " br label %case0\n"
|
||||||
|
try io.write_all(output, target)
|
||||||
|
}
|
||||||
|
for (shape.fields) |field, index| {
|
||||||
|
if (shape.tag_type == .void and index != 0) break
|
||||||
|
try io.print("case{}:\n", output, {index})
|
||||||
|
if (field.type != .void) {
|
||||||
|
try io.print(" %p{} = getelementptr i8, ptr %storage, i64 {}\n %r{} = load ", output, {index, shape.payload_offset, index})
|
||||||
|
try write_type(output, module, Type{ kind = field.type, shape = field.array_type })
|
||||||
|
try io.print(", ptr %p{}\n", output, {index})
|
||||||
|
if (field.type != .function) try print_call(output, module, Type{ kind = field.type, shape = field.array_type }, index)
|
||||||
|
else try io.write_all(output, " call void @exit(i32 2)\n")
|
||||||
|
}
|
||||||
|
try io.write_all(output, " br label %end\n")
|
||||||
|
}
|
||||||
|
try io.write_all(output, "end:\n call void @out(i32 1, ptr @record_close, i64 1)\n ret void\n}\n")
|
||||||
|
}
|
||||||
|
|
||||||
@hide
|
@hide
|
||||||
fresh proc(state @mut State) usize {
|
fresh proc(state @mut State) usize {
|
||||||
value :: state.next_register
|
value :: state.next_register
|
||||||
@@ -357,6 +411,7 @@ trap proc(state @State, code diagnostics.Code, span diagnostics.Span) void ! io.
|
|||||||
symbol []u8 :: match code {
|
symbol []u8 :: match code {
|
||||||
.integer_overflow: "overflow"
|
.integer_overflow: "overflow"
|
||||||
.index_out_of_bounds: "bounds"
|
.index_out_of_bounds: "bounds"
|
||||||
|
.inactive_union_field: "inactive_union"
|
||||||
else: unreachable
|
else: unreachable
|
||||||
}
|
}
|
||||||
message :: enummap.get(diagnostics.Code, []u8, &diagnostics.messages, code)?
|
message :: enummap.get(diagnostics.Code, []u8, &diagnostics.messages, code)?
|
||||||
@@ -398,6 +453,17 @@ write_constant proc(output io.Writer, module @ir.Module, value ir.Value) void !
|
|||||||
}
|
}
|
||||||
.record: {
|
.record: {
|
||||||
shape :: module.arrays.items[usize(value.array_type)]
|
shape :: module.arrays.items[usize(value.array_type)]
|
||||||
|
if (shape.is_union) {
|
||||||
|
words usize :: divtrunc!(shape.byte_size, 8)
|
||||||
|
try io.print("{{ [{} x i64] [", output, {words})
|
||||||
|
for (0..words) |index| {
|
||||||
|
if (index != 0) try io.write_all(output, ", ")
|
||||||
|
word :: ir.read_storage(module, .u64, ir.no_array, value.data.elements[index * 8..], true)
|
||||||
|
try io.print("i64 {}", output, {word.data.unsigned})
|
||||||
|
}
|
||||||
|
try io.write_all(output, "] }")
|
||||||
|
return
|
||||||
|
}
|
||||||
try io.write_all(output, "{ ")
|
try io.write_all(output, "{ ")
|
||||||
for (shape.fields) |_, index| {
|
for (shape.fields) |_, index| {
|
||||||
if (index != 0) try io.write_all(output, ", ")
|
if (index != 0) try io.write_all(output, ", ")
|
||||||
@@ -457,6 +523,7 @@ emit_function proc(state @mut State, function @ir.Function, id usize) void ! io.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.trap: try io.print(" call void @trap(i64 {}, i64 0, i64 0, i64 0, ptr null, i64 0)\n unreachable\n", output, {u32(terminator.diagnostic)})
|
.trap: try io.print(" call void @trap(i64 {}, i64 0, i64 0, i64 0, ptr null, i64 0)\n unreachable\n", output, {u32(terminator.diagnostic)})
|
||||||
|
.unreachable: try io.write_all(output, " unreachable\n")
|
||||||
.jump: try io.print(" br label %b{}\n", output, {u32(terminator.target)})
|
.jump: try io.print(" br label %b{}\n", output, {u32(terminator.target)})
|
||||||
.branch: {
|
.branch: {
|
||||||
condition :: try load(state, terminator.value)
|
condition :: try load(state, terminator.value)
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ runtime ::
|
|||||||
`@cycle = private constant [27 x i8] c"global initialization cycle"
|
`@cycle = private constant [27 x i8] c"global initialization cycle"
|
||||||
`@overflow = private constant [27 x i8] c"integer arithmetic overflow"
|
`@overflow = private constant [27 x i8] c"integer arithmetic overflow"
|
||||||
`@bounds = private constant [19 x i8] c"index out of bounds"
|
`@bounds = private constant [19 x i8] c"index out of bounds"
|
||||||
|
`@inactive_union = private constant [32 x i8] c"tagged union field is not active"
|
||||||
`define internal void @out(i32 %fd, ptr %bytes, i64 %len) {
|
`define internal void @out(i32 %fd, ptr %bytes, i64 %len) {
|
||||||
`entry:
|
`entry:
|
||||||
` br label %loop
|
` br label %loop
|
||||||
|
|||||||
+242
-23
@@ -102,10 +102,11 @@ lower_package proc(
|
|||||||
}
|
}
|
||||||
.statement: {
|
.statement: {
|
||||||
if (typecheck.type_of(checked, id).kind == .type_value) continue
|
if (typecheck.type_of(checked, id).kind == .type_value) continue
|
||||||
|
if (ast.expr_kind(tree, ast.statement_decl(tree, id).initializer) == .alias_expr) continue
|
||||||
initializer :: try add_function(&module, ast.node_span(tree, id))
|
initializer :: try add_function(&module, ast.node_span(tree, id))
|
||||||
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(&state, typecheck.type_of(checked, id)),
|
||||||
array_type = try array_shape(&state, 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),
|
||||||
@@ -121,6 +122,7 @@ lower_package proc(
|
|||||||
match ast.decl_kind(tree, id) {
|
match ast.decl_kind(tree, id) {
|
||||||
.function: try lower_function(&state, id)
|
.function: try lower_function(&state, id)
|
||||||
.statement: {
|
.statement: {
|
||||||
|
if (ast.expr_kind(tree, ast.statement_decl(tree, id).initializer) == .alias_expr) continue
|
||||||
if (typecheck.type_of(checked, id).kind != .type_value) try lower_global(&state, id)
|
if (typecheck.type_of(checked, id).kind != .type_value) try lower_global(&state, id)
|
||||||
}
|
}
|
||||||
.import, .invalid: {}
|
.import, .invalid: {}
|
||||||
@@ -130,7 +132,10 @@ lower_package proc(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
lower_type proc(type typecheck.Type) ir.Type {
|
lower_type proc(state @State, type typecheck.Type) ir.Type {
|
||||||
|
if (type.kind == .enumeration) {
|
||||||
|
return lower_type(state, typecheck.Type{ kind = typecheck.enum_type(state.checked, type.enum_type).backing })
|
||||||
|
}
|
||||||
return match type.kind {
|
return match type.kind {
|
||||||
.int: .int
|
.int: .int
|
||||||
.usize: .usize
|
.usize: .usize
|
||||||
@@ -149,6 +154,7 @@ lower_type proc(type typecheck.Type) ir.Type {
|
|||||||
.string: .string
|
.string: .string
|
||||||
.void: .void
|
.void: .void
|
||||||
.function: .function
|
.function: .function
|
||||||
|
.enumeration: unreachable
|
||||||
.none, .invalid, .range, .type_value: .invalid
|
.none, .invalid, .range, .type_value: .invalid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -169,7 +175,7 @@ prepare_array proc(state @mut State, id typecheck.ArrayId) ir.ArrayId ! (mem.All
|
|||||||
if (descriptor.element.kind == .array or descriptor.element.kind == .record) child = try array_shape(state, descriptor.element)
|
if (descriptor.element.kind == .array or descriptor.element.kind == .record) child = try array_shape(state, descriptor.element)
|
||||||
result :: try ir.add_array(
|
result :: try ir.add_array(
|
||||||
state.module,
|
state.module,
|
||||||
lower_type(descriptor.element),
|
lower_type(state, descriptor.element),
|
||||||
child,
|
child,
|
||||||
descriptor.len,
|
descriptor.len,
|
||||||
descriptor.mutable,
|
descriptor.mutable,
|
||||||
@@ -186,12 +192,24 @@ prepare_record proc(state @mut State, id typecheck.RecordId) ir.ArrayId ! (mem.A
|
|||||||
defer arraylist.deinit(&fields)
|
defer arraylist.deinit(&fields)
|
||||||
try arraylist.reserve(&fields, descriptor.fields.len)
|
try arraylist.reserve(&fields, descriptor.fields.len)
|
||||||
for (descriptor.fields) |field| {
|
for (descriptor.fields) |field| {
|
||||||
|
tag := ir.Value{}
|
||||||
|
if (descriptor.is_union and descriptor.tag_type.kind == .enumeration) {
|
||||||
|
members :: typecheck.enum_type(state.checked, descriptor.tag_type.enum_type).members
|
||||||
|
for members |member, index| {
|
||||||
|
if (ast.name_id(state.tree, member.name) == ast.name_id(state.tree, field.name?)) {
|
||||||
|
tag = enum_value(state, descriptor.tag_type, index)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
try arraylist.append(&fields, ir.RecordField{
|
try arraylist.append(&fields, ir.RecordField{
|
||||||
type = lower_type(field.type),
|
type = lower_type(state, field.type),
|
||||||
array_type = try array_shape(state, field.type),
|
array_type = try array_shape(state, field.type),
|
||||||
|
tag = tag,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
shape :: try ir.add_record(state.module, fields.items)
|
shape :: if (descriptor.is_union) try ir.add_union(state.module, fields.items, descriptor.tag_type.kind == .enumeration)
|
||||||
|
else try ir.add_record(state.module, fields.items)
|
||||||
state.records[usize(id)] = shape
|
state.records[usize(id)] = shape
|
||||||
return shape
|
return shape
|
||||||
}
|
}
|
||||||
@@ -225,7 +243,7 @@ find_entry proc(state @mut State, root_package ast.NodeId) void ! (mem.AllocErro
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
declaration :: ast.function_decl(tree, id)
|
declaration :: ast.function_decl(tree, id)
|
||||||
result :: lower_type(typecheck.type_of(state.checked, declaration.return_type))
|
result :: lower_type(state, typecheck.type_of(state.checked, declaration.return_type))
|
||||||
if (ast.function_param_count(tree, id) != 0 or result == .function) {
|
if (ast.function_param_count(tree, id) != 0 or result == .function) {
|
||||||
state.module.entry_diagnostic = try diagnostics.add(state.diagnostics, .invalid_entry, name)
|
state.module.entry_diagnostic = try diagnostics.add(state.diagnostics, .invalid_entry, name)
|
||||||
return
|
return
|
||||||
@@ -308,7 +326,7 @@ lower_function proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | di
|
|||||||
state.function = usize(state.storage[usize(id)].index)
|
state.function = usize(state.storage[usize(id)].index)
|
||||||
state.block = ir.BlockId(0)
|
state.block = ir.BlockId(0)
|
||||||
state.terminated = false
|
state.terminated = false
|
||||||
state.module.functions.items[state.function].result = lower_type(typecheck.type_of(state.checked, declaration.return_type))
|
state.module.functions.items[state.function].result = lower_type(state, typecheck.type_of(state.checked, declaration.return_type))
|
||||||
state.module.functions.items[state.function].result_array_type = try array_shape(state, typecheck.type_of(state.checked, declaration.return_type))
|
state.module.functions.items[state.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)
|
||||||
@@ -316,7 +334,7 @@ lower_function proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | di
|
|||||||
local :: try ir.add_parameter(
|
local :: try ir.add_parameter(
|
||||||
state.module,
|
state.module,
|
||||||
ir.FunctionId(state.function),
|
ir.FunctionId(state.function),
|
||||||
lower_type(type),
|
lower_type(state, type),
|
||||||
try array_shape(state, type),
|
try array_shape(state, type),
|
||||||
)
|
)
|
||||||
state.storage[usize(parameter)] = Storage{ kind = .local, index = u32(local) }
|
state.storage[usize(parameter)] = Storage{ kind = .local, index = u32(local) }
|
||||||
@@ -344,11 +362,12 @@ predeclare_locals proc(state @mut State, block ast.NodeId) void ! (mem.AllocErro
|
|||||||
match ast.stmt_kind(state.tree, statement) {
|
match ast.stmt_kind(state.tree, statement) {
|
||||||
.declaration: {
|
.declaration: {
|
||||||
if (typecheck.type_of(state.checked, statement).kind == .type_value) continue
|
if (typecheck.type_of(state.checked, statement).kind == .type_value) continue
|
||||||
|
if (ast.expr_kind(state.tree, ast.statement_decl(state.tree, statement).initializer) == .alias_expr) continue
|
||||||
type :: typecheck.type_of(state.checked, statement)
|
type :: typecheck.type_of(state.checked, statement)
|
||||||
local :: try ir.add_local(
|
local :: try ir.add_local(
|
||||||
state.module,
|
state.module,
|
||||||
ir.FunctionId(state.function),
|
ir.FunctionId(state.function),
|
||||||
lower_type(type),
|
lower_type(state, type),
|
||||||
try array_shape(state, type),
|
try array_shape(state, type),
|
||||||
)
|
)
|
||||||
state.storage[usize(statement)] = Storage{ kind = .local, index = u32(local) }
|
state.storage[usize(statement)] = Storage{ kind = .local, index = u32(local) }
|
||||||
@@ -362,7 +381,7 @@ predeclare_locals proc(state @mut State, block ast.NodeId) void ! (mem.AllocErro
|
|||||||
.for_stmt: {
|
.for_stmt: {
|
||||||
loop :: ast.for_stmt(state.tree, statement)
|
loop :: ast.for_stmt(state.tree, statement)
|
||||||
family :: typecheck.type_of(state.checked, loop.iterable).range_element
|
family :: typecheck.type_of(state.checked, loop.iterable).range_element
|
||||||
type :: lower_type(typecheck.Type{ kind = family })
|
type :: lower_type(state, typecheck.Type{ kind = family })
|
||||||
slot :: try ir.add_local(state.module, ir.FunctionId(state.function), type, ir.no_array)
|
slot :: try ir.add_local(state.module, ir.FunctionId(state.function), type, ir.no_array)
|
||||||
state.storage[usize(statement)] = Storage{ kind = .local, index = u32(slot) }
|
state.storage[usize(statement)] = Storage{ kind = .local, index = u32(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)]
|
||||||
@@ -385,6 +404,7 @@ lower_block proc(state @mut State, block ast.NodeId, fresh_scope bool) void ! (m
|
|||||||
declaration :: ast.block_statement(state.tree, block, i)
|
declaration :: ast.block_statement(state.tree, block, i)
|
||||||
if (ast.stmt_kind(state.tree, declaration) != .declaration) continue
|
if (ast.stmt_kind(state.tree, declaration) != .declaration) continue
|
||||||
if (typecheck.type_of(state.checked, declaration).kind == .type_value) continue
|
if (typecheck.type_of(state.checked, declaration).kind == .type_value) continue
|
||||||
|
if (ast.expr_kind(state.tree, ast.statement_decl(state.tree, declaration).initializer) == .alias_expr) continue
|
||||||
_ = try emit(state, ir.Instruction{
|
_ = try emit(state, ir.Instruction{
|
||||||
op = .reset_local,
|
op = .reset_local,
|
||||||
slot = state.storage[usize(declaration)].index,
|
slot = state.storage[usize(declaration)].index,
|
||||||
@@ -498,7 +518,7 @@ 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
|
||||||
family :: lower_type(typecheck.Type{ kind = typecheck.type_of(state.checked, loop.iterable).range_element })
|
family :: lower_type(state, 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 })
|
_ = 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)
|
||||||
@@ -596,7 +616,7 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
|
|||||||
binding :: sema.binding(state.resolution, assignment.target)
|
binding :: sema.binding(state.resolution, assignment.target)
|
||||||
_ = 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(state, typecheck.type_of(state.checked, assignment.target)),
|
||||||
array_type = try array_shape(state, 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,
|
||||||
@@ -610,6 +630,7 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
|
|||||||
.expression: _ = try lower_expression(state, ast.statement_expr(tree, id))
|
.expression: _ = try lower_expression(state, ast.statement_expr(tree, id))
|
||||||
.declaration: {
|
.declaration: {
|
||||||
if (typecheck.type_of(state.checked, id).kind == .type_value) return
|
if (typecheck.type_of(state.checked, id).kind == .type_value) return
|
||||||
|
if (ast.expr_kind(tree, ast.statement_decl(tree, id).initializer) == .alias_expr) return
|
||||||
if (trap_binding(state, id)) return
|
if (trap_binding(state, id)) return
|
||||||
declaration :: ast.statement_decl(tree, id)
|
declaration :: ast.statement_decl(tree, id)
|
||||||
if (declaration.type) |annotation| if (trap_type(state, annotation)) return
|
if (declaration.type) |annotation| if (trap_type(state, annotation)) return
|
||||||
@@ -617,7 +638,7 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
|
|||||||
if (state.terminated or trap_type(state, id)) return
|
if (state.terminated or trap_type(state, id)) return
|
||||||
_ = 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(state, typecheck.type_of(state.checked, id)),
|
||||||
array_type = try array_shape(state, 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,
|
||||||
@@ -676,7 +697,186 @@ lower_logical proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError
|
|||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
lower_record proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
enum_value proc(state @State, type typecheck.Type, index usize) ir.Value {
|
||||||
|
descriptor :: typecheck.enum_type(state.checked, type.enum_type)
|
||||||
|
member :: descriptor.members[index]
|
||||||
|
runtime :: lower_type(state, type)
|
||||||
|
if (ir.unsigned_integer(runtime)) return ir.Value{ type = runtime, data = ir.ValueData{ unsigned = member.magnitude } }
|
||||||
|
integer := i64(0)
|
||||||
|
if (member.negative) {
|
||||||
|
if (member.magnitude == u64(maxval!(i64)) + 1) integer = minval!(i64)
|
||||||
|
else integer = -i64(member.magnitude)
|
||||||
|
} else integer = i64(member.magnitude)
|
||||||
|
return ir.Value{ type = runtime, data = ir.ValueData{ integer = integer } }
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
match_condition proc(
|
||||||
|
state @mut State, subject ir.ValueId, pattern ast.NodeId,
|
||||||
|
) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
|
span :: ast.node_span(state.tree, pattern)
|
||||||
|
if (ast.expr_kind(state.tree, pattern) == .range) {
|
||||||
|
interval :: ast.range_expr(state.tree, pattern)
|
||||||
|
start :: try lower_expression(state, interval.start)
|
||||||
|
if (state.terminated) return ir.no_value
|
||||||
|
end :: try lower_expression(state, interval.end)
|
||||||
|
if (state.terminated) return ir.no_value
|
||||||
|
lower_bound :: try emit(state, ir.Instruction{ op = .greater_equal, type = .bool, left = subject, right = start, span = span })
|
||||||
|
upper_op :: if (interval.inclusive) ir.Op.less_equal else ir.Op.less
|
||||||
|
upper :: try emit(state, ir.Instruction{ op = upper_op, type = .bool, left = subject, right = end, span = span })
|
||||||
|
slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), .bool, ir.no_array))
|
||||||
|
_ = try emit(state, ir.Instruction{ op = .store_local, left = lower_bound, slot = slot, span = span })
|
||||||
|
origin :: state.block
|
||||||
|
check_upper :: try add_block(state)
|
||||||
|
join :: try add_block(state)
|
||||||
|
state.module.functions.items[state.function].blocks.items[usize(origin)].terminator = ir.Terminator{
|
||||||
|
kind = .branch, value = lower_bound, target = check_upper, alternative = join, span = span,
|
||||||
|
}
|
||||||
|
state.block = check_upper
|
||||||
|
_ = try emit(state, ir.Instruction{ op = .store_local, left = upper, slot = slot, span = span })
|
||||||
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||||
|
kind = .jump, target = join, span = span,
|
||||||
|
}
|
||||||
|
state.block = join
|
||||||
|
return try emit(state, ir.Instruction{ op = .load_local, type = .bool, slot = slot, span = span })
|
||||||
|
}
|
||||||
|
value :: try lower_expression(state, pattern)
|
||||||
|
if (state.terminated) return ir.no_value
|
||||||
|
return try emit(state, ir.Instruction{ op = .equal, type = .bool, left = subject, right = value, span = span })
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
lower_match proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
|
tree :: state.tree
|
||||||
|
span :: ast.node_span(tree, id)
|
||||||
|
view :: ast.match_expr(tree, id)
|
||||||
|
subject :: try lower_expression(state, view.subject)
|
||||||
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
||||||
|
subject_type :: typecheck.type_of(state.checked, view.subject)
|
||||||
|
tagged :: subject_type.kind == .record
|
||||||
|
discriminant := subject
|
||||||
|
if (tagged) {
|
||||||
|
tag_type :: typecheck.record_type(state.checked, subject_type.record_type).tag_type
|
||||||
|
discriminant = try emit(state, ir.Instruction{
|
||||||
|
op = .union_tag, type = lower_type(state, tag_type), left = subject, span = span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
type :: typecheck.type_of(state.checked, id)
|
||||||
|
runtime :: lower_type(state, type)
|
||||||
|
shape :: try array_shape(state, type)
|
||||||
|
result_slot := u32(0)
|
||||||
|
if (runtime != .void) result_slot = u32(try ir.add_local(state.module, ir.FunctionId(state.function), runtime, shape))
|
||||||
|
join :: try add_block(state)
|
||||||
|
falls := false
|
||||||
|
count :: ast.match_arm_count(tree, id)
|
||||||
|
for 0..count |i| {
|
||||||
|
arm :: ast.match_arm(tree, id, i)
|
||||||
|
arm_view :: ast.match_arm_view(tree, arm)
|
||||||
|
body :: try add_block(state)
|
||||||
|
if (arm_view.capture) |capture| {
|
||||||
|
capture_type :: typecheck.type_of(state.checked, capture)
|
||||||
|
local :: try ir.add_local(state.module, ir.FunctionId(state.function), lower_type(state, capture_type), try array_shape(state, capture_type))
|
||||||
|
state.storage[usize(capture)] = Storage{ kind = .local, index = u32(local) }
|
||||||
|
}
|
||||||
|
if (!arm_view.expression) try predeclare_locals(state, arm_view.body)
|
||||||
|
patterns :: ast.match_pattern_count(tree, arm)
|
||||||
|
if (arm_view.catch_all) {
|
||||||
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||||
|
kind = .jump, target = body, span = span,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for 0..patterns |j| {
|
||||||
|
pattern :: ast.match_pattern(tree, arm, j)
|
||||||
|
selected :: try add_block(state)
|
||||||
|
final :: i + 1 == count and j + 1 == patterns
|
||||||
|
next := join
|
||||||
|
if (!final) {
|
||||||
|
condition :: try match_condition(state, discriminant, pattern)
|
||||||
|
if (state.terminated) return ir.no_value
|
||||||
|
next = try add_block(state)
|
||||||
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||||
|
kind = .branch, value = condition, target = selected, alternative = next, span = ast.node_span(tree, pattern),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
# checking proves exhaustiveness; the final pattern is the remaining case.
|
||||||
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||||
|
kind = .jump, target = selected, span = span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.block = selected
|
||||||
|
if (arm_view.capture) |capture| {
|
||||||
|
capture_type :: typecheck.type_of(state.checked, capture)
|
||||||
|
captured := subject
|
||||||
|
if (tagged) captured = try emit(state, ir.Instruction{
|
||||||
|
op = .field, type = lower_type(state, capture_type), array_type = try array_shape(state, capture_type),
|
||||||
|
left = subject, slot = u32(typecheck.union_variant_index(state.checked, pattern)), span = ast.node_span(tree, pattern),
|
||||||
|
})
|
||||||
|
_ = try emit(state, ir.Instruction{
|
||||||
|
op = .store_local, left = captured, slot = state.storage[usize(capture)].index, span = span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||||
|
kind = .jump, target = body, span = span,
|
||||||
|
}
|
||||||
|
state.block = next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next :: state.block
|
||||||
|
state.block = body
|
||||||
|
state.terminated = false
|
||||||
|
if (arm_view.expression) {
|
||||||
|
value :: try lower_expression(state, arm_view.body)
|
||||||
|
if (!state.terminated and runtime != .void) _ = try emit(state, ir.Instruction{
|
||||||
|
op = .store_local, left = value, slot = result_slot, span = span,
|
||||||
|
})
|
||||||
|
} else try lower_block(state, arm_view.body, true)
|
||||||
|
if (!state.terminated) {
|
||||||
|
falls = true
|
||||||
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||||
|
kind = .jump, target = join, span = span,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.block = next
|
||||||
|
state.terminated = false
|
||||||
|
}
|
||||||
|
state.block = join
|
||||||
|
state.terminated = !falls
|
||||||
|
if (!falls) state.module.functions.items[state.function].blocks.items[usize(join)].terminator = ir.Terminator{ kind = .unreachable }
|
||||||
|
if (!falls or runtime == .void) return ir.no_value
|
||||||
|
return try emit(state, ir.Instruction{
|
||||||
|
op = .load_local, type = runtime, array_type = shape, slot = result_slot, span = span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
lower_union proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
|
tree :: state.tree
|
||||||
|
if (trap_type(state, id)) return ir.no_value
|
||||||
|
type :: typecheck.type_of(state.checked, id)
|
||||||
|
slot :: typecheck.union_variant_index(state.checked, id)
|
||||||
|
shape :: try array_shape(state, type)
|
||||||
|
field :: state.module.arrays.items[usize(shape)].fields[slot]
|
||||||
|
payload := ir.no_value
|
||||||
|
if (ast.expr_kind(tree, id) == .record_literal) {
|
||||||
|
view :: ast.record_literal(tree, id)
|
||||||
|
count :: ast.record_field_count(tree, id)
|
||||||
|
contextual := false
|
||||||
|
if (view.type) |annotation| contextual = ast.expr_kind(tree, annotation) == .enum_literal
|
||||||
|
if (contextual and field.type == .record and (count != 1 or ast.field(tree, ast.record_field(tree, id, 0)).name != null)) {
|
||||||
|
payload = try lower_record_as(state, id, typecheck.union_payload_type(state.checked, id))
|
||||||
|
} else if (count != 0) {
|
||||||
|
payload = try lower_expression(state, ast.field(tree, ast.record_field(tree, id, 0)).value?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (state.terminated) return ir.no_value
|
||||||
|
return try emit(state, ir.Instruction{
|
||||||
|
op = .union_literal, type = .record, array_type = shape, slot = u32(slot),
|
||||||
|
left = payload, constant = field.tag, span = ast.node_span(tree, id),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
lower_record_as proc(state @mut State, id ast.NodeId, type typecheck.Type) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
tree :: state.tree
|
tree :: state.tree
|
||||||
scratch_start :: state.arguments.items.len
|
scratch_start :: state.arguments.items.len
|
||||||
defer state.arguments.items = state.arguments.items.ptr[..scratch_start]
|
defer state.arguments.items = state.arguments.items.ptr[..scratch_start]
|
||||||
@@ -689,7 +889,6 @@ lower_record proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError
|
|||||||
state.arguments.items[scratch_start + i] = value
|
state.arguments.items[scratch_start + i] = value
|
||||||
}
|
}
|
||||||
if (trap_type(state, id)) return ir.no_value
|
if (trap_type(state, id)) return ir.no_value
|
||||||
type :: typecheck.type_of(state.checked, id)
|
|
||||||
descriptor :: typecheck.record_type(state.checked, type.record_type)
|
descriptor :: typecheck.record_type(state.checked, type.record_type)
|
||||||
ordered_start :: state.arguments.items.len
|
ordered_start :: state.arguments.items.len
|
||||||
try arraylist.reserve(&state.arguments, ordered_start + descriptor.fields.len)
|
try arraylist.reserve(&state.arguments, ordered_start + descriptor.fields.len)
|
||||||
@@ -758,7 +957,7 @@ lower_place proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError |
|
|||||||
return try emit(state, ir.Instruction{
|
return try emit(state, ir.Instruction{
|
||||||
op = .field_address,
|
op = .field_address,
|
||||||
type = .address,
|
type = .address,
|
||||||
pointee = lower_type(checked),
|
pointee = lower_type(state, checked),
|
||||||
array_type = try array_shape(state, checked),
|
array_type = try array_shape(state, checked),
|
||||||
left = base,
|
left = base,
|
||||||
slot = u32(typecheck.field_index(state.checked, id)),
|
slot = u32(typecheck.field_index(state.checked, id)),
|
||||||
@@ -774,7 +973,7 @@ lower_place proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError |
|
|||||||
return try emit(state, ir.Instruction{
|
return try emit(state, ir.Instruction{
|
||||||
op = .index_address,
|
op = .index_address,
|
||||||
type = .address,
|
type = .address,
|
||||||
pointee = lower_type(checked),
|
pointee = lower_type(state, checked),
|
||||||
array_type = try array_shape(state, checked),
|
array_type = try array_shape(state, checked),
|
||||||
left = base,
|
left = base,
|
||||||
right = index,
|
right = index,
|
||||||
@@ -788,7 +987,7 @@ lower_place proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError |
|
|||||||
return try emit(state, ir.Instruction{
|
return try emit(state, ir.Instruction{
|
||||||
op = operation,
|
op = operation,
|
||||||
type = .address,
|
type = .address,
|
||||||
pointee = lower_type(checked),
|
pointee = lower_type(state, checked),
|
||||||
array_type = try array_shape(state, checked),
|
array_type = try array_shape(state, checked),
|
||||||
slot = location.index,
|
slot = location.index,
|
||||||
span = span,
|
span = span,
|
||||||
@@ -799,8 +998,13 @@ lower_place proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError |
|
|||||||
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(state, typecheck.type_of(state.checked, id))
|
||||||
shape :: try array_shape(state, typecheck.type_of(state.checked, id))
|
shape :: try array_shape(state, typecheck.type_of(state.checked, id))
|
||||||
|
if (typecheck.is_enum_member(state.checked, id)) {
|
||||||
|
if (trap_type(state, id)) return ir.no_value
|
||||||
|
return try constant(state, enum_value(state, typecheck.type_of(state.checked, id), typecheck.field_index(state.checked, id)), span)
|
||||||
|
}
|
||||||
|
if (typecheck.is_union_variant(state.checked, id)) return try lower_union(state, 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)
|
||||||
@@ -810,12 +1014,23 @@ 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, .record_type: {
|
.array_type, .record_type, .enum_type, .union_type, .distinct_type: {
|
||||||
if (!trap_type(state, id)) {
|
if (!trap_type(state, id)) {
|
||||||
trap(state, try diagnostics.add(state.diagnostics, .type_not_value, span), span)
|
trap(state, try diagnostics.add(state.diagnostics, .type_not_value, span), span)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.record_literal: return try lower_record(state, id)
|
.alias_expr: return try lower_expression(state, ast.type_operand(tree, id))
|
||||||
|
.enum_literal: {
|
||||||
|
if (!trap_type(state, id)) unreachable
|
||||||
|
}
|
||||||
|
.match_expr: return try lower_match(state, id)
|
||||||
|
.record_literal: {
|
||||||
|
checked :: typecheck.type_of(state.checked, id)
|
||||||
|
if (checked.kind == .record and typecheck.record_type(state.checked, checked.record_type).is_union) {
|
||||||
|
return try lower_union(state, id)
|
||||||
|
}
|
||||||
|
return try lower_record_as(state, id, checked)
|
||||||
|
}
|
||||||
.array_literal: {
|
.array_literal: {
|
||||||
scratch_start :: state.arguments.items.len
|
scratch_start :: state.arguments.items.len
|
||||||
defer state.arguments.items = state.arguments.items.ptr[..scratch_start]
|
defer state.arguments.items = state.arguments.items.ptr[..scratch_start]
|
||||||
@@ -948,7 +1163,7 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
|||||||
}
|
}
|
||||||
.call: {
|
.call: {
|
||||||
target :: sema.binding(state.resolution, ast.call_expr(tree, id).callee)
|
target :: sema.binding(state.resolution, ast.call_expr(tree, id).callee)
|
||||||
if (target.kind == .builtin) {
|
if (target.kind == .builtin or typecheck.is_type_constructor(state.checked, id)) {
|
||||||
operand := ir.no_value
|
operand := ir.no_value
|
||||||
for (0..ast.call_arg_count(tree, id)) |i| {
|
for (0..ast.call_arg_count(tree, id)) |i| {
|
||||||
value :: try lower_expression(state, ast.call_arg(tree, id, i))
|
value :: try lower_expression(state, ast.call_arg(tree, id, i))
|
||||||
@@ -956,6 +1171,10 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
|||||||
if (i == 0) operand = value
|
if (i == 0) operand = value
|
||||||
}
|
}
|
||||||
if (trap_type(state, id)) return ir.no_value
|
if (trap_type(state, id)) return ir.no_value
|
||||||
|
if (typecheck.is_type_constructor(state.checked, id)) {
|
||||||
|
source :: lower_type(state, typecheck.type_of(state.checked, ast.call_arg(tree, id, 0)))
|
||||||
|
if (source == type) return operand
|
||||||
|
}
|
||||||
return try emit(state, ir.Instruction{ op = .convert, type = type, left = operand, span = span })
|
return try emit(state, ir.Instruction{ op = .convert, type = type, left = operand, span = span })
|
||||||
}
|
}
|
||||||
callee :: try lower_expression(state, ast.call_expr(tree, id).callee)
|
callee :: try lower_expression(state, ast.call_expr(tree, id).callee)
|
||||||
@@ -1018,7 +1237,7 @@ exact_float_integer proc(text []u8, value f64) bool {
|
|||||||
lower_number proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
lower_number proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
span :: ast.node_span(state.tree, id)
|
span :: ast.node_span(state.tree, id)
|
||||||
text :: ast.source_text(state.tree, span)
|
text :: ast.source_text(state.tree, span)
|
||||||
type :: lower_type(typecheck.type_of(state.checked, id))
|
type :: lower_type(state, typecheck.type_of(state.checked, id))
|
||||||
if (ir.integer_bits(type) != 0 and !ir.unsigned_integer(type)) {
|
if (ir.integer_bits(type) != 0 and !ir.unsigned_integer(type)) {
|
||||||
if (integer_magnitude(text, ir.integer_max(type))) |magnitude| {
|
if (integer_magnitude(text, ir.integer_max(type))) |magnitude| {
|
||||||
return try constant(state, ir.Value{ type = type, data = ir.ValueData{ integer = i64(magnitude) } }, span)
|
return try constant(state, ir.Value{ type = type, data = ir.ValueData{ integer = i64(magnitude) } }, span)
|
||||||
|
|||||||
@@ -132,14 +132,14 @@ executes_forward_lazy_globals_and_function_aliases test {
|
|||||||
defer diagnostics.deinit(&diagnostic_store)
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
source ::
|
source ::
|
||||||
`forward :: later + 1
|
`forward :: later + 1
|
||||||
`alias :: add
|
`forwarded :: add
|
||||||
`later :: 18
|
`later :: 18
|
||||||
`add proc(left int, right int) int {
|
`add proc(left int, right int) int {
|
||||||
` local_alias :: alias
|
` local_alias :: forwarded
|
||||||
` return left + right
|
` return left + right
|
||||||
`}
|
`}
|
||||||
`main proc() int {
|
`main proc() int {
|
||||||
` invoke :: alias
|
` invoke :: forwarded
|
||||||
` return invoke(forward, 23)
|
` return invoke(forward, 23)
|
||||||
`}
|
`}
|
||||||
module := try compile_owned(source, &diagnostic_store)
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
@@ -1684,7 +1684,7 @@ array_context_inference_empty_shapes_and_scalar_leaf_families test {
|
|||||||
"main proc() string {\n a [2]mut string := [\"one\", \"two\"]\n b := a\n a[1] = \"three\"\n return b[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",
|
"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",
|
"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",
|
"count :: forwarded + 1\nforwarded :: 1\nmain proc() bool {\n a [count][0]bool :: [[], []]\n return true\n}\n",
|
||||||
]
|
]
|
||||||
for (sources) |source| {
|
for (sources) |source| {
|
||||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
|||||||
@@ -214,6 +214,15 @@ print_value proc(output io.Writer, module @ir.Module, value ir.Value) void ! io.
|
|||||||
opening :: if (value.type == .array) "[" else "{"
|
opening :: if (value.type == .array) "[" else "{"
|
||||||
closing :: if (value.type == .array) "]" else "}"
|
closing :: if (value.type == .array) "]" else "}"
|
||||||
try io.write_all(output, opening)
|
try io.write_all(output, opening)
|
||||||
|
if (shape.is_union) {
|
||||||
|
for (shape.fields) |field, index| {
|
||||||
|
if (!ir.union_active(module, value, index)) continue
|
||||||
|
if (field.type != .void) try print_value(output, module, ir.aggregate_item(module, value, index))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
try io.write_all(output, closing)
|
||||||
|
return
|
||||||
|
}
|
||||||
for (0..count) |index| {
|
for (0..count) |index| {
|
||||||
if (index != 0) try io.write_all(output, ", ")
|
if (index != 0) try io.write_all(output, ", ")
|
||||||
try print_value(output, module, ir.aggregate_item(module, value, index))
|
try print_value(output, module, ir.aggregate_item(module, value, index))
|
||||||
|
|||||||
+289
-13
@@ -30,6 +30,7 @@ State :: struct {
|
|||||||
next_token usize
|
next_token usize
|
||||||
paren_depth usize = 0
|
paren_depth usize = 0
|
||||||
allow_record_literals bool = true
|
allow_record_literals bool = true
|
||||||
|
match_arm_body bool = false
|
||||||
}
|
}
|
||||||
|
|
||||||
#! borrows `tokens` and `source` while parsing; the returned Tree borrows `source` until ast.deinit.
|
#! borrows `tokens` and `source` while parsing; the returned Tree borrows `source` until ast.deinit.
|
||||||
@@ -156,7 +157,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)
|
try arraylist.append(&names, token)
|
||||||
if (next_is(state, .ident) or next_is(state, .open_bracket) or next_is(state, .struct)) break
|
if (starts_type(current(state).kind)) break
|
||||||
_ = try consume(state, .comma)
|
_ = try consume(state, .comma)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,6 +238,8 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
|
|||||||
and (
|
and (
|
||||||
next_n_is(state, 1, .ident)
|
next_n_is(state, 1, .ident)
|
||||||
or next_n_is(state, 1, .struct)
|
or next_n_is(state, 1, .struct)
|
||||||
|
or next_n_is(state, 1, .enum) or next_n_is(state, 1, .union)
|
||||||
|
or next_n_is(state, 1, .distinct)
|
||||||
or (next_n_is(state, 1, .open_bracket) and array_starts_type(state))
|
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)
|
||||||
@@ -250,7 +253,8 @@ 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, .open_bracket, .open_curly, .struct: {
|
.ident, .int, .float, .string, .true, .false, .minus, .bang, .open_paren, .open_bracket, .open_curly, .struct,
|
||||||
|
.enum, .union, .distinct, .alias, .dot, .match: {
|
||||||
expression :: try parse_expr(state)
|
expression :: try parse_expr(state)
|
||||||
if next_is(state, .equal) return try parse_assignment(state, expression, allow_else)
|
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)
|
||||||
@@ -264,6 +268,7 @@ parse_return proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
|||||||
keyword :: try consume(state, .return)
|
keyword :: try consume(state, .return)
|
||||||
if (
|
if (
|
||||||
next_is(state, .newline) or next_is(state, .close_curly) or next_is(state, .eof)
|
next_is(state, .newline) or next_is(state, .close_curly) or next_is(state, .eof)
|
||||||
|
or (state.match_arm_body and next_is(state, .comma))
|
||||||
or (allow_else and next_is(state, .else))
|
or (allow_else and next_is(state, .else))
|
||||||
) {
|
) {
|
||||||
return try ast.add_return_stmt(state.builder, keyword.span, null)
|
return try ast.add_return_stmt(state.builder, keyword.span, null)
|
||||||
@@ -272,6 +277,7 @@ parse_return proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
|||||||
if (
|
if (
|
||||||
!next_is(state, .newline) and !next_is(state, .close_curly) and !next_is(state, .eof)
|
!next_is(state, .newline) and !next_is(state, .close_curly) and !next_is(state, .eof)
|
||||||
and !(allow_else and next_is(state, .else))
|
and !(allow_else and next_is(state, .else))
|
||||||
|
and !(state.match_arm_body and next_is(state, .comma))
|
||||||
) {
|
) {
|
||||||
return .unexpected_token
|
return .unexpected_token
|
||||||
}
|
}
|
||||||
@@ -297,6 +303,7 @@ parse_loop_control proc(state @mut State, allow_else bool) ast.NodeId ! ParseErr
|
|||||||
and !next_is(state, .close_curly)
|
and !next_is(state, .close_curly)
|
||||||
and !next_is(state, .eof)
|
and !next_is(state, .eof)
|
||||||
and !(allow_else and next_is(state, .else))
|
and !(allow_else and next_is(state, .else))
|
||||||
|
and !(state.match_arm_body and next_is(state, .comma))
|
||||||
) return .unexpected_token
|
) return .unexpected_token
|
||||||
|
|
||||||
if (keyword.kind == .break)
|
if (keyword.kind == .break)
|
||||||
@@ -314,6 +321,7 @@ parse_assignment proc(state @mut State, target ast.NodeId, allow_else bool) ast.
|
|||||||
and !next_is(state, .close_curly)
|
and !next_is(state, .close_curly)
|
||||||
and !next_is(state, .eof)
|
and !next_is(state, .eof)
|
||||||
and !(allow_else and next_is(state, .else))
|
and !(allow_else and next_is(state, .else))
|
||||||
|
and !(state.match_arm_body and next_is(state, .comma))
|
||||||
) {
|
) {
|
||||||
return .unexpected_token
|
return .unexpected_token
|
||||||
}
|
}
|
||||||
@@ -569,7 +577,7 @@ parse_arm proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
|||||||
@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) or next_is(state, .open_bracket) or next_is(state, .struct))
|
type_id ?ast.NodeId :: if (starts_type(current(state).kind))
|
||||||
try parse_type(state)
|
try parse_type(state)
|
||||||
else
|
else
|
||||||
null
|
null
|
||||||
@@ -622,11 +630,14 @@ parse_type proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if next_is(state, .struct) return try parse_record_type(state)
|
if next_is(state, .struct) return try parse_record_type(state)
|
||||||
|
if next_is(state, .enum) return try parse_enum_type(state)
|
||||||
|
if next_is(state, .union) return try parse_union_type(state)
|
||||||
|
if (next_is(state, .distinct) or next_is(state, .alias)) return try parse_type_wrapper(state)
|
||||||
token :: try consume(state, .ident)
|
token :: try consume(state, .ident)
|
||||||
type := try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
type := try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
||||||
while next_is(state, .dot) {
|
while next_is(state, .dot) {
|
||||||
state.next_token += 1
|
state.next_token += 1
|
||||||
member :: try consume(state, .ident)
|
member :: try consume_member(state, false)
|
||||||
member_id :: try ast.add_identifier(state.builder, member.span, member.data.str_id)
|
member_id :: try ast.add_identifier(state.builder, member.span, member.data.str_id)
|
||||||
type = try ast.add_selection(state.builder, ast.Span{
|
type = try ast.add_selection(state.builder, ast.Span{
|
||||||
start = token.span.start,
|
start = token.span.start,
|
||||||
@@ -637,6 +648,261 @@ parse_type proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
return type
|
return type
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
starts_type proc(kind lexer.TokenKind) bool {
|
||||||
|
return (
|
||||||
|
kind == .ident or kind == .open_bracket or kind == .struct
|
||||||
|
or kind == .enum or kind == .union or kind == .distinct or kind == .alias
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
member_name proc(kind lexer.TokenKind) bool {
|
||||||
|
return match kind {
|
||||||
|
.ident, .if, .else, .for, .while, .proc, .return, .break, .continue,
|
||||||
|
.true, .false, .and, .or, .mut, .struct, .enum, .union, .distinct,
|
||||||
|
.alias, .match, .import, .test: true
|
||||||
|
else: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
consume_member proc(state @mut State, numeric bool) lexer.Token ! SyntaxError {
|
||||||
|
token :: current(state)
|
||||||
|
if (!member_name(token.kind) and !(numeric and token.kind == .int)) return .unexpected_token
|
||||||
|
state.next_token += 1
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
parse_type_wrapper proc(state @mut State) ast.NodeId ! ParseError {
|
||||||
|
keyword :: current(state)
|
||||||
|
state.next_token += 1
|
||||||
|
operand :: try parse_type(state)
|
||||||
|
return try ast.add_type_operand(state.builder, ast.Span{
|
||||||
|
start = keyword.span.start,
|
||||||
|
end = ast.builder_span(state.builder, operand).end,
|
||||||
|
source = keyword.span.source,
|
||||||
|
}, operand, keyword.kind == .alias)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
parse_enum_type proc(state @mut State) ast.NodeId ! ParseError {
|
||||||
|
keyword :: try consume(state, .enum)
|
||||||
|
backing ?ast.NodeId := null
|
||||||
|
if next_is(state, .open_paren) {
|
||||||
|
state.next_token += 1
|
||||||
|
backing = try parse_type(state)
|
||||||
|
_ = try consume(state, .close_paren)
|
||||||
|
}
|
||||||
|
skip_newlines(state)
|
||||||
|
_ = try consume(state, .open_curly)
|
||||||
|
members arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
||||||
|
defer arraylist.deinit(&members)
|
||||||
|
skip_newlines(state)
|
||||||
|
while (!next_is(state, .close_curly) and !next_is(state, .eof)) {
|
||||||
|
if (
|
||||||
|
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, .proc))
|
||||||
|
) return try invalid_record(state, keyword.span.start, false)
|
||||||
|
start :: state.next_token
|
||||||
|
member :: parse_enum_member(state) catch |err| {
|
||||||
|
match err {
|
||||||
|
.unexpected_token: {}
|
||||||
|
else: return err
|
||||||
|
}
|
||||||
|
diagnostic :: try unexpected_diagnostic(state)
|
||||||
|
span :: recover_record_field(state, start)
|
||||||
|
invalid :: try ast.add_invalid_expr(state.builder, span, diagnostic)
|
||||||
|
yield try ast.add_field(state.builder, span, null, null, invalid)
|
||||||
|
}
|
||||||
|
try arraylist.append(&members, member)
|
||||||
|
if next_is(state, .close_curly) break
|
||||||
|
if next_is(state, .comma) state.next_token += 1
|
||||||
|
else if (!next_is(state, .newline)) return try invalid_record(state, keyword.span.start, true)
|
||||||
|
skip_newlines(state)
|
||||||
|
}
|
||||||
|
if (!next_is(state, .close_curly)) return try invalid_record(state, keyword.span.start, false)
|
||||||
|
close :: try consume(state, .close_curly)
|
||||||
|
return try ast.add_enum_type(state.builder, ast.Span{
|
||||||
|
start = keyword.span.start, end = close.span.end, source = keyword.span.source,
|
||||||
|
}, backing, members.items)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
parse_enum_member proc(state @mut State) ast.NodeId ! ParseError {
|
||||||
|
token :: try consume_member(state, false)
|
||||||
|
name :: try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
||||||
|
value ?ast.NodeId := null
|
||||||
|
if next_is(state, .equal) {
|
||||||
|
state.next_token += 1
|
||||||
|
value = try parse_expr(state)
|
||||||
|
}
|
||||||
|
end :: if (value) |id| ast.builder_span(state.builder, id).end else token.span.end
|
||||||
|
return try ast.add_field(state.builder, ast.Span{
|
||||||
|
start = token.span.start,
|
||||||
|
end = end,
|
||||||
|
source = token.span.source,
|
||||||
|
}, name, null, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
parse_union_type proc(state @mut State) ast.NodeId ! ParseError {
|
||||||
|
keyword :: try consume(state, .union)
|
||||||
|
tag ?ast.NodeId := null
|
||||||
|
auto_tag := false
|
||||||
|
if next_is(state, .open_paren) {
|
||||||
|
state.next_token += 1
|
||||||
|
if next_is(state, .enum) {
|
||||||
|
state.next_token += 1
|
||||||
|
auto_tag = true
|
||||||
|
} else {
|
||||||
|
tag = try parse_type(state)
|
||||||
|
}
|
||||||
|
_ = try consume(state, .close_paren)
|
||||||
|
}
|
||||||
|
skip_newlines(state)
|
||||||
|
_ = try consume(state, .open_curly)
|
||||||
|
fields arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
||||||
|
defer arraylist.deinit(&fields)
|
||||||
|
skip_newlines(state)
|
||||||
|
while (!next_is(state, .close_curly) and !next_is(state, .eof)) {
|
||||||
|
if (
|
||||||
|
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, .proc))
|
||||||
|
) return try invalid_record(state, keyword.span.start, false)
|
||||||
|
start :: state.next_token
|
||||||
|
field :: parse_union_field(state, auto_tag or tag != null) catch |err| {
|
||||||
|
match err {
|
||||||
|
.unexpected_token: {}
|
||||||
|
else: return err
|
||||||
|
}
|
||||||
|
diagnostic :: try unexpected_diagnostic(state)
|
||||||
|
span :: recover_record_field(state, start)
|
||||||
|
invalid :: try ast.add_invalid_expr(state.builder, span, diagnostic)
|
||||||
|
yield try ast.add_field(state.builder, span, null, invalid, null)
|
||||||
|
}
|
||||||
|
try arraylist.append(&fields, field)
|
||||||
|
if next_is(state, .close_curly) break
|
||||||
|
if next_is(state, .comma) state.next_token += 1
|
||||||
|
else if (!next_is(state, .newline)) return try invalid_record(state, keyword.span.start, true)
|
||||||
|
skip_newlines(state)
|
||||||
|
}
|
||||||
|
if (!next_is(state, .close_curly)) return try invalid_record(state, keyword.span.start, false)
|
||||||
|
close :: try consume(state, .close_curly)
|
||||||
|
return try ast.add_union_type(state.builder, ast.Span{
|
||||||
|
start = keyword.span.start, end = close.span.end, source = keyword.span.source,
|
||||||
|
}, tag, auto_tag, fields.items)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
parse_union_field proc(state @mut State, tagged bool) ast.NodeId ! ParseError {
|
||||||
|
token :: if (tagged) try consume_member(state, false) else try consume(state, .ident)
|
||||||
|
name :: try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
||||||
|
type :: try parse_type(state)
|
||||||
|
return try ast.add_field(state.builder, ast.Span{
|
||||||
|
start = token.span.start, end = ast.builder_span(state.builder, type).end, source = token.span.source,
|
||||||
|
}, name, type, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
parse_match proc(state @mut State) ast.NodeId ! ParseError {
|
||||||
|
keyword :: try consume(state, .match)
|
||||||
|
skip_newlines(state)
|
||||||
|
subject :: try parse_guard_expr(state)
|
||||||
|
skip_newlines(state)
|
||||||
|
_ = try consume(state, .open_curly)
|
||||||
|
previous_depth :: state.paren_depth
|
||||||
|
state.paren_depth = 0
|
||||||
|
defer state.paren_depth = previous_depth
|
||||||
|
arms arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
||||||
|
defer arraylist.deinit(&arms)
|
||||||
|
skip_newlines(state)
|
||||||
|
while (!next_is(state, .close_curly) and !next_is(state, .eof)) {
|
||||||
|
if (
|
||||||
|
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, .proc))
|
||||||
|
) return try invalid_record(state, keyword.span.start, false)
|
||||||
|
start :: state.next_token
|
||||||
|
arm :: parse_match_arm(state) catch |err| {
|
||||||
|
match err {
|
||||||
|
.unexpected_token: {}
|
||||||
|
else: return err
|
||||||
|
}
|
||||||
|
diagnostic :: try unexpected_diagnostic(state)
|
||||||
|
span :: recover_record_field(state, start)
|
||||||
|
invalid :: try ast.add_invalid_expr(state.builder, span, diagnostic)
|
||||||
|
yield try ast.add_match_arm(state.builder, span, &[], null, invalid, true, false)
|
||||||
|
}
|
||||||
|
try arraylist.append(&arms, arm)
|
||||||
|
if next_is(state, .close_curly) break
|
||||||
|
if next_is(state, .comma) state.next_token += 1
|
||||||
|
else if (!next_is(state, .newline)) return try invalid_record(state, keyword.span.start, true)
|
||||||
|
skip_newlines(state)
|
||||||
|
}
|
||||||
|
if (!next_is(state, .close_curly)) return try invalid_record(state, keyword.span.start, false)
|
||||||
|
close :: try consume(state, .close_curly)
|
||||||
|
return try ast.add_match(state.builder, ast.Span{
|
||||||
|
start = keyword.span.start, end = close.span.end, source = keyword.span.source,
|
||||||
|
}, subject, arms.items)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
parse_match_arm proc(state @mut State) ast.NodeId ! ParseError {
|
||||||
|
start :: current(state).span
|
||||||
|
patterns arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
||||||
|
defer arraylist.deinit(&patterns)
|
||||||
|
catch_all :: next_is(state, .else)
|
||||||
|
capture ?ast.NodeId := null
|
||||||
|
if (catch_all) state.next_token += 1
|
||||||
|
else {
|
||||||
|
while true {
|
||||||
|
try arraylist.append(&patterns, try parse_guard_expr(state))
|
||||||
|
if (!next_is(state, .comma)) break
|
||||||
|
state.next_token += 1
|
||||||
|
skip_newlines(state)
|
||||||
|
}
|
||||||
|
if next_is(state, .pipe) {
|
||||||
|
state.next_token += 1
|
||||||
|
# pointer payload captures are outside the current runtime representation.
|
||||||
|
if (next_is(state, .at) or next_is(state, .star)) return .unexpected_token
|
||||||
|
token :: try consume(state, .ident)
|
||||||
|
if (
|
||||||
|
token.span.end != token.span.start + 1
|
||||||
|
or state.builder.source[usize(token.span.start)] != '_'
|
||||||
|
) capture = try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
||||||
|
_ = try consume(state, .pipe)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ = try consume(state, .colon)
|
||||||
|
skip_newlines(state)
|
||||||
|
previous :: state.match_arm_body
|
||||||
|
state.match_arm_body = true
|
||||||
|
defer state.match_arm_body = previous
|
||||||
|
statement_body :: match current(state).kind {
|
||||||
|
.open_curly, .return, .break, .continue, .if, .while, .for: true
|
||||||
|
else: false
|
||||||
|
}
|
||||||
|
if (statement_body or named_binding_starts(state)) {
|
||||||
|
body :: try parse_arm(state, false)
|
||||||
|
return try ast.add_match_arm(state.builder, ast.Span{
|
||||||
|
start = start.start, end = ast.builder_span(state.builder, body).end, source = start.source,
|
||||||
|
}, patterns.items, capture, body, false, catch_all)
|
||||||
|
}
|
||||||
|
value :: try parse_expr(state)
|
||||||
|
if next_is(state, .equal) {
|
||||||
|
statement :: try parse_assignment(state, value, false)
|
||||||
|
statements [1]ast.NodeId :: [statement]
|
||||||
|
body :: try ast.add_block(state.builder, ast.builder_span(state.builder, statement), statements[..])
|
||||||
|
return try ast.add_match_arm(state.builder, ast.Span{
|
||||||
|
start = start.start, end = ast.builder_span(state.builder, body).end, source = start.source,
|
||||||
|
}, patterns.items, capture, body, false, catch_all)
|
||||||
|
}
|
||||||
|
return try ast.add_match_arm(state.builder, ast.Span{
|
||||||
|
start = start.start, end = ast.builder_span(state.builder, value).end, source = start.source,
|
||||||
|
}, patterns.items, capture, value, true, catch_all)
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
parse_record_type proc(state @mut State) ast.NodeId ! ParseError {
|
parse_record_type proc(state @mut State) ast.NodeId ! ParseError {
|
||||||
keyword :: try consume(state, .struct)
|
keyword :: try consume(state, .struct)
|
||||||
@@ -711,15 +977,16 @@ parse_record_field proc(state @mut State, declaration bool) ast.NodeId ! ParseEr
|
|||||||
name ?ast.NodeId := null
|
name ?ast.NodeId := null
|
||||||
type ?ast.NodeId := null
|
type ?ast.NodeId := null
|
||||||
value ?ast.NodeId := null
|
value ?ast.NodeId := null
|
||||||
named :: next_is(state, .ident) and (
|
named :: member_name(current(state).kind) and (
|
||||||
(!declaration and next_n_is(state, 1, .equal))
|
(!declaration and next_n_is(state, 1, .equal))
|
||||||
or (declaration and (
|
or (declaration and (
|
||||||
next_n_is(state, 1, .ident) or next_n_is(state, 1, .open_bracket)
|
next_n_is(state, 1, .ident) or next_n_is(state, 1, .open_bracket)
|
||||||
or next_n_is(state, 1, .struct)
|
or next_n_is(state, 1, .struct) or next_n_is(state, 1, .enum)
|
||||||
|
or next_n_is(state, 1, .union) or next_n_is(state, 1, .distinct)
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
if (named) {
|
if (named) {
|
||||||
token :: try consume(state, .ident)
|
token :: if (declaration) try consume(state, .ident) else try consume_member(state, false)
|
||||||
name = try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
name = try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
||||||
}
|
}
|
||||||
if (declaration) {
|
if (declaration) {
|
||||||
@@ -836,7 +1103,7 @@ parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! Par
|
|||||||
}
|
}
|
||||||
if (token.kind == .dot) {
|
if (token.kind == .dot) {
|
||||||
state.next_token += 1
|
state.next_token += 1
|
||||||
member :: try consume_either(state, &[.ident, .int])
|
member :: try consume_member(state, true)
|
||||||
member_id :: if (member.kind == .int)
|
member_id :: if (member.kind == .int)
|
||||||
try ast.add_literal(state.builder, .int, member.span)
|
try ast.add_literal(state.builder, .int, member.span)
|
||||||
else
|
else
|
||||||
@@ -999,7 +1266,7 @@ array_starts_type proc(state @State) bool {
|
|||||||
if (depth == 0) {
|
if (depth == 0) {
|
||||||
if (i + 1 >= state.tokens.len) return false
|
if (i + 1 >= state.tokens.len) return false
|
||||||
next :: state.tokens[i + 1].kind
|
next :: state.tokens[i + 1].kind
|
||||||
return next == .ident or next == .mut or next == .open_bracket or next == .struct
|
return starts_type(next) or next == .mut
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (kind == .open_curly or kind == .close_curly or kind == .eof) return false
|
if (kind == .open_curly or kind == .close_curly or kind == .eof) return false
|
||||||
@@ -1073,6 +1340,18 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
token :: current(state)
|
token :: current(state)
|
||||||
match token.kind {
|
match token.kind {
|
||||||
.struct: return try parse_record_type(state)
|
.struct: return try parse_record_type(state)
|
||||||
|
.enum: return try parse_enum_type(state)
|
||||||
|
.union: return try parse_union_type(state)
|
||||||
|
.distinct, .alias: return try parse_type_wrapper(state)
|
||||||
|
.match: return try parse_match(state)
|
||||||
|
.dot: {
|
||||||
|
state.next_token += 1
|
||||||
|
member :: try consume_member(state, false)
|
||||||
|
member_id :: try ast.add_identifier(state.builder, member.span, member.data.str_id)
|
||||||
|
return try ast.add_enum_literal(state.builder, ast.Span{
|
||||||
|
start = token.span.start, end = member.span.end, source = token.span.source,
|
||||||
|
}, member_id)
|
||||||
|
}
|
||||||
.open_curly: {
|
.open_curly: {
|
||||||
if (state.allow_record_literals) return try parse_record_literal(state, null, token.span.start)
|
if (state.allow_record_literals) return try parse_record_literal(state, null, token.span.start)
|
||||||
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, token.span)
|
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, token.span)
|
||||||
@@ -1126,10 +1405,7 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
return try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
return try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
||||||
}
|
}
|
||||||
else: {
|
else: {
|
||||||
diagnostic :: if (token.data.diagnostic != diagnostics.no_diagnostic)
|
diagnostic :: try unexpected_diagnostic(state)
|
||||||
token.data.diagnostic
|
|
||||||
else
|
|
||||||
try diagnostics.add(state.diagnostics, .unexpected_token, token.span)
|
|
||||||
|
|
||||||
if !is_expression_recovery_boundary(token.kind) {
|
if !is_expression_recovery_boundary(token.kind) {
|
||||||
state.next_token += 1
|
state.next_token += 1
|
||||||
|
|||||||
@@ -940,7 +940,6 @@ recovers_missing_logical_operands_and_preserves_enclosing_nodes test {
|
|||||||
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||||
defer ast.deinit(&tree)
|
defer ast.deinit(&tree)
|
||||||
|
|
||||||
try testing.expect_equal(6, diagnostics.count(&diagnostic_store))
|
|
||||||
try testing.expect_equal(2, ast.package_decl_count(&tree))
|
try testing.expect_equal(2, ast.package_decl_count(&tree))
|
||||||
function :: ast.function_decl(&tree, ast.package_decl(&tree, 0))
|
function :: ast.function_decl(&tree, ast.package_decl(&tree, 0))
|
||||||
try testing.expect_equal(5, ast.block_statement_count(&tree, function.body))
|
try testing.expect_equal(5, ast.block_statement_count(&tree, function.body))
|
||||||
@@ -952,6 +951,11 @@ recovers_missing_logical_operands_and_preserves_enclosing_nodes test {
|
|||||||
try testing.expect_equal(ast.BinaryOperator.logical_or, second_or.operator)
|
try testing.expect_equal(ast.BinaryOperator.logical_or, second_or.operator)
|
||||||
second_and :: ast.binary_expr(&tree, second_or.left)
|
second_and :: ast.binary_expr(&tree, second_or.left)
|
||||||
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, second_and.right))
|
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, second_and.right))
|
||||||
|
missing :: ast.invalid(&tree, second_and.right)
|
||||||
|
diagnostic :: diagnostics.get(&diagnostic_store, missing.diagnostic)?
|
||||||
|
try testing.expect_equal(diagnostics.Code.unexpected_token, diagnostic.code)
|
||||||
|
try testing.expect_equal(missing.span.start, diagnostic.span.start)
|
||||||
|
try testing.expect_equal(missing.span.end, diagnostic.span.end)
|
||||||
try testing.expect_equal(ast.ExprKind.literal_bool, ast.expr_kind(&tree, second_or.right))
|
try testing.expect_equal(ast.ExprKind.literal_bool, ast.expr_kind(&tree, second_or.right))
|
||||||
third :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 2))
|
third :: ast.statement_decl(&tree, ast.block_statement(&tree, function.body, 2))
|
||||||
try testing.expect_equal(2, ast.call_arg_count(&tree, third.initializer))
|
try testing.expect_equal(2, ast.call_arg_count(&tree, third.initializer))
|
||||||
@@ -1036,13 +1040,17 @@ rejects_control_tails_and_preserves_following_declarations test {
|
|||||||
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
|
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
|
||||||
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||||
defer ast.deinit(&tree)
|
defer ast.deinit(&tree)
|
||||||
try testing.expect_equal(9, diagnostics.count(&diagnostic_store))
|
|
||||||
try testing.expect_equal(6, ast.package_decl_count(&tree))
|
try testing.expect_equal(6, ast.package_decl_count(&tree))
|
||||||
for (usize(0)..usize(2)) |index| try testing.expect_equal(ast.DeclKind.invalid, ast.decl_kind(&tree, ast.package_decl(&tree, index)))
|
for (usize(0)..usize(2)) |index| try testing.expect_equal(ast.DeclKind.invalid, ast.decl_kind(&tree, ast.package_decl(&tree, index)))
|
||||||
for (usize(2)..usize(4)) |index| {
|
for (usize(2)..usize(4)) |index| {
|
||||||
declaration :: ast.package_decl(&tree, index)
|
declaration :: ast.package_decl(&tree, index)
|
||||||
try testing.expect_equal(ast.DeclKind.statement, ast.decl_kind(&tree, declaration))
|
try testing.expect_equal(ast.DeclKind.statement, ast.decl_kind(&tree, declaration))
|
||||||
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, ast.statement_decl(&tree, declaration).initializer))
|
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, ast.statement_decl(&tree, declaration).initializer))
|
||||||
|
invalid :: ast.invalid(&tree, ast.statement_decl(&tree, declaration).initializer)
|
||||||
|
diagnostic :: diagnostics.get(&diagnostic_store, invalid.diagnostic)?
|
||||||
|
try testing.expect_equal(diagnostics.Code.unexpected_token, diagnostic.code)
|
||||||
|
try testing.expect_equal(invalid.span.start, diagnostic.span.start)
|
||||||
|
try testing.expect_equal(invalid.span.end, diagnostic.span.end)
|
||||||
}
|
}
|
||||||
body :: ast.function_decl(&tree, ast.package_decl(&tree, 4)).body
|
body :: ast.function_decl(&tree, ast.package_decl(&tree, 4)).body
|
||||||
try testing.expect_equal(2, ast.block_statement_count(&tree, body))
|
try testing.expect_equal(2, ast.block_statement_count(&tree, body))
|
||||||
@@ -1588,3 +1596,117 @@ rolls_back_shared_nodes_extra_spans_and_diagnostics_on_allocation_failure test {
|
|||||||
}
|
}
|
||||||
try testing.expect(failures > 0)
|
try testing.expect(failures > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
parses_enum_union_wrappers_and_keyword_variants test {
|
||||||
|
strings := strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strings)
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
source ::
|
||||||
|
`Tag :: enum(i32) { if = -2, else = 3 # preserve the member boundary
|
||||||
|
` return
|
||||||
|
`}
|
||||||
|
`Payload :: union(Tag) { if i32, else void, return struct { value i32 } }
|
||||||
|
`Auto :: union(enum) { value i32, empty void }
|
||||||
|
`Raw :: union { integer i32, real f64 }
|
||||||
|
`Id :: distinct i32
|
||||||
|
`Other :: alias Id
|
||||||
|
`value Payload :: Payload{ if = 4 }
|
||||||
|
`context Payload :: .return{value = 5}
|
||||||
|
`empty Payload :: .else
|
||||||
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
|
defer lexer.deinit(&scan_state)
|
||||||
|
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
|
||||||
|
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||||
|
defer ast.deinit(&tree)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
try testing.expect_equal(9, ast.package_decl_count(&tree))
|
||||||
|
enumeration :: ast.statement_decl(&tree, ast.package_decl(&tree, 0)).initializer
|
||||||
|
try testing.expect(ast.enum_type(&tree, enumeration).backing != null)
|
||||||
|
try testing.expect_equal(3, ast.enum_member_count(&tree, enumeration))
|
||||||
|
member :: ast.field(&tree, ast.enum_member(&tree, enumeration, 0))
|
||||||
|
try testing.expect_equal("if", strpool.get_str(&strings, ast.name_id(&tree, member.name?))?)
|
||||||
|
try testing.expect_equal(ast.ExprKind.unary, ast.expr_kind(&tree, member.value?))
|
||||||
|
tagged :: ast.statement_decl(&tree, ast.package_decl(&tree, 1)).initializer
|
||||||
|
automatic :: ast.statement_decl(&tree, ast.package_decl(&tree, 2)).initializer
|
||||||
|
raw :: ast.statement_decl(&tree, ast.package_decl(&tree, 3)).initializer
|
||||||
|
try testing.expect(ast.union_type(&tree, tagged).tag != null)
|
||||||
|
try testing.expect(ast.union_type(&tree, automatic).auto_tag)
|
||||||
|
try testing.expect(!ast.union_type(&tree, raw).auto_tag)
|
||||||
|
try testing.expect_equal(2, ast.record_field_count(&tree, raw))
|
||||||
|
distinct_type :: ast.statement_decl(&tree, ast.package_decl(&tree, 4)).initializer
|
||||||
|
alias_value :: ast.statement_decl(&tree, ast.package_decl(&tree, 5)).initializer
|
||||||
|
try testing.expect_equal(ast.ExprKind.distinct_type, ast.expr_kind(&tree, distinct_type))
|
||||||
|
try testing.expect_equal(ast.ExprKind.alias_expr, ast.expr_kind(&tree, alias_value))
|
||||||
|
contextual :: ast.statement_decl(&tree, ast.package_decl(&tree, 7)).initializer
|
||||||
|
variant :: ast.record_literal(&tree, contextual).type?
|
||||||
|
try testing.expect_equal(ast.ExprKind.enum_literal, ast.expr_kind(&tree, variant))
|
||||||
|
try testing.expect_equal("return", strpool.get_str(&strings, ast.name_id(&tree, ast.enum_literal(&tree, variant)))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
parses_match_patterns_payload_captures_and_statement_arms test {
|
||||||
|
strings := strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strings)
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
source ::
|
||||||
|
`choose proc(value int) int {
|
||||||
|
` return match value {
|
||||||
|
` 0, 1..=3: 10,
|
||||||
|
` 4: 20 # preserve the arm boundary
|
||||||
|
` else: 30
|
||||||
|
` }
|
||||||
|
`}
|
||||||
|
`use proc(value int) void {
|
||||||
|
` match value {
|
||||||
|
` .if, .return |payload|: { local :: payload }
|
||||||
|
` .else: return,
|
||||||
|
` else: {}
|
||||||
|
` }
|
||||||
|
`}
|
||||||
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
|
defer lexer.deinit(&scan_state)
|
||||||
|
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
|
||||||
|
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||||
|
defer ast.deinit(&tree)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
choose :: ast.function_decl(&tree, ast.package_decl(&tree, 0))
|
||||||
|
value_match :: ast.return_stmt(&tree, ast.block_statement(&tree, choose.body, 0)).value?
|
||||||
|
try testing.expect_equal(3, ast.match_arm_count(&tree, value_match))
|
||||||
|
first :: ast.match_arm(&tree, value_match, 0)
|
||||||
|
try testing.expect_equal(2, ast.match_pattern_count(&tree, first))
|
||||||
|
try testing.expect_equal(ast.ExprKind.range, ast.expr_kind(&tree, ast.match_pattern(&tree, first, 1)))
|
||||||
|
try testing.expect(ast.match_arm_view(&tree, first).expression)
|
||||||
|
use :: ast.function_decl(&tree, ast.package_decl(&tree, 1))
|
||||||
|
statement_match :: ast.statement_expr(&tree, ast.block_statement(&tree, use.body, 0))
|
||||||
|
captured :: ast.match_arm_view(&tree, ast.match_arm(&tree, statement_match, 0))
|
||||||
|
try testing.expect(captured.capture != null)
|
||||||
|
try testing.expect(!captured.expression)
|
||||||
|
try testing.expect(ast.match_arm_view(&tree, ast.match_arm(&tree, statement_match, 2)).catch_all)
|
||||||
|
}
|
||||||
|
|
||||||
|
rejects_pointer_match_captures_and_preserves_siblings test {
|
||||||
|
strings := strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strings)
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
source ::
|
||||||
|
`broken :: enum { first
|
||||||
|
`after :: 1
|
||||||
|
`use proc(value int) void {
|
||||||
|
` match value {
|
||||||
|
` .item |@payload|: payload
|
||||||
|
` else: {}
|
||||||
|
` }
|
||||||
|
`}
|
||||||
|
`last :: 2
|
||||||
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
|
defer lexer.deinit(&scan_state)
|
||||||
|
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
|
||||||
|
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||||
|
defer ast.deinit(&tree)
|
||||||
|
try testing.expect(diagnostics.count(&diagnostic_store) > 0)
|
||||||
|
try testing.expect_equal(4, ast.package_decl_count(&tree))
|
||||||
|
try testing.expect_equal("after", ast.source_text(&tree, ast.statement_decl(&tree, ast.package_decl(&tree, 1)).name))
|
||||||
|
try testing.expect_equal("last", ast.source_text(&tree, ast.statement_decl(&tree, ast.package_decl(&tree, 3)).name))
|
||||||
|
}
|
||||||
|
|||||||
+1087
-47
File diff suppressed because it is too large
Load Diff
@@ -69,8 +69,8 @@ propagates_numeric_context_before_defaulting_forward_and_mutable_aliases test {
|
|||||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
defer diagnostics.deinit(&diagnostic_store)
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
source ::
|
source ::
|
||||||
`forward float :: -alias * 2 + seed
|
`forward float :: -forwarded * 2 + seed
|
||||||
`alias :: seed
|
`forwarded :: seed
|
||||||
`seed := 3
|
`seed := 3
|
||||||
`backward :: 4
|
`backward :: 4
|
||||||
`demand float :: backward
|
`demand float :: backward
|
||||||
@@ -81,7 +81,7 @@ propagates_numeric_context_before_defaulting_forward_and_mutable_aliases test {
|
|||||||
`text :: "honey"
|
`text :: "honey"
|
||||||
`fixed int :: 7
|
`fixed int :: 7
|
||||||
`anchored :: fixed + 2
|
`anchored :: fixed + 2
|
||||||
`nested float :: quotient / (alias + 1)
|
`nested float :: quotient / (forwarded + 1)
|
||||||
scan_state := lexer.init(mem.c_allocator)
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
defer lexer.deinit(&scan_state)
|
defer lexer.deinit(&scan_state)
|
||||||
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
|
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
|
||||||
@@ -192,9 +192,9 @@ checks_forward_alias_calls_grouped_parameters_and_mutual_recursion test {
|
|||||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
defer diagnostics.deinit(&diagnostic_store)
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
source ::
|
source ::
|
||||||
`alias :: alpha
|
`forwarded :: alpha
|
||||||
`alpha proc(a, b int) void {
|
`alpha proc(a, b int) void {
|
||||||
` (alias)(a, b)
|
` (forwarded)(a, b)
|
||||||
` beta(a + b, seed)
|
` beta(a + b, seed)
|
||||||
`}
|
`}
|
||||||
`beta proc(total int, amount float) void {
|
`beta proc(total int, amount float) void {
|
||||||
@@ -203,7 +203,7 @@ checks_forward_alias_calls_grouped_parameters_and_mutual_recursion test {
|
|||||||
`seed := 3
|
`seed := 3
|
||||||
`main proc() void {
|
`main proc() void {
|
||||||
` beta(1, 2)
|
` beta(1, 2)
|
||||||
` (alias)(3, 4)
|
` (forwarded)(3, 4)
|
||||||
`}
|
`}
|
||||||
scan_state := lexer.init(mem.c_allocator)
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
defer lexer.deinit(&scan_state)
|
defer lexer.deinit(&scan_state)
|
||||||
@@ -359,8 +359,8 @@ reports_nonvoid_fallthrough_without_poisoning_signatures_and_checks_unused_bodie
|
|||||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
defer diagnostics.deinit(&diagnostic_store)
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
source ::
|
source ::
|
||||||
`answer :: alias()
|
`answer :: forwarded()
|
||||||
`alias :: make
|
`forwarded :: make
|
||||||
`make proc() int {}
|
`make proc() int {}
|
||||||
`unused proc() float {}
|
`unused proc() float {}
|
||||||
`broken proc() void {
|
`broken proc() void {
|
||||||
@@ -762,13 +762,13 @@ checks_bool_comparisons_recursive_returns_and_nested_numeric_context test {
|
|||||||
`flag bool :: true
|
`flag bool :: true
|
||||||
`same :: flag == false
|
`same :: flag == false
|
||||||
`different :: true != flag
|
`different :: true != flag
|
||||||
`less :: alias < 4.0
|
`less :: forwarded < 4.0
|
||||||
`less_equal :: seed <= 5
|
`less_equal :: seed <= 5
|
||||||
`greater :: 6 > seed
|
`greater :: 6 > seed
|
||||||
`greater_equal :: seed >= 1
|
`greater_equal :: seed >= 1
|
||||||
`equal :: seed == 3
|
`equal :: seed == 3
|
||||||
`not_equal :: seed != 2
|
`not_equal :: seed != 2
|
||||||
`alias :: seed
|
`forwarded :: seed
|
||||||
`seed := 3
|
`seed := 3
|
||||||
`identity proc(value bool) bool { return value }
|
`identity proc(value bool) bool { return value }
|
||||||
`factorial proc(n int) int {
|
`factorial proc(n int) int {
|
||||||
@@ -1789,11 +1789,11 @@ range_checks_both_bounds_and_preserves_fixed_declarations_and_source_errors test
|
|||||||
source ::
|
source ::
|
||||||
`real float :: 2
|
`real float :: 2
|
||||||
`flag :: true
|
`flag :: true
|
||||||
`alias :: flag
|
`forwarded :: flag
|
||||||
`nothing proc() void {}
|
`nothing proc() void {}
|
||||||
`main proc() void {
|
`main proc() void {
|
||||||
` for 1.5..2.5 |_| {}
|
` for 1.5..2.5 |_| {}
|
||||||
` for alias.."text" |_| {}
|
` for forwarded.."text" |_| {}
|
||||||
` for nothing..nothing() |_| {}
|
` for nothing..nothing() |_| {}
|
||||||
` for real..3 |_| {}
|
` for real..3 |_| {}
|
||||||
` for absent_start..absent_end |_| {}
|
` for absent_start..absent_end |_| {}
|
||||||
@@ -2206,8 +2206,8 @@ expect_array proc(result @Result, id ast.NodeId, len usize, mutable bool, elemen
|
|||||||
|
|
||||||
fixed_arrays_contextualize_forward_aliases_counts_nested_literals_and_signatures test {
|
fixed_arrays_contextualize_forward_aliases_counts_nested_literals_and_signatures test {
|
||||||
source ::
|
source ::
|
||||||
`context [2]f64 :: alias
|
`context [2]f64 :: forwarded
|
||||||
`alias :: seed
|
`forwarded :: seed
|
||||||
`seed := [1, 2]
|
`seed := [1, 2]
|
||||||
`count :: later * 2 - 1
|
`count :: later * 2 - 1
|
||||||
`later :: 2
|
`later :: 2
|
||||||
|
|||||||
+91
-1
@@ -1,6 +1,7 @@
|
|||||||
import "@std/debug"
|
import "@std/debug"
|
||||||
import "@std/mem"
|
import "@std/mem"
|
||||||
import "@std/hashmap"
|
import "@std/hashmap"
|
||||||
|
import "@std/arraylist"
|
||||||
import "@source/strpool"
|
import "@source/strpool"
|
||||||
|
|
||||||
import "@source/ast"
|
import "@source/ast"
|
||||||
@@ -99,6 +100,8 @@ State :: struct {
|
|||||||
builtin_ids []strpool.StringId
|
builtin_ids []strpool.StringId
|
||||||
allocator mem.Allocator
|
allocator mem.Allocator
|
||||||
namespaces hashmap.AutoHashMap(ast.NodeId, Scope)
|
namespaces hashmap.AutoHashMap(ast.NodeId, Scope)
|
||||||
|
aliases []mut ast.NodeId
|
||||||
|
selections arraylist.ArrayList(ast.NodeId)
|
||||||
}
|
}
|
||||||
|
|
||||||
#! borrows `tree`, its identifier `pool`, and `diagnostic_store` during resolution.
|
#! borrows `tree`, its identifier `pool`, and `diagnostic_store` during resolution.
|
||||||
@@ -138,6 +141,9 @@ resolve_packages proc(
|
|||||||
errdefer mem.free(allocator, bindings)
|
errdefer mem.free(allocator, bindings)
|
||||||
mem.set(bindings, Binding{})
|
mem.set(bindings, Binding{})
|
||||||
builtin_ids :: try intern_builtins(pool, &builtins)
|
builtin_ids :: try intern_builtins(pool, &builtins)
|
||||||
|
aliases :: try mem.alloc(ast.NodeId, allocator, ast.node_count(tree))
|
||||||
|
defer mem.free(allocator, aliases)
|
||||||
|
mem.set(aliases, maxval!(ast.NodeId))
|
||||||
|
|
||||||
state := State{
|
state := State{
|
||||||
tree = tree,
|
tree = tree,
|
||||||
@@ -146,7 +152,10 @@ resolve_packages proc(
|
|||||||
builtin_ids = builtin_ids[..],
|
builtin_ids = builtin_ids[..],
|
||||||
allocator = allocator,
|
allocator = allocator,
|
||||||
namespaces = hashmap.init(allocator),
|
namespaces = hashmap.init(allocator),
|
||||||
|
aliases = aliases,
|
||||||
|
selections = arraylist.init(allocator),
|
||||||
}
|
}
|
||||||
|
defer arraylist.deinit(&state.selections)
|
||||||
defer {
|
defer {
|
||||||
for (state.namespaces.entries) |entry| {
|
for (state.namespaces.entries) |entry| {
|
||||||
if (entry.hash == 0) continue
|
if (entry.hash == 0) continue
|
||||||
@@ -184,6 +193,7 @@ resolve_packages proc(
|
|||||||
}
|
}
|
||||||
for (imports) |imported| try resolve_import(&state, &file_scopes[usize(ast.node_span(tree, imported.node).source)], imported)
|
for (imports) |imported| try resolve_import(&state, &file_scopes[usize(ast.node_span(tree, imported.node).source)], imported)
|
||||||
for (roots) |root| try resolve_package(&state, root, file_scopes)
|
for (roots) |root| try resolve_package(&state, root, file_scopes)
|
||||||
|
try canonicalize_aliases(&state)
|
||||||
|
|
||||||
return Resolution{ storage = Storage{
|
return Resolution{ storage = Storage{
|
||||||
bindings = bindings,
|
bindings = bindings,
|
||||||
@@ -402,10 +412,62 @@ resolve_statement_decl proc(
|
|||||||
id ast.NodeId,
|
id ast.NodeId,
|
||||||
) void ! (mem.AllocError | diagnostics.CapacityError) {
|
) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
declaration :: ast.statement_decl(state.tree, id)
|
declaration :: ast.statement_decl(state.tree, id)
|
||||||
|
if (ast.expr_kind(state.tree, declaration.initializer) == .alias_expr) {
|
||||||
|
state.aliases[usize(id)] = ast.type_operand(state.tree, declaration.initializer)
|
||||||
|
}
|
||||||
if (declaration.type) |type| try resolve_expr(state, scope, type)
|
if (declaration.type) |type| try resolve_expr(state, scope, type)
|
||||||
try resolve_expr(state, scope, declaration.initializer)
|
try resolve_expr(state, scope, declaration.initializer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
canonical_binding proc(state @State, original Binding) Binding {
|
||||||
|
value := original
|
||||||
|
for (0..(state.aliases.len)) |_| {
|
||||||
|
if (value.kind != .declaration) break
|
||||||
|
operand :: state.aliases[usize(value.data.declaration)]
|
||||||
|
if (operand == maxval!(ast.NodeId)) break
|
||||||
|
target :: state.bindings[usize(operand)]
|
||||||
|
if (target.kind == .none) break
|
||||||
|
if (target.kind == .declaration and target.data.declaration == value.data.declaration) break
|
||||||
|
value = target
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
canonicalize_aliases proc(state @mut State) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
|
# aliases may forward-reference other aliases or imported namespace members.
|
||||||
|
for (0..(state.aliases.len)) |_| {
|
||||||
|
changed := false
|
||||||
|
for (state.bindings) |@entry| {
|
||||||
|
value :: canonical_binding(state, entry^)
|
||||||
|
if (value.kind != entry.kind) changed = true
|
||||||
|
else if (value.kind == .declaration and value.data.declaration != entry.data.declaration) changed = true
|
||||||
|
entry^ = value
|
||||||
|
}
|
||||||
|
for (state.selections.items) |id| {
|
||||||
|
expression :: ast.selection_expr(state.tree, id)
|
||||||
|
base :: state.bindings[usize(expression.base)]
|
||||||
|
if (base.kind != .namespace or state.bindings[usize(id)].kind != .none) continue
|
||||||
|
namespace :: hashmap.get(&state.namespaces, base.data.namespace)?
|
||||||
|
found :: if (ast.expr_kind(state.tree, expression.member) == .identifier)
|
||||||
|
hashmap.get(&namespace.names, ast.name_id(state.tree, expression.member))
|
||||||
|
else
|
||||||
|
null
|
||||||
|
value :: if (found) |member| canonical_binding(state, member) else Binding{
|
||||||
|
kind = .invalid,
|
||||||
|
data = BindingData{ diagnostic = try diagnostics.add(
|
||||||
|
state.diagnostics, .unknown_name, ast.node_span(state.tree, expression.member),
|
||||||
|
) },
|
||||||
|
}
|
||||||
|
state.bindings[usize(id)] = value
|
||||||
|
state.bindings[usize(expression.member)] = value
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
if (!changed) break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
resolve_invalid proc(state @mut State, id ast.NodeId) void {
|
resolve_invalid proc(state @mut State, id ast.NodeId) void {
|
||||||
if (state.bindings[usize(id)].kind != .none) return
|
if (state.bindings[usize(id)].kind != .none) return
|
||||||
@@ -426,6 +488,30 @@ resolve_expr proc(
|
|||||||
tree :: state.tree
|
tree :: state.tree
|
||||||
match ast.expr_kind(tree, id) {
|
match ast.expr_kind(tree, id) {
|
||||||
.literal_int, .literal_float, .literal_bool, .literal_string: {}
|
.literal_int, .literal_float, .literal_bool, .literal_string: {}
|
||||||
|
.enum_literal: {}
|
||||||
|
.distinct_type, .alias_expr: try resolve_expr(state, scope, ast.type_operand(tree, id))
|
||||||
|
.enum_type: {
|
||||||
|
if (ast.enum_type(tree, id).backing) |backing| try resolve_expr(state, scope, backing)
|
||||||
|
for (0..ast.enum_member_count(tree, id)) |i| {
|
||||||
|
member :: ast.field(tree, ast.enum_member(tree, id, i))
|
||||||
|
if (member.value) |value| try resolve_expr(state, scope, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.match_expr: {
|
||||||
|
try resolve_expr(state, scope, ast.match_expr(tree, id).subject)
|
||||||
|
for (0..ast.match_arm_count(tree, id)) |i| {
|
||||||
|
arm_id :: ast.match_arm(tree, id, i)
|
||||||
|
arm :: ast.match_arm_view(tree, arm_id)
|
||||||
|
for (0..ast.match_pattern_count(tree, arm_id)) |j| {
|
||||||
|
try resolve_expr(state, scope, ast.match_pattern(tree, arm_id, j))
|
||||||
|
}
|
||||||
|
arm_scope := Scope{ names = hashmap.init(state.allocator), parent = scope }
|
||||||
|
defer hashmap.deinit(&arm_scope.names)
|
||||||
|
if (arm.capture) |capture| try predeclare(state, &arm_scope, capture, ast.node_span(tree, capture))
|
||||||
|
if (arm.expression) try resolve_expr(state, &arm_scope, arm.body)
|
||||||
|
else try resolve_block(state, &arm_scope, arm.body)
|
||||||
|
}
|
||||||
|
}
|
||||||
.identifier: {
|
.identifier: {
|
||||||
span :: ast.node_span(tree, id)
|
span :: ast.node_span(tree, id)
|
||||||
name :: ast.name_id(tree, id)
|
name :: ast.name_id(tree, id)
|
||||||
@@ -441,6 +527,7 @@ resolve_expr proc(
|
|||||||
}
|
}
|
||||||
.selection: {
|
.selection: {
|
||||||
expression :: ast.selection_expr(tree, id)
|
expression :: ast.selection_expr(tree, id)
|
||||||
|
try arraylist.append(&state.selections, id)
|
||||||
try resolve_expr(state, scope, expression.base)
|
try resolve_expr(state, scope, expression.base)
|
||||||
base :: state.bindings[usize(expression.base)]
|
base :: state.bindings[usize(expression.base)]
|
||||||
value := Binding{}
|
value := Binding{}
|
||||||
@@ -494,10 +581,13 @@ resolve_expr proc(
|
|||||||
try resolve_expr(state, scope, ast.array_element(tree, id, i))
|
try resolve_expr(state, scope, ast.array_element(tree, id, i))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.record_type, .record_literal: {
|
.record_type, .record_literal, .union_type: {
|
||||||
if (ast.expr_kind(tree, id) == .record_literal) {
|
if (ast.expr_kind(tree, id) == .record_literal) {
|
||||||
if (ast.record_literal(tree, id).type) |type| try resolve_expr(state, scope, type)
|
if (ast.record_literal(tree, id).type) |type| try resolve_expr(state, scope, type)
|
||||||
}
|
}
|
||||||
|
if (ast.expr_kind(tree, id) == .union_type) {
|
||||||
|
if (ast.union_type(tree, id).tag) |tag| try resolve_expr(state, scope, tag)
|
||||||
|
}
|
||||||
for (0..ast.record_field_count(tree, id)) |i| {
|
for (0..ast.record_field_count(tree, id)) |i| {
|
||||||
field :: ast.field(tree, ast.record_field(tree, id, i))
|
field :: ast.field(tree, ast.record_field(tree, id, i))
|
||||||
if (field.type) |type| try resolve_expr(state, scope, type)
|
if (field.type) |type| try resolve_expr(state, scope, type)
|
||||||
|
|||||||
@@ -809,3 +809,126 @@ resolves_interleaved_compilations_with_independent_identifier_orders test {
|
|||||||
try testing.expect_equal(usize(0), diagnostics.count(&first_diagnostics))
|
try testing.expect_equal(usize(0), diagnostics.count(&first_diagnostics))
|
||||||
try testing.expect_equal(usize(0), diagnostics.count(&second_diagnostics))
|
try testing.expect_equal(usize(0), diagnostics.count(&second_diagnostics))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resolves_match_capture_scopes_without_looking_up_member_names test {
|
||||||
|
pool := strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&pool)
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
source ::
|
||||||
|
`Tag :: enum { if, else }
|
||||||
|
`Value :: union(Tag) { if i32, else void }
|
||||||
|
`inspect proc(value Value) void {
|
||||||
|
` match value {
|
||||||
|
` .if |payload|: { local :: payload }
|
||||||
|
` .else: { local :: 1 }
|
||||||
|
` }
|
||||||
|
` payload
|
||||||
|
` local
|
||||||
|
`}
|
||||||
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
|
defer lexer.deinit(&scan_state)
|
||||||
|
try lexer.scan(&scan_state, source, &pool, &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, &pool, &diagnostic_store)
|
||||||
|
defer deinit(&resolution)
|
||||||
|
try testing.expect_equal(2, diagnostics.count(&diagnostic_store))
|
||||||
|
body :: ast.function_decl(&tree, ast.package_decl(&tree, 2)).body
|
||||||
|
matched :: ast.statement_expr(&tree, ast.block_statement(&tree, body, 0))
|
||||||
|
first :: ast.match_arm(&tree, matched, 0)
|
||||||
|
arm :: ast.match_arm_view(&tree, first)
|
||||||
|
local :: ast.statement_decl(&tree, ast.block_statement(&tree, arm.body, 0))
|
||||||
|
try testing.expect_equal(arm.capture?, binding(&resolution, local.initializer).data.declaration)
|
||||||
|
member :: ast.enum_literal(&tree, ast.match_pattern(&tree, first, 0))
|
||||||
|
try testing.expect_equal(BindingKind.none, binding(&resolution, member).kind)
|
||||||
|
for (usize(1)..usize(3)) |index| {
|
||||||
|
reference :: ast.statement_expr(&tree, ast.block_statement(&tree, body, index))
|
||||||
|
try testing.expect_equal(BindingKind.invalid, binding(&resolution, reference).kind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
canonicalizes_forward_function_global_and_builtin_aliases test {
|
||||||
|
pool := strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&pool)
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
source ::
|
||||||
|
`invoke :: alias call
|
||||||
|
`copy :: alias storage
|
||||||
|
`Number :: alias i32
|
||||||
|
`call :: alias original
|
||||||
|
`original proc() i32 { return storage }
|
||||||
|
`storage i32 := 1
|
||||||
|
`main proc() Number {
|
||||||
|
` copy = invoke()
|
||||||
|
` return copy
|
||||||
|
`}
|
||||||
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
|
defer lexer.deinit(&scan_state)
|
||||||
|
try lexer.scan(&scan_state, source, &pool, &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, &pool, &diagnostic_store)
|
||||||
|
defer deinit(&resolution)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
original :: ast.package_decl(&tree, 4)
|
||||||
|
storage :: ast.package_decl(&tree, 5)
|
||||||
|
main :: ast.function_decl(&tree, ast.package_decl(&tree, 6))
|
||||||
|
assignment :: ast.assignment_stmt(&tree, ast.block_statement(&tree, main.body, 0))
|
||||||
|
try testing.expect_equal(storage, binding(&resolution, assignment.target).data.declaration)
|
||||||
|
try testing.expect_equal(original, binding(&resolution, ast.call_expr(&tree, assignment.value).callee).data.declaration)
|
||||||
|
try testing.expect_equal(BindingKind.builtin, binding(&resolution, main.return_type).kind)
|
||||||
|
try testing.expect_equal(BuiltinType.i32, binding(&resolution, main.return_type).data.builtin)
|
||||||
|
try testing.expect_equal(original, binding(&resolution, ast.package_decl(&tree, 0)).data.declaration)
|
||||||
|
}
|
||||||
|
|
||||||
|
canonicalizes_imported_aliases_and_namespace_alias_selections test {
|
||||||
|
pool := strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&pool)
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
builder := ast.init_builder(mem.c_allocator, "")
|
||||||
|
defer ast.deinit_builder(&builder)
|
||||||
|
library_source :: "original proc() i32 { return storage }\nstorage i32 := 1\nexported :: alias original\n"
|
||||||
|
library_id :: try ast.set_source(&builder, "library.hon", library_source)
|
||||||
|
library_scan := lexer.init(mem.c_allocator)
|
||||||
|
defer lexer.deinit(&library_scan)
|
||||||
|
library_scan.source_id = library_id
|
||||||
|
try lexer.scan(&library_scan, library_source, &pool, &diagnostic_store)
|
||||||
|
library :: try parser.parse_source(&builder, library_scan.tokens.items, &diagnostic_store)
|
||||||
|
main_source ::
|
||||||
|
`lib :: import "library"
|
||||||
|
`invoke :: alias namespace.exported
|
||||||
|
`copy :: alias namespace.storage
|
||||||
|
`namespace :: alias lib
|
||||||
|
`main proc() i32 { copy = invoke()
|
||||||
|
` return copy
|
||||||
|
`}
|
||||||
|
main_id :: try ast.set_source(&builder, "main.hon", main_source)
|
||||||
|
main_scan := lexer.init(mem.c_allocator)
|
||||||
|
defer lexer.deinit(&main_scan)
|
||||||
|
main_scan.source_id = main_id
|
||||||
|
try lexer.scan(&main_scan, main_source, &pool, &diagnostic_store)
|
||||||
|
root :: try parser.parse_source(&builder, main_scan.tokens.items, &diagnostic_store)
|
||||||
|
tree := ast.finish(&builder, root)
|
||||||
|
defer ast.deinit(&tree)
|
||||||
|
roots [2]ast.NodeId :: [root, library]
|
||||||
|
imports [1]Import :: [Import{
|
||||||
|
node = ast.package_decl_at(&tree, root, 0),
|
||||||
|
name = strpool.get_id(&pool, "lib")?,
|
||||||
|
target = library,
|
||||||
|
}]
|
||||||
|
resolution := try resolve_packages(mem.c_allocator, &tree, roots[..], imports[..], &pool, &diagnostic_store)
|
||||||
|
defer deinit(&resolution)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
original :: ast.package_decl_at(&tree, library, 0)
|
||||||
|
storage :: ast.package_decl_at(&tree, library, 1)
|
||||||
|
main :: ast.function_decl(&tree, ast.package_decl_at(&tree, root, 4))
|
||||||
|
assignment :: ast.assignment_stmt(&tree, ast.block_statement(&tree, main.body, 0))
|
||||||
|
try testing.expect_equal(storage, binding(&resolution, assignment.target).data.declaration)
|
||||||
|
try testing.expect_equal(original, binding(&resolution, ast.call_expr(&tree, assignment.value).callee).data.declaration)
|
||||||
|
namespace :: ast.package_decl_at(&tree, root, 3)
|
||||||
|
try testing.expect_equal(BindingKind.namespace, binding(&resolution, namespace).kind)
|
||||||
|
}
|
||||||
|
|||||||
+285
@@ -11,6 +11,245 @@ HONEY = ROOT / "build/honey"
|
|||||||
|
|
||||||
# Explicit expectations guard against frontend bugs shared by both execution paths.
|
# Explicit expectations guard against frontend bugs shared by both execution paths.
|
||||||
CASES = {
|
CASES = {
|
||||||
|
"match_float_ranges": (
|
||||||
|
"""
|
||||||
|
score proc(n f64) i16 {
|
||||||
|
return match n { (-1.5)..0.5: 1, 0.5..=2.5: 42, else: 0 }
|
||||||
|
}
|
||||||
|
main proc() [3]i16 { return [score(-1.0), score(2.5), score(3.0)] }
|
||||||
|
""", b"[1, 42, 0]\n", None,
|
||||||
|
),
|
||||||
|
"distinct_float_literal_context": (
|
||||||
|
"""
|
||||||
|
Amount :: distinct f64
|
||||||
|
main proc() f64 {
|
||||||
|
first Amount :: 1.5
|
||||||
|
second :: 0.5 + first
|
||||||
|
return f64(second + 40)
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"distinct_enum_construction": (
|
||||||
|
"E :: enum { a, b }\nD :: distinct E\nmain proc() bool { return D(E.a) == D(E.a) }",
|
||||||
|
b"true\n", None,
|
||||||
|
),
|
||||||
|
"union_aligned_record_payload": (
|
||||||
|
"""
|
||||||
|
Pair :: struct { a u8, b u64 }
|
||||||
|
U :: union { pair Pair, words [2]mut u64 }
|
||||||
|
main proc() [2]u64 {
|
||||||
|
value := U { words = [1, 42] }
|
||||||
|
value.pair.b = 99
|
||||||
|
copy :: value
|
||||||
|
value.words[1] = 7
|
||||||
|
return [u64(copy.pair.a), copy.pair.b]
|
||||||
|
}
|
||||||
|
""", b"[1, 99]\n", None,
|
||||||
|
),
|
||||||
|
"tagged_union_result": (
|
||||||
|
"C :: union(enum) { empty void, value struct { x u8, y bool } }\nmain proc() C { return .value { x = 42, y = true } }",
|
||||||
|
b"{{42, true}}\n", None,
|
||||||
|
),
|
||||||
|
"tagged_union_void_result": (
|
||||||
|
"C :: union(enum) { empty void, value u8 }\nmain proc() C { return .empty }",
|
||||||
|
b"{}\n", None,
|
||||||
|
),
|
||||||
|
"distinct_boolean_equality": (
|
||||||
|
"Flag :: distinct bool\nmain proc() bool { return Flag(true) == Flag(true) and bool(Flag(true)) and !bool(Flag(false)) }",
|
||||||
|
b"true\n", None,
|
||||||
|
),
|
||||||
|
"match_aggregate_result": (
|
||||||
|
"""
|
||||||
|
E :: enum { a, b }
|
||||||
|
Point :: struct { x i16 }
|
||||||
|
choose proc(e E) Point { return match e { .a: Point { x = 42 }, .b: Point { x = 9 } } }
|
||||||
|
main proc() i16 { return choose(.a).x }
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"enum_context_and_values": (
|
||||||
|
"""
|
||||||
|
Token :: enum(i16) { if = -2, else, return = 40 }
|
||||||
|
identity proc(t Token) Token { return t }
|
||||||
|
main proc() i16 {
|
||||||
|
values [2]Token :: [.if, Token.return]
|
||||||
|
if (identity(values[0]) == .if) return i16(values[1]) + 2
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"enum_unsigned_backing": (
|
||||||
|
"E :: enum(u64) { high = 18446744073709551615 }\nmain proc() u64 { return u64(E.high) }",
|
||||||
|
b"18446744073709551615\n", None,
|
||||||
|
),
|
||||||
|
"distinct_arithmetic_and_layers": (
|
||||||
|
"""
|
||||||
|
Id :: distinct u8
|
||||||
|
Wrapped :: distinct Id
|
||||||
|
Alias :: alias Id
|
||||||
|
identity proc(n Id) Id { return n }
|
||||||
|
main proc() u8 {
|
||||||
|
n Alias := identity(Alias(40))
|
||||||
|
n = n + 2
|
||||||
|
return u8(Id(Wrapped(n)))
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"distinct_aggregate_copy": (
|
||||||
|
"""
|
||||||
|
Point :: struct { x i16 }
|
||||||
|
Wrapped :: distinct Point
|
||||||
|
identity proc(w Wrapped) Wrapped { return w }
|
||||||
|
main proc() i16 {
|
||||||
|
p Point := { x = 42 }
|
||||||
|
w :: identity(Wrapped(p))
|
||||||
|
p.x = 9
|
||||||
|
copy :: Point(w)
|
||||||
|
return copy.x
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"alias_function_and_global_identity": (
|
||||||
|
"""
|
||||||
|
value i16 :: 41
|
||||||
|
add proc(n i16) i16 { return n + 1 }
|
||||||
|
invoke :: alias add
|
||||||
|
other :: alias value
|
||||||
|
main proc() i16 { return invoke(other) }
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"distinct_overflow": (
|
||||||
|
"Id :: distinct u8\nmain proc() Id { a Id :: 255\nreturn a + 1 }",
|
||||||
|
b"", b"integer arithmetic overflow",
|
||||||
|
),
|
||||||
|
"distinct_constructor_range": (
|
||||||
|
"Id :: distinct u8\nmain proc() Id { return Id(256) }",
|
||||||
|
b"", b"integer arithmetic overflow",
|
||||||
|
),
|
||||||
|
"distinct_implicit_rejected": (
|
||||||
|
"Id :: distinct u8\nmain proc() Id { n u8 :: 42\nreturn n }",
|
||||||
|
b"", b"return does not match",
|
||||||
|
),
|
||||||
|
"distinct_identity_rejected": (
|
||||||
|
"A :: distinct u8\nB :: distinct u8\nmain proc() A { return B(42) }",
|
||||||
|
b"", b"return does not match",
|
||||||
|
),
|
||||||
|
"union_scalar_overlay": (
|
||||||
|
"""
|
||||||
|
Bits :: union { integer u64, real f64 }
|
||||||
|
main proc() f64 {
|
||||||
|
b := Bits { integer = 4607182418800017408 }
|
||||||
|
copy :: b
|
||||||
|
b.real = 2.0
|
||||||
|
return copy.real + b.real
|
||||||
|
}
|
||||||
|
""", b"3\n", None,
|
||||||
|
),
|
||||||
|
"union_nested_layout_and_copy": (
|
||||||
|
"""
|
||||||
|
Pair :: struct { low u8, high u8 }
|
||||||
|
Bits :: union { pair Pair, integer u16 }
|
||||||
|
Box :: struct { values [2]mut Bits }
|
||||||
|
identity proc(b Box) Box { return b }
|
||||||
|
main proc() [3]u16 {
|
||||||
|
b Box := { values = [Bits { integer = 513 }, Bits { pair = { low = 3, high = 4 } }] }
|
||||||
|
saved :: identity(b)
|
||||||
|
b.values[0].pair.high = 9
|
||||||
|
return [saved.values[0].integer, saved.values[1].integer, b.values[0].integer]
|
||||||
|
}
|
||||||
|
""", b"[513, 1027, 2305]\n", None,
|
||||||
|
),
|
||||||
|
"tagged_union_match_copy_and_mutation": (
|
||||||
|
"""
|
||||||
|
Tag :: enum(i16) { empty = -1, number = 42, pair = 90 }
|
||||||
|
Choice :: union(Tag) { pair struct { x i16, y i16 = 2 }, number i16, empty void }
|
||||||
|
score proc(c Choice) i16 {
|
||||||
|
return match c {
|
||||||
|
.number |n|: n
|
||||||
|
.pair |p|: p.x + p.y
|
||||||
|
.empty: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main proc() [3]i16 {
|
||||||
|
a Choice := .pair { x = 40 }
|
||||||
|
b :: a
|
||||||
|
a.pair.x = 9
|
||||||
|
c Choice :: .number{42}
|
||||||
|
return [score(b), score(a), score(c)]
|
||||||
|
}
|
||||||
|
""", b"[42, 11, 42]\n", None,
|
||||||
|
),
|
||||||
|
"tagged_union_void_and_group_capture": (
|
||||||
|
"""
|
||||||
|
Choice :: union(enum) { left u8, right u8, empty void }
|
||||||
|
score proc(c Choice) u8 {
|
||||||
|
match c {
|
||||||
|
.left, .right |n|: return n
|
||||||
|
.empty: return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main proc() u8 { a Choice :: .empty
|
||||||
|
return score(Choice { right = 42 }) + score(a) }
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"tagged_union_inactive_read": (
|
||||||
|
"C :: union(enum) { a u8, b u8 }\nmain proc() u8 { c :: C { a = 42 }\nreturn c.b }",
|
||||||
|
b"", b"tagged union field is not active",
|
||||||
|
),
|
||||||
|
"tagged_union_inactive_write": (
|
||||||
|
"C :: union(enum) { a u8, b u8 }\nmain proc() u8 { c := C { a = 42 }\nc.b = 9\nreturn c.a }",
|
||||||
|
b"", b"tagged union field is not active",
|
||||||
|
),
|
||||||
|
"match_expression_context_and_laziness": (
|
||||||
|
"""
|
||||||
|
Kind :: enum { first, second }
|
||||||
|
poison i16 :: 32767 + 1
|
||||||
|
choose proc() Kind { return .second }
|
||||||
|
main proc() i16 {
|
||||||
|
result i16 :: match choose() { .first: poison, .second: 42 }
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"match_scalar_ranges_and_loop_control": (
|
||||||
|
"""
|
||||||
|
main proc() i16 {
|
||||||
|
sum i16 := 0
|
||||||
|
for 0..10 |i| {
|
||||||
|
match i {
|
||||||
|
0..3: continue
|
||||||
|
3, 4: sum = sum + 1
|
||||||
|
5..=7: sum = sum + 10
|
||||||
|
else: break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sum
|
||||||
|
}
|
||||||
|
""", b"32\n", None,
|
||||||
|
),
|
||||||
|
"match_boolean_exhaustive": (
|
||||||
|
"main proc() i16 { return match true { false: 1, true: 42 } }",
|
||||||
|
b"42\n", None,
|
||||||
|
),
|
||||||
|
"match_non_exhaustive": (
|
||||||
|
"E :: enum { a, b }\nmain proc() i16 { return match E.a { .a: 42 } }",
|
||||||
|
b"", b"match is not exhaustive",
|
||||||
|
),
|
||||||
|
"match_overlapping_range": (
|
||||||
|
"main proc() i16 { return match 3 { 0..=3: 1, 3..5: 2, else: 42 } }",
|
||||||
|
b"", b"overlapping match pattern",
|
||||||
|
),
|
||||||
|
"enum_invalid_value": (
|
||||||
|
"E :: enum(u8) { a = 256 }\nmain proc() E { return .a }",
|
||||||
|
b"", b"representable constant integer",
|
||||||
|
),
|
||||||
|
"enum_nominal_identity": (
|
||||||
|
"A :: enum { a }\nB :: enum { a }\nmain proc() A { return B.a }",
|
||||||
|
b"", b"return does not match",
|
||||||
|
),
|
||||||
|
"union_requires_one_member": (
|
||||||
|
"U :: union { a u8, b u8 }\nmain proc() U { return U { a = 1, b = 2 } }",
|
||||||
|
b"", b"exactly one member",
|
||||||
|
),
|
||||||
"record_defaults_and_copies": (
|
"record_defaults_and_copies": (
|
||||||
"""
|
"""
|
||||||
Point :: struct { x i16, y i16 = 2 }
|
Point :: struct { x i16, y i16 = 2 }
|
||||||
@@ -768,6 +1007,51 @@ def check_record_packages(temporary, executable):
|
|||||||
return 2
|
return 2
|
||||||
|
|
||||||
|
|
||||||
|
def check_data_model_packages(temporary, executable):
|
||||||
|
project = temporary / "data-models"
|
||||||
|
app, library = project / "app", project / "library"
|
||||||
|
app.mkdir(parents=True)
|
||||||
|
library.mkdir()
|
||||||
|
(library / "types.hon").write_text(textwrap.dedent("""
|
||||||
|
Kind :: enum { number, empty }
|
||||||
|
Id :: distinct i16
|
||||||
|
Choice :: union(Kind) { empty void, number Id }
|
||||||
|
Count :: alias Id
|
||||||
|
seed Id :: Id(41)
|
||||||
|
""").strip() + "\n")
|
||||||
|
(library / "functions.hon").write_text(textwrap.dedent("""
|
||||||
|
identity proc(c Choice) Choice { return c }
|
||||||
|
score proc(c Choice) Id {
|
||||||
|
return match c { .number |n|: n + 1, .empty: Id(0) }
|
||||||
|
}
|
||||||
|
invoke :: alias score
|
||||||
|
""").strip() + "\n")
|
||||||
|
source = app / "main.hon"
|
||||||
|
source.write_text(textwrap.dedent("""
|
||||||
|
lib :: import "../library"
|
||||||
|
Alias :: alias lib.Choice
|
||||||
|
call :: alias lib.invoke
|
||||||
|
shared :: alias lib.seed
|
||||||
|
main proc() i16 {
|
||||||
|
value Alias :: { number = shared }
|
||||||
|
return i16(call(lib.identity(value)))
|
||||||
|
}
|
||||||
|
""").strip() + "\n")
|
||||||
|
check(app, executable, (b"42\n", None))
|
||||||
|
# Distinct and enum identity remain package/declaration specific.
|
||||||
|
for declaration, body in (
|
||||||
|
("Id :: distinct i16", "return lib.seed"),
|
||||||
|
("Kind :: enum { number, empty }", "return lib.Kind.number"),
|
||||||
|
):
|
||||||
|
result = declaration.split()[0]
|
||||||
|
source.write_text(
|
||||||
|
'lib :: import "../library"\n'
|
||||||
|
+ declaration + f"\nmain proc() {result} {{ {body} }}\n"
|
||||||
|
)
|
||||||
|
check(app, executable, (b"", b"return does not match"))
|
||||||
|
return 3
|
||||||
|
|
||||||
|
|
||||||
def check_dependency_provenance(temporary, executable):
|
def check_dependency_provenance(temporary, executable):
|
||||||
project = temporary / "provenance"
|
project = temporary / "provenance"
|
||||||
app = project / "app"
|
app = project / "app"
|
||||||
@@ -891,6 +1175,7 @@ def main():
|
|||||||
count += check_packages(temporary, executable)
|
count += check_packages(temporary, executable)
|
||||||
count += check_namespace_packages(temporary, executable)
|
count += check_namespace_packages(temporary, executable)
|
||||||
count += check_record_packages(temporary, executable)
|
count += check_record_packages(temporary, executable)
|
||||||
|
count += check_data_model_packages(temporary, executable)
|
||||||
count += check_dependency_provenance(temporary, executable)
|
count += check_dependency_provenance(temporary, executable)
|
||||||
source = ROOT / "programs/factorial.hon"
|
source = ROOT / "programs/factorial.hon"
|
||||||
emitted = run(str(HONEY), "emit-llvm", str(source))
|
emitted = run(str(HONEY), "emit-llvm", str(source))
|
||||||
|
|||||||
Reference in New Issue
Block a user