feat: add records and tuples across execution paths
This commit is contained in:
@@ -22,7 +22,7 @@ run a source file with the interpreter:
|
|||||||
|
|
||||||
bare `import "../library"` and aliased `library :: import "@library"` declarations load dependencies. relative paths start at the importing package directory; `@` starts at the nearest ancestor containing `build.hon` or `build.bro`, falling back to the entry package directory. package directories are canonicalized through symlinks and loaded once. cycles, missing/empty dependencies and invalid import paths produce source diagnostics. absolute paths and empty, escaped or control-containing import strings are rejected. production loading ignores test-only imports.
|
bare `import "../library"` and aliased `library :: import "@library"` declarations load dependencies. relative paths start at the importing package directory; `@` starts at the nearest ancestor containing `build.hon` or `build.bro`, falling back to the entry package directory. package directories are canonicalized through symlinks and loaded once. cycles, missing/empty dependencies and invalid import paths produce source diagnostics. absolute paths and empty, escaped or control-containing import strings are rejected. production loading ignores test-only imports.
|
||||||
|
|
||||||
imports bind file-local namespaces, never dependency declarations in the importing package. bare imports use the canonical package directory's final name; directories without a valid identifier name require an explicit alias. sibling files can use the same alias for different packages, but an alias cannot conflict with a package declaration, builtin type or another import in its own file. local shadowing remains forbidden. `library.add(...)` selects a public declaration and uses ordinary signature checking, function values and IR calls without a runtime namespace value. imported namespaces are not re-exported; missing members and non-namespace selections are diagnosed. general record fields, imported types and visibility attributes are not implemented; unsupported attributes produce syntax diagnostics.
|
imports bind file-local namespaces, never dependency declarations in the importing package. bare imports use the canonical package directory's final name; directories without a valid identifier name require an explicit alias. sibling files can use the same alias for different packages, but an alias cannot conflict with a package declaration, builtin type or another import in its own file. local shadowing remains forbidden. `library.add(...)` selects a public declaration and uses ordinary signature checking, function values and IR calls without a runtime namespace value. qualified record types and constructors such as `library.Point` and `library.Point { x = 1 }` use the same declaration identity. imported namespaces are not re-exported; missing members are diagnosed. visibility attributes are not implemented; unsupported attributes produce syntax diagnostics.
|
||||||
|
|
||||||
```hon
|
```hon
|
||||||
add proc(left, right int) int {
|
add proc(left, right int) int {
|
||||||
@@ -55,7 +55,7 @@ clang -target arm64-apple-macosx11.0.0 -O2 build/factorial.ll -o build/factorial
|
|||||||
|
|
||||||
`emit-llvm` writes LLVM text to stdout and source diagnostics to stderr. both emission commands return `1` for recoverable source diagnostics while still producing their output when emission and linking succeed. the resulting executable preserves demand-driven traps, reports all source diagnostics, and returns `1` even if unaffected code completes. runtime-generated diagnostics retain source locations; the original source file and compiler are not needed to run the executable. invocation or Clang failures return `2`.
|
`emit-llvm` writes LLVM text to stdout and source diagnostics to stderr. both emission commands return `1` for recoverable source diagnostics while still producing their output when emission and linking succeed. the resulting executable preserves demand-driven traps, reports all source diagnostics, and returns `1` even if unaffected code completes. runtime-generated diagnostics retain source locations; the original source file and compiler are not needed to run the executable. invocation or Clang failures return `2`.
|
||||||
|
|
||||||
the backend (`@source/llvm`) consumes the existing typed IR, emits native functions and control flow, and uses entry-block stack slots for locals and instruction results, including non-SSA result IDs. LLVM can promote eligible slots during optimization. arrays use typed native aggregates with by-value copies; loop storage is allocated once per invocation. signed and unsigned arithmetic use checked overflow intrinsics, array accesses retain bounds checks, local initialization flags reset on scope re-entry, globals initialize lazily with cycle checks, and floating operations preserve IEEE behavior without fast-math flags. native calls use the native stack rather than the interpreter's heap-backed frames.
|
the backend (`@source/llvm`) consumes the existing typed IR, emits native functions and control flow, and uses entry-block stack slots for locals and instruction results, including non-SSA result IDs. LLVM can promote eligible slots during optimization. arrays and records use typed native aggregates with by-value copies; loop storage is allocated once per invocation. signed and unsigned arithmetic use checked overflow intrinsics, array accesses retain bounds checks, local initialization flags reset on scope re-entry, globals initialize lazily with cycle checks, and floating operations preserve IEEE behavior without fast-math flags. native calls use the native stack rather than the interpreter's heap-backed frames.
|
||||||
|
|
||||||
run the bootstrap suite with `bro test`. after `bro build`, run `python3 tests/native.py` for executable-level interpreter comparisons at both `-O0` and `-O2`, including traps, source diagnostics, array ownership, calling conventions, and CLI error handling.
|
run the bootstrap suite with `bro test`. after `bro build`, run `python3 tests/native.py` for executable-level interpreter comparisons at both `-O0` and `-O2`, including traps, source diagnostics, array ownership, calling conventions, and CLI error handling.
|
||||||
|
|
||||||
@@ -93,6 +93,10 @@ array lengths currently support nonnegative integer literals, unary signs, `+`,
|
|||||||
|
|
||||||
indices require `usize`; integer literals and flexible inferred values receive this context, while fixed signed integers do not convert implicitly. out-of-bounds access traps before loading or storing, including for empty arrays. reads evaluate the base before the index; indexed assignment evaluates its right-hand side before the destination, each once. leading nested assignment targets follow Bro's declaration ambiguity: write `(matrix[1])[0] = 9`, not `matrix[1][0] = 9`. [programs/array_sum.hon](programs/array_sum.hon) uses a `usize` range bound to index, mutate and sum an array; `./build/honey run programs/array_sum.hon` prints `17`.
|
indices require `usize`; integer literals and flexible inferred values receive this context, while fixed signed integers do not convert implicitly. out-of-bounds access traps before loading or storing, including for empty arrays. reads evaluate the base before the index; indexed assignment evaluates its right-hand side before the destination, each once. leading nested assignment targets follow Bro's declaration ambiguity: write `(matrix[1])[0] = 9`, not `matrix[1][0] = 9`. [programs/array_sum.hon](programs/array_sum.hon) uses a `usize` range bound to index, mutate and sum an array; `./build/honey run programs/array_sum.hon` prints `17`.
|
||||||
|
|
||||||
|
records use `Point :: struct { x i16, y i16 = 2 }`, with comma- or newline-separated fields. named record declarations have nominal identity; two separately declared records are not interchangeable. construct them with `Point { x = 40 }` or a contextual keyed literal, such as `point Point := { x = 40 }`. omitted fields use their declared defaults; missing required fields, duplicate/unknown fields, incompatible values and recursive by-value layouts are diagnosed. explicit initializers run in source order, followed by omitted defaults in declaration order. an explicit value overrides its field's default without evaluating that default.
|
||||||
|
|
||||||
|
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}`.
|
||||||
|
|
||||||
`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.
|
||||||
@@ -113,7 +117,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 and interned array descriptors; query with `check.type_of` and `check.array_type`, and release with `check.deinit`. declarations infer their initializer types, including forward references and function aliases; function types identify the declaration supplying the callable signature. annotations support builtin types and fixed arrays; type names are not ordinary values. `void` is valid as a function result, not as a parameter or stored value.
|
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.
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
@@ -131,10 +135,10 @@ 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.
|
||||||
|
|
||||||
array IR retains recursive element types, counts and mutability. typed aggregate construction, element addresses and load/store instructions use flattened storage reserved per invocation; nested copies and indexed accesses reuse that storage without per-iteration allocation. addresses are internal IR values, not source-language pointers.
|
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.
|
||||||
|
|
||||||
IR construction owns derived layout metadata. `ir.add_array` checks shape capacity and computes flattened leaf counts; `ir.add_parameter` and `ir.add_local` create slots with their disjoint array backing. parameters precede other locals. `ir.emit` assigns fresh result IDs by default and maintains temporary counts, array-result segments and address presence. hand-authored non-SSA IR may supply explicit result IDs; repeated array 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` 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.
|
||||||
|
|
||||||
`interpreter.run` consumes only IR and returns an `Outcome` containing a value or diagnostic trap. returned arrays own their escaped cell storage; release every outcome with `interpreter.deinit` before releasing its module. string values carry a module-local `ir.StringId`, not a slice; resolve bytes with `ir.string_bytes(&module, id)` before releasing the module. decoded bytes and their full backing allocations remain module-owned, without copying strings during calls or returns. array descriptors also borrow the module. heap-backed invocation frames track their current block and resume it after calls or global initialization; loop iterations reuse the invocation's storage without allocating new frames. parameters are initialized on entry and local declarations execute in statement order. premature local reads and assignments trap; assignment evaluates its right-hand side before checking the destination's initialization. globals initialize lazily on their first read, cache their result for that run, and trap on initialization cycles. `int`/`i64` use signed 64-bit storage, `usize` uses unsigned 64-bit storage, both with checked arithmetic; `float`/`f64` use 64-bit IEEE arithmetic and comparisons, including NaN behavior. `usize` supports `+`, `-`, `*` and comparisons, but not unary negation or division. numeric literal overflow and float underflow to zero are diagnosed; representable subnormals are accepted. strings support `\\`, `\"`, `\n`, `\r`, `\t`, `\0` and `\xNN` escapes.
|
`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.
|
||||||
|
|
||||||
recoverable source errors do not stop execution of unaffected code. unused invalid functions, globals and nested bodies retain diagnostic traps; reaching invalid code reports its original diagnostic identity. missing-return traps affect only paths that fall through, not paths that already return or trap. diagnostics include the filename, line and byte column. exit status is `0` for clean execution, `1` for source diagnostics or a runtime trap, and `2` for usage or infrastructure failure. compound assignment remains unimplemented.
|
recoverable source errors do not stop execution of unaffected code. unused invalid functions, globals and nested bodies retain diagnostic traps; reaching invalid code reports its original diagnostic identity. missing-return traps affect only paths that fall through, not paths that already return or trap. diagnostics include the filename, line and byte column. exit status is `0` for clean execution, `1` for source diagnostics or a runtime trap, and `2` for usage or infrastructure failure. compound assignment remains unimplemented.
|
||||||
|
|||||||
@@ -6,8 +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 and checked numeric conversions work through both execution paths;
|
integers, checked numeric conversions, records/tuples, construction/defaults and
|
||||||
records and the richer types/control flow used by the compiler remain unsupported.
|
field reads/writes work through both execution paths. enums, distinct/alias types,
|
||||||
|
unions and matching remain unsupported.
|
||||||
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
|
||||||
@@ -28,14 +29,16 @@ needs records, pointers, generics and C calls.
|
|||||||
- [x] verify sibling source files, transitive/shared imports, same-spelled declarations in different packages, alias conflicts, missing packages/members, argument errors and dependency-file diagnostics. preserve existing single-file commands.
|
- [x] verify sibling source files, transitive/shared imports, same-spelled declarations in different packages, alias conflicts, missing packages/members, argument errors and dependency-file diagnostics. preserve existing single-file commands.
|
||||||
|
|
||||||
visibility follows Bro's public-by-default contract; unsupported visibility
|
visibility follows Bro's public-by-default contract; unsupported visibility
|
||||||
attributes must be diagnosed, never ignored. this milestone does not include
|
attributes must be diagnosed, never ignored. the scalar-function milestone did not
|
||||||
general record fields, imported types, test discovery or FFI.
|
include record fields or imported types; those now work for the record/tuple slice.
|
||||||
|
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`.
|
- [ ] 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.
|
||||||
- [ ] records/tuples and construction, fields, enums, distinct/alias types, unions and matching.
|
- [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.
|
||||||
- [ ] 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.
|
||||||
|
|||||||
+92
-2
@@ -1,9 +1,9 @@
|
|||||||
#! grammar:
|
#! grammar:
|
||||||
#! * type ..... -> identifier | '[' (expr | '_') ']' ['mut'] type
|
#! * type ..... -> identifier {'.' identifier} | '[' (expr | '_') ']' ['mut'] type | 'struct' '{' [field {(',' | newline) field}] '}'
|
||||||
#! * 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} [',']] ']' | '(' expr ')' | ('-' | '!') expr | expr op expr | expr '(' [expr {',' expr} [',']] ')' | expr '[' expr ']' | expr '.' identifier | expr ('..' | '..=') expr
|
#! * expr ..... -> identifier | literal | type | '[' [expr {',' expr} [',']] ']' | [type] '{' [initializer {',' initializer} [',']] '}' | '(' expr ')' | ('-' | '!') expr | expr op expr | expr '(' [expr {',' expr} [',']] ')' | expr '[' expr ']' | expr '.' (identifier | int) | expr ('..' | '..=') expr
|
||||||
#! * 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)
|
||||||
@@ -43,6 +43,8 @@ ExprKind :: enum {
|
|||||||
range
|
range
|
||||||
array_type
|
array_type
|
||||||
array_literal
|
array_literal
|
||||||
|
record_type
|
||||||
|
record_literal
|
||||||
index
|
index
|
||||||
selection
|
selection
|
||||||
invalid
|
invalid
|
||||||
@@ -96,6 +98,9 @@ NodeKind :: enum {
|
|||||||
expr_range
|
expr_range
|
||||||
expr_array_type
|
expr_array_type
|
||||||
expr_array_literal
|
expr_array_literal
|
||||||
|
expr_record_type
|
||||||
|
expr_record_literal
|
||||||
|
record_field
|
||||||
expr_index
|
expr_index
|
||||||
expr_selection
|
expr_selection
|
||||||
stmt_expr
|
stmt_expr
|
||||||
@@ -211,6 +216,18 @@ ArrayTypeView :: struct {
|
|||||||
mutable bool
|
mutable bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RecordLiteralView :: struct {
|
||||||
|
span Span
|
||||||
|
type ?NodeId
|
||||||
|
}
|
||||||
|
|
||||||
|
FieldView :: struct {
|
||||||
|
span Span
|
||||||
|
name ?NodeId
|
||||||
|
type ?NodeId
|
||||||
|
value ?NodeId
|
||||||
|
}
|
||||||
|
|
||||||
IndexView :: struct {
|
IndexView :: struct {
|
||||||
span Span
|
span Span
|
||||||
base NodeId
|
base NodeId
|
||||||
@@ -450,6 +467,44 @@ add_array_literal proc(builder @mut Builder, span Span, elements []NodeId) NodeI
|
|||||||
}, span)
|
}, span)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add_record_type proc(builder @mut Builder, span Span, fields []NodeId) NodeId ! mem.AllocError {
|
||||||
|
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_record_type,
|
||||||
|
data0 = NodeData{ extra = u32(start) },
|
||||||
|
data1 = NodeData{ extra = u32(builder.storage.extra.items.len) },
|
||||||
|
}, span)
|
||||||
|
}
|
||||||
|
|
||||||
|
add_record_literal proc(
|
||||||
|
builder @mut Builder, span Span, type ?NodeId, fields []NodeId,
|
||||||
|
) NodeId ! mem.AllocError {
|
||||||
|
start :: builder.storage.extra.items.len
|
||||||
|
for (fields) |field_id| _ = try add_extra(builder, u32(field_id))
|
||||||
|
raw_type :: if (type) |id| id else no_node
|
||||||
|
return try add_node(builder, Node{
|
||||||
|
kind = .expr_record_literal,
|
||||||
|
main = NodeData{ node_id = raw_type },
|
||||||
|
data0 = NodeData{ extra = u32(start) },
|
||||||
|
data1 = NodeData{ extra = u32(builder.storage.extra.items.len) },
|
||||||
|
}, span)
|
||||||
|
}
|
||||||
|
|
||||||
|
add_field proc(
|
||||||
|
builder @mut Builder, span Span, name, type, value ?NodeId,
|
||||||
|
) NodeId ! mem.AllocError {
|
||||||
|
raw_name :: if (name) |id| id else no_node
|
||||||
|
raw_type :: if (type) |id| id else no_node
|
||||||
|
raw_value :: if (value) |id| id else no_node
|
||||||
|
return try add_node(builder, Node{
|
||||||
|
kind = .record_field,
|
||||||
|
main = NodeData{ node_id = raw_name },
|
||||||
|
data0 = NodeData{ node_id = raw_type },
|
||||||
|
data1 = NodeData{ node_id = raw_value },
|
||||||
|
}, span)
|
||||||
|
}
|
||||||
|
|
||||||
add_index proc(builder @mut Builder, span Span, base, index NodeId) NodeId ! mem.AllocError {
|
add_index proc(builder @mut Builder, span Span, base, index NodeId) NodeId ! mem.AllocError {
|
||||||
return try add_node(builder, Node{
|
return try add_node(builder, Node{
|
||||||
kind = .expr_index,
|
kind = .expr_index,
|
||||||
@@ -922,6 +977,8 @@ expression_kind proc(kind NodeKind) ExprKind {
|
|||||||
.expr_range: .range
|
.expr_range: .range
|
||||||
.expr_array_type: .array_type
|
.expr_array_type: .array_type
|
||||||
.expr_array_literal: .array_literal
|
.expr_array_literal: .array_literal
|
||||||
|
.expr_record_type: .record_type
|
||||||
|
.expr_record_literal: .record_literal
|
||||||
.expr_index: .index
|
.expr_index: .index
|
||||||
.expr_selection: .selection
|
.expr_selection: .selection
|
||||||
.invalid_expr: .invalid
|
.invalid_expr: .invalid
|
||||||
@@ -1007,6 +1064,39 @@ array_element proc(tree @Tree, id NodeId, index usize) NodeId {
|
|||||||
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index])
|
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
record_literal proc(tree @Tree, id NodeId) RecordLiteralView {
|
||||||
|
node :: get_node(&tree.storage, id)
|
||||||
|
debug.assert(node.kind == .expr_record_literal)
|
||||||
|
return RecordLiteralView{
|
||||||
|
span = get_span(&tree.storage, id),
|
||||||
|
type = maybe_node_id(u32(node.main.node_id)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
field proc(tree @Tree, id NodeId) FieldView {
|
||||||
|
node :: get_node(&tree.storage, id)
|
||||||
|
debug.assert(node.kind == .record_field)
|
||||||
|
return FieldView{
|
||||||
|
span = get_span(&tree.storage, id),
|
||||||
|
name = maybe_node_id(u32(node.main.node_id)),
|
||||||
|
type = maybe_node_id(u32(node.data0.node_id)),
|
||||||
|
value = maybe_node_id(u32(node.data1.node_id)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record_field_count proc(tree @Tree, id NodeId) usize {
|
||||||
|
node :: get_node(&tree.storage, id)
|
||||||
|
debug.assert(node.kind == .expr_record_type or node.kind == .expr_record_literal)
|
||||||
|
return usize(node.data1.extra - node.data0.extra)
|
||||||
|
}
|
||||||
|
|
||||||
|
record_field proc(tree @Tree, id NodeId, index usize) NodeId {
|
||||||
|
node :: get_node(&tree.storage, id)
|
||||||
|
debug.assert(node.kind == .expr_record_type or node.kind == .expr_record_literal)
|
||||||
|
debug.assert(index < usize(node.data1.extra - node.data0.extra))
|
||||||
|
return NodeId(tree.storage.extra.items[usize(node.data0.extra) + index])
|
||||||
|
}
|
||||||
|
|
||||||
index_expr proc(tree @Tree, id NodeId) IndexView {
|
index_expr proc(tree @Tree, id NodeId) IndexView {
|
||||||
node :: get_node(&tree.storage, id)
|
node :: get_node(&tree.storage, id)
|
||||||
debug.assert(node.kind == .expr_index)
|
debug.assert(node.kind == .expr_index)
|
||||||
|
|||||||
@@ -54,6 +54,13 @@ Code :: enum {
|
|||||||
not_indexable
|
not_indexable
|
||||||
invalid_index_assignment
|
invalid_index_assignment
|
||||||
index_out_of_bounds
|
index_out_of_bounds
|
||||||
|
unknown_field
|
||||||
|
duplicate_field
|
||||||
|
missing_field
|
||||||
|
tuple_arity_mismatch
|
||||||
|
not_record
|
||||||
|
recursive_record
|
||||||
|
invalid_record_field_type
|
||||||
missing_entry
|
missing_entry
|
||||||
invalid_entry
|
invalid_entry
|
||||||
literal_out_of_range
|
literal_out_of_range
|
||||||
@@ -144,6 +151,13 @@ messages :: $enummap.init(Code, []u8, {
|
|||||||
not_indexable = "indexing is currently implemented for fixed arrays only",
|
not_indexable = "indexing is currently implemented for fixed arrays only",
|
||||||
invalid_index_assignment = "indexed assignment requires a writable array element",
|
invalid_index_assignment = "indexed assignment requires a writable array element",
|
||||||
index_out_of_bounds = "index out of bounds",
|
index_out_of_bounds = "index out of bounds",
|
||||||
|
unknown_field = "record has no such field",
|
||||||
|
duplicate_field = "record field is specified more than once",
|
||||||
|
missing_field = "record initializer is missing a required field",
|
||||||
|
tuple_arity_mismatch = "tuple initializer arity does not match its type",
|
||||||
|
not_record = "field selection requires a record or tuple",
|
||||||
|
recursive_record = "record has a recursive by-value layout",
|
||||||
|
invalid_record_field_type = "record fields require a concrete value type",
|
||||||
missing_entry = "program requires a main function",
|
missing_entry = "program requires a main function",
|
||||||
invalid_entry = "main must be a function with no parameters",
|
invalid_entry = "main must be a function with no parameters",
|
||||||
literal_out_of_range = "numeric literal is outside its runtime type's range",
|
literal_out_of_range = "numeric literal is outside its runtime type's range",
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ Outcome :: struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#! release an escaped aggregate with its retained allocator; scalar and trap outcomes need no allocation.
|
#! release an escaped aggregate with its retained allocator; scalar and trap outcomes need no allocation.
|
||||||
#! strings and array descriptors borrow the module, which must outlive this outcome.
|
#! strings and aggregate descriptors borrow the module, which must outlive this outcome.
|
||||||
deinit proc(outcome @mut Outcome) void {
|
deinit proc(outcome @mut Outcome) void {
|
||||||
if (outcome.owned_elements) |elements| mem.free(outcome.allocator?, elements)
|
if (outcome.owned_elements) |elements| mem.free(outcome.allocator?, elements)
|
||||||
outcome.owned_elements = null
|
outcome.owned_elements = null
|
||||||
@@ -57,7 +57,7 @@ Frame :: struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#! executes typed IR with invocation/global storage released on every exit.
|
#! executes typed IR with invocation/global storage released on every exit.
|
||||||
#! array results own flat storage released by `deinit`; string IDs and shapes resolve through `module`.
|
#! aggregate results own flat storage released by `deinit`; string IDs and shapes resolve through `module`.
|
||||||
run proc(
|
run proc(
|
||||||
allocator mem.Allocator,
|
allocator mem.Allocator,
|
||||||
module @ir.Module,
|
module @ir.Module,
|
||||||
@@ -75,7 +75,7 @@ run proc(
|
|||||||
mem.set(globals, GlobalState{})
|
mem.set(globals, GlobalState{})
|
||||||
global_cells usize := 0
|
global_cells usize := 0
|
||||||
for (module.globals.items) |global| {
|
for (module.globals.items) |global| {
|
||||||
if (global.type != .array) continue
|
if (!ir.aggregate(global.type)) continue
|
||||||
cells :: module.arrays.items[usize(global.array_type)].cells
|
cells :: module.arrays.items[usize(global.array_type)].cells
|
||||||
if (cells > maxval!(usize) - global_cells) return .storage_limit_reached
|
if (cells > maxval!(usize) - global_cells) return .storage_limit_reached
|
||||||
global_cells += cells
|
global_cells += cells
|
||||||
@@ -84,9 +84,9 @@ run proc(
|
|||||||
defer mem.free(allocator, aggregate_globals)
|
defer mem.free(allocator, aggregate_globals)
|
||||||
global_offset usize := 0
|
global_offset usize := 0
|
||||||
for (module.globals.items) |global, i| {
|
for (module.globals.items) |global, i| {
|
||||||
if (global.type != .array) continue
|
if (!ir.aggregate(global.type)) continue
|
||||||
cells :: module.arrays.items[usize(global.array_type)].cells
|
cells :: module.arrays.items[usize(global.array_type)].cells
|
||||||
globals[i].value = array_view(global.array_type, aggregate_globals[global_offset..global_offset + cells])
|
globals[i].value = aggregate_view(global.type, global.array_type, aggregate_globals[global_offset..global_offset + cells])
|
||||||
global_offset += cells
|
global_offset += cells
|
||||||
}
|
}
|
||||||
frames := arraylist.init(Frame, allocator)
|
frames := arraylist.init(Frame, allocator)
|
||||||
@@ -125,7 +125,7 @@ run proc(
|
|||||||
globals[global_index].status = .ready
|
globals[global_index].status = .ready
|
||||||
}
|
}
|
||||||
if (frames.items.len == 1) {
|
if (frames.items.len == 1) {
|
||||||
if (value.type == .array) {
|
if (ir.aggregate(value.type)) {
|
||||||
escaped :: try mem.alloc(ir.Value, allocator, value.data.elements.len)
|
escaped :: try mem.alloc(ir.Value, allocator, value.data.elements.len)
|
||||||
memcopy!(escaped, value.data.elements)
|
memcopy!(escaped, value.data.elements)
|
||||||
value.data.elements = escaped
|
value.data.elements = escaped
|
||||||
@@ -204,13 +204,13 @@ run proc(
|
|||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
.array_literal: {
|
.array_literal, .record_literal: {
|
||||||
destination :: &frames.items[frame_index].values[usize(instruction.destination)]
|
destination :: &frames.items[frame_index].values[usize(instruction.destination)]
|
||||||
offset usize := 0
|
offset usize := 0
|
||||||
for (0..instruction.arguments_count) |i| {
|
for (0..instruction.arguments_count) |i| {
|
||||||
operand :: block.arguments.items[instruction.arguments_start + i]
|
operand :: block.arguments.items[instruction.arguments_start + i]
|
||||||
element :: frames.items[frame_index].values[usize(operand)]
|
element :: frames.items[frame_index].values[usize(operand)]
|
||||||
if (element.type == .array) {
|
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)
|
memcopy!(destination.data.elements[offset..offset + count], element.data.elements)
|
||||||
offset += count
|
offset += count
|
||||||
@@ -228,7 +228,11 @@ 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)
|
||||||
}
|
}
|
||||||
value = indexed_value(module, base.array_type, base.data.elements, usize(index))
|
value = ir.aggregate_item(module, base, usize(index))
|
||||||
|
}
|
||||||
|
.field: {
|
||||||
|
base :: frames.items[frame_index].values[usize(instruction.left)]
|
||||||
|
value = ir.aggregate_item(module, base, usize(instruction.slot))
|
||||||
}
|
}
|
||||||
.address_local: {
|
.address_local: {
|
||||||
local :: &frames.items[frame_index].locals[usize(instruction.slot)]
|
local :: &frames.items[frame_index].locals[usize(instruction.slot)]
|
||||||
@@ -238,7 +242,7 @@ run proc(
|
|||||||
cells = &[],
|
cells = &[],
|
||||||
initialized = &local.initialized,
|
initialized = &local.initialized,
|
||||||
}
|
}
|
||||||
if (address.type == .array) address.cells = local.value.data.elements
|
if (ir.aggregate(address.type)) address.cells = local.value.data.elements
|
||||||
else address.scalar = &local.value
|
else address.scalar = &local.value
|
||||||
frames.items[frame_index].addresses[usize(instruction.destination)] = address
|
frames.items[frame_index].addresses[usize(instruction.destination)] = address
|
||||||
value = ir.Value{
|
value = ir.Value{
|
||||||
@@ -264,7 +268,7 @@ run proc(
|
|||||||
array_type = instruction.array_type,
|
array_type = instruction.array_type,
|
||||||
cells = &[],
|
cells = &[],
|
||||||
}
|
}
|
||||||
if (address.type == .array) address.cells = global.value.data.elements
|
if (ir.aggregate(address.type)) address.cells = global.value.data.elements
|
||||||
else address.scalar = &global.value
|
else address.scalar = &global.value
|
||||||
frames.items[frame_index].addresses[usize(instruction.destination)] = address
|
frames.items[frame_index].addresses[usize(instruction.destination)] = address
|
||||||
value = ir.Value{
|
value = ir.Value{
|
||||||
@@ -286,7 +290,20 @@ run proc(
|
|||||||
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,
|
||||||
}
|
}
|
||||||
if (address.type != .array) address.scalar = &address.cells[0]
|
if (!ir.aggregate(address.type)) address.scalar = &address.cells[0]
|
||||||
|
frames.items[frame_index].addresses[usize(instruction.destination)] = address
|
||||||
|
value = ir.Value{ type = .address, data = ir.ValueData{ address = u32(instruction.destination) } }
|
||||||
|
}
|
||||||
|
.field_address: {
|
||||||
|
base_id :: frames.items[frame_index].values[usize(instruction.left)].data.address
|
||||||
|
base :: frames.items[frame_index].addresses[usize(base_id)]
|
||||||
|
field :: module.arrays.items[usize(base.array_type)].fields[usize(instruction.slot)]
|
||||||
|
width :: if (ir.aggregate(field.type)) module.arrays.items[usize(field.array_type)].cells else 1
|
||||||
|
address := Address{
|
||||||
|
type = instruction.pointee, array_type = instruction.array_type,
|
||||||
|
cells = base.cells[field.offset..field.offset + width], initialized = base.initialized,
|
||||||
|
}
|
||||||
|
if (!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) } }
|
||||||
}
|
}
|
||||||
@@ -300,10 +317,10 @@ run proc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (instruction.op == .load_address) {
|
if (instruction.op == .load_address) {
|
||||||
value = if (address.type == .array) array_view(address.array_type, address.cells) else address.scalar?^
|
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 (address.type == .array) memcopy!(address.cells, source.data.elements)
|
if (ir.aggregate(address.type)) memcopy!(address.cells, source.data.elements)
|
||||||
else address.scalar?^ = source
|
else address.scalar?^ = source
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -432,7 +449,7 @@ push_frame proc(
|
|||||||
errdefer mem.free(allocator, addresses)
|
errdefer mem.free(allocator, addresses)
|
||||||
for (signature.array_storage.items) |storage| {
|
for (signature.array_storage.items) |storage| {
|
||||||
cells :: module.arrays.items[usize(storage.array_type)].cells
|
cells :: module.arrays.items[usize(storage.array_type)].cells
|
||||||
value :: array_view(storage.array_type, aggregate[storage.offset..storage.offset + cells])
|
value :: aggregate_view(module.arrays.items[usize(storage.array_type)].kind, storage.array_type, aggregate[storage.offset..storage.offset + cells])
|
||||||
if (storage.local) locals[usize(storage.index)].value = value
|
if (storage.local) locals[usize(storage.index)].value = value
|
||||||
else values[usize(storage.index)] = value
|
else values[usize(storage.index)] = value
|
||||||
}
|
}
|
||||||
@@ -496,13 +513,13 @@ checked_integer proc(op ir.Op, left i64, right i64) ?i64 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
array_view proc(shape ir.ArrayId, cells []mut ir.Value) ir.Value {
|
aggregate_view proc(type ir.Type, shape ir.ArrayId, cells []mut ir.Value) ir.Value {
|
||||||
return ir.Value{ type = .array, array_type = shape, data = ir.ValueData{ elements = cells } }
|
return ir.Value{ type = type, array_type = shape, data = ir.ValueData{ elements = cells } }
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
copy_value proc(destination @mut ir.Value, source ir.Value) void {
|
copy_value proc(destination @mut ir.Value, source ir.Value) void {
|
||||||
if (source.type == .array) {
|
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)
|
memcopy!(destination.data.elements, source.data.elements)
|
||||||
} else {
|
} else {
|
||||||
@@ -512,17 +529,9 @@ copy_value proc(destination @mut ir.Value, source ir.Value) void {
|
|||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
element_cells proc(module @ir.Module, shape ir.ArrayType) usize {
|
element_cells proc(module @ir.Module, shape ir.ArrayType) usize {
|
||||||
return if (shape.element == .array) module.arrays.items[usize(shape.element_array)].cells else 1
|
return if (ir.aggregate(shape.element)) module.arrays.items[usize(shape.element_array)].cells else 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
|
||||||
indexed_value proc(module @ir.Module, shape_id ir.ArrayId, cells []mut ir.Value, index usize) ir.Value {
|
|
||||||
shape :: module.arrays.items[usize(shape_id)]
|
|
||||||
if (shape.element != .array) return cells[index]
|
|
||||||
width :: element_cells(module, shape)
|
|
||||||
start :: index * width
|
|
||||||
return array_view(shape.element_array, cells[start..start + width])
|
|
||||||
}
|
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
checked_unsigned proc(op ir.Op, left u64, right u64) ?u64 {
|
checked_unsigned proc(op ir.Op, left u64, right u64) ?u64 {
|
||||||
|
|||||||
+85
-24
@@ -14,18 +14,22 @@ no_array :: maxval!(ArrayId)
|
|||||||
no_value :: maxval!(ValueId)
|
no_value :: maxval!(ValueId)
|
||||||
|
|
||||||
Type :: enum {
|
Type :: enum {
|
||||||
invalid,
|
invalid
|
||||||
int,
|
int
|
||||||
float,
|
float
|
||||||
bool,
|
bool
|
||||||
string,
|
string
|
||||||
void,
|
void
|
||||||
function,
|
function
|
||||||
array,
|
array
|
||||||
usize,
|
record
|
||||||
address,
|
address
|
||||||
i8, i16, i32, isize,
|
i8, i16, i32, isize
|
||||||
u8, u16, u32, u64,
|
u8, u16, u32, u64, usize
|
||||||
|
}
|
||||||
|
|
||||||
|
aggregate proc(type Type) bool {
|
||||||
|
return type == .array or type == .record
|
||||||
}
|
}
|
||||||
|
|
||||||
#! integer storage widths for the supported 64-bit target; zero for noninteger types.
|
#! integer storage widths for the supported 64-bit target; zero for noninteger types.
|
||||||
@@ -84,13 +88,22 @@ Value :: struct {
|
|||||||
data ValueData = ValueData{ integer = 0 }
|
data ValueData = ValueData{ integer = 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
#! fixed recursive shape; add_array derives `cells`, the flattened scalar-leaf count.
|
RecordField :: struct {
|
||||||
|
type Type
|
||||||
|
array_type ArrayId = no_array
|
||||||
|
offset usize = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#! fixed recursive aggregate shape; constructors derive the flattened scalar-leaf count.
|
||||||
|
#! record fields are owned by the module; array metadata retains its existing representation.
|
||||||
ArrayType :: struct {
|
ArrayType :: struct {
|
||||||
element Type
|
kind Type = .array
|
||||||
|
element Type = .invalid
|
||||||
element_array ArrayId = no_array
|
element_array ArrayId = no_array
|
||||||
len usize
|
len usize = 0
|
||||||
cells usize
|
cells usize = 0
|
||||||
mutable bool
|
mutable bool = false
|
||||||
|
fields []mut RecordField = &[]
|
||||||
}
|
}
|
||||||
|
|
||||||
#! 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.
|
||||||
@@ -109,10 +122,13 @@ Op :: enum {
|
|||||||
reset_local
|
reset_local
|
||||||
load_global
|
load_global
|
||||||
array_literal
|
array_literal
|
||||||
|
record_literal
|
||||||
|
field
|
||||||
index
|
index
|
||||||
address_local
|
address_local
|
||||||
address_global
|
address_global
|
||||||
index_address
|
index_address
|
||||||
|
field_address
|
||||||
load_address
|
load_address
|
||||||
store_address
|
store_address
|
||||||
negate
|
negate
|
||||||
@@ -244,8 +260,8 @@ add_array proc(
|
|||||||
len usize,
|
len usize,
|
||||||
mutable bool,
|
mutable bool,
|
||||||
) ArrayId ! (mem.AllocError | diagnostics.CapacityError) {
|
) ArrayId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
debug.assert((element == .array) == (element_array != no_array))
|
debug.assert(aggregate(element) == (element_array != no_array))
|
||||||
width :: if (element == .array) module.arrays.items[usize(element_array)].cells else 1
|
width :: if (aggregate(element)) module.arrays.items[usize(element_array)].cells else 1
|
||||||
if (width != 0 and len > divtrunc!(maxval!(usize), width)) return .storage_limit_reached
|
if (width != 0 and len > divtrunc!(maxval!(usize), width)) return .storage_limit_reached
|
||||||
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
|
||||||
@@ -261,6 +277,48 @@ add_array proc(
|
|||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#! copies declaration-order fields and derives their flattened offsets; child shapes must exist.
|
||||||
|
add_record proc(
|
||||||
|
module @mut Module,
|
||||||
|
fields []RecordField,
|
||||||
|
) 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)
|
||||||
|
cells usize := 0
|
||||||
|
for (fields) |field, index| {
|
||||||
|
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
|
||||||
|
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 }
|
||||||
|
cells += width
|
||||||
|
}
|
||||||
|
id :: ArrayId(module.arrays.items.len)
|
||||||
|
try arraylist.append(&module.arrays, ArrayType{ kind = .record, fields = owned, cells = cells })
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
#! returns a borrowed child value from flattened aggregate storage, preserving zero-cell shapes.
|
||||||
|
aggregate_item proc(module @Module, value Value, index usize) Value {
|
||||||
|
shape :: module.arrays.items[usize(value.array_type)]
|
||||||
|
type := shape.element
|
||||||
|
child := shape.element_array
|
||||||
|
start usize := 0
|
||||||
|
width usize := 1
|
||||||
|
if (shape.kind == .record) {
|
||||||
|
field :: shape.fields[index]
|
||||||
|
type = field.type
|
||||||
|
child = field.array_type
|
||||||
|
start = field.offset
|
||||||
|
} else {
|
||||||
|
if (aggregate(type)) width = module.arrays.items[usize(child)].cells
|
||||||
|
start = index * width
|
||||||
|
}
|
||||||
|
if (!aggregate(type)) return value.data.elements[start]
|
||||||
|
width = module.arrays.items[usize(child)].cells
|
||||||
|
return Value{ type = type, array_type = child, data = ValueData{ elements = value.data.elements[start..start + width] } }
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
prepare_storage proc(module @Module, function @mut Function, index u32, shape ArrayId, local bool) void ! (mem.AllocError | diagnostics.CapacityError) {
|
prepare_storage proc(module @Module, function @mut Function, index u32, shape ArrayId, local bool) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
cells :: module.arrays.items[usize(shape)].cells
|
cells :: module.arrays.items[usize(shape)].cells
|
||||||
@@ -281,13 +339,13 @@ add_local proc(
|
|||||||
type Type,
|
type Type,
|
||||||
shape ArrayId,
|
shape ArrayId,
|
||||||
) LocalId ! (mem.AllocError | diagnostics.CapacityError) {
|
) LocalId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
debug.assert((type == .array) == (shape != no_array))
|
debug.assert(aggregate(type) == (shape != no_array))
|
||||||
body :: &module.functions.items[usize(function)]
|
body :: &module.functions.items[usize(function)]
|
||||||
if (body.locals.items.len >= usize(maxval!(LocalId))) return .storage_limit_reached
|
if (body.locals.items.len >= usize(maxval!(LocalId))) return .storage_limit_reached
|
||||||
id :: LocalId(body.locals.items.len)
|
id :: LocalId(body.locals.items.len)
|
||||||
try arraylist.append(&body.locals, type)
|
try arraylist.append(&body.locals, type)
|
||||||
errdefer _ = arraylist.pop(&body.locals)
|
errdefer _ = arraylist.pop(&body.locals)
|
||||||
if (type == .array) try prepare_storage(module, body, u32(id), shape, true)
|
if (aggregate(type)) try prepare_storage(module, body, u32(id), shape, true)
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,9 +363,9 @@ add_parameter proc(
|
|||||||
return try add_local(module, function, type, shape)
|
return try add_local(module, function, type, shape)
|
||||||
}
|
}
|
||||||
|
|
||||||
#! appends an instruction and derives its result count, array backing, and address presence.
|
#! appends an instruction and derives its result count, aggregate backing, and address presence.
|
||||||
#! no_value requests a fresh result; explicit result IDs support non-SSA definitions across blocks.
|
#! no_value requests a fresh result; explicit result IDs support non-SSA definitions across blocks.
|
||||||
#! repeated array results must retain their shape and reuse their existing segment.
|
#! repeated aggregate results must retain their shape and reuse their existing segment.
|
||||||
#! failures leave instruction and storage lengths, totals, counts, and flags unchanged.
|
#! failures leave instruction and storage lengths, totals, counts, and flags unchanged.
|
||||||
emit proc(
|
emit proc(
|
||||||
module @mut Module,
|
module @mut Module,
|
||||||
@@ -336,7 +394,7 @@ emit proc(
|
|||||||
}
|
}
|
||||||
try arraylist.append(&target.instructions, operation)
|
try arraylist.append(&target.instructions, operation)
|
||||||
errdefer _ = arraylist.pop(&target.instructions)
|
errdefer _ = arraylist.pop(&target.instructions)
|
||||||
if (produces_value and operation.type == .array) {
|
if (produces_value and aggregate(operation.type)) {
|
||||||
debug.assert(operation.array_type != no_array)
|
debug.assert(operation.array_type != no_array)
|
||||||
registered := false
|
registered := false
|
||||||
if (usize(operation.destination) < body.value_count) {
|
if (usize(operation.destination) < body.value_count) {
|
||||||
@@ -377,6 +435,9 @@ deinit proc(module @mut Module) void {
|
|||||||
for (module.strings.items) |string|
|
for (module.strings.items) |string|
|
||||||
mem.free(module.allocator, string.allocation)
|
mem.free(module.allocator, string.allocation)
|
||||||
|
|
||||||
|
for (module.arrays.items) |shape| {
|
||||||
|
if (shape.kind == .record) mem.free(module.allocator, shape.fields)
|
||||||
|
}
|
||||||
arraylist.deinit(&module.functions)
|
arraylist.deinit(&module.functions)
|
||||||
arraylist.deinit(&module.globals)
|
arraylist.deinit(&module.globals)
|
||||||
arraylist.deinit(&module.strings)
|
arraylist.deinit(&module.strings)
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ keywords strmap.StaticStringMap(TokenKind) :: strmap.init([
|
|||||||
{ "and", .and },
|
{ "and", .and },
|
||||||
{ "or", .or },
|
{ "or", .or },
|
||||||
{ "mut", .mut },
|
{ "mut", .mut },
|
||||||
|
{ "struct", .struct },
|
||||||
{ "import", .import },
|
{ "import", .import },
|
||||||
{ "test", .test },
|
{ "test", .test },
|
||||||
])
|
])
|
||||||
@@ -102,7 +103,12 @@ scan proc(
|
|||||||
|
|
||||||
if is_digit(char) {
|
if is_digit(char) {
|
||||||
start :: cursor
|
start :: cursor
|
||||||
result :: scan_number(start, program) catch |err| {
|
member :: (
|
||||||
|
state.tokens.items.len > 0
|
||||||
|
and state.tokens.items[state.tokens.items.len - 1].kind == .dot
|
||||||
|
and usize(state.tokens.items[state.tokens.items.len - 1].span.end) == start
|
||||||
|
)
|
||||||
|
result :: scan_number(start, program, !member) catch |err| {
|
||||||
id :: try diagnostics.add(
|
id :: try diagnostics.add(
|
||||||
diagnostic_store,
|
diagnostic_store,
|
||||||
diagnostic_code(err.code),
|
diagnostic_code(err.code),
|
||||||
@@ -362,12 +368,12 @@ ScanNumResult :: struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
scan_number proc(start usize, program []u8) ScanNumResult ! ScanError {
|
scan_number proc(start usize, program []u8, allow_decimal bool) ScanNumResult ! ScanError {
|
||||||
cursor := start
|
cursor := start
|
||||||
has_decimal := false
|
has_decimal := false
|
||||||
while (cursor < program.len and is_digit(program[cursor])) cursor += 1
|
while (cursor < program.len and is_digit(program[cursor])) cursor += 1
|
||||||
if (
|
if (
|
||||||
cursor < program.len and program[cursor] == '.'
|
allow_decimal and cursor < program.len and program[cursor] == '.'
|
||||||
and !(cursor + 1 < program.len and program[cursor + 1] == '.')
|
and !(cursor + 1 < program.len and program[cursor + 1] == '.')
|
||||||
) {
|
) {
|
||||||
has_decimal = true
|
has_decimal = true
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ TokenKind :: enum {
|
|||||||
true, false
|
true, false
|
||||||
and, or
|
and, or
|
||||||
mut
|
mut
|
||||||
|
struct
|
||||||
import, test
|
import, test
|
||||||
|
|
||||||
# literals
|
# literals
|
||||||
|
|||||||
@@ -43,19 +43,41 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
|
|||||||
try write_type(output, state.module, type)
|
try write_type(output, state.module, type)
|
||||||
try io.print(", ptr %r{}\n", output, {pointer})
|
try io.print(", ptr %r{}\n", output, {pointer})
|
||||||
}
|
}
|
||||||
.array_literal: {
|
.array_literal, .record_literal: {
|
||||||
for (0..instruction.arguments_count) |index| {
|
for (0..instruction.arguments_count) |index| {
|
||||||
operand :: block.arguments.items[instruction.arguments_start + index]
|
operand :: block.arguments.items[instruction.arguments_start + index]
|
||||||
value :: try load(state, operand)
|
value :: try load(state, operand)
|
||||||
pointer :: fresh(state)
|
pointer :: fresh(state)
|
||||||
try io.print(" %r{} = getelementptr ", output, {pointer})
|
try io.print(" %r{} = getelementptr ", output, {pointer})
|
||||||
try write_type(output, state.module, type)
|
try write_type(output, state.module, type)
|
||||||
try io.print(", ptr %v{}, i64 0, i64 {}\n store ", output, {u32(instruction.destination), index})
|
index_type []u8 :: if (instruction.op == .record_literal) "i32" else "i64"
|
||||||
|
try io.print(", ptr %v{}, i64 0, {s} {}\n store ", output, {u32(instruction.destination), index_type, index})
|
||||||
try write_type(output, state.module, state.values[usize(operand)])
|
try write_type(output, state.module, state.values[usize(operand)])
|
||||||
try io.print(" %r{}, ptr %r{}\n", output, {value, pointer})
|
try io.print(" %r{}, ptr %r{}\n", output, {value, pointer})
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
.field: {
|
||||||
|
base :: try load(state, instruction.left)
|
||||||
|
try io.print(" %r{} = extractvalue ", output, {result})
|
||||||
|
try write_type(output, state.module, state.values[usize(instruction.left)])
|
||||||
|
try io.print(" %r{}, {}\n", output, {base, instruction.slot})
|
||||||
|
}
|
||||||
|
.field_address: {
|
||||||
|
base_type :: state.values[usize(instruction.left)]
|
||||||
|
base :: try load(state, instruction.left)
|
||||||
|
data :: fresh(state)
|
||||||
|
flag :: fresh(state)
|
||||||
|
pointer :: fresh(state)
|
||||||
|
address :: fresh(state)
|
||||||
|
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{} = getelementptr ", output, {pointer})
|
||||||
|
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(" %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})
|
||||||
|
}
|
||||||
.index, .index_address: {
|
.index, .index_address: {
|
||||||
index :: try load(state, instruction.right)
|
index :: try load(state, instruction.right)
|
||||||
base_type :: state.values[usize(instruction.left)]
|
base_type :: state.values[usize(instruction.left)]
|
||||||
|
|||||||
+58
-20
@@ -72,7 +72,10 @@ emit proc(
|
|||||||
try io.print(" zeroinitializer\n@gs{} = internal global i8 0\n", output, {index})
|
try io.print(" zeroinitializer\n@gs{} = internal global i8 0\n", output, {index})
|
||||||
try emit_global(output, module, global, index)
|
try emit_global(output, module, global, index)
|
||||||
}
|
}
|
||||||
for (module.arrays.items) |_, index| try emit_array_printer(output, module, ir.ArrayId(index))
|
for (module.arrays.items) |shape, index| {
|
||||||
|
if (shape.kind == .record) try emit_record_printer(output, module, ir.ArrayId(index))
|
||||||
|
else try emit_array_printer(output, module, ir.ArrayId(index))
|
||||||
|
}
|
||||||
for (module.functions.items) |function, index| {
|
for (module.functions.items) |function, index| {
|
||||||
values :: try mem.alloc(Type, allocator, function.value_count)
|
values :: try mem.alloc(Type, allocator, function.value_count)
|
||||||
defer mem.free(allocator, values)
|
defer mem.free(allocator, values)
|
||||||
@@ -91,13 +94,13 @@ emit proc(
|
|||||||
if (module.entry) |entry| {
|
if (module.entry) |entry| {
|
||||||
function :: module.functions.items[usize(entry)]
|
function :: module.functions.items[usize(entry)]
|
||||||
result :: Type{ kind = function.result, shape = function.result_array_type }
|
result :: Type{ kind = function.result, shape = function.result_array_type }
|
||||||
if (result.kind != .void) try io.write_all(output, " %result = ")
|
if (result.kind != .void) try io.write_all(output, " %r0 = ")
|
||||||
else try io.write_all(output, " ")
|
else try io.write_all(output, " ")
|
||||||
try io.write_all(output, "call ")
|
try io.write_all(output, "call ")
|
||||||
try write_type(output, module, result)
|
try write_type(output, module, result)
|
||||||
try io.print(" @f{}()\n", output, {u32(entry)})
|
try io.print(" @f{}()\n", output, {u32(entry)})
|
||||||
if (result.kind != .void and result.kind != .invalid) {
|
if (result.kind != .void and result.kind != .invalid) {
|
||||||
try print_call(output, module, result, "%result")
|
try print_call(output, module, result, 0)
|
||||||
try io.write_all(output, " call void @out(i32 1, ptr @newline, i64 1)\n")
|
try io.write_all(output, " call void @out(i32 1, ptr @newline, i64 1)\n")
|
||||||
}
|
}
|
||||||
try io.write_all(output, " call void @report(i64 -1)\n")
|
try io.write_all(output, " call void @report(i64 -1)\n")
|
||||||
@@ -138,6 +141,15 @@ write_type proc(output io.Writer, module @ir.Module, type Type) void ! io.WriteE
|
|||||||
try write_type(output, module, Type{ kind = shape.element, shape = shape.element_array })
|
try write_type(output, module, Type{ kind = shape.element, shape = shape.element_array })
|
||||||
try io.write_all(output, "]")
|
try io.write_all(output, "]")
|
||||||
}
|
}
|
||||||
|
.record: {
|
||||||
|
shape :: module.arrays.items[usize(type.shape)]
|
||||||
|
try io.write_all(output, "{ ")
|
||||||
|
for (shape.fields) |field, index| {
|
||||||
|
if (index != 0) try io.write_all(output, ", ")
|
||||||
|
try write_type(output, module, Type{ kind = field.type, shape = field.array_type })
|
||||||
|
}
|
||||||
|
try io.write_all(output, " }")
|
||||||
|
}
|
||||||
.void: try io.write_all(output, "void")
|
.void: try io.write_all(output, "void")
|
||||||
# invalid storage only belongs to recovery paths that trap before reading it.
|
# invalid storage only belongs to recovery paths that trap before reading it.
|
||||||
.invalid: try io.write_all(output, "i8")
|
.invalid: try io.write_all(output, "i8")
|
||||||
@@ -148,7 +160,7 @@ write_type proc(output io.Writer, module @ir.Module, type Type) void ! io.WriteE
|
|||||||
local_type proc(function @ir.Function, index usize) Type {
|
local_type proc(function @ir.Function, index usize) Type {
|
||||||
type := Type{ kind = function.locals.items[index] }
|
type := Type{ kind = function.locals.items[index] }
|
||||||
if (type.kind == .void) type.kind = .invalid
|
if (type.kind == .void) type.kind = .invalid
|
||||||
if (type.kind == .array) {
|
if (ir.aggregate(type.kind)) {
|
||||||
for (function.array_storage.items) |storage| {
|
for (function.array_storage.items) |storage| {
|
||||||
if (storage.local and usize(storage.index) == index) {
|
if (storage.local and usize(storage.index) == index) {
|
||||||
type.shape = storage.array_type
|
type.shape = storage.array_type
|
||||||
@@ -244,24 +256,24 @@ emit_global proc(output io.Writer, module @ir.Module, global ir.Global, index us
|
|||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
print_call proc(output io.Writer, module @ir.Module, type Type, operand []u8) void ! io.WriteError {
|
print_call proc(output io.Writer, module @ir.Module, type Type, operand usize) void ! io.WriteError {
|
||||||
match type.kind {
|
match type.kind {
|
||||||
.i8, .i16, .i32, .int, .isize, .u8, .u16, .u32, .u64, .usize: {
|
.i8, .i16, .i32, .int, .isize, .u8, .u16, .u32, .u64, .usize: {
|
||||||
bits :: ir.integer_bits(type.kind)
|
bits :: ir.integer_bits(type.kind)
|
||||||
unsigned :: ir.unsigned_integer(type.kind)
|
unsigned :: ir.unsigned_integer(type.kind)
|
||||||
if (bits < 64) {
|
if (bits < 64) {
|
||||||
extension []u8 :: if (unsigned) "zext" else "sext"
|
extension []u8 :: if (unsigned) "zext" else "sext"
|
||||||
try io.print(" %print_value = {s} i{} {s} to i64\n", output, {extension, bits, operand})
|
try io.print(" %print_value{} = {s} i{} %r{} to i64\n", output, {operand, extension, bits, operand})
|
||||||
try io.print(" call void @print_integer(i32 1, i64 %print_value, i1 {})\n", output, {unsigned})
|
try io.print(" call void @print_integer(i32 1, i64 %print_value{}, i1 {})\n", output, {operand, unsigned})
|
||||||
} else try io.print(" call void @print_integer(i32 1, i64 {s}, i1 {})\n", output, {operand, unsigned})
|
} else try io.print(" call void @print_integer(i32 1, i64 %r{}, i1 {})\n", output, {operand, unsigned})
|
||||||
}
|
}
|
||||||
.float: try io.print(" call void @print_float(double {s})\n", output, {operand})
|
.float: try io.print(" call void @print_float(double %r{})\n", output, {operand})
|
||||||
.bool: try io.print(" call void @print_bool(i1 {s})\n", output, {operand})
|
.bool: try io.print(" call void @print_bool(i1 %r{})\n", output, {operand})
|
||||||
.string: try io.print(" call void @print_string(%string {s})\n", output, {operand})
|
.string: try io.print(" call void @print_string(%string %r{})\n", output, {operand})
|
||||||
.array: {
|
.array, .record: {
|
||||||
try io.print(" call void @print_array{}(", output, {u32(type.shape)})
|
try io.print(" call void @print_array{}(", output, {u32(type.shape)})
|
||||||
try write_type(output, module, type)
|
try write_type(output, module, type)
|
||||||
try io.print(" {s})\n", output, {operand})
|
try io.print(" %r{})\n", output, {operand})
|
||||||
}
|
}
|
||||||
else: unreachable
|
else: unreachable
|
||||||
}
|
}
|
||||||
@@ -282,15 +294,33 @@ emit_array_printer proc(output io.Writer, module @ir.Module, id ir.ArrayId) void
|
|||||||
try io.print(" %done = icmp eq i64 %index, {}\n", output, {shape.len})
|
try io.print(" %done = icmp eq i64 %index, {}\n", output, {shape.len})
|
||||||
try io.write_all(output, " br i1 %done, label %end, label %separator\nseparator:\n %first = icmp eq i64 %index, 0\n br i1 %first, label %element, label %comma\ncomma:\n call void @out(i32 1, ptr @separator, i64 2)\n br label %element\nelement:\n %pointer = getelementptr ")
|
try io.write_all(output, " br i1 %done, label %end, label %separator\nseparator:\n %first = icmp eq i64 %index, 0\n br i1 %first, label %element, label %comma\ncomma:\n call void @out(i32 1, ptr @separator, i64 2)\n br label %element\nelement:\n %pointer = getelementptr ")
|
||||||
try write_type(output, module, type)
|
try write_type(output, module, type)
|
||||||
try io.write_all(output, ", ptr %storage, i64 0, i64 %index\n %item = load ")
|
try io.write_all(output, ", ptr %storage, i64 0, i64 %index\n %r0 = load ")
|
||||||
try write_type(output, module, element)
|
try write_type(output, module, element)
|
||||||
try io.write_all(output, ", ptr %pointer\n")
|
try io.write_all(output, ", ptr %pointer\n")
|
||||||
# functions may occur inside arrays without being printable source results.
|
# functions may occur inside arrays without being printable source results.
|
||||||
if (element.kind != .function) try print_call(output, module, element, "%item")
|
if (element.kind != .function) try print_call(output, module, element, 0)
|
||||||
else try io.write_all(output, " call void @exit(i32 2)\n")
|
else try io.write_all(output, " call void @exit(i32 2)\n")
|
||||||
try io.write_all(output, " %next = add i64 %index, 1\n br label %loop\nend:\n call void @out(i32 1, ptr @close, i64 1)\n ret void\n}\n")
|
try io.write_all(output, " %next = add i64 %index, 1\n br label %loop\nend:\n call void @out(i32 1, ptr @close, i64 1)\n ret void\n}\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
emit_record_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 call void @out(i32 1, ptr @record_open, i64 1)\n")
|
||||||
|
for (shape.fields) |field, index| {
|
||||||
|
if (index != 0) try io.write_all(output, " call void @out(i32 1, ptr @separator, i64 2)\n")
|
||||||
|
try io.print(" %r{} = extractvalue ", output, {index})
|
||||||
|
try write_type(output, module, type)
|
||||||
|
try io.print(" %value, {}\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, " 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
|
||||||
@@ -356,20 +386,28 @@ write_constant proc(output io.Writer, module @ir.Module, value ir.Value) void !
|
|||||||
.function: try io.print("@f{}", output, {u32(value.data.function)})
|
.function: try io.print("@f{}", output, {u32(value.data.function)})
|
||||||
.array: {
|
.array: {
|
||||||
shape :: module.arrays.items[usize(value.array_type)]
|
shape :: module.arrays.items[usize(value.array_type)]
|
||||||
width :: if (shape.element == .array) module.arrays.items[usize(shape.element_array)].cells else 1
|
|
||||||
try io.write_all(output, "[")
|
try io.write_all(output, "[")
|
||||||
for (0..shape.len) |index| {
|
for (0..shape.len) |index| {
|
||||||
if (index != 0) try io.write_all(output, ", ")
|
if (index != 0) try io.write_all(output, ", ")
|
||||||
element := ir.Value{}
|
element :: ir.aggregate_item(module, value, index)
|
||||||
if (shape.element == .array) {
|
|
||||||
element = ir.Value{ type = .array, array_type = shape.element_array, data = ir.ValueData{ elements = value.data.elements[index * width..(index + 1) * width] } }
|
|
||||||
} else element = value.data.elements[index]
|
|
||||||
try write_type(output, module, Type{ kind = element.type, shape = element.array_type })
|
try write_type(output, module, Type{ kind = element.type, shape = element.array_type })
|
||||||
try io.write_all(output, " ")
|
try io.write_all(output, " ")
|
||||||
try write_constant(output, module, element)
|
try write_constant(output, module, element)
|
||||||
}
|
}
|
||||||
try io.write_all(output, "]")
|
try io.write_all(output, "]")
|
||||||
}
|
}
|
||||||
|
.record: {
|
||||||
|
shape :: module.arrays.items[usize(value.array_type)]
|
||||||
|
try io.write_all(output, "{ ")
|
||||||
|
for (shape.fields) |_, index| {
|
||||||
|
if (index != 0) try io.write_all(output, ", ")
|
||||||
|
field :: ir.aggregate_item(module, value, index)
|
||||||
|
try write_type(output, module, Type{ kind = field.type, shape = field.array_type })
|
||||||
|
try io.write_all(output, " ")
|
||||||
|
try write_constant(output, module, field)
|
||||||
|
}
|
||||||
|
try io.write_all(output, " }")
|
||||||
|
}
|
||||||
else: unreachable
|
else: unreachable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ runtime ::
|
|||||||
`@newline = private constant [1 x i8] c"\0A"
|
`@newline = private constant [1 x i8] c"\0A"
|
||||||
`@open = private constant [1 x i8] c"["
|
`@open = private constant [1 x i8] c"["
|
||||||
`@close = private constant [1 x i8] c"]"
|
`@close = private constant [1 x i8] c"]"
|
||||||
|
`@record_open = private constant [1 x i8] c"{"
|
||||||
|
`@record_close = private constant [1 x i8] c"}"
|
||||||
`@separator = private constant [2 x i8] c", "
|
`@separator = private constant [2 x i8] c", "
|
||||||
`@write_error = private constant [44 x i8] c": infrastructure error: cannot write output\0A"
|
`@write_error = private constant [44 x i8] c": infrastructure error: cannot write output\0A"
|
||||||
`@read = private constant [42 x i8] c"local value was read before initialization"
|
`@read = private constant [42 x i8] c"local value was read before initialization"
|
||||||
|
|||||||
+126
-5
@@ -33,6 +33,7 @@ State :: struct {
|
|||||||
storage []mut Storage
|
storage []mut Storage
|
||||||
arrays []mut ir.ArrayId
|
arrays []mut ir.ArrayId
|
||||||
arguments arraylist.ArrayList(ir.ValueId)
|
arguments arraylist.ArrayList(ir.ValueId)
|
||||||
|
records []mut ir.ArrayId
|
||||||
function usize = 0
|
function usize = 0
|
||||||
block ir.BlockId = ir.BlockId(0)
|
block ir.BlockId = ir.BlockId(0)
|
||||||
terminated bool = false
|
terminated bool = false
|
||||||
@@ -75,6 +76,9 @@ lower_package proc(
|
|||||||
arrays :: try mem.alloc(ir.ArrayId, allocator, typecheck.array_type_count(checked))
|
arrays :: try mem.alloc(ir.ArrayId, allocator, typecheck.array_type_count(checked))
|
||||||
defer mem.free(allocator, arrays)
|
defer mem.free(allocator, arrays)
|
||||||
mem.set(arrays, ir.no_array)
|
mem.set(arrays, ir.no_array)
|
||||||
|
records :: try mem.alloc(ir.ArrayId, allocator, typecheck.record_type_count(checked))
|
||||||
|
defer mem.free(allocator, records)
|
||||||
|
mem.set(records, ir.no_array)
|
||||||
state := State{
|
state := State{
|
||||||
tree = tree,
|
tree = tree,
|
||||||
resolution = resolution,
|
resolution = resolution,
|
||||||
@@ -83,6 +87,7 @@ lower_package proc(
|
|||||||
module = &module,
|
module = &module,
|
||||||
storage = storage,
|
storage = storage,
|
||||||
arrays = arrays,
|
arrays = arrays,
|
||||||
|
records = records,
|
||||||
arguments = arraylist.init(ir.ValueId, allocator),
|
arguments = arraylist.init(ir.ValueId, allocator),
|
||||||
}
|
}
|
||||||
defer arraylist.deinit(&state.arguments)
|
defer arraylist.deinit(&state.arguments)
|
||||||
@@ -96,6 +101,7 @@ lower_package proc(
|
|||||||
storage[usize(id)] = Storage{ kind = .function, index = u32(function) }
|
storage[usize(id)] = Storage{ kind = .function, index = u32(function) }
|
||||||
}
|
}
|
||||||
.statement: {
|
.statement: {
|
||||||
|
if (typecheck.type_of(checked, id).kind == .type_value) 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{
|
||||||
@@ -114,7 +120,9 @@ lower_package proc(
|
|||||||
id :: ast.package_decl(tree, i)
|
id :: ast.package_decl(tree, i)
|
||||||
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: try lower_global(&state, id)
|
.statement: {
|
||||||
|
if (typecheck.type_of(checked, id).kind != .type_value) try lower_global(&state, id)
|
||||||
|
}
|
||||||
.import, .invalid: {}
|
.import, .invalid: {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -135,17 +143,19 @@ lower_type proc(type typecheck.Type) ir.Type {
|
|||||||
.u32: .u32
|
.u32: .u32
|
||||||
.u64: .u64
|
.u64: .u64
|
||||||
.array: .array
|
.array: .array
|
||||||
|
.record: .record
|
||||||
.float: .float
|
.float: .float
|
||||||
.bool: .bool
|
.bool: .bool
|
||||||
.string: .string
|
.string: .string
|
||||||
.void: .void
|
.void: .void
|
||||||
.function: .function
|
.function: .function
|
||||||
.none, .invalid, .range: .invalid
|
.none, .invalid, .range, .type_value: .invalid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
array_shape proc(state @mut State, type typecheck.Type) ir.ArrayId ! (mem.AllocError | diagnostics.CapacityError) {
|
array_shape proc(state @mut State, type typecheck.Type) ir.ArrayId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
|
if (type.kind == .record) return try prepare_record(state, type.record_type)
|
||||||
if (type.kind != .array) return ir.no_array
|
if (type.kind != .array) return ir.no_array
|
||||||
# checker history includes unresolved shapes; materialize only final used types.
|
# checker history includes unresolved shapes; materialize only final used types.
|
||||||
return try prepare_array(state, type.array_type)
|
return try prepare_array(state, type.array_type)
|
||||||
@@ -156,7 +166,7 @@ prepare_array proc(state @mut State, id typecheck.ArrayId) ir.ArrayId ! (mem.All
|
|||||||
if (state.arrays[usize(id)] != ir.no_array) return state.arrays[usize(id)]
|
if (state.arrays[usize(id)] != ir.no_array) return state.arrays[usize(id)]
|
||||||
descriptor :: typecheck.array_type(state.checked, id)
|
descriptor :: typecheck.array_type(state.checked, id)
|
||||||
child := ir.no_array
|
child := ir.no_array
|
||||||
if (descriptor.element.kind == .array) child = try prepare_array(state, descriptor.element.array_type)
|
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(descriptor.element),
|
||||||
@@ -168,6 +178,24 @@ prepare_array proc(state @mut State, id typecheck.ArrayId) ir.ArrayId ! (mem.All
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
prepare_record proc(state @mut State, id typecheck.RecordId) ir.ArrayId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
|
if (state.records[usize(id)] != ir.no_array) return state.records[usize(id)]
|
||||||
|
descriptor :: typecheck.record_type(state.checked, id)
|
||||||
|
fields := arraylist.init(ir.RecordField, state.module.allocator)
|
||||||
|
defer arraylist.deinit(&fields)
|
||||||
|
try arraylist.reserve(&fields, descriptor.fields.len)
|
||||||
|
for (descriptor.fields) |field| {
|
||||||
|
try arraylist.append(&fields, ir.RecordField{
|
||||||
|
type = lower_type(field.type),
|
||||||
|
array_type = try array_shape(state, field.type),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
shape :: try ir.add_record(state.module, fields.items)
|
||||||
|
state.records[usize(id)] = shape
|
||||||
|
return shape
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
add_function proc(module @mut ir.Module, span diagnostics.Span) ir.FunctionId ! mem.AllocError {
|
add_function proc(module @mut ir.Module, span diagnostics.Span) ir.FunctionId ! mem.AllocError {
|
||||||
id :: ir.FunctionId(module.functions.items.len)
|
id :: ir.FunctionId(module.functions.items.len)
|
||||||
@@ -315,6 +343,7 @@ predeclare_locals proc(state @mut State, block ast.NodeId) void ! (mem.AllocErro
|
|||||||
statement :: ast.block_statement(state.tree, block, i)
|
statement :: ast.block_statement(state.tree, block, i)
|
||||||
match ast.stmt_kind(state.tree, statement) {
|
match ast.stmt_kind(state.tree, statement) {
|
||||||
.declaration: {
|
.declaration: {
|
||||||
|
if (typecheck.type_of(state.checked, statement).kind == .type_value) 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,
|
||||||
@@ -355,6 +384,7 @@ lower_block proc(state @mut State, block ast.NodeId, fresh_scope bool) void ! (m
|
|||||||
for (0..ast.block_statement_count(state.tree, block)) |i| {
|
for (0..ast.block_statement_count(state.tree, block)) |i| {
|
||||||
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
|
||||||
_ = 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,
|
||||||
@@ -539,7 +569,9 @@ lower_statement proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
|
|||||||
}
|
}
|
||||||
.assignment_stmt: {
|
.assignment_stmt: {
|
||||||
assignment :: ast.assignment_stmt(tree, id)
|
assignment :: ast.assignment_stmt(tree, id)
|
||||||
if (ast.expr_kind(tree, assignment.target) == .index) {
|
if (ast.expr_kind(tree, assignment.target) == .index
|
||||||
|
or (ast.expr_kind(tree, assignment.target) == .selection
|
||||||
|
and sema.binding(state.resolution, assignment.target).kind == .none)) {
|
||||||
value :: try lower_expression(state, assignment.value)
|
value :: try lower_expression(state, assignment.value)
|
||||||
if (state.terminated) return
|
if (state.terminated) return
|
||||||
if (!is_place(state, assignment.target)) {
|
if (!is_place(state, assignment.target)) {
|
||||||
@@ -577,6 +609,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 (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
|
||||||
@@ -642,10 +675,66 @@ lower_logical proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError
|
|||||||
return try emit(state, ir.Instruction{ op = .load_local, type = .bool, slot = slot, span = span })
|
return try emit(state, ir.Instruction{ op = .load_local, type = .bool, slot = slot, span = span })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
lower_record proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
|
tree :: state.tree
|
||||||
|
scratch_start :: state.arguments.items.len
|
||||||
|
defer state.arguments.items = state.arguments.items.ptr[..scratch_start]
|
||||||
|
explicit_count :: ast.record_field_count(tree, id)
|
||||||
|
try arraylist.reserve(&state.arguments, scratch_start + explicit_count)
|
||||||
|
for (0..explicit_count) |_| try arraylist.append(&state.arguments, ir.no_value)
|
||||||
|
for (0..explicit_count) |i| {
|
||||||
|
value :: try lower_expression(state, ast.field(tree, ast.record_field(tree, id, i)).value?)
|
||||||
|
if (state.terminated) return ir.no_value
|
||||||
|
state.arguments.items[scratch_start + i] = 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)
|
||||||
|
ordered_start :: state.arguments.items.len
|
||||||
|
try arraylist.reserve(&state.arguments, ordered_start + descriptor.fields.len)
|
||||||
|
for (descriptor.fields) |_| try arraylist.append(&state.arguments, ir.no_value)
|
||||||
|
for (0..(descriptor.fields.len)) |i| {
|
||||||
|
initializer :: typecheck.initializer_field(state.checked, id, i)
|
||||||
|
value := ir.no_value
|
||||||
|
# ponytail: quadratic field reorder; retain an inverse map if large records make lowering costly.
|
||||||
|
for (0..explicit_count) |j| {
|
||||||
|
if (ast.field(tree, ast.record_field(tree, id, j)).value? == initializer) {
|
||||||
|
value = state.arguments.items[scratch_start + j]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (value == ir.no_value) value = try lower_expression(state, initializer)
|
||||||
|
if (state.terminated) return ir.no_value
|
||||||
|
state.arguments.items[ordered_start + i] = value
|
||||||
|
}
|
||||||
|
block := &state.module.functions.items[state.function].blocks.items[usize(state.block)]
|
||||||
|
start :: block.arguments.items.len
|
||||||
|
try arraylist.reserve(&block.arguments, start + descriptor.fields.len)
|
||||||
|
for (0..(descriptor.fields.len)) |i| try arraylist.append(&block.arguments, state.arguments.items[ordered_start + i])
|
||||||
|
return try emit(state, ir.Instruction{
|
||||||
|
op = .record_literal,
|
||||||
|
type = .record,
|
||||||
|
array_type = try array_shape(state, type),
|
||||||
|
arguments_start = start,
|
||||||
|
arguments_count = descriptor.fields.len,
|
||||||
|
span = ast.node_span(tree, id),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
is_place proc(state @State, id ast.NodeId) bool {
|
is_place proc(state @State, id ast.NodeId) bool {
|
||||||
match ast.expr_kind(state.tree, id) {
|
match ast.expr_kind(state.tree, id) {
|
||||||
.index: return is_place(state, ast.index_expr(state.tree, id).base)
|
.index: return is_place(state, ast.index_expr(state.tree, id).base)
|
||||||
|
.selection: {
|
||||||
|
if (sema.binding(state.resolution, id).kind == .none) {
|
||||||
|
return is_place(state, ast.selection_expr(state.tree, id).base)
|
||||||
|
}
|
||||||
|
binding :: sema.binding(state.resolution, id)
|
||||||
|
if (binding.kind != .declaration) return false
|
||||||
|
location :: state.storage[usize(binding.data.declaration)]
|
||||||
|
return location.kind == .local or location.kind == .global
|
||||||
|
}
|
||||||
.identifier: {
|
.identifier: {
|
||||||
binding :: sema.binding(state.resolution, id)
|
binding :: sema.binding(state.resolution, id)
|
||||||
if (binding.kind == .invalid) return true
|
if (binding.kind == .invalid) return true
|
||||||
@@ -661,6 +750,21 @@ is_place proc(state @State, id ast.NodeId) bool {
|
|||||||
lower_place proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError | diagnostics.CapacityError) {
|
lower_place 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)
|
||||||
checked :: typecheck.type_of(state.checked, id)
|
checked :: typecheck.type_of(state.checked, id)
|
||||||
|
if (ast.expr_kind(state.tree, id) == .selection
|
||||||
|
and sema.binding(state.resolution, id).kind == .none) {
|
||||||
|
expression :: ast.selection_expr(state.tree, id)
|
||||||
|
base :: try lower_place(state, expression.base)
|
||||||
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
||||||
|
return try emit(state, ir.Instruction{
|
||||||
|
op = .field_address,
|
||||||
|
type = .address,
|
||||||
|
pointee = lower_type(checked),
|
||||||
|
array_type = try array_shape(state, checked),
|
||||||
|
left = base,
|
||||||
|
slot = u32(typecheck.field_index(state.checked, id)),
|
||||||
|
span = span,
|
||||||
|
})
|
||||||
|
}
|
||||||
if (ast.expr_kind(state.tree, id) == .index) {
|
if (ast.expr_kind(state.tree, id) == .index) {
|
||||||
expression :: ast.index_expr(state.tree, id)
|
expression :: ast.index_expr(state.tree, id)
|
||||||
base :: try lower_place(state, expression.base)
|
base :: try lower_place(state, expression.base)
|
||||||
@@ -706,11 +810,12 @@ 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: {
|
.array_type, .record_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)
|
||||||
.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]
|
||||||
@@ -769,6 +874,22 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
|||||||
return try lower_string(state, id)
|
return try lower_string(state, id)
|
||||||
}
|
}
|
||||||
.identifier, .selection: {
|
.identifier, .selection: {
|
||||||
|
if (ast.expr_kind(tree, id) == .selection
|
||||||
|
and sema.binding(state.resolution, id).kind == .none) {
|
||||||
|
if (is_place(state, id)) {
|
||||||
|
address :: try lower_place(state, id)
|
||||||
|
if (state.terminated) return ir.no_value
|
||||||
|
return try emit(state, ir.Instruction{
|
||||||
|
op = .load_address, type = type, array_type = shape, left = address, span = span,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
base :: try lower_expression(state, ast.selection_expr(tree, id).base)
|
||||||
|
if (state.terminated or trap_type(state, id)) return ir.no_value
|
||||||
|
return try emit(state, ir.Instruction{
|
||||||
|
op = .field, type = type, array_type = shape, left = base,
|
||||||
|
slot = u32(typecheck.field_index(state.checked, id)), span = span,
|
||||||
|
})
|
||||||
|
}
|
||||||
if (trap_binding(state, id) or trap_type(state, id)) return ir.no_value
|
if (trap_binding(state, id) or trap_type(state, id)) return ir.no_value
|
||||||
binding :: sema.binding(state.resolution, id)
|
binding :: sema.binding(state.resolution, id)
|
||||||
location :: state.storage[usize(binding.data.declaration)]
|
location :: state.storage[usize(binding.data.declaration)]
|
||||||
|
|||||||
+9
-21
@@ -208,29 +208,17 @@ print_value proc(output io.Writer, module @ir.Module, value ir.Value) void ! io.
|
|||||||
.float: try io.print("{d}", output, {value.data.real})
|
.float: try io.print("{d}", output, {value.data.real})
|
||||||
.bool: try io.print("{}", output, {value.data.boolean})
|
.bool: try io.print("{}", output, {value.data.boolean})
|
||||||
.string: try io.print("{s}", output, {ir.string_bytes(module, value.data.string)})
|
.string: try io.print("{s}", output, {ir.string_bytes(module, value.data.string)})
|
||||||
.array: {
|
.array, .record: {
|
||||||
shape :: module.arrays.items[usize(value.array_type)]
|
shape :: module.arrays.items[usize(value.array_type)]
|
||||||
stride usize :: if (shape.element == .array)
|
count :: if (value.type == .array) shape.len else shape.fields.len
|
||||||
module.arrays.items[usize(shape.element_array)].cells
|
opening :: if (value.type == .array) "[" else "{"
|
||||||
else
|
closing :: if (value.type == .array) "]" else "}"
|
||||||
1
|
try io.write_all(output, opening)
|
||||||
try io.print("[", output, {})
|
for (0..count) |index| {
|
||||||
for 0..(shape.len) |index| {
|
if (index != 0) try io.write_all(output, ", ")
|
||||||
if (index != 0) try io.print(", ", output, {})
|
try print_value(output, module, ir.aggregate_item(module, value, index))
|
||||||
element := ir.Value{}
|
|
||||||
if shape.element == .array {
|
|
||||||
start :: index * stride
|
|
||||||
element = ir.Value{
|
|
||||||
type = .array,
|
|
||||||
array_type = shape.element_array,
|
|
||||||
data = ir.ValueData{ elements = value.data.elements[start..(start + stride)] },
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
element = value.data.elements[index]
|
|
||||||
}
|
|
||||||
try print_value(output, module, element)
|
|
||||||
}
|
}
|
||||||
try io.print("]", output, {})
|
try io.write_all(output, closing)
|
||||||
}
|
}
|
||||||
else: unreachable
|
else: unreachable
|
||||||
}
|
}
|
||||||
|
|||||||
+220
-10
@@ -29,6 +29,7 @@ State :: struct {
|
|||||||
allocator mem.Allocator
|
allocator mem.Allocator
|
||||||
next_token usize
|
next_token usize
|
||||||
paren_depth usize = 0
|
paren_depth usize = 0
|
||||||
|
allow_record_literals bool = true
|
||||||
}
|
}
|
||||||
|
|
||||||
#! 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.
|
||||||
@@ -155,7 +156,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)) break
|
if (next_is(state, .ident) or next_is(state, .open_bracket) or next_is(state, .struct)) break
|
||||||
_ = try consume(state, .comma)
|
_ = try consume(state, .comma)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,6 +236,7 @@ parse_statement proc(state @mut State, allow_else bool) ast.NodeId ! ParseError
|
|||||||
next_is(state, .ident)
|
next_is(state, .ident)
|
||||||
and (
|
and (
|
||||||
next_n_is(state, 1, .ident)
|
next_n_is(state, 1, .ident)
|
||||||
|
or next_n_is(state, 1, .struct)
|
||||||
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)
|
||||||
@@ -248,7 +250,7 @@ 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: {
|
.ident, .int, .float, .string, .true, .false, .minus, .bang, .open_paren, .open_bracket, .open_curly, .struct: {
|
||||||
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)
|
||||||
@@ -424,7 +426,7 @@ parse_for proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
|||||||
keyword :: try consume(state, .for)
|
keyword :: try consume(state, .for)
|
||||||
skip_newlines(state)
|
skip_newlines(state)
|
||||||
iterable_start :: state.next_token
|
iterable_start :: state.next_token
|
||||||
iterable := try parse_expr(state)
|
iterable := try parse_guard_expr(state)
|
||||||
grouped :: parenthesized_guard(state, iterable_start)
|
grouped :: parenthesized_guard(state, iterable_start)
|
||||||
skip_newlines(state)
|
skip_newlines(state)
|
||||||
captures :: parse_captures(state) catch |err| {
|
captures :: parse_captures(state) catch |err| {
|
||||||
@@ -478,10 +480,18 @@ parse_for proc(state @mut State, allow_else bool) ast.NodeId ! ParseError {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
parse_guard_expr proc(state @mut State) ast.NodeId ! ParseError {
|
||||||
|
previous :: state.allow_record_literals
|
||||||
|
state.allow_record_literals = false
|
||||||
|
defer state.allow_record_literals = previous
|
||||||
|
return try parse_expr(state)
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
parse_guard proc(state @mut State) ast.NodeId ! ParseError {
|
parse_guard proc(state @mut State) ast.NodeId ! ParseError {
|
||||||
condition_start :: state.next_token
|
condition_start :: state.next_token
|
||||||
condition := parse_expr(state) catch |err| {
|
condition := parse_guard_expr(state) catch |err| {
|
||||||
match err {
|
match err {
|
||||||
.unexpected_token: {}
|
.unexpected_token: {}
|
||||||
else: return err
|
else: return err
|
||||||
@@ -559,7 +569,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))
|
type_id ?ast.NodeId :: if (next_is(state, .ident) or next_is(state, .open_bracket) or next_is(state, .struct))
|
||||||
try parse_type(state)
|
try parse_type(state)
|
||||||
else
|
else
|
||||||
null
|
null
|
||||||
@@ -611,8 +621,169 @@ parse_type proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
count, element, mutable,
|
count, element, mutable,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if next_is(state, .struct) return try parse_record_type(state)
|
||||||
token :: try consume(state, .ident)
|
token :: try consume(state, .ident)
|
||||||
return 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) {
|
||||||
|
state.next_token += 1
|
||||||
|
member :: try consume(state, .ident)
|
||||||
|
member_id :: try ast.add_identifier(state.builder, member.span, member.data.str_id)
|
||||||
|
type = try ast.add_selection(state.builder, ast.Span{
|
||||||
|
start = token.span.start,
|
||||||
|
end = member.span.end,
|
||||||
|
source = token.span.source,
|
||||||
|
}, type, member_id)
|
||||||
|
}
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
parse_record_type proc(state @mut State) ast.NodeId ! ParseError {
|
||||||
|
keyword :: try consume(state, .struct)
|
||||||
|
return try parse_record(state, null, keyword.span.start, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
parse_record_literal proc(
|
||||||
|
state @mut State, type ?ast.NodeId, start u32,
|
||||||
|
) ast.NodeId ! ParseError {
|
||||||
|
return try parse_record(state, type, start, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
parse_record proc(
|
||||||
|
state @mut State, type ?ast.NodeId, start u32, declaration bool,
|
||||||
|
) ast.NodeId ! ParseError {
|
||||||
|
open :: try consume(state, .open_curly)
|
||||||
|
previous :: state.allow_record_literals
|
||||||
|
state.allow_record_literals = true
|
||||||
|
defer state.allow_record_literals = previous
|
||||||
|
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)) {
|
||||||
|
# leave the next declaration intact when a record's closing brace is missing.
|
||||||
|
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, start, false)
|
||||||
|
field_start :: state.next_token
|
||||||
|
field :: parse_record_field(state, declaration) catch |err| {
|
||||||
|
match err {
|
||||||
|
.unexpected_token: {}
|
||||||
|
else: return err
|
||||||
|
}
|
||||||
|
diagnostic :: try unexpected_diagnostic(state)
|
||||||
|
span :: recover_record_field(state, field_start)
|
||||||
|
invalid :: try ast.add_invalid_expr(state.builder, span, diagnostic)
|
||||||
|
recovered_type ?ast.NodeId :: if (declaration) invalid else null
|
||||||
|
recovered_value ?ast.NodeId :: if (declaration) null else invalid
|
||||||
|
yield try ast.add_field(state.builder, span, null, recovered_type, recovered_value)
|
||||||
|
}
|
||||||
|
try arraylist.append(&fields, field)
|
||||||
|
if next_is(state, .close_curly) break
|
||||||
|
if next_is(state, .comma) {
|
||||||
|
state.next_token += 1
|
||||||
|
skip_newlines(state)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (declaration and next_is(state, .newline)) {
|
||||||
|
skip_newlines(state)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
after_field :: state.next_token
|
||||||
|
skip_newlines(state)
|
||||||
|
if next_is(state, .close_curly) break
|
||||||
|
state.next_token = after_field
|
||||||
|
return try invalid_record(state, start, true)
|
||||||
|
}
|
||||||
|
if (!next_is(state, .close_curly)) return try invalid_record(state, start, false)
|
||||||
|
close :: try consume(state, .close_curly)
|
||||||
|
span :: ast.Span{ start = start, end = close.span.end, source = open.span.source }
|
||||||
|
if (declaration) return try ast.add_record_type(state.builder, span, fields.items)
|
||||||
|
return try ast.add_record_literal(state.builder, span, type, fields.items)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
parse_record_field proc(state @mut State, declaration bool) ast.NodeId ! ParseError {
|
||||||
|
start :: current(state).span
|
||||||
|
start_token :: state.next_token
|
||||||
|
name ?ast.NodeId := null
|
||||||
|
type ?ast.NodeId := null
|
||||||
|
value ?ast.NodeId := null
|
||||||
|
named :: next_is(state, .ident) and (
|
||||||
|
(!declaration and next_n_is(state, 1, .equal))
|
||||||
|
or (declaration and (
|
||||||
|
next_n_is(state, 1, .ident) or next_n_is(state, 1, .open_bracket)
|
||||||
|
or next_n_is(state, 1, .struct)
|
||||||
|
))
|
||||||
|
)
|
||||||
|
if (named) {
|
||||||
|
token :: try consume(state, .ident)
|
||||||
|
name = try ast.add_identifier(state.builder, token.span, token.data.str_id)
|
||||||
|
}
|
||||||
|
if (declaration) {
|
||||||
|
type = try parse_type(state)
|
||||||
|
if next_is(state, .equal) {
|
||||||
|
state.next_token += 1
|
||||||
|
value = try parse_expr(state)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (named) _ = try consume(state, .equal)
|
||||||
|
value = try parse_expr(state)
|
||||||
|
}
|
||||||
|
end :: if (state.next_token > start_token) state.tokens[state.next_token - 1].span.end else start.end
|
||||||
|
return try ast.add_field(state.builder, ast.Span{
|
||||||
|
start = start.start,
|
||||||
|
end = end,
|
||||||
|
source = start.source,
|
||||||
|
}, name, type, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
recover_record_field proc(state @mut State, start usize) ast.Span {
|
||||||
|
depth usize := 0
|
||||||
|
for (start..state.next_token) |index| {
|
||||||
|
kind :: state.tokens[index].kind
|
||||||
|
if (kind == .open_curly or kind == .open_paren or kind == .open_bracket) depth += 1
|
||||||
|
if ((kind == .close_curly or kind == .close_paren or kind == .close_bracket) and depth > 0) depth -= 1
|
||||||
|
}
|
||||||
|
while (!next_is(state, .eof)) {
|
||||||
|
kind :: current(state).kind
|
||||||
|
if (depth == 0 and (kind == .comma or kind == .newline or kind == .close_curly)) break
|
||||||
|
if (kind == .open_curly or kind == .open_paren or kind == .open_bracket) depth += 1
|
||||||
|
if (kind == .close_curly or kind == .close_paren or kind == .close_bracket) {
|
||||||
|
if (depth == 0) break
|
||||||
|
depth -= 1
|
||||||
|
}
|
||||||
|
state.next_token += 1
|
||||||
|
}
|
||||||
|
first :: state.tokens[start].span
|
||||||
|
end :: if (state.next_token > start) state.tokens[state.next_token - 1].span.end else first.end
|
||||||
|
return ast.Span{
|
||||||
|
start = first.start,
|
||||||
|
end = end,
|
||||||
|
source = first.source,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
invalid_record proc(state @mut State, start u32, recover_tail bool) ast.NodeId ! ParseError {
|
||||||
|
diagnostic :: try unexpected_diagnostic(state)
|
||||||
|
if (recover_tail) {
|
||||||
|
while (!next_is(state, .newline) and !next_is(state, .close_curly) and !next_is(state, .eof)) {
|
||||||
|
before :: state.next_token
|
||||||
|
_ = recover_record_field(state, before)
|
||||||
|
if (next_is(state, .comma)) state.next_token += 1
|
||||||
|
if (state.next_token == before) break
|
||||||
|
}
|
||||||
|
if (next_is(state, .close_curly)) state.next_token += 1
|
||||||
|
}
|
||||||
|
return try ast.add_invalid_expr(state.builder, ast.Span{
|
||||||
|
start = start,
|
||||||
|
end = state.tokens[state.next_token - 1].span.end,
|
||||||
|
source = state.builder.source_id,
|
||||||
|
}, diagnostic)
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
@@ -658,10 +829,18 @@ parse_expr_bp proc(state @mut State, min_bp BindingPowerScalar) ast.NodeId ! Par
|
|||||||
left_grouped = false
|
left_grouped = false
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if (token.kind == .open_curly and state.allow_record_literals) {
|
||||||
|
left = try parse_record_literal(state, left, state.tokens[expression_start].span.start)
|
||||||
|
left_grouped = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
if (token.kind == .dot) {
|
if (token.kind == .dot) {
|
||||||
state.next_token += 1
|
state.next_token += 1
|
||||||
member :: try consume(state, .ident)
|
member :: try consume_either(state, &[.ident, .int])
|
||||||
member_id :: try ast.add_identifier(state.builder, member.span, member.data.str_id)
|
member_id :: if (member.kind == .int)
|
||||||
|
try ast.add_literal(state.builder, .int, member.span)
|
||||||
|
else
|
||||||
|
try ast.add_identifier(state.builder, member.span, member.data.str_id)
|
||||||
left = try ast.add_selection(
|
left = try ast.add_selection(
|
||||||
state.builder,
|
state.builder,
|
||||||
ast.Span{
|
ast.Span{
|
||||||
@@ -736,11 +915,18 @@ validate_range_bound proc(
|
|||||||
@hide:file
|
@hide:file
|
||||||
parse_call proc(state @mut State, callee ast.NodeId, start u32) ast.NodeId ! ParseError {
|
parse_call proc(state @mut State, callee ast.NodeId, start u32) ast.NodeId ! ParseError {
|
||||||
_ = try consume(state, .open_paren)
|
_ = try consume(state, .open_paren)
|
||||||
|
previous :: state.allow_record_literals
|
||||||
|
state.allow_record_literals = true
|
||||||
|
defer state.allow_record_literals = previous
|
||||||
state.paren_depth += 1
|
state.paren_depth += 1
|
||||||
defer state.paren_depth -= 1
|
defer state.paren_depth -= 1
|
||||||
skip_operand_newlines(state)
|
skip_operand_newlines(state)
|
||||||
arguments arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
arguments arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
||||||
defer arraylist.deinit(&arguments)
|
defer arraylist.deinit(&arguments)
|
||||||
|
argument_start :: state.next_token
|
||||||
|
node_count :: state.builder.storage.nodes.items.len
|
||||||
|
extra_count :: state.builder.storage.extra.items.len
|
||||||
|
diagnostic_count :: state.diagnostics.list.items.len
|
||||||
|
|
||||||
while (!is_call_boundary(current(state).kind)) {
|
while (!is_call_boundary(current(state).kind)) {
|
||||||
argument :: try parse_expr(state)
|
argument :: try parse_expr(state)
|
||||||
@@ -749,6 +935,15 @@ parse_call proc(state @mut State, callee ast.NodeId, start u32) ast.NodeId ! Par
|
|||||||
state.next_token += 1
|
state.next_token += 1
|
||||||
skip_operand_newlines(state)
|
skip_operand_newlines(state)
|
||||||
}
|
}
|
||||||
|
if (!previous and !next_is(state, .close_paren)) {
|
||||||
|
# a guard's body may look like a record argument until its missing ')' is known.
|
||||||
|
state.next_token = argument_start
|
||||||
|
state.builder.storage.nodes.items = state.builder.storage.nodes.items.ptr[..node_count]
|
||||||
|
state.builder.storage.spans.items = state.builder.storage.spans.items.ptr[..node_count]
|
||||||
|
state.builder.storage.extra.items = state.builder.storage.extra.items.ptr[..extra_count]
|
||||||
|
state.diagnostics.list.items = state.diagnostics.list.items.ptr[..diagnostic_count]
|
||||||
|
return .unexpected_token
|
||||||
|
}
|
||||||
close :: try consume(state, .close_paren)
|
close :: try consume(state, .close_paren)
|
||||||
return try ast.add_call(
|
return try ast.add_call(
|
||||||
state.builder,
|
state.builder,
|
||||||
@@ -765,6 +960,9 @@ parse_call proc(state @mut State, callee ast.NodeId, start u32) ast.NodeId ! Par
|
|||||||
@hide:file
|
@hide:file
|
||||||
parse_index proc(state @mut State, base ast.NodeId, start u32) ast.NodeId ! ParseError {
|
parse_index proc(state @mut State, base ast.NodeId, start u32) ast.NodeId ! ParseError {
|
||||||
_ = try consume(state, .open_bracket)
|
_ = try consume(state, .open_bracket)
|
||||||
|
previous :: state.allow_record_literals
|
||||||
|
state.allow_record_literals = true
|
||||||
|
defer state.allow_record_literals = previous
|
||||||
index :: try parse_expr(state)
|
index :: try parse_expr(state)
|
||||||
if !next_is(state, .close_bracket) {
|
if !next_is(state, .close_bracket) {
|
||||||
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, current(state).span)
|
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, current(state).span)
|
||||||
@@ -801,7 +999,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
|
return next == .ident or next == .mut or next == .open_bracket or next == .struct
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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
|
||||||
@@ -812,6 +1010,9 @@ array_starts_type proc(state @State) bool {
|
|||||||
@hide:file
|
@hide:file
|
||||||
parse_array_literal proc(state @mut State) ast.NodeId ! ParseError {
|
parse_array_literal proc(state @mut State) ast.NodeId ! ParseError {
|
||||||
open :: try consume(state, .open_bracket)
|
open :: try consume(state, .open_bracket)
|
||||||
|
previous :: state.allow_record_literals
|
||||||
|
state.allow_record_literals = true
|
||||||
|
defer state.allow_record_literals = previous
|
||||||
elements arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
elements arraylist.ArrayList(ast.NodeId) := arraylist.init(state.allocator)
|
||||||
defer arraylist.deinit(&elements)
|
defer arraylist.deinit(&elements)
|
||||||
after_open :: state.next_token
|
after_open :: state.next_token
|
||||||
@@ -871,12 +1072,21 @@ named_binding_starts proc(state @State) bool {
|
|||||||
parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
||||||
token :: current(state)
|
token :: current(state)
|
||||||
match token.kind {
|
match token.kind {
|
||||||
|
.struct: return try parse_record_type(state)
|
||||||
|
.open_curly: {
|
||||||
|
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)
|
||||||
|
return try ast.add_invalid_expr(state.builder, token.span, diagnostic)
|
||||||
|
}
|
||||||
.open_bracket: {
|
.open_bracket: {
|
||||||
if array_starts_type(state) return try parse_type(state)
|
if array_starts_type(state) return try parse_type(state)
|
||||||
return try parse_array_literal(state)
|
return try parse_array_literal(state)
|
||||||
}
|
}
|
||||||
.open_paren: {
|
.open_paren: {
|
||||||
state.next_token += 1
|
state.next_token += 1
|
||||||
|
previous :: state.allow_record_literals
|
||||||
|
state.allow_record_literals = true
|
||||||
|
defer state.allow_record_literals = previous
|
||||||
state.paren_depth += 1
|
state.paren_depth += 1
|
||||||
defer state.paren_depth -= 1
|
defer state.paren_depth -= 1
|
||||||
expression :: try parse_expr(state)
|
expression :: try parse_expr(state)
|
||||||
@@ -943,7 +1153,7 @@ is_call_boundary proc(kind lexer.TokenKind) bool {
|
|||||||
return (
|
return (
|
||||||
kind == .break
|
kind == .break
|
||||||
or kind == .continue
|
or kind == .continue
|
||||||
or is_expression_boundary(kind)
|
or (kind != .open_curly and is_expression_boundary(kind))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -745,41 +745,6 @@ rejects_top_level_assignment_loop_else_and_assignment_expressions test {
|
|||||||
try testing.expect_equal("kept", ast.source_text(&tree, kept.name))
|
try testing.expect_equal("kept", ast.source_text(&tree, kept.name))
|
||||||
}
|
}
|
||||||
|
|
||||||
recovers_nested_braces_in_malformed_loop_body_and_assignment test {
|
|
||||||
strings := strpool.init(mem.c_allocator)
|
|
||||||
defer strpool.deinit(&strings)
|
|
||||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
|
||||||
defer diagnostics.deinit(&diagnostic_store)
|
|
||||||
source ::
|
|
||||||
`main proc() void {
|
|
||||||
` while (true) @ {
|
|
||||||
` bad()
|
|
||||||
` }
|
|
||||||
` x = {
|
|
||||||
` bad()
|
|
||||||
` }
|
|
||||||
` kept :: 7
|
|
||||||
`}
|
|
||||||
`tail :: 8
|
|
||||||
scan_state := lexer.init(mem.c_allocator)
|
|
||||||
defer lexer.deinit(&scan_state)
|
|
||||||
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
|
|
||||||
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
|
||||||
defer ast.deinit(&tree)
|
|
||||||
|
|
||||||
try testing.expect(diagnostics.count(&diagnostic_store) > 0)
|
|
||||||
try testing.expect_equal(2, ast.package_decl_count(&tree))
|
|
||||||
body :: ast.function_decl(&tree, ast.package_decl(&tree, 0)).body
|
|
||||||
try testing.expect_equal(3, ast.block_statement_count(&tree, body))
|
|
||||||
loop :: ast.while_stmt(&tree, ast.block_statement(&tree, body, 0))
|
|
||||||
try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, ast.block_statement(&tree, loop.body, 0)))
|
|
||||||
try testing.expect_equal(ast.StmtKind.invalid, ast.stmt_kind(&tree, ast.block_statement(&tree, body, 1)))
|
|
||||||
kept :: ast.statement_decl(&tree, ast.block_statement(&tree, body, 2))
|
|
||||||
try testing.expect_equal("kept", ast.source_text(&tree, kept.name))
|
|
||||||
tail :: ast.statement_decl(&tree, ast.package_decl(&tree, 1))
|
|
||||||
try testing.expect_equal("tail", ast.source_text(&tree, tail.name))
|
|
||||||
}
|
|
||||||
|
|
||||||
parses_logical_precedence_and_associativity test {
|
parses_logical_precedence_and_associativity test {
|
||||||
strings := strpool.init(mem.c_allocator)
|
strings := strpool.init(mem.c_allocator)
|
||||||
defer strpool.deinit(&strings)
|
defer strpool.deinit(&strings)
|
||||||
|
|||||||
+484
-25
@@ -7,25 +7,39 @@ import "@source/diagnostics"
|
|||||||
sema :: import "@source/sema"
|
sema :: import "@source/sema"
|
||||||
|
|
||||||
TypeKind :: enum {
|
TypeKind :: enum {
|
||||||
none,
|
none
|
||||||
invalid,
|
invalid
|
||||||
int,
|
int
|
||||||
float,
|
float
|
||||||
bool,
|
bool
|
||||||
string,
|
string
|
||||||
void,
|
void
|
||||||
function,
|
function
|
||||||
range,
|
range
|
||||||
array,
|
array
|
||||||
usize,
|
record
|
||||||
i8,
|
type_value
|
||||||
i16,
|
u8, u16, u32, u64, usize
|
||||||
i32,
|
i8, i16, i32, isize
|
||||||
isize,
|
}
|
||||||
u8,
|
|
||||||
u16,
|
RecordId :: distinct u32
|
||||||
u32,
|
no_record :: maxval!(RecordId)
|
||||||
u64,
|
|
||||||
|
RecordField :: struct {
|
||||||
|
name ?ast.NodeId
|
||||||
|
type Type
|
||||||
|
default ?ast.NodeId
|
||||||
|
}
|
||||||
|
|
||||||
|
RecordType :: struct {
|
||||||
|
declaration ast.NodeId
|
||||||
|
fields []RecordField
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
RecordInitializer :: struct {
|
||||||
|
fields []mut ast.NodeId
|
||||||
}
|
}
|
||||||
|
|
||||||
ArrayId :: distinct u32
|
ArrayId :: distinct u32
|
||||||
@@ -43,17 +57,21 @@ Type :: struct {
|
|||||||
diagnostic diagnostics.DiagnosticId = diagnostics.no_diagnostic
|
diagnostic diagnostics.DiagnosticId = diagnostics.no_diagnostic
|
||||||
array_type ArrayId = no_array
|
array_type ArrayId = no_array
|
||||||
range_element TypeKind = .none
|
range_element TypeKind = .none
|
||||||
|
record_type RecordId = no_record
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
Storage :: struct {
|
Storage :: struct {
|
||||||
types []mut Type
|
types []mut Type
|
||||||
arrays arraylist.ArrayList(ArrayType)
|
arrays arraylist.ArrayList(ArrayType)
|
||||||
|
records arraylist.ArrayList(RecordType)
|
||||||
|
initializers arraylist.ArrayList(RecordInitializer)
|
||||||
|
slots []mut usize
|
||||||
fallthroughs hashmap.AutoHashMap(ast.NodeId, diagnostics.DiagnosticId)
|
fallthroughs hashmap.AutoHashMap(ast.NodeId, diagnostics.DiagnosticId)
|
||||||
allocator mem.Allocator
|
allocator mem.Allocator
|
||||||
}
|
}
|
||||||
|
|
||||||
#! owns node-indexed types, interned array descriptors, and function fallthrough obligations; release with deinit.
|
#! owns node-indexed types, aggregate descriptors, initializer mappings, and function fallthrough obligations; release with deinit.
|
||||||
#! function IDs refer to the caller-owned AST; diagnostic IDs refer to the caller-owned store.
|
#! function IDs refer to the caller-owned AST; diagnostic IDs refer to the caller-owned store.
|
||||||
Result :: struct { storage Storage }
|
Result :: struct { storage Storage }
|
||||||
|
|
||||||
@@ -104,6 +122,8 @@ Cell :: struct {
|
|||||||
literal ast.NodeId = ast.NodeId(maxval!(u32))
|
literal ast.NodeId = ast.NodeId(maxval!(u32))
|
||||||
producer ast.NodeId = ast.NodeId(maxval!(u32))
|
producer ast.NodeId = ast.NodeId(maxval!(u32))
|
||||||
capture ast.NodeId = ast.NodeId(maxval!(u32))
|
capture ast.NodeId = ast.NodeId(maxval!(u32))
|
||||||
|
record_checking bool = false
|
||||||
|
nominal bool = false
|
||||||
first_edge usize = no_index
|
first_edge usize = no_index
|
||||||
return_annotation ast.NodeId = ast.NodeId(maxval!(u32))
|
return_annotation ast.NodeId = ast.NodeId(maxval!(u32))
|
||||||
}
|
}
|
||||||
@@ -124,12 +144,15 @@ State :: struct {
|
|||||||
groups []mut GroupLink
|
groups []mut GroupLink
|
||||||
edges arraylist.ArrayList(Edge)
|
edges arraylist.ArrayList(Edge)
|
||||||
arrays arraylist.ArrayList(ArrayType)
|
arrays arraylist.ArrayList(ArrayType)
|
||||||
|
records arraylist.ArrayList(RecordType)
|
||||||
|
initializers arraylist.ArrayList(RecordInitializer)
|
||||||
|
slots []mut usize
|
||||||
collecting_assignment bool = false
|
collecting_assignment bool = false
|
||||||
has_assignments bool = false
|
has_assignments bool = false
|
||||||
}
|
}
|
||||||
|
|
||||||
#! borrows the AST, resolution, and diagnostic store only during checking.
|
#! borrows the AST, resolution, and diagnostic store only during checking.
|
||||||
#! temporary constraints are freed on every exit; Result owns types, array descriptors, and fallthrough obligations.
|
#! temporary constraints are freed on every exit; Result owns types, aggregate descriptors, initializer mappings, and fallthrough obligations.
|
||||||
check proc(
|
check proc(
|
||||||
allocator mem.Allocator,
|
allocator mem.Allocator,
|
||||||
tree @ast.Tree,
|
tree @ast.Tree,
|
||||||
@@ -149,6 +172,9 @@ check proc(
|
|||||||
for (0..count) |i| groups[i].parent = i
|
for (0..count) |i| groups[i].parent = i
|
||||||
queue :: try mem.alloc(ast.NodeId, allocator, count)
|
queue :: try mem.alloc(ast.NodeId, allocator, count)
|
||||||
defer mem.free(allocator, queue)
|
defer mem.free(allocator, queue)
|
||||||
|
slots :: try mem.alloc(usize, allocator, count)
|
||||||
|
errdefer mem.free(allocator, slots)
|
||||||
|
mem.set(slots, no_index)
|
||||||
state := State{
|
state := State{
|
||||||
tree = tree,
|
tree = tree,
|
||||||
resolution = resolution,
|
resolution = resolution,
|
||||||
@@ -158,9 +184,18 @@ check proc(
|
|||||||
groups = groups,
|
groups = groups,
|
||||||
edges = arraylist.init(allocator),
|
edges = arraylist.init(allocator),
|
||||||
arrays = arraylist.init(allocator),
|
arrays = arraylist.init(allocator),
|
||||||
|
records = arraylist.init(allocator),
|
||||||
|
initializers = arraylist.init(allocator),
|
||||||
|
slots = slots,
|
||||||
}
|
}
|
||||||
defer arraylist.deinit(&state.edges)
|
defer arraylist.deinit(&state.edges)
|
||||||
errdefer arraylist.deinit(&state.arrays)
|
errdefer arraylist.deinit(&state.arrays)
|
||||||
|
errdefer {
|
||||||
|
for (state.records.items) |record| mem.free(allocator, record.fields)
|
||||||
|
arraylist.deinit(&state.records)
|
||||||
|
for (state.initializers.items) |initializer| mem.free(allocator, initializer.fields)
|
||||||
|
arraylist.deinit(&state.initializers)
|
||||||
|
}
|
||||||
|
|
||||||
for 0..ast.package_decl_count(tree) |i| {
|
for 0..ast.package_decl_count(tree) |i| {
|
||||||
id :: ast.package_decl(tree, i)
|
id :: ast.package_decl(tree, i)
|
||||||
@@ -195,6 +230,7 @@ check proc(
|
|||||||
for (0..count) |i| {
|
for (0..count) |i| {
|
||||||
id :: ast.NodeId(i)
|
id :: ast.NodeId(i)
|
||||||
if (cells[i].role != .expression or !declaration_reference(tree, id)) continue
|
if (cells[i].role != .expression or !declaration_reference(tree, id)) continue
|
||||||
|
if (cells[i].type.kind == .invalid) continue
|
||||||
binding :: sema.binding(resolution, id)
|
binding :: sema.binding(resolution, id)
|
||||||
if (binding.kind != .declaration) continue
|
if (binding.kind != .declaration) continue
|
||||||
target :: binding.data.declaration
|
target :: binding.data.declaration
|
||||||
@@ -207,6 +243,7 @@ check proc(
|
|||||||
join(&state, id, ast.statement_decl(tree, id).initializer)
|
join(&state, id, ast.statement_decl(tree, id).initializer)
|
||||||
}
|
}
|
||||||
if (cells[i].role != .expression or !declaration_reference(tree, id)) continue
|
if (cells[i].role != .expression or !declaration_reference(tree, id)) continue
|
||||||
|
if (cells[i].type.kind == .invalid) continue
|
||||||
binding :: sema.binding(resolution, id)
|
binding :: sema.binding(resolution, id)
|
||||||
if (binding.kind == .declaration and !cells[i].assignment_value and !cells[usize(binding.data.declaration)].fixed) {
|
if (binding.kind == .declaration and !cells[i].assignment_value and !cells[usize(binding.data.declaration)].fixed) {
|
||||||
join(&state, id, binding.data.declaration)
|
join(&state, id, binding.data.declaration)
|
||||||
@@ -225,6 +262,7 @@ check proc(
|
|||||||
or cells[i].role != .expression
|
or cells[i].role != .expression
|
||||||
or !declaration_reference(tree, id)
|
or !declaration_reference(tree, id)
|
||||||
) continue
|
) continue
|
||||||
|
if (cells[i].type.kind == .invalid) continue
|
||||||
binding :: sema.binding(resolution, id)
|
binding :: sema.binding(resolution, id)
|
||||||
if (binding.kind == .declaration) cells[i].type = types[usize(binding.data.declaration)]
|
if (binding.kind == .declaration) cells[i].type = types[usize(binding.data.declaration)]
|
||||||
}
|
}
|
||||||
@@ -356,6 +394,11 @@ check proc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (0..count) |i| try validate(&state, ast.NodeId(i))
|
for (0..count) |i| try validate(&state, ast.NodeId(i))
|
||||||
|
for state.records.items |record| {
|
||||||
|
for record.fields |field| {
|
||||||
|
if (field.default) |value| try compatible(&state, value, field.type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
# errors travel only from dependencies to users. A rejected use never poisons
|
# errors travel only from dependencies to users. A rejected use never poisons
|
||||||
# an explicitly typed declaration, and body errors do not erase signatures.
|
# an explicitly typed declaration, and body errors do not erase signatures.
|
||||||
@@ -401,6 +444,9 @@ check proc(
|
|||||||
return Result{ storage = Storage{
|
return Result{ storage = Storage{
|
||||||
types = types,
|
types = types,
|
||||||
arrays = state.arrays,
|
arrays = state.arrays,
|
||||||
|
records = state.records,
|
||||||
|
initializers = state.initializers,
|
||||||
|
slots = slots,
|
||||||
fallthroughs = fallthroughs,
|
fallthroughs = fallthroughs,
|
||||||
allocator = allocator,
|
allocator = allocator,
|
||||||
} }
|
} }
|
||||||
@@ -427,11 +473,36 @@ array_type proc(result @Result, id ArrayId) ArrayType {
|
|||||||
return result.storage.arrays.items[usize(id)]
|
return result.storage.arrays.items[usize(id)]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
record_type_count proc(result @Result) usize {
|
||||||
|
return result.storage.records.items.len
|
||||||
|
}
|
||||||
|
|
||||||
|
record_type proc(result @Result, id RecordId) RecordType {
|
||||||
|
debug.assert(usize(id) < result.storage.records.items.len)
|
||||||
|
return result.storage.records.items[usize(id)]
|
||||||
|
}
|
||||||
|
|
||||||
|
field_index proc(result @Result, selection ast.NodeId) usize {
|
||||||
|
debug.assert(result.storage.slots[usize(selection)] != no_index)
|
||||||
|
return result.storage.slots[usize(selection)]
|
||||||
|
}
|
||||||
|
|
||||||
|
initializer_field proc(result @Result, literal ast.NodeId, index usize) ast.NodeId {
|
||||||
|
slot :: result.storage.slots[usize(literal)]
|
||||||
|
debug.assert(slot != no_index)
|
||||||
|
return result.storage.initializers.items[slot].fields[index]
|
||||||
|
}
|
||||||
|
|
||||||
#! releases type, descriptor, and fallthrough storage using the allocator retained by check.
|
#! releases type, descriptor, and fallthrough storage using the allocator retained by check.
|
||||||
deinit proc(result @mut Result) void {
|
deinit proc(result @mut Result) void {
|
||||||
mem.free(result.storage.allocator, result.storage.types)
|
mem.free(result.storage.allocator, result.storage.types)
|
||||||
arraylist.deinit(&result.storage.arrays)
|
arraylist.deinit(&result.storage.arrays)
|
||||||
hashmap.deinit(&result.storage.fallthroughs)
|
hashmap.deinit(&result.storage.fallthroughs)
|
||||||
|
for (result.storage.records.items) |record| mem.free(result.storage.allocator, record.fields)
|
||||||
|
arraylist.deinit(&result.storage.records)
|
||||||
|
for (result.storage.initializers.items) |initializer| mem.free(result.storage.allocator, initializer.fields)
|
||||||
|
arraylist.deinit(&result.storage.initializers)
|
||||||
|
mem.free(result.storage.allocator, result.storage.slots)
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
@@ -518,6 +589,7 @@ join proc(state @mut State, left, right ast.NodeId) void {
|
|||||||
state.cells[a].numeric = state.cells[a].numeric or state.cells[b].numeric
|
state.cells[a].numeric = state.cells[a].numeric or state.cells[b].numeric
|
||||||
state.cells[a].anchored = state.cells[a].anchored or state.cells[b].anchored
|
state.cells[a].anchored = state.cells[a].anchored or state.cells[b].anchored
|
||||||
if (state.cells[a].demand == .none) state.cells[a].demand = state.cells[b].demand
|
if (state.cells[a].demand == .none) state.cells[a].demand = state.cells[b].demand
|
||||||
|
if (state.cells[a].expected.kind == .none) state.cells[a].expected = state.cells[b].expected
|
||||||
state.cells[a].conversion_unsigned = state.cells[a].conversion_unsigned or state.cells[b].conversion_unsigned
|
state.cells[a].conversion_unsigned = state.cells[a].conversion_unsigned or state.cells[b].conversion_unsigned
|
||||||
if (state.cells[a].literal == ast.NodeId(maxval!(u32))) state.cells[a].literal = state.cells[b].literal
|
if (state.cells[a].literal == ast.NodeId(maxval!(u32))) state.cells[a].literal = state.cells[b].literal
|
||||||
if (state.cells[a].producer == ast.NodeId(maxval!(u32))) state.cells[a].producer = state.cells[b].producer
|
if (state.cells[a].producer == ast.NodeId(maxval!(u32))) state.cells[a].producer = state.cells[b].producer
|
||||||
@@ -540,6 +612,12 @@ collect_invalid proc(state @mut State, id ast.NodeId) void {
|
|||||||
check_annotation proc(state @mut State, id ast.NodeId, allow_void bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
|
check_annotation proc(state @mut State, id ast.NodeId, allow_void bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
if (state.cells[usize(id)].role == .annotation) return state.types[usize(id)]
|
if (state.cells[usize(id)].role == .annotation) return state.types[usize(id)]
|
||||||
state.cells[usize(id)].role = .annotation
|
state.cells[usize(id)].role = .annotation
|
||||||
|
if (ast.expr_kind(state.tree, id) == .record_type) {
|
||||||
|
type :: try check_record(state, id, false)
|
||||||
|
state.types[usize(id)] = type
|
||||||
|
state.cells[usize(id)].type = type
|
||||||
|
return type
|
||||||
|
}
|
||||||
if (ast.expr_kind(state.tree, id) == .array_type) {
|
if (ast.expr_kind(state.tree, id) == .array_type) {
|
||||||
view :: ast.array_type(state.tree, id)
|
view :: ast.array_type(state.tree, id)
|
||||||
element := try check_annotation(state, view.element, true)
|
element := try check_annotation(state, view.element, true)
|
||||||
@@ -582,6 +660,20 @@ check_annotation proc(state @mut State, id ast.NodeId, allow_void bool) Type ! (
|
|||||||
type = invalid_type(try diagnostics.add(state.diagnostics, .invalid_void_type, ast.node_span(state.tree, id)))
|
type = invalid_type(try diagnostics.add(state.diagnostics, .invalid_void_type, ast.node_span(state.tree, id)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.declaration: {
|
||||||
|
target :: binding.data.declaration
|
||||||
|
role :: state.cells[usize(target)].role
|
||||||
|
if (role != .parameter and role != .loop_capture and ast.decl_kind(state.tree, target) == .statement) {
|
||||||
|
initializer :: ast.statement_decl(state.tree, target).initializer
|
||||||
|
if (ast.expr_kind(state.tree, initializer) == .record_type) {
|
||||||
|
type = try check_record(state, initializer, true)
|
||||||
|
} else {
|
||||||
|
type = invalid_type(try diagnostics.add(state.diagnostics, .expected_type, ast.node_span(state.tree, id)))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
type = invalid_type(try diagnostics.add(state.diagnostics, .expected_type, ast.node_span(state.tree, id)))
|
||||||
|
}
|
||||||
|
}
|
||||||
else: type = invalid_type(try diagnostics.add(state.diagnostics, .expected_type, ast.node_span(state.tree, id)))
|
else: type = invalid_type(try diagnostics.add(state.diagnostics, .expected_type, ast.node_span(state.tree, id)))
|
||||||
}
|
}
|
||||||
state.types[usize(id)] = type
|
state.types[usize(id)] = type
|
||||||
@@ -593,6 +685,9 @@ check_annotation proc(state @mut State, id ast.NodeId, allow_void bool) Type ! (
|
|||||||
collect_declaration proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
collect_declaration proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
declaration :: ast.statement_decl(state.tree, id)
|
declaration :: ast.statement_decl(state.tree, id)
|
||||||
state.cells[usize(id)].role = .declaration
|
state.cells[usize(id)].role = .declaration
|
||||||
|
if (ast.expr_kind(state.tree, declaration.initializer) == .record_type) {
|
||||||
|
state.cells[usize(declaration.initializer)].nominal = true
|
||||||
|
}
|
||||||
if (declaration.type) |type| {
|
if (declaration.type) |type| {
|
||||||
state.cells[usize(id)].fixed = true
|
state.cells[usize(id)].fixed = true
|
||||||
state.cells[usize(id)].type = try check_annotation(state, type, false)
|
state.cells[usize(id)].type = try check_annotation(state, type, false)
|
||||||
@@ -604,6 +699,11 @@ collect_declaration proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
|
|||||||
state.cells[usize(id)].type = invalid_type(binding.data.diagnostic)
|
state.cells[usize(id)].type = invalid_type(binding.data.diagnostic)
|
||||||
}
|
}
|
||||||
try collect_expression(state, declaration.initializer)
|
try collect_expression(state, declaration.initializer)
|
||||||
|
if (ast.expr_kind(state.tree, declaration.initializer) == .record_type
|
||||||
|
and state.cells[usize(id)].type.kind != .invalid) {
|
||||||
|
state.cells[usize(id)].fixed = true
|
||||||
|
state.cells[usize(id)].type = state.cells[usize(declaration.initializer)].type
|
||||||
|
}
|
||||||
try depend(state, declaration.initializer, id)
|
try depend(state, declaration.initializer, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -772,6 +872,19 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
|
|||||||
.literal_string: state.cells[usize(id)].type = Type{ kind = .string }
|
.literal_string: state.cells[usize(id)].type = Type{ kind = .string }
|
||||||
.identifier, .selection: {
|
.identifier, .selection: {
|
||||||
binding :: sema.binding(state.resolution, id)
|
binding :: sema.binding(state.resolution, id)
|
||||||
|
if (binding.kind == .none and ast.expr_kind(tree, id) == .selection) {
|
||||||
|
base :: ast.selection_expr(tree, id).base
|
||||||
|
try collect_expression(state, base)
|
||||||
|
try depend(state, base, id)
|
||||||
|
}
|
||||||
|
if (binding.kind == .declaration) {
|
||||||
|
target :: binding.data.declaration
|
||||||
|
role :: state.cells[usize(target)].role
|
||||||
|
if (role != .parameter and role != .loop_capture and ast.decl_kind(tree, target) == .statement
|
||||||
|
and ast.expr_kind(tree, ast.statement_decl(tree, target).initializer) == .record_type) {
|
||||||
|
state.cells[usize(id)].type = invalid_type(try diagnostics.add(state.diagnostics, .type_not_value, ast.node_span(tree, id)))
|
||||||
|
}
|
||||||
|
}
|
||||||
match binding.kind {
|
match binding.kind {
|
||||||
.invalid: state.cells[usize(id)].type = invalid_type(binding.data.diagnostic)
|
.invalid: state.cells[usize(id)].type = invalid_type(binding.data.diagnostic)
|
||||||
.builtin: state.cells[usize(id)].type = invalid_type(try diagnostics.add(state.diagnostics, .type_not_value, ast.node_span(tree, id)))
|
.builtin: state.cells[usize(id)].type = invalid_type(try diagnostics.add(state.diagnostics, .type_not_value, ast.node_span(tree, id)))
|
||||||
@@ -839,6 +952,23 @@ collect_expression proc(state @mut State, id ast.NodeId) void ! (mem.AllocError
|
|||||||
.array_type: {
|
.array_type: {
|
||||||
state.cells[usize(id)].type = invalid_type(try diagnostics.add(state.diagnostics, .type_not_value, ast.node_span(tree, id)))
|
state.cells[usize(id)].type = invalid_type(try diagnostics.add(state.diagnostics, .type_not_value, ast.node_span(tree, id)))
|
||||||
}
|
}
|
||||||
|
.record_type: {
|
||||||
|
type := try check_record(state, id, state.cells[usize(id)].nominal)
|
||||||
|
if (type.kind == .record) type.kind = .type_value
|
||||||
|
state.cells[usize(id)].type = type
|
||||||
|
}
|
||||||
|
.record_literal: {
|
||||||
|
state.cells[usize(id)].literal = id
|
||||||
|
if (ast.record_literal(tree, id).type) |annotation| {
|
||||||
|
state.cells[usize(id)].expected = try check_annotation(state, annotation, false)
|
||||||
|
try depend(state, annotation, id)
|
||||||
|
}
|
||||||
|
for 0..ast.record_field_count(tree, id) |i| {
|
||||||
|
value :: ast.field(tree, ast.record_field(tree, id, i)).value?
|
||||||
|
try collect_expression(state, value)
|
||||||
|
try depend(state, value, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
.array_literal: {
|
.array_literal: {
|
||||||
state.cells[usize(id)].literal = id
|
state.cells[usize(id)].literal = id
|
||||||
for 0..ast.array_element_count(tree, id) |i| {
|
for 0..ast.array_element_count(tree, id) |i| {
|
||||||
@@ -992,9 +1122,24 @@ numeric_constraint proc(state @mut State, expression, operand, operation ast.Nod
|
|||||||
demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Code) void ! (mem.AllocError | diagnostics.CapacityError) {
|
demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Code) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
if (state.types[usize(id)].kind == .invalid) return
|
if (state.types[usize(id)].kind == .invalid) return
|
||||||
index :: root(state, usize(id))
|
index :: root(state, usize(id))
|
||||||
|
if (expected.kind == .record) {
|
||||||
|
literal :: state.cells[index].literal
|
||||||
|
if (literal == ast.NodeId(maxval!(u32)) or ast.expr_kind(state.tree, literal) != .record_literal) return
|
||||||
|
if (ast.record_literal(state.tree, literal).type == null) {
|
||||||
|
previous :: state.cells[index].expected
|
||||||
|
if (previous.kind == .record and !same_type(state, previous, expected)) {
|
||||||
|
try fail(state, literal, code, ast.node_span(state.tree, literal))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
state.cells[index].expected = expected
|
||||||
|
}
|
||||||
|
_ = try infer_record_literal(state, literal, index, false)
|
||||||
|
return
|
||||||
|
}
|
||||||
if (expected.kind == .array) {
|
if (expected.kind == .array) {
|
||||||
literal :: state.cells[index].literal
|
literal :: state.cells[index].literal
|
||||||
if (literal == ast.NodeId(maxval!(u32))) return
|
if (literal == ast.NodeId(maxval!(u32))) return
|
||||||
|
if (ast.expr_kind(state.tree, literal) != .array_literal) return
|
||||||
previous :: state.cells[index].expected
|
previous :: state.cells[index].expected
|
||||||
if (previous.kind == .array and !same_type(state, previous, expected)) {
|
if (previous.kind == .array and !same_type(state, previous, expected)) {
|
||||||
if (!has_inferred_count(state, previous) and !has_inferred_count(state, expected)) {
|
if (!has_inferred_count(state, previous) and !has_inferred_count(state, expected)) {
|
||||||
@@ -1050,6 +1195,11 @@ mutable_target proc(state @State, id ast.NodeId) bool {
|
|||||||
base :: state.types[usize(view.base)]
|
base :: state.types[usize(view.base)]
|
||||||
return base.kind == .array and state.arrays.items[usize(base.array_type)].mutable and mutable_target(state, view.base)
|
return base.kind == .array and state.arrays.items[usize(base.array_type)].mutable and mutable_target(state, view.base)
|
||||||
}
|
}
|
||||||
|
if (ast.expr_kind(state.tree, id) == .selection
|
||||||
|
and sema.binding(state.resolution, id).kind == .none) {
|
||||||
|
view :: ast.selection_expr(state.tree, id)
|
||||||
|
return state.types[usize(view.base)].kind == .record and mutable_target(state, view.base)
|
||||||
|
}
|
||||||
binding :: sema.binding(state.resolution, id)
|
binding :: sema.binding(state.resolution, id)
|
||||||
if (binding.kind != .declaration) return false
|
if (binding.kind != .declaration) return false
|
||||||
target :: binding.data.declaration
|
target :: binding.data.declaration
|
||||||
@@ -1178,6 +1328,18 @@ validate proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | diagnost
|
|||||||
}
|
}
|
||||||
if (array_cells(state, actual) == null) try fail(state, id, .array_size_overflow, ast.node_span(tree, id))
|
if (array_cells(state, actual) == null) try fail(state, id, .array_size_overflow, ast.node_span(tree, id))
|
||||||
}
|
}
|
||||||
|
.record_literal: {
|
||||||
|
actual :: state.types[usize(id)]
|
||||||
|
if (actual.kind != .record) return
|
||||||
|
fields :: state.records.items[usize(actual.record_type)].fields
|
||||||
|
slot :: state.slots[usize(id)]
|
||||||
|
if (slot == no_index) return
|
||||||
|
values :: state.initializers.items[slot].fields
|
||||||
|
for fields |field, i| {
|
||||||
|
if (values[i] != ast.NodeId(maxval!(u32))) try compatible(state, values[i], field.type)
|
||||||
|
}
|
||||||
|
if (array_cells(state, actual) == null) try fail(state, id, .array_size_overflow, ast.node_span(tree, id))
|
||||||
|
}
|
||||||
.index: {
|
.index: {
|
||||||
view :: ast.index_expr(tree, id)
|
view :: ast.index_expr(tree, id)
|
||||||
base :: state.types[usize(view.base)]
|
base :: state.types[usize(view.base)]
|
||||||
@@ -1334,6 +1496,7 @@ intern_array proc(state @mut State, descriptor ArrayType) Type ! mem.AllocError
|
|||||||
if (existing.len == descriptor.len and existing.mutable == descriptor.mutable
|
if (existing.len == descriptor.len and existing.mutable == descriptor.mutable
|
||||||
and existing.element.kind == descriptor.element.kind
|
and existing.element.kind == descriptor.element.kind
|
||||||
and existing.element.function == descriptor.element.function
|
and existing.element.function == descriptor.element.function
|
||||||
|
and existing.element.record_type == descriptor.element.record_type
|
||||||
and existing.element.array_type == descriptor.element.array_type) {
|
and existing.element.array_type == descriptor.element.array_type) {
|
||||||
return Type{ kind = .array, array_type = ArrayId(i) }
|
return Type{ kind = .array, array_type = ArrayId(i) }
|
||||||
}
|
}
|
||||||
@@ -1346,6 +1509,22 @@ intern_array proc(state @mut State, descriptor ArrayType) Type ! mem.AllocError
|
|||||||
same_type proc(state @State, left, right Type) bool {
|
same_type proc(state @State, left, right Type) bool {
|
||||||
if (left.kind != right.kind) return false
|
if (left.kind != right.kind) return false
|
||||||
if (left.kind == .function) return same_signature(state, left.function, right.function)
|
if (left.kind == .function) return same_signature(state, left.function, right.function)
|
||||||
|
if (left.kind == .record or left.kind == .type_value) {
|
||||||
|
if (left.record_type == right.record_type) return true
|
||||||
|
a :: state.records.items[usize(left.record_type)]
|
||||||
|
b :: state.records.items[usize(right.record_type)]
|
||||||
|
if (a.declaration != ast.NodeId(maxval!(u32)) or b.declaration != ast.NodeId(maxval!(u32))
|
||||||
|
or a.fields.len != b.fields.len) return false
|
||||||
|
for a.fields |field, i| {
|
||||||
|
other :: b.fields[i]
|
||||||
|
if ((field.name == null) != (other.name == null)) return false
|
||||||
|
if (field.name) |name| {
|
||||||
|
if (ast.name_id(state.tree, name) != ast.name_id(state.tree, other.name?)) return false
|
||||||
|
}
|
||||||
|
if (!same_type(state, field.type, other.type)) return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
if (left.kind != .array) return true
|
if (left.kind != .array) return true
|
||||||
a :: state.arrays.items[usize(left.array_type)]
|
a :: state.arrays.items[usize(left.array_type)]
|
||||||
b :: state.arrays.items[usize(right.array_type)]
|
b :: state.arrays.items[usize(right.array_type)]
|
||||||
@@ -1354,6 +1533,16 @@ same_type proc(state @State, left, right Type) bool {
|
|||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
array_cells proc(state @State, type Type) ?usize {
|
array_cells proc(state @State, type Type) ?usize {
|
||||||
|
if (type.kind == .record) {
|
||||||
|
total := usize(0)
|
||||||
|
for state.records.items[usize(type.record_type)].fields |field| {
|
||||||
|
child := usize(0)
|
||||||
|
if (array_cells(state, field.type)) |value| child = value else return null
|
||||||
|
if (total > maxval!(usize) - child) return null
|
||||||
|
total += child
|
||||||
|
}
|
||||||
|
return total
|
||||||
|
}
|
||||||
if (type.kind != .array) return usize(1)
|
if (type.kind != .array) return usize(1)
|
||||||
view :: state.arrays.items[usize(type.array_type)]
|
view :: state.arrays.items[usize(type.array_type)]
|
||||||
if (view.len == no_index) return null
|
if (view.len == no_index) return null
|
||||||
@@ -1366,7 +1555,7 @@ array_cells proc(state @State, type Type) ?usize {
|
|||||||
@hide:file
|
@hide:file
|
||||||
array_origin proc(state @mut State, id ast.NodeId) ast.NodeId {
|
array_origin proc(state @mut State, id ast.NodeId) ast.NodeId {
|
||||||
origin :: state.cells[root(state, usize(id))].literal
|
origin :: state.cells[root(state, usize(id))].literal
|
||||||
if (origin != ast.NodeId(maxval!(u32))) return origin
|
if (origin != ast.NodeId(maxval!(u32)) and ast.expr_kind(state.tree, origin) == .array_literal) return origin
|
||||||
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .index) {
|
if (state.cells[usize(id)].role == .expression and ast.expr_kind(state.tree, id) == .index) {
|
||||||
base :: array_origin(state, ast.index_expr(state.tree, id).base)
|
base :: array_origin(state, ast.index_expr(state.tree, id).base)
|
||||||
if (base != ast.NodeId(maxval!(u32)) and ast.array_element_count(state.tree, base) != 0) {
|
if (base != ast.NodeId(maxval!(u32)) and ast.array_element_count(state.tree, base) != 0) {
|
||||||
@@ -1396,8 +1585,8 @@ unify_elements proc(state @mut State, left, right ast.NodeId) void ! (mem.AllocE
|
|||||||
if (state.cells[left_root].numeric and state.cells[right_root].numeric) {
|
if (state.cells[left_root].numeric and state.cells[right_root].numeric) {
|
||||||
try numeric_constraint(state, left, right, right)
|
try numeric_constraint(state, left, right, right)
|
||||||
}
|
}
|
||||||
if (left_type.kind == .array) try demand(state, right, left_type, .type_mismatch)
|
if (left_type.kind == .array or left_type.kind == .record) try demand(state, right, left_type, .type_mismatch)
|
||||||
if (right_type.kind == .array) try demand(state, left, right_type, .type_mismatch)
|
if (right_type.kind == .array or right_type.kind == .record) try demand(state, left, right_type, .type_mismatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
@@ -1414,6 +1603,48 @@ infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
|
|||||||
return group_type(state, id)
|
return group_type(state, id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (state.cells[usize(id)].role == .expression
|
||||||
|
and ast.expr_kind(state.tree, id) == .selection
|
||||||
|
and sema.binding(state.resolution, id).kind == .none) {
|
||||||
|
view :: ast.selection_expr(state.tree, id)
|
||||||
|
base :: try infer_shape(state, view.base, defaults)
|
||||||
|
if (base.kind == .record) {
|
||||||
|
fields :: state.records.items[usize(base.record_type)].fields
|
||||||
|
slot :: selected_field(state, fields, view.member)
|
||||||
|
if (slot != no_index) {
|
||||||
|
state.slots[usize(id)] = slot
|
||||||
|
seed(state, id, fields[slot].type)
|
||||||
|
return fields[slot].type
|
||||||
|
}
|
||||||
|
try fail(state, id, .unknown_field, ast.node_span(state.tree, view.member))
|
||||||
|
} else if (base.kind == .invalid) {
|
||||||
|
seed(state, id, base)
|
||||||
|
} else if (base.kind != .none) {
|
||||||
|
try fail(state, id, .not_record, ast.node_span(state.tree, view.base))
|
||||||
|
}
|
||||||
|
if (base.kind == .none) {
|
||||||
|
origin :: state.cells[root(state, usize(view.base))].literal
|
||||||
|
if (origin != ast.NodeId(maxval!(u32)) and ast.expr_kind(state.tree, origin) == .record_literal) {
|
||||||
|
for 0..ast.record_field_count(state.tree, origin) |i| {
|
||||||
|
field :: ast.field(state.tree, ast.record_field(state.tree, origin, i))
|
||||||
|
matches := false
|
||||||
|
if (field.name) |name| {
|
||||||
|
matches = (
|
||||||
|
ast.expr_kind(state.tree, view.member) == .identifier
|
||||||
|
and ast.name_id(state.tree, name) == ast.name_id(state.tree, view.member)
|
||||||
|
)
|
||||||
|
} else if (constant_integer(state, view.member)) |ordinal| {
|
||||||
|
matches = !ordinal.negative and ordinal.magnitude == u64(i)
|
||||||
|
}
|
||||||
|
if (!matches) continue
|
||||||
|
child :: field.value?
|
||||||
|
if (state.cells[root(state, usize(child))].numeric) try numeric_constraint(state, id, child, id)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return group_type(state, id)
|
||||||
|
}
|
||||||
if (state.cells[usize(id)].role == .expression and declaration_reference(state.tree, id)) {
|
if (state.cells[usize(id)].role == .expression and declaration_reference(state.tree, id)) {
|
||||||
binding :: sema.binding(state.resolution, id)
|
binding :: sema.binding(state.resolution, id)
|
||||||
if (binding.kind == .declaration) {
|
if (binding.kind == .declaration) {
|
||||||
@@ -1431,6 +1662,9 @@ infer_shape proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.All
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
literal :: state.cells[index].literal
|
literal :: state.cells[index].literal
|
||||||
|
if (literal != ast.NodeId(maxval!(u32)) and ast.expr_kind(state.tree, literal) == .record_literal) {
|
||||||
|
return try infer_record_literal(state, literal, index, defaults)
|
||||||
|
}
|
||||||
if (literal != ast.NodeId(maxval!(u32))) {
|
if (literal != ast.NodeId(maxval!(u32))) {
|
||||||
len :: ast.array_element_count(state.tree, literal)
|
len :: ast.array_element_count(state.tree, literal)
|
||||||
expected :: state.cells[index].expected
|
expected :: state.cells[index].expected
|
||||||
@@ -1495,6 +1729,14 @@ prepare_shapes proc(state @mut State, assignment_values, defaults bool) void ! (
|
|||||||
@hide:file
|
@hide:file
|
||||||
apply_contexts proc(state @mut State, assignment_values, early bool) void ! (mem.AllocError | diagnostics.CapacityError) {
|
apply_contexts proc(state @mut State, assignment_values, early bool) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
tree :: state.tree
|
tree :: state.tree
|
||||||
|
if (!assignment_values) {
|
||||||
|
for 0..(state.records.items.len) |i| {
|
||||||
|
record :: state.records.items[i]
|
||||||
|
for record.fields |field| {
|
||||||
|
if (field.default) |value| try context_demand(state, value, field.type, .type_mismatch, early)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
for 0..(state.cells.len) |i| {
|
for 0..(state.cells.len) |i| {
|
||||||
id :: ast.NodeId(i)
|
id :: ast.NodeId(i)
|
||||||
if (!assignment_values and state.cells[i].role == .declaration) {
|
if (!assignment_values and state.cells[i].role == .declaration) {
|
||||||
@@ -1535,7 +1777,7 @@ apply_contexts proc(state @mut State, assignment_values, early bool) void ! (mem
|
|||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
context_demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Code, early bool) void ! (mem.AllocError | diagnostics.CapacityError) {
|
context_demand proc(state @mut State, id ast.NodeId, expected Type, code diagnostics.Code, early bool) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
if (early and expected.kind != .array and !integer_kind(expected.kind)) return
|
if (early and expected.kind != .array and expected.kind != .record and !integer_kind(expected.kind)) return
|
||||||
try demand(state, id, expected, code)
|
try demand(state, id, expected, code)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1654,3 +1896,220 @@ specialize_annotations proc(state @mut State) void ! (mem.AllocError | diagnosti
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
named_field proc(tree @ast.Tree, fields []RecordField, name ast.NodeId) usize {
|
||||||
|
for fields |field, i| {
|
||||||
|
if (field.name) |existing| {
|
||||||
|
if (ast.name_id(tree, existing) == ast.name_id(tree, name)) return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return no_index
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
selected_field proc(state @State, fields []RecordField, member ast.NodeId) usize {
|
||||||
|
if (ast.expr_kind(state.tree, member) == .identifier) return named_field(state.tree, fields, member)
|
||||||
|
value := usize(0)
|
||||||
|
for ast.source_text(state.tree, ast.node_span(state.tree, member)) |byte| {
|
||||||
|
if (byte < '0' or byte > '9') return no_index
|
||||||
|
digit :: usize(byte - '0')
|
||||||
|
if (value > divtrunc!(maxval!(usize) - digit, usize(10))) return no_index
|
||||||
|
value = value * 10 + digit
|
||||||
|
}
|
||||||
|
if (value >= fields.len or fields[value].name != null) return no_index
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
intern_record proc(state @mut State, declaration ast.NodeId, fields []mut RecordField) Type ! mem.AllocError {
|
||||||
|
for state.records.items |existing, i| {
|
||||||
|
if (existing.declaration != declaration or existing.fields.len != fields.len) continue
|
||||||
|
equal := true
|
||||||
|
same_defaults := true
|
||||||
|
for fields |field, j| {
|
||||||
|
slot := j
|
||||||
|
if (field.name) |name| slot = named_field(state.tree, existing.fields, name)
|
||||||
|
if (slot == no_index or (field.name == null and existing.fields[slot].name != null)
|
||||||
|
or !same_type(state, field.type, existing.fields[slot].type)) {
|
||||||
|
equal = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
other_default :: existing.fields[slot].default
|
||||||
|
if ((field.default == null) != (other_default == null)) {
|
||||||
|
same_defaults = false
|
||||||
|
} else if (field.default) |value| {
|
||||||
|
if (value != other_default?) same_defaults = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (equal and same_defaults) {
|
||||||
|
mem.free(state.records.allocator, fields)
|
||||||
|
return Type{ kind = .record, record_type = RecordId(i) }
|
||||||
|
}
|
||||||
|
if (equal) {
|
||||||
|
for existing.fields |field, j| {
|
||||||
|
if (field.name) |name| {
|
||||||
|
slot :: named_field(state.tree, fields, name)
|
||||||
|
saved :: fields[j]
|
||||||
|
fields[j] = fields[slot]
|
||||||
|
fields[slot] = saved
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try arraylist.append(&state.records, RecordType{ declaration = declaration, fields = fields })
|
||||||
|
return Type{ kind = .record, record_type = RecordId(state.records.items.len - 1) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
check_record proc(state @mut State, id ast.NodeId, nominal bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
|
cached := state.cells[usize(id)].type
|
||||||
|
if (cached.kind == .record or cached.kind == .type_value) {
|
||||||
|
cached.kind = .record
|
||||||
|
return cached
|
||||||
|
}
|
||||||
|
if (cached.kind == .invalid) return cached
|
||||||
|
if (state.cells[usize(id)].record_checking) {
|
||||||
|
type :: invalid_type(try diagnostics.add(state.diagnostics, .recursive_record, ast.node_span(state.tree, id)))
|
||||||
|
state.cells[usize(id)].type = type
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
state.cells[usize(id)].record_checking = true
|
||||||
|
defer state.cells[usize(id)].record_checking = false
|
||||||
|
count :: ast.record_field_count(state.tree, id)
|
||||||
|
fields :: try mem.alloc(RecordField, state.records.allocator, count)
|
||||||
|
owned := true
|
||||||
|
defer {
|
||||||
|
if (owned) mem.free(state.records.allocator, fields)
|
||||||
|
}
|
||||||
|
failure := Type{}
|
||||||
|
for 0..count |i| {
|
||||||
|
node :: ast.record_field(state.tree, id, i)
|
||||||
|
view :: ast.field(state.tree, node)
|
||||||
|
type := try check_annotation(state, view.type?, false)
|
||||||
|
if (type.kind == .void or type.kind == .range or type.kind == .type_value or type.kind == .none) {
|
||||||
|
type = invalid_type(try diagnostics.add(state.diagnostics, .invalid_record_field_type, view.span))
|
||||||
|
}
|
||||||
|
if (view.name) |name| {
|
||||||
|
if (named_field(state.tree, fields[0..i], name) != no_index) {
|
||||||
|
type = invalid_type(try diagnostics.add(state.diagnostics, .duplicate_field, ast.node_span(state.tree, name)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fields[i] = RecordField{ name = view.name, type = type, default = view.value }
|
||||||
|
if (type.kind == .invalid) failure = type
|
||||||
|
}
|
||||||
|
if (failure.kind == .invalid) {
|
||||||
|
state.cells[usize(id)].type = failure
|
||||||
|
return failure
|
||||||
|
}
|
||||||
|
declaration :: if (nominal) id else ast.NodeId(maxval!(u32))
|
||||||
|
type :: try intern_record(state, declaration, fields)
|
||||||
|
owned = false
|
||||||
|
state.cells[usize(id)].type = type
|
||||||
|
state.cells[usize(id)].record_checking = false
|
||||||
|
saved_assignment :: state.collecting_assignment
|
||||||
|
state.collecting_assignment = false
|
||||||
|
defer state.collecting_assignment = saved_assignment
|
||||||
|
for 0..count |i| {
|
||||||
|
if (ast.field(state.tree, ast.record_field(state.tree, id, i)).value) |value| {
|
||||||
|
try collect_expression(state, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return type
|
||||||
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
infer_record_literal proc(state @mut State, literal ast.NodeId, index usize, defaults bool) Type ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
|
if (state.types[usize(literal)].kind == .invalid) return state.types[usize(literal)]
|
||||||
|
count :: ast.record_field_count(state.tree, literal)
|
||||||
|
expected := state.cells[index].expected
|
||||||
|
if (expected.kind == .none and state.cells[index].type.kind == .record) expected = state.cells[index].type
|
||||||
|
if (expected.kind == .invalid) {
|
||||||
|
seed(state, literal, expected)
|
||||||
|
return expected
|
||||||
|
}
|
||||||
|
if (expected.kind != .none and expected.kind != .record) {
|
||||||
|
try fail(state, literal, .expected_type, ast.node_span(state.tree, literal))
|
||||||
|
return state.types[usize(literal)]
|
||||||
|
}
|
||||||
|
if (expected.kind == .none) {
|
||||||
|
ready := true
|
||||||
|
for 0..count |i| {
|
||||||
|
value :: ast.field(state.tree, ast.record_field(state.tree, literal, i)).value?
|
||||||
|
actual :: try infer_shape(state, value, defaults)
|
||||||
|
if (actual.kind == .none) ready = false
|
||||||
|
if (actual.kind == .invalid) {
|
||||||
|
seed(state, literal, actual)
|
||||||
|
return actual
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!ready) return Type{}
|
||||||
|
fields :: try mem.alloc(RecordField, state.records.allocator, count)
|
||||||
|
errdefer mem.free(state.records.allocator, fields)
|
||||||
|
for 0..count |i| {
|
||||||
|
view :: ast.field(state.tree, ast.record_field(state.tree, literal, i))
|
||||||
|
type := group_type(state, view.value?)
|
||||||
|
if (type.kind == .void or type.kind == .type_value or type.kind == .range) {
|
||||||
|
try fail(state, literal, .invalid_record_field_type, view.span)
|
||||||
|
mem.free(state.records.allocator, fields)
|
||||||
|
return state.types[usize(literal)]
|
||||||
|
}
|
||||||
|
if (view.name) |name| {
|
||||||
|
if (named_field(state.tree, fields[0..i], name) != no_index) {
|
||||||
|
try fail(state, literal, .duplicate_field, ast.node_span(state.tree, name))
|
||||||
|
mem.free(state.records.allocator, fields)
|
||||||
|
return state.types[usize(literal)]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fields[i] = RecordField{ name = view.name, type = type, default = null }
|
||||||
|
}
|
||||||
|
expected = try intern_record(state, ast.NodeId(maxval!(u32)), fields)
|
||||||
|
}
|
||||||
|
fields :: state.records.items[usize(expected.record_type)].fields
|
||||||
|
slot := state.slots[usize(literal)]
|
||||||
|
fresh :: slot == no_index
|
||||||
|
if (slot == no_index) {
|
||||||
|
values :: try mem.alloc(ast.NodeId, state.initializers.allocator, fields.len)
|
||||||
|
errdefer mem.free(state.initializers.allocator, values)
|
||||||
|
try arraylist.append(&state.initializers, RecordInitializer{ fields = values })
|
||||||
|
slot = state.initializers.items.len - 1
|
||||||
|
state.slots[usize(literal)] = slot
|
||||||
|
}
|
||||||
|
values :: state.initializers.items[slot].fields
|
||||||
|
if (values.len != fields.len) {
|
||||||
|
try fail(state, literal, .type_mismatch, ast.node_span(state.tree, literal))
|
||||||
|
return state.types[usize(literal)]
|
||||||
|
}
|
||||||
|
mem.set(values, ast.NodeId(maxval!(u32)))
|
||||||
|
for 0..count |i| {
|
||||||
|
node :: ast.record_field(state.tree, literal, i)
|
||||||
|
view :: ast.field(state.tree, node)
|
||||||
|
field_slot := i
|
||||||
|
if (view.name) |name| field_slot = named_field(state.tree, fields, name)
|
||||||
|
if (field_slot >= fields.len) {
|
||||||
|
code :: if (view.name != null) diagnostics.Code.unknown_field else diagnostics.Code.tuple_arity_mismatch
|
||||||
|
try fail(state, literal, code, view.span)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (values[field_slot] != ast.NodeId(maxval!(u32))) {
|
||||||
|
try fail(state, literal, .duplicate_field, view.span)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
values[field_slot] = view.value?
|
||||||
|
try demand(state, view.value?, fields[field_slot].type, .type_mismatch)
|
||||||
|
_ = try infer_shape(state, view.value?, defaults)
|
||||||
|
}
|
||||||
|
for fields |field, i| {
|
||||||
|
if (values[i] != ast.NodeId(maxval!(u32))) continue
|
||||||
|
if (field.default) |value| {
|
||||||
|
values[i] = value
|
||||||
|
if (fresh) try depend(state, value, literal)
|
||||||
|
} else {
|
||||||
|
code :: if (field.name == null) diagnostics.Code.tuple_arity_mismatch else diagnostics.Code.missing_field
|
||||||
|
try fail(state, literal, code, ast.node_span(state.tree, literal))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (state.types[usize(literal)].kind == .invalid) return state.types[usize(literal)]
|
||||||
|
state.cells[index].type = expected
|
||||||
|
return expected
|
||||||
|
}
|
||||||
|
|||||||
+26
-12
@@ -443,20 +443,24 @@ resolve_expr proc(
|
|||||||
expression :: ast.selection_expr(tree, id)
|
expression :: ast.selection_expr(tree, 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 := base
|
value := Binding{}
|
||||||
if (base.kind != .invalid) {
|
if (base.kind == .invalid) {
|
||||||
if (base.kind == .namespace) {
|
value = base
|
||||||
namespace :: hashmap.get(&state.namespaces, base.data.namespace)?
|
} else if (base.kind == .namespace) {
|
||||||
if hashmap.get(&namespace.names, ast.name_id(tree, expression.member)) |found| {
|
namespace :: hashmap.get(&state.namespaces, base.data.namespace)?
|
||||||
value = found
|
found :: if (ast.expr_kind(tree, expression.member) == .identifier)
|
||||||
} else {
|
hashmap.get(&namespace.names, ast.name_id(tree, expression.member))
|
||||||
value = Binding{ kind = .invalid, data = BindingData{
|
else
|
||||||
diagnostic = try diagnostics.add(state.diagnostics, .unknown_name, ast.node_span(tree, expression.member)),
|
null
|
||||||
} }
|
if (found) |member| {
|
||||||
}
|
value = member
|
||||||
} else {
|
} else {
|
||||||
value = Binding{ kind = .invalid, data = BindingData{
|
value = Binding{ kind = .invalid, data = BindingData{
|
||||||
diagnostic = try diagnostics.add(state.diagnostics, .not_namespace, ast.node_span(tree, expression.base)),
|
diagnostic = try diagnostics.add(
|
||||||
|
state.diagnostics,
|
||||||
|
.unknown_name,
|
||||||
|
ast.node_span(tree, expression.member),
|
||||||
|
),
|
||||||
} }
|
} }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -490,6 +494,16 @@ 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: {
|
||||||
|
if (ast.expr_kind(tree, id) == .record_literal) {
|
||||||
|
if (ast.record_literal(tree, id).type) |type| try resolve_expr(state, scope, type)
|
||||||
|
}
|
||||||
|
for (0..ast.record_field_count(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.value) |value| try resolve_expr(state, scope, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
.index: {
|
.index: {
|
||||||
expression :: ast.index_expr(tree, id)
|
expression :: ast.index_expr(tree, id)
|
||||||
try resolve_expr(state, scope, expression.base)
|
try resolve_expr(state, scope, expression.base)
|
||||||
|
|||||||
+204
-1
@@ -11,6 +11,183 @@ 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 = {
|
||||||
|
"record_defaults_and_copies": (
|
||||||
|
"""
|
||||||
|
Point :: struct { x i16, y i16 = 2 }
|
||||||
|
seed Point :: { x = 40 }
|
||||||
|
identity proc(p Point) Point { return p }
|
||||||
|
main proc() [3]i16 {
|
||||||
|
a := identity(seed)
|
||||||
|
b := a
|
||||||
|
a.x = 9
|
||||||
|
return [b.x + b.y, a.x, seed.x]
|
||||||
|
}
|
||||||
|
""", b"[42, 9, 40]\n", None,
|
||||||
|
),
|
||||||
|
"contextual_record_arguments": (
|
||||||
|
"""
|
||||||
|
Point :: struct { x u8, y u8 = 2 }
|
||||||
|
add proc(p Point) u8 { return p.x + p.y }
|
||||||
|
main proc() u8 { return add({ x = 40 }) }
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"nested_records_arrays_tuples": (
|
||||||
|
"""
|
||||||
|
Point :: struct { x i16, y i16 = 2 }
|
||||||
|
Box :: struct { points [2]mut Point, pair struct { u8, bool }, empty struct {} }
|
||||||
|
identity proc(b Box) Box { return b }
|
||||||
|
main proc() Box {
|
||||||
|
a Box := { points = [{ x = 1 }, { x = 40 }], pair = {255, true}, empty = {} }
|
||||||
|
b :: identity(a)
|
||||||
|
a.points[0].x = 9
|
||||||
|
a.pair.0 = 8
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
""", b"{[{1, 2}, {40, 2}], {255, true}, {}}\n", None,
|
||||||
|
),
|
||||||
|
"anonymous_records_and_numeric_fields": (
|
||||||
|
"""
|
||||||
|
main proc() i64 {
|
||||||
|
a := { x = 40, pair = {{2, true}, false} }
|
||||||
|
b :: a
|
||||||
|
a.x = 1
|
||||||
|
return b.x + b.pair.0.0
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"record_temporary_fields_and_guards": (
|
||||||
|
"""
|
||||||
|
Point :: struct { x u8, y u8 = 2 }
|
||||||
|
make proc() Point { return { x = 40 } }
|
||||||
|
main proc() u8 {
|
||||||
|
flag :: { ready = true }
|
||||||
|
if flag.ready { return make().x + make().y }
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"zero_cell_records": (
|
||||||
|
"""
|
||||||
|
Empty :: struct {}
|
||||||
|
Box :: struct { values [3]Empty, empty Empty }
|
||||||
|
main proc() Box { return { values = [{}, {}, {}], empty = {} } }
|
||||||
|
""", b"{[{}, {}, {}], {}}\n", None,
|
||||||
|
),
|
||||||
|
"record_default_override": (
|
||||||
|
"""
|
||||||
|
Point :: struct { x i64 = 9223372036854775807 + 1 }
|
||||||
|
main proc() i64 { p :: Point { x = 42 }
|
||||||
|
return p.x }
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"record_default_trap": (
|
||||||
|
"""
|
||||||
|
Point :: struct { x i64 = 9223372036854775807 + 1 }
|
||||||
|
main proc() i64 { p :: Point {}
|
||||||
|
return p.x }
|
||||||
|
""", b"", b"integer arithmetic overflow",
|
||||||
|
),
|
||||||
|
"record_branchful_default": (
|
||||||
|
"""
|
||||||
|
Point :: struct { x bool = false and (9223372036854775807 + 1 == 0) }
|
||||||
|
main proc() bool { p :: Point {}
|
||||||
|
return !p.x }
|
||||||
|
""", b"true\n", None,
|
||||||
|
),
|
||||||
|
"record_explicit_evaluation_order": (
|
||||||
|
"""
|
||||||
|
cycle i64 :: cycle
|
||||||
|
Point :: struct { x i64, y i64 }
|
||||||
|
main proc() Point { return Point { y = 9223372036854775807 + 1, x = cycle } }
|
||||||
|
""", b"", b"integer arithmetic overflow",
|
||||||
|
),
|
||||||
|
"record_explicit_before_default": (
|
||||||
|
"""
|
||||||
|
cycle i64 :: cycle
|
||||||
|
Point :: struct { x i64 = cycle, y i64 }
|
||||||
|
main proc() Point { return Point { y = 9223372036854775807 + 1 } }
|
||||||
|
""", b"", b"integer arithmetic overflow",
|
||||||
|
),
|
||||||
|
"record_assignment_rhs_before_place": (
|
||||||
|
"""
|
||||||
|
Point :: struct { xs [1]mut i64 }
|
||||||
|
main proc() Point { p Point := { xs = [0] }
|
||||||
|
p.xs[1] = 9223372036854775807 + 1
|
||||||
|
return p }
|
||||||
|
""", b"", b"integer arithmetic overflow",
|
||||||
|
),
|
||||||
|
"record_reordered_structural_assignment": (
|
||||||
|
"main proc() int { a := { x = 1, y = 2 }\na = { y = 40, x = 2 }\nreturn a.x + a.y }",
|
||||||
|
b"42\n", None,
|
||||||
|
),
|
||||||
|
"record_local_type_and_loop": (
|
||||||
|
"""
|
||||||
|
main proc() i16 {
|
||||||
|
Point :: struct { x i16 }
|
||||||
|
p Point := { x = 0 }
|
||||||
|
for 0..100 |i| {
|
||||||
|
copy := p
|
||||||
|
copy.x = p.x + 1
|
||||||
|
p = copy
|
||||||
|
}
|
||||||
|
return p.x
|
||||||
|
}
|
||||||
|
""", b"100\n", None,
|
||||||
|
),
|
||||||
|
"record_function_field": (
|
||||||
|
"""
|
||||||
|
plus proc(n i64) i64 { return n + 1 }
|
||||||
|
minus proc(n i64) i64 { return n - 1 }
|
||||||
|
main proc() i64 { p := { f = plus }
|
||||||
|
p.f = minus
|
||||||
|
return p.f(43) }
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"record_nominal_mismatch": (
|
||||||
|
"A :: struct { x i64 }\nB :: struct { x i64 }\nmain proc() A { b :: B { x = 42 }\nreturn b }",
|
||||||
|
b"", b"",
|
||||||
|
),
|
||||||
|
"record_missing_field": (
|
||||||
|
"Point :: struct { x i64, y i64 }\nmain proc() Point { return { x = 42 } }", b"", b"",
|
||||||
|
),
|
||||||
|
"record_duplicate_field": (
|
||||||
|
"Point :: struct { x i64 }\nmain proc() Point { return { x = 1, x = 2 } }", b"", b"",
|
||||||
|
),
|
||||||
|
"record_unknown_field": (
|
||||||
|
"Point :: struct { x i64 }\nmain proc() Point { return { x = 1, z = 2 } }", b"", b"",
|
||||||
|
),
|
||||||
|
"record_wrong_field_type": (
|
||||||
|
"Point :: struct { x u8 }\nmain proc() Point { n u16 :: 42\nreturn { x = n } }", b"", b"",
|
||||||
|
),
|
||||||
|
"record_readonly_field": (
|
||||||
|
"Point :: struct { x i64 }\nmain proc() i64 { p :: Point { x = 1 }\np.x = 2\nreturn p.x }",
|
||||||
|
b"", b"",
|
||||||
|
),
|
||||||
|
"record_readonly_array_field": (
|
||||||
|
"Point :: struct { xs [1]i64 }\nmain proc() i64 { p Point := { xs = [1] }\np.xs[0] = 2\nreturn p.xs[0] }",
|
||||||
|
b"", b"",
|
||||||
|
),
|
||||||
|
"record_parameter_readonly": (
|
||||||
|
"Point :: struct { x i64 }\nmutate proc(p Point) i64 { p.x = 2\nreturn p.x }\nmain proc() i64 { return mutate({ x = 1 }) }",
|
||||||
|
b"", b"",
|
||||||
|
),
|
||||||
|
"record_recursive_layout": (
|
||||||
|
"Point :: struct { next Point }\nmain proc() int { return 42 }", b"42\n", b"",
|
||||||
|
),
|
||||||
|
"record_tuple_arity": (
|
||||||
|
"main proc() struct { i64, bool } { return {42,} }", b"", b"",
|
||||||
|
),
|
||||||
|
"record_unused_bad_body": (
|
||||||
|
"Point :: struct { x i64 }\nbad proc() Point { return { missing = 1 } }\nmain proc() i64 { return 42 }",
|
||||||
|
b"42\n", b"",
|
||||||
|
),
|
||||||
|
"record_field_bounds": (
|
||||||
|
"Point :: struct { xs [1]i64 }\nmain proc() i64 { p :: Point { xs = [42] }\nreturn p.xs[1] }",
|
||||||
|
b"", b"index out of bounds",
|
||||||
|
),
|
||||||
|
"record_type_not_value": (
|
||||||
|
"Point :: struct { x i64 }\nmain proc() i64 { Point\nreturn 42 }", b"", b"type name cannot",
|
||||||
|
),
|
||||||
"void": ("main proc() void {}", b"", None),
|
"void": ("main proc() void {}", b"", None),
|
||||||
"signed_limits": (
|
"signed_limits": (
|
||||||
"main proc() [2]i64 { return [-9223372036854775808, 9223372036854775807] }",
|
"main proc() [2]i64 { return [-9223372036854775808, 9223372036854775807] }",
|
||||||
@@ -517,7 +694,7 @@ main proc() bool {
|
|||||||
('import "../shared"\nmain proc() int { return shared.helper(1) }', b"", b"argument count"),
|
('import "../shared"\nmain proc() int { return shared.helper(1) }', b"", b"argument count"),
|
||||||
('import "../shared"\nmain proc() f64 { return shared.identity(true) }', b"", b"incompatible value type"),
|
('import "../shared"\nmain proc() f64 { return shared.identity(true) }', b"", b"incompatible value type"),
|
||||||
('import "../shared"\nmain proc() int { shared\nreturn 42 }', b"", b"namespace cannot be used as a value"),
|
('import "../shared"\nmain proc() int { shared\nreturn 42 }', b"", b"namespace cannot be used as a value"),
|
||||||
('import "../shared"\nmain proc() int { return shared.value.member }', b"", b"requires a package namespace"),
|
('import "../shared"\nmain proc() int { return shared.value.member }', b"", b""),
|
||||||
('lib :: import "../absent"\nmain proc() int { return lib.helper() }', b"", b"cannot load package directory"),
|
('lib :: import "../absent"\nmain proc() int { return lib.helper() }', b"", b"cannot load package directory"),
|
||||||
('lib :: import "../left"\nlib :: import "../right"\nmain proc() int { return lib.helper() }', b"1\n", b"duplicate name"),
|
('lib :: import "../left"\nlib :: import "../right"\nmain proc() int { return lib.helper() }', b"1\n", b"duplicate name"),
|
||||||
('lib :: import "../shared"\nlib :: 42\nmain proc() int { return lib.helper() }', b"", b"duplicate name"),
|
('lib :: import "../shared"\nlib :: 42\nmain proc() int { return lib.helper() }', b"", b"duplicate name"),
|
||||||
@@ -566,6 +743,31 @@ main proc() int { return missing }
|
|||||||
return len(cases) + 6
|
return len(cases) + 6
|
||||||
|
|
||||||
|
|
||||||
|
def check_record_packages(temporary, executable):
|
||||||
|
project = temporary / "records"
|
||||||
|
app, library = project / "app", project / "library"
|
||||||
|
app.mkdir(parents=True)
|
||||||
|
library.mkdir()
|
||||||
|
types = library / "types.hon"
|
||||||
|
types.write_text("Point :: struct { x i16, y i16 = 2 }\n")
|
||||||
|
(library / "functions.hon").write_text("identity proc(p Point) Point { return p }\n")
|
||||||
|
(app / "main.hon").write_text(textwrap.dedent("""
|
||||||
|
lib :: import "../library"
|
||||||
|
main proc() i16 {
|
||||||
|
p lib.Point := lib.Point { x = 40 }
|
||||||
|
q :: lib.identity(p)
|
||||||
|
p.x = 1
|
||||||
|
return q.x + q.y
|
||||||
|
}
|
||||||
|
""").strip() + "\n")
|
||||||
|
check(app, executable, (b"42\n", None))
|
||||||
|
types.write_text("Point :: struct { x i16, y i16 = 32767 + 1 }\n")
|
||||||
|
check(app, executable, (b"", b"integer arithmetic overflow"))
|
||||||
|
trapped = run(str(HONEY), "run", str(app))
|
||||||
|
assert trapped.stderr.startswith(f"{types.resolve()}:1:".encode()), outcome(trapped)
|
||||||
|
return 2
|
||||||
|
|
||||||
|
|
||||||
def check_dependency_provenance(temporary, executable):
|
def check_dependency_provenance(temporary, executable):
|
||||||
project = temporary / "provenance"
|
project = temporary / "provenance"
|
||||||
app = project / "app"
|
app = project / "app"
|
||||||
@@ -688,6 +890,7 @@ def main():
|
|||||||
count += 1
|
count += 1
|
||||||
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_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