feat: support existing io.print formatting
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. 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. declarations are public by default; `@hide:file` limits a declaration to its originating file, allowing independent private names in sibling files. other visibility 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. declarations are public by default; bare `@hide` and `@hide:package` permit sibling access but hide declarations from importing packages. `@hide:file` limits a declaration to its originating file, allowing independent private names in sibling files. unknown visibility attributes produce syntax diagnostics.
|
||||||
|
|
||||||
```hon
|
```hon
|
||||||
add proc(left, right int) int {
|
add proc(left, right int) int {
|
||||||
@@ -79,7 +79,7 @@ the parser supports arithmetic expressions with nested parenthesized groups, suc
|
|||||||
|
|
||||||
function calls are postfix expressions: `f()`, `f(a, b)`, and `factory()(x)`. calls bind tighter than unary and binary arithmetic and can appear in initializers or as expression statements inside function bodies. argument lists allow newlines and a trailing comma; outside parentheses, newlines remain statement boundaries. type checking validates callable values, argument counts and argument types.
|
function calls are postfix expressions: `f()`, `f(a, b)`, and `factory()(x)`. calls bind tighter than unary and binary arithmetic and can appear in initializers or as expression statements inside function bodies. argument lists allow newlines and a trailing comma; outside parentheses, newlines remain statement boundaries. type checking validates callable values, argument counts and argument types.
|
||||||
|
|
||||||
generic procedures mark compile-time parameters with `$`, such as `identity proc($T type, value T) T`. pass all parameters explicitly (`identity(i16, 42)`) or omit every compile-time argument when runtime argument types or an expected result type determine them (`identity(value)`). inference follows pointers, arrays, slices, optionals, fallible channels, callback signatures and specialized record constructors, including array counts; conflicting or undetermined arguments are diagnosed. specializations are cached by declaration and compile-time arguments, retain nominal generated-record identity, and erase compile-time parameters from runtime calls.
|
generic procedures mark compile-time parameters with `$`, such as `identity proc($T type, value T) T`. pass all parameters explicitly (`identity(i16, 42)`), omit every compile-time argument when runtime argument types or an expected result determine them (`identity(value)`), or supply a declaration-order prefix of compile-time arguments and infer the rest. for example, `io.print("{}", writer, {value})` supplies the format while inferring the tuple type. inference follows pointers, arrays, slices, optionals, fallible channels, callback signatures and specialized record constructors, including array counts; conflicting or undetermined arguments are diagnosed. specializations are cached by declaration and compile-time arguments, retain nominal generated-record identity, and erase compile-time parameters from runtime calls.
|
||||||
|
|
||||||
expected result types flow from typed bindings, assignments, returns, ordinary call parameters, aggregate fields/elements, defaults, match arms, value-block yields and cleanup handlers. they also reach explicit `$()` expressions, `some!` payloads and arithmetic/bitwise operands; shift counts keep their independent types. rigid runtime arguments constrain inference before result context and contextual literal defaults; result context also types nested generic calls. `answer proc($T type) T { return 42 }` can therefore be called as `value i16 :: answer()`. optional presence and nominal generated types remain exact, explicit arguments are never overridden, and type-producing procedures still require every input explicitly.
|
expected result types flow from typed bindings, assignments, returns, ordinary call parameters, aggregate fields/elements, defaults, match arms, value-block yields and cleanup handlers. they also reach explicit `$()` expressions, `some!` payloads and arithmetic/bitwise operands; shift counts keep their independent types. rigid runtime arguments constrain inference before result context and contextual literal defaults; result context also types nested generic calls. `answer proc($T type) T { return 42 }` can therefore be called as `value i16 :: answer()`. optional presence and nominal generated types remain exact, explicit arguments are never overridden, and type-producing procedures still require every input explicitly.
|
||||||
|
|
||||||
@@ -156,10 +156,12 @@ readonly slices of other materializable element types also own their snapshots,
|
|||||||
|
|
||||||
untagged union constants snapshot every byte of their native-layout storage, including padding and bytes beyond a smaller overlapping member. scalar reinterpretation, nested records/arrays, optionals, tagged payloads, fallible channels and generated defaults preserve exact nominal types and by-value copies through `$()` and compile-time arguments. specialization keys compare the full snapshot, not one selected field. every storage byte must be initialized. because untagged storage has no active-member identity, all member layouts must be free of data pointers, callbacks, strings and slices, recursively; otherwise materialization diagnoses an escape rather than retaining interpreter handles, even if a numeric member was initialized.
|
untagged union constants snapshot every byte of their native-layout storage, including padding and bytes beyond a smaller overlapping member. scalar reinterpretation, nested records/arrays, optionals, tagged payloads, fallible channels and generated defaults preserve exact nominal types and by-value copies through `$()` and compile-time arguments. specialization keys compare the full snapshot, not one selected field. every storage byte must be initialized. because untagged storage has no active-member identity, all member layouts must be free of data pointers, callbacks, strings and slices, recursively; otherwise materialization diagnoses an escape rather than retaining interpreter handles, even if a numeric member was initialized.
|
||||||
|
|
||||||
`$(expression)` explicitly evaluates a constant expression, including an ordinary procedure call, even in a package without templates. use a typed expression when its shape cannot be inferred independently, such as `$(Point { x = 40 })`; integer literals retain contextual typing. the resulting value works in runtime expressions, bindings, expression statements and array lengths, while the erased expression still receives ordinary checking. each interpreter evaluation is isolated and limited to 100,000 IR instructions/terminators and 128 active frames; failures retain their originating source locations. runtime parameters and mutable local/global dependencies cannot supply compile-time inputs. nonnull data pointers, unsupported slice types and address-bearing untagged union layouts cannot escape temporary evaluation as generated constants; active payloads or aggregate fields containing unsupported values are diagnosed rather than retaining interpreter handles. executed foreign calls remain interpreter traps. general type-valued execution, broader storage-bearing materialization and remaining existing-library acceptance, including broader `meta` operations and `io.print`, remain open.
|
`$(expression)` explicitly evaluates a constant expression, including an ordinary procedure call, even in a package without templates. use a typed expression when its shape cannot be inferred independently, such as `$(Point { x = 40 })`; integer literals retain contextual typing. the resulting value works in runtime expressions, bindings, expression statements and array lengths, while the erased expression still receives ordinary checking. each interpreter evaluation is isolated and limited to 100,000 IR instructions/terminators and 128 active frames; failures retain their originating source locations. runtime parameters and mutable local/global dependencies cannot supply compile-time inputs. nonnull data pointers, unsupported slice types and address-bearing untagged union layouts cannot escape temporary evaluation as generated constants; active payloads or aggregate fields containing unsupported values are diagnosed rather than retaining interpreter handles. executed foreign calls remain interpreter traps. general type-valued execution, broader storage-bearing materialization and broader `meta` operations remain open.
|
||||||
|
|
||||||
`if $(condition) { ... } else { ... }` evaluates a boolean guard before expanding or checking either body, including calls to ordinary constant-input procedures. concrete generic specializations also select branches whose guards use constants, immutable constant bindings, type comparisons or substituted compile-time parameters. only the selected body is specialized and checked; both bodies are still parsed and name-resolved. selected bodies retain their scope, cleanup and return behavior, and erased guards retain ordinary type checking and source diagnostics. ordinary calls in an implicit guard stay runtime; use `$()` to request their evaluation. optional captures remain runtime, and ordinary nongeneric `if (true)` still checks both bodies.
|
`if $(condition) { ... } else { ... }` evaluates a boolean guard before expanding or checking either body, including calls to ordinary constant-input procedures. concrete generic specializations also select branches whose guards use constants, immutable constant bindings, type comparisons or substituted compile-time parameters. only the selected body is specialized and checked; both bodies are still parsed and name-resolved. selected bodies retain their scope, cleanup and return behavior, and erased guards retain ordinary type checking and source diagnostics. ordinary calls in an implicit guard stay runtime; use `$()` to request their evaluation. optional captures remain runtime, and ordinary nongeneric `if (true)` still checks both bodies.
|
||||||
|
|
||||||
|
generic guards also specialize side-effect-free `minval!`, `maxval!`, `sizeof!` and `alignof!` expressions with constant type operands.
|
||||||
|
|
||||||
`compile_error!(message)` requires a compile-time string and reports its owned text at the call site when that body is elaborated. unselected compile-time branches do not report it; ordinary runtime branches remain checked. messages from constant-input procedures retain their bytes through later evaluation, interpreter diagnostics and native executable emission.
|
`compile_error!(message)` requires a compile-time string and reports its owned text at the call site when that body is elaborated. unselected compile-time branches do not report it; ordinary runtime branches remain checked. messages from constant-input procedures retain their bytes through later evaluation, interpreter diagnostics and native executable emission.
|
||||||
|
|
||||||
`match $(subject) { ... }` selects an expression or statement arm before specializing or checking its body, including ordinary constant-input calls. concrete generic procedures also select matches on side-effect-free constant subjects, including substituted scalar/enum/tagged-union arguments and pure field/index projections. ordinary checked IR validates every pattern and capture declaration before selection; only the selected body is elaborated, while all bodies remain parsed and name-resolved. by-value payload captures retain exact types and owned strings, and selected bodies may use runtime parameters, nested matches, cleanup, returns and enclosing-loop transfers. ordinary call subjects, runtime parameters and mutable locals stay runtime, as do contextual aggregate bindings that might omit runtime defaults; ordinary nongeneric literal matches still check all arms. use `$()` to request explicit evaluation. unsupported storage-bearing payloads retain the escape boundary above.
|
`match $(subject) { ... }` selects an expression or statement arm before specializing or checking its body, including ordinary constant-input calls. concrete generic procedures also select matches on side-effect-free constant subjects, including substituted scalar/enum/tagged-union arguments and pure field/index projections. ordinary checked IR validates every pattern and capture declaration before selection; only the selected body is elaborated, while all bodies remain parsed and name-resolved. by-value payload captures retain exact types and owned strings, and selected bodies may use runtime parameters, nested matches, cleanup, returns and enclosing-loop transfers. ordinary call subjects, runtime parameters and mutable locals stay runtime, as do contextual aggregate bindings that might omit runtime defaults; ordinary nongeneric literal matches still check all arms. use `$()` to request explicit evaluation. unsupported storage-bearing payloads retain the escape boundary above.
|
||||||
@@ -216,12 +218,16 @@ fallible values use explicit `T ! E` channels, where `E` is an enum, record or t
|
|||||||
|
|
||||||
`memcopy!(destination, source)` copies matching typed slices or pointers to arrays, checking equal lengths and rejecting nonempty overlap. `memset!(destination, value)` repeats complete typed values, including aggregate fields, rather than repeating a byte. destinations require writable elements; empty and zero-sized copies/sets do not access storage. interpreter byte views synchronize with the original typed storage so casts and memory operations preserve aliases.
|
`memcopy!(destination, source)` copies matching typed slices or pointers to arrays, checking equal lengths and rejecting nonempty overlap. `memset!(destination, value)` repeats complete typed values, including aggregate fields, rather than repeating a byte. destinations require writable elements; empty and zero-sized copies/sets do not access storage. interpreter byte views synchronize with the original typed storage so casts and memory operations preserve aliases.
|
||||||
|
|
||||||
native external declarations use `malloc c_func(size c_ulong) ?*mut anyopaque`, without a body. supported signatures contain concrete integer/float/boolean scalars, data/function pointers, nullable pointers and `void` results. generated bindings may declare `c_struct` records, nominal pointer-only `opaque` types and `*c_func(...) Result` callback signatures. unused foreign declarations do not force ABI validation or native symbol emission; demanding a variadic or aggregate-by-value signature diagnoses unsupported operands. Darwin `c_char`/`c_uchar`, `c_short`/`c_ushort`, `c_int`/`c_uint`, `c_long`/`c_ulong` and `c_longlong`/`c_ulonglong` map to the corresponding 8/16/32/64-bit integer representations; `c_double` maps to `f64`, while demanded `c_float` and `c_longdouble` types remain unsupported. foreign calls trap only when reached in the interpreter; arbitrary interpreter FFI is not supported.
|
native external declarations use `malloc c_func(size c_ulong) ?*mut anyopaque`, without a body. supported signatures contain concrete integer/float/boolean scalars, data/function pointers, nullable pointers and `void` results. `c_func` declarations and callbacks may end their parameter list with `...`; calls require the fixed prefix and promote trailing booleans and 8/16-bit integers to `c_int`. supported floating arguments are already `f64`; trailing aggregates and Honey string descriptors are not C ABI values. strings may initialize immutable byte pointers, including nullable C format parameters; pass `.ptr` explicitly for variadic strings. generated bindings may declare `c_struct` records, nominal pointer-only `opaque` types and `*c_func(...) Result` callback signatures. unused foreign declarations do not force ABI validation or native symbol emission; aggregate-by-value signatures remain unsupported. Darwin `c_char`/`c_uchar`, `c_short`/`c_ushort`, `c_int`/`c_uint`, `c_long`/`c_ulong` and `c_longlong`/`c_ulonglong` map to the corresponding 8/16/32/64-bit integer representations; `c_double` maps to `f64`, while demanded `c_float` and `c_longdouble` types remain unsupported. foreign calls trap only when reached in the interpreter; arbitrary interpreter FFI is not supported.
|
||||||
|
|
||||||
the existing `std/arraylist` and `std/mem` work without replacement bindings or library code. interpreter/native checks cover initialization, reserve/growth, append/pop/clear, allocation failure without losing the original list, zero-sized elements and deferred cleanup on error. native checks use the real `mem.c_allocator` and imported `ffi/c` declarations for aggregate element storage and over-aligned allocation/reallocation; interpreter allocation checks use a fixed-capacity test allocator, not foreign calls. broader metadata operations and formatted IO remain later acceptance gates.
|
the existing `std/arraylist` and `std/mem` work without replacement bindings or library code. interpreter/native checks cover initialization, reserve/growth, append/pop/clear, allocation failure without losing the original list, zero-sized elements and deferred cleanup on error. native checks use the real `mem.c_allocator` and imported `ffi/c` declarations for aggregate element storage and over-aligned allocation/reallocation; interpreter allocation checks use a fixed-capacity test allocator, not foreign calls. broader metadata operations remain a later acceptance gate.
|
||||||
|
|
||||||
existing `std/hashmap.HashMap`, `AutoHashMap` and `StringHashMap` execute without replacement library code. checks cover collision chains, zero-hash normalization, growth/rehashing, duplicate-key errors, absent lookups, aggregate copy isolation, allocation failure preserving the map and deferred cleanup. native checks use the real C allocator for integer boundaries, booleans, sparse/aliased enums, nested distinct/array/record keys (ignoring record padding), content-based strings and custom callbacks; interpreter checks use separate fixed-capacity typed buffers. a declared result supplies the initializer parameters: `map hashmap.AutoHashMap(i16, i16) := hashmap.init(allocator)`; `StringHashMap` and custom `HashMap` results also infer their exact private/custom callbacks. explicit calls such as `hashmap.init(i16, i16, null, null, allocator)` remain supported.
|
existing `std/hashmap.HashMap`, `AutoHashMap` and `StringHashMap` execute without replacement library code. checks cover collision chains, zero-hash normalization, growth/rehashing, duplicate-key errors, absent lookups, aggregate copy isolation, allocation failure preserving the map and deferred cleanup. native checks use the real C allocator for integer boundaries, booleans, sparse/aliased enums, nested distinct/array/record keys (ignoring record padding), content-based strings and custom callbacks; interpreter checks use separate fixed-capacity typed buffers. a declared result supplies the initializer parameters: `map hashmap.AutoHashMap(i16, i16) := hashmap.init(allocator)`; `StringHashMap` and custom `HashMap` results also infer their exact private/custom callbacks. explicit calls such as `hashmap.init(i16, i16, null, null, allocator)` remain supported.
|
||||||
|
|
||||||
|
existing `std/io.print(format, writer, args)` parses constant formats and expands tuple fields before runtime. escaped braces, default bool/integer/string/enum formatting, nested scalar distinct values, `{s}`, `{d}`, `{b}`, `{o}`, `{x}`, `{X}` and `{c}` work through interpreter/native execution; malformed formats, wrong argument counts and unsupported types diagnose. native `f64` default/decimal/scientific (`{e}`) formatting uses the real imported variadic `snprintf`; that path retains its interpreter foreign-call trap. checks compare exact bytes with short-writing callbacks and cover write failures, zero progress and invalid callback counts. the private parser uses owned token initialization and a retained metadata local to select constant-input execution; field-name lookup uses inline metadata iteration, and helper predicates explicitly request compile-time branch selection.
|
||||||
|
|
||||||
|
single pointers to fixed arrays implicitly borrow matching slice storage without copying, respecting pointer/element mutability, exact nominal elements and sentinel weakening. optional coercions preserve absence and borrow only present payloads.
|
||||||
|
|
||||||
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.
|
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}`.
|
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}`.
|
||||||
@@ -271,7 +277,7 @@ the driver uses `@source/compilation` to own loaded source buffers, copied paths
|
|||||||
|
|
||||||
AST `NodeId` values are compilation-wide, so declarations in different files cannot alias. spans carry their originating `diagnostics.SourceId`; `ast.source_files` exposes borrowed paths and text. `compilation.compile` resolves each package independently, checks the complete arena and lowers one independently owned IR module, selecting an entry only from the requested root package. interpreter diagnostics and generated native traps retain the originating filename and local line/byte-column. borrowed `ast.view` trees must be refreshed after builder mutation and must not be passed to `ast.deinit`.
|
AST `NodeId` values are compilation-wide, so declarations in different files cannot alias. spans carry their originating `diagnostics.SourceId`; `ast.source_files` exposes borrowed paths and text. `compilation.compile` resolves each package independently, checks the complete arena and lowers one independently owned IR module, selecting an entry only from the requested root package. interpreter diagnostics and generated native traps retain the originating filename and local line/byte-column. borrowed `ast.view` trees must be refreshed after builder mutation and must not be passed to `ast.deinit`.
|
||||||
|
|
||||||
AST nodes retain a compact 16-byte layout on the bootstrap target: a kind, three 32-bit payload words and one packed flag byte in existing padding. file-local, foreign-callable, variadic and opaque metadata therefore need neither per-node boolean fields nor another allocation. spans and variable-length payloads remain in parallel storage; cloning and replacement copy the flags with the node.
|
AST nodes retain a compact 16-byte layout on the bootstrap target: a kind, three 32-bit payload words and one packed flag byte in existing padding. file/package-local, foreign-callable, variadic and opaque metadata therefore need neither per-node boolean fields nor another allocation. spans and variable-length payloads remain in parallel storage; cloning and replacement copy the flags with the node.
|
||||||
|
|
||||||
the AST renderer displays nested blocks, cleanup bodies/captures and compound operators alongside ordinary statements.
|
the AST renderer displays nested blocks, cleanup bodies/captures and compound operators alongside ordinary statements.
|
||||||
|
|
||||||
@@ -297,6 +303,10 @@ explicit numeric conversion uses `T(value)`, such as `u8(index)`, `usize(byte)`
|
|||||||
|
|
||||||
`minval!(T)` and `maxval!(T)` return exact concrete integer bounds, including distinct integer types. `divtrunc!(left, right)` requires matching integer types and truncates toward zero, with ordinary contextual literal inference and single left-to-right operand evaluation. integer division by zero and signed minimum divided by `-1` trap before executing host/LLVM division; `/` remains floating-point division. these builtins also work in checked compile-time execution.
|
`minval!(T)` and `maxval!(T)` return exact concrete integer bounds, including distinct integer types. `divtrunc!(left, right)` requires matching integer types and truncates toward zero, with ordinary contextual literal inference and single left-to-right operand evaluation. integer division by zero and signed minimum divided by `-1` trap before executing host/LLVM division; `/` remains floating-point division. these builtins also work in checked compile-time execution.
|
||||||
|
|
||||||
|
`rem!(left, right)` requires matching integer or floating types and evaluates operands once, left to right. integer remainders follow the dividend's sign; signed minimum remainder `-1` is zero. floating remainders use truncating IEEE remainder. zero divisors, including floating negative zero, trap before native division/remainder. distinct integer identity and checked compile-time execution are preserved.
|
||||||
|
|
||||||
|
character literals contain one Unicode scalar value, such as `'A'` or `'é'`, and use contextual integer typing. escapes support `\0`, `\n`, `\r`, `\t`, `\\` and `\'`; invalid UTF-8, multiple characters, unknown escapes and unterminated literals diagnose. destination integer widths still constrain code points.
|
||||||
|
|
||||||
`@std/strconv.parse_float(allocator, text)` parses a complete borrowed `[]u8` decimal into `f64`. it accepts an optional sign, decimal point, and exponent; whitespace, embedded NUL bytes, trailing input, hexadecimal, infinities, and NaNs return `invalid_number`. finite subnormals and signed zero are preserved; overflow and nonzero values rounded to zero return `out_of_range`. allocation failures propagate as `out_of_memory`, and temporary storage is always released. the Darwin backend uses libc's C-locale conversion without changing the process locale; rounding follows the caller's floating-point rounding mode. numeric lowering uses this interface, keeping exact integer-to-float validation compiler-specific.
|
`@std/strconv.parse_float(allocator, text)` parses a complete borrowed `[]u8` decimal into `f64`. it accepts an optional sign, decimal point, and exponent; whitespace, embedded NUL bytes, trailing input, hexadecimal, infinities, and NaNs return `invalid_number`. finite subnormals and signed zero are preserved; overflow and nonzero values rounded to zero return `out_of_range`. allocation failures propagate as `out_of_memory`, and temporary storage is always released. the Darwin backend uses libc's C-locale conversion without changing the process locale; rounding follows the caller's floating-point rounding mode. numeric lowering uses this interface, keeping exact integer-to-float validation compiler-specific.
|
||||||
|
|
||||||
type errors carry source spans and diagnostic identities; invalid operands propagate their existing identity without duplicate errors, while independent expressions, arguments and nested bodies are still checked. inference cycles without a type anchor are diagnosed. function signatures are checked independently of their bodies. bare `return` and same-line `return expression` are supported inside function bodies and must match the declared result type. nonvoid return-path checking is structural: every path must return or terminate in a diagnostic trap, and both conditional successors are considered without constant folding. a missing `else` or a loop permits fallthrough, even for a literal `true` condition; a later return can close that path. statements after a return and untaken branches or loops are still type-checked but are not executed.
|
type errors carry source spans and diagnostic identities; invalid operands propagate their existing identity without duplicate errors, while independent expressions, arguments and nested bodies are still checked. inference cycles without a type anchor are diagnosed. function signatures are checked independently of their bodies. bare `return` and same-line `return expression` are supported inside function bodies and must match the declared result type. nonvoid return-path checking is structural: every path must return or terminate in a diagnostic trap, and both conditional successors are considered without constant folding. a missing `else` or a loop permits fallthrough, even for a literal `true` condition; a later return can close that path. statements after a return and untaken branches or loops are still type-checked but are not executed.
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ and exhaustive statement/value matching work through both execution paths.
|
|||||||
source data pointers/references support aliasing, mutation, calls and aggregate
|
source data pointers/references support aliasing, mutation, calls and aggregate
|
||||||
storage; explicit callback signatures, many-pointer addition, mutable/sentinel
|
storage; explicit callback signatures, many-pointer addition, mutable/sentinel
|
||||||
slices, optionals and layout/cast/memory builtins work through both execution paths.
|
slices, optionals and layout/cast/memory builtins work through both execution paths.
|
||||||
native scalar/pointer `c_func` declarations support allocator-required libc calls;
|
native scalar/pointer `c_func` declarations include variadic calls and C integer
|
||||||
executed foreign calls remain diagnostic traps in the interpreter.
|
promotions; executed foreign calls remain diagnostic traps in the interpreter.
|
||||||
explicit fallible channels, composed error sets, `try`/`catch` and scoped LIFO
|
explicit fallible channels, composed error sets, `try`/`catch` and scoped LIFO
|
||||||
`defer`/`errdefer` run through both paths; source errors are values, not diagnostic
|
`defer`/`errdefer` run through both paths; source errors are values, not diagnostic
|
||||||
traps or compiler infrastructure failures.
|
traps or compiler infrastructure failures.
|
||||||
@@ -82,6 +82,11 @@ aggregate copies and allocator failure/cleanup through both paths; native checks
|
|||||||
cover automatic integer/bool/enum/distinct/array/record keys, content-based strings
|
cover automatic integer/bool/enum/distinct/array/record keys, content-based strings
|
||||||
and custom callbacks with the real C allocator. expected result types infer initializer
|
and custom callbacks with the real C allocator. expected result types infer initializer
|
||||||
types/callbacks, including automatic, string and custom maps.
|
types/callbacks, including automatic, string and custom maps.
|
||||||
|
existing `io.print` expands constant formats and tuple fields, with exact boolean,
|
||||||
|
integer, string, enum and distinct formatting through both paths. native float
|
||||||
|
formatting uses imported `snprintf`; interpreter float formatting retains the FFI trap.
|
||||||
|
character literals, multiline conditional branches, partial compile-time argument
|
||||||
|
prefixes and checked `rem!` support the formatter without a replacement library.
|
||||||
`unreachable` is a demanded source trap; terminal literal-true loops need no return.
|
`unreachable` is a demanded source trap; terminal literal-true loops need no return.
|
||||||
`compile_error!` retains owned user messages and skips unselected compile-time bodies.
|
`compile_error!` retains owned user messages and skips unselected compile-time bodies.
|
||||||
boolean and optional-capture `if ... else` expressions reuse checked matches for
|
boolean and optional-capture `if ... else` expressions reuse checked matches for
|
||||||
@@ -111,9 +116,10 @@ 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; `@hide:file` isolates private
|
visibility follows Bro's public-by-default contract; `@hide:file` isolates private
|
||||||
declarations from sibling files and import selection. other unsupported attributes
|
declarations from sibling files and import selection. bare `@hide` and
|
||||||
are diagnosed. the scalar-function milestone did not
|
`@hide:package` retain sibling access but reject imports. unknown attributes diagnose.
|
||||||
include record fields or imported types; those now work for the record/tuple slice.
|
the scalar-function milestone did not include record fields or imported types;
|
||||||
|
those now work for the record/tuple slice.
|
||||||
test discovery and FFI remain outside these completed slices.
|
test discovery and FFI remain outside these completed slices.
|
||||||
|
|
||||||
## subsequent dependency gates
|
## subsequent dependency gates
|
||||||
@@ -156,7 +162,7 @@ test discovery and FFI remain outside these completed slices.
|
|||||||
- [x] composed inline-match checkpoint: composed error sets expand remaining variants through interpreter/native execution, including nested/repeated/aliased enum, tagged-union and named struct members. all-void sets use one static tag capture; payload-bearing sets use exact immutable by-value payloads and optional static tags, including void members. catch captures, nested inline tag matches, generic calls, owned aggregate copies, deferred cleanup and error returns retain ordinary semantics. constant inputs and type-producing bodies select generated storage with cached nominal identity; struct variant names remain identifiers in generated constants rather than embedding declarations. invalid compositions/captures, narrow payload types, redundant arms, imported dependencies and repeatable compilation retain diagnostics and ownership. member discovery shares the type-control bound; pointer captures and metadata reflection remain open.
|
- [x] composed inline-match checkpoint: composed error sets expand remaining variants through interpreter/native execution, including nested/repeated/aliased enum, tagged-union and named struct members. all-void sets use one static tag capture; payload-bearing sets use exact immutable by-value payloads and optional static tags, including void members. catch captures, nested inline tag matches, generic calls, owned aggregate copies, deferred cleanup and error returns retain ordinary semantics. constant inputs and type-producing bodies select generated storage with cached nominal identity; struct variant names remain identifiers in generated constants rather than embedding declarations. invalid compositions/captures, narrow payload types, redundant arms, imported dependencies and repeatable compilation retain diagnostics and ownership. member discovery shares the type-control bound; pointer captures and metadata reflection remain open.
|
||||||
- [x] type-metadata reflection checkpoint: `typeinfo!(T)` uses the actual `@std/meta` ABI to classify supported types and expose array child/length, immediate distinct backing, record names/tuple/layout and enum/record fields through interpreter/native execution. readonly owned metadata collections retain exact nominal/type leaves, ordinal indices, composed enum identity, checked projections, by-value iteration/copies and compile-time procedure boundaries. ordinary/inline matches select runtime bodies; reflected fields drive `field!`, generics and generated storage/defaults. arity/type/import/ABI failures, match/capture validation, bounds, readonly updates, runtime escape rejection, imported provenance, shared construction bounds and repeatable compilation retain diagnostics and ownership. existing `meta.EnumFieldStruct` and `enummap.EnumMap` generated storage are verified without replacement library code; full `enummap.init/get` is covered below, while general type-valued execution and broader constant materialization remain open.
|
- [x] type-metadata reflection checkpoint: `typeinfo!(T)` uses the actual `@std/meta` ABI to classify supported types and expose array child/length, immediate distinct backing, record names/tuple/layout and enum/record fields through interpreter/native execution. readonly owned metadata collections retain exact nominal/type leaves, ordinal indices, composed enum identity, checked projections, by-value iteration/copies and compile-time procedure boundaries. ordinary/inline matches select runtime bodies; reflected fields drive `field!`, generics and generated storage/defaults. arity/type/import/ABI failures, match/capture validation, bounds, readonly updates, runtime escape rejection, imported provenance, shared construction bounds and repeatable compilation retain diagnostics and ownership. existing `meta.EnumFieldStruct` and `enummap.EnumMap` generated storage are verified without replacement library code; full `enummap.init/get` is covered below, while general type-valued execution and broader constant materialization remain open.
|
||||||
- [x] enum-map acceptance checkpoint: existing `enummap.init/get` run through interpreter/native execution with sparse/aliased/composed enum keys, absent entries and owned aggregate values. contextual `.tag` and aggregate arguments reuse key/value types inferred from rigid generated-map arguments; unresolved generics and rigid conflicts still diagnose. `some!` constructs exact, single-evaluation optional payloads and preserves nested present-null values through compile-time results, cache keys and generated defaults. concrete mutable runtime locals initialize from `undefined`, including nested record/array updates, pointer access and by-value copies; ordinary compile-time procedures materialize fully initialized storage and reject incomplete snapshots. native undefined storage is poison, not zeroed or per-leaf checked. `get` now explicitly returns `null` on lookup fallthrough, preserving the ordinary nonvoid return contract.
|
- [x] enum-map acceptance checkpoint: existing `enummap.init/get` run through interpreter/native execution with sparse/aliased/composed enum keys, absent entries and owned aggregate values. contextual `.tag` and aggregate arguments reuse key/value types inferred from rigid generated-map arguments; unresolved generics and rigid conflicts still diagnose. `some!` constructs exact, single-evaluation optional payloads and preserves nested present-null values through compile-time results, cache keys and generated defaults. concrete mutable runtime locals initialize from `undefined`, including nested record/array updates, pointer access and by-value copies; ordinary compile-time procedures materialize fully initialized storage and reject incomplete snapshots. native undefined storage is poison, not zeroed or per-leaf checked. `get` now explicitly returns `null` on lookup fallthrough, preserving the ordinary nonvoid return contract.
|
||||||
- [x] array-list acceptance checkpoint: existing `std/arraylist` and `std/mem` initialize, reserve/grow, append/pop/clear and release ordinary/aggregate/zero-sized storage without replacement library code. nested generic inference canonicalizes generated types through pointers and derives exact slice `.ptr` types; rigid conflicts remain diagnostic. `minval!`/`maxval!` preserve integer types and checked `divtrunc!` traps on zero and signed overflow through interpreter/native execution. file-local helpers retain real `@hide:file` visibility, newline `else` preserves match default arms, and repeated `_` parameters remain sinks. generated C records, nominal opaque pointers and foreign callbacks load from existing `ffi/c`; unused unsupported signatures neither diagnose nor emit external symbols, while demanded unsupported ABI/types diagnose. interpreter fixed-capacity allocator checks cover failure without losing storage and deferred error cleanup; native `mem.c_allocator` checks cover aggregate growth and over-aligned allocation/reallocation at `-O0`/`-O2`. arbitrary interpreter FFI, variadic/aggregate C calls and broader library acceptance remain open.
|
- [x] array-list acceptance checkpoint: existing `std/arraylist` and `std/mem` initialize, reserve/grow, append/pop/clear and release ordinary/aggregate/zero-sized storage without replacement library code. nested generic inference canonicalizes generated types through pointers and derives exact slice `.ptr` types; rigid conflicts remain diagnostic. `minval!`/`maxval!` preserve integer types and checked `divtrunc!` traps on zero and signed overflow through interpreter/native execution. file-local helpers retain real `@hide:file` visibility, newline `else` preserves match default arms, and repeated `_` parameters remain sinks. generated C records, nominal opaque pointers and foreign callbacks load from existing `ffi/c`; unused unsupported signatures neither diagnose nor emit external symbols, while demanded unsupported ABI/types diagnose. interpreter fixed-capacity allocator checks cover failure without losing storage and deferred error cleanup; native `mem.c_allocator` checks cover aggregate growth and over-aligned allocation/reallocation at `-O0`/`-O2`. variadic C calls and formatted IO are covered below; arbitrary interpreter FFI, aggregate C calls and broader metadata acceptance remain open.
|
||||||
- [x] boolean conditional-expression checkpoint: `if (guard) then_value else else_value` reuses canonical matches through interpreter/native execution. guards evaluate once and only the selected expression executes; nested conditionals bind `else` to the nearest conditional. exact result types, aggregate copy isolation, contextual optional/fallible channels, direct `$()` values, constant-input procedures, generic branch selection and nested generated type positions retain existing checked execution. missing `else`, nonboolean guards and incompatible runtime branch types diagnose. optional captures, yielding value blocks, compound updates and bitwise operators are covered below.
|
- [x] boolean conditional-expression checkpoint: `if (guard) then_value else else_value` reuses canonical matches through interpreter/native execution. guards evaluate once and only the selected expression executes; nested conditionals bind `else` to the nearest conditional. exact result types, aggregate copy isolation, contextual optional/fallible channels, direct `$()` values, constant-input procedures, generic branch selection and nested generated type positions retain existing checked execution. missing `else`, nonboolean guards and incompatible runtime branch types diagnose. optional captures, yielding value blocks, compound updates and bitwise operators are covered below.
|
||||||
- [x] optional conditional-expression checkpoint: `if value |payload| then_value else else_value` reuses canonical presence matches through interpreter/native execution. single-evaluation guards and lazy branches preserve exact immutable by-value captures, nested optional presence, aggregate isolation and runtime pointer/callback/slice/zero-sized payloads. contextual optional/fallible results, constant-input procedures, generated types/defaults and explicit compile-time subject selection retain checked execution and owned payload snapshots. nonoptional guards, capture scope/shadowing, malformed captures, missing `else`, incompatible branches and compile-time overflow retain diagnostics. yielding value blocks are covered below; broader constant materialization remains open.
|
- [x] optional conditional-expression checkpoint: `if value |payload| then_value else else_value` reuses canonical presence matches through interpreter/native execution. single-evaluation guards and lazy branches preserve exact immutable by-value captures, nested optional presence, aggregate isolation and runtime pointer/callback/slice/zero-sized payloads. contextual optional/fallible results, constant-input procedures, generated types/defaults and explicit compile-time subject selection retain checked execution and owned payload snapshots. nonoptional guards, capture scope/shadowing, malformed captures, missing `else`, incompatible branches and compile-time overflow retain diagnostics. yielding value blocks are covered below; broader constant materialization remains open.
|
||||||
- [x] yielding-block checkpoint: value blocks and `yield expression` produce exact/contextual results through interpreter/native execution, including conditional branches, match arms and catch handlers. nested value sources select the nearest target; ordinary blocks/loops preserve it. yielded aggregates snapshot before LIFO cleanup, successful yields skip `errdefer`, and return/error/break/continue retain enclosing targets. ordinary constants, generic branch specialization and type-producing blocks generate owned values, types, storage and defaults. missing operands/results, incompatible yields, capture mutation and outward cleanup transfers retain diagnostics and demand-driven traps; imported provenance and repeatable compilation are verified. labeled yields and value-producing loops remain outside this checkpoint.
|
- [x] yielding-block checkpoint: value blocks and `yield expression` produce exact/contextual results through interpreter/native execution, including conditional branches, match arms and catch handlers. nested value sources select the nearest target; ordinary blocks/loops preserve it. yielded aggregates snapshot before LIFO cleanup, successful yields skip `errdefer`, and return/error/break/continue retain enclosing targets. ordinary constants, generic branch specialization and type-producing blocks generate owned values, types, storage and defaults. missing operands/results, incompatible yields, capture mutation and outward cleanup transfers retain diagnostics and demand-driven traps; imported provenance and repeatable compilation are verified. labeled yields and value-producing loops remain outside this checkpoint.
|
||||||
@@ -176,7 +182,8 @@ test discovery and FFI remain outside these completed slices.
|
|||||||
- [x] metadata-input execution checkpoint: procedures with ordinary type-valued parameters execute entirely at compile time even when returning runtime-representable values. all inputs remain explicit constants; owned scalar/record/array/string results, recursive readonly metadata iteration, reflected field names/counts and private nominal leaves work through interpreter/native execution. plain/fallible `void` retain bare-return/fallthrough semantics; ordinary fallible results propagate through `try`/`catch` and run scoped `defer`/`errdefer`. runtime dependencies and incomplete payloads retain diagnostics; source/session disposal and repeat compilation preserve ownership. dependent signature parameters still require `$`; general type-valued execution, mutable metadata borrowing and broader constant materialization remain open.
|
- [x] metadata-input execution checkpoint: procedures with ordinary type-valued parameters execute entirely at compile time even when returning runtime-representable values. all inputs remain explicit constants; owned scalar/record/array/string results, recursive readonly metadata iteration, reflected field names/counts and private nominal leaves work through interpreter/native execution. plain/fallible `void` retain bare-return/fallthrough semantics; ordinary fallible results propagate through `try`/`catch` and run scoped `defer`/`errdefer`. runtime dependencies and incomplete payloads retain diagnostics; source/session disposal and repeat compilation preserve ownership. dependent signature parameters still require `$`; general type-valued execution, mutable metadata borrowing and broader constant materialization remain open.
|
||||||
- [x] explicit metadata-local execution checkpoint: procedures with explicitly annotated type-valued locals execute entirely at compile time without metadata inputs or results. scalar inputs remain explicit constants; nested metadata records/arrays, undefined initialization, owned copies, checked field/index updates, loops, captures, recursion and scoped fallible cleanup reuse existing type-control execution through interpreter/native paths. runtime inputs and uninitialized leaves diagnose; ordinary unannotated local type aliases retain runtime parameters. imported private nominal leaves, source/session disposal and repeat compilation preserve ownership. inferred metadata-only locals are covered below; mixed runtime/type-valued execution, mutable metadata borrowing and broader constant materialization remain open.
|
- [x] explicit metadata-local execution checkpoint: procedures with explicitly annotated type-valued locals execute entirely at compile time without metadata inputs or results. scalar inputs remain explicit constants; nested metadata records/arrays, undefined initialization, owned copies, checked field/index updates, loops, captures, recursion and scoped fallible cleanup reuse existing type-control execution through interpreter/native paths. runtime inputs and uninitialized leaves diagnose; ordinary unannotated local type aliases retain runtime parameters. imported private nominal leaves, source/session disposal and repeat compilation preserve ownership. inferred metadata-only locals are covered below; mixed runtime/type-valued execution, mutable metadata borrowing and broader constant materialization remain open.
|
||||||
- [x] inferred metadata-local execution checkpoint: inferred mutable type bindings and metadata array/record/tuple locals select fully constant-input execution without metadata inputs, results or local annotations. literal/helper results, checked field/index/optional projections, yielding blocks and conditional/fallible selection preserve owned copies, nominal leaves, whole-value replacement, loops, recursion and scoped cleanup through interpreter/native execution. inferred array elements remain readonly; runtime inputs diagnose. ordinary local aliases and scalar metadata projections retain runtime parameters. ordinary fallible success/error snapshots use checked channel discrimination rather than assuming every payload carries a nominal type. imported private types, source/session disposal and repeat compilation preserve ownership. erased compile-time-only procedures cannot become runtime entries. mixed runtime/type-valued execution, mutable metadata borrowing and broader materialization remain open.
|
- [x] inferred metadata-local execution checkpoint: inferred mutable type bindings and metadata array/record/tuple locals select fully constant-input execution without metadata inputs, results or local annotations. literal/helper results, checked field/index/optional projections, yielding blocks and conditional/fallible selection preserve owned copies, nominal leaves, whole-value replacement, loops, recursion and scoped cleanup through interpreter/native execution. inferred array elements remain readonly; runtime inputs diagnose. ordinary local aliases and scalar metadata projections retain runtime parameters. ordinary fallible success/error snapshots use checked channel discrimination rather than assuming every payload carries a nominal type. imported private types, source/session disposal and repeat compilation preserve ownership. erased compile-time-only procedures cannot become runtime entries. mixed runtime/type-valued execution, mutable metadata borrowing and broader materialization remain open.
|
||||||
- [ ] remaining gate: general type-valued compile-time execution, broader constant materialization (including address-bearing untagged unions, nonnull data pointers and mutable slices), plus remaining existing-library acceptance for broader `meta` operations and `io.print`. formatting parsing and broader imported C-binding support may interleave with later gates.
|
- [x] formatted-IO acceptance checkpoint: existing `std/io.print` parses constant formats and expands tuple fields through interpreter/native execution. escaped braces, all integer bases/boundaries, booleans, strings/arrays, sparse/aliased enums, nested scalar distinct values, runtime arguments and computed formats retain exact output; short writes, zero progress, invalid callback counts and write errors retain checked propagation. malformed formats, wrong tuple/count/type inputs and unsupported specifiers diagnose. native `f64` default/decimal/scientific formatting uses imported variadic `snprintf` at `-O0`/`-O2`, while interpreter FFI remains a demanded trap. character literals preserve Unicode code points and byte escapes; newline-separated conditional branches and package-local visibility match Bro. partial compile-time prefixes infer remaining arguments without overriding explicit values; pure type builtins specialize guards. `rem!` preserves exact numeric types, signed-minimum remainder and checked zero divisors. immutable C byte-pointer coercions, borrowed pointer-to-array slices and optional presence retain mutability/nominal checks. the private format parser reuses inferred metadata-local execution and owned token initialization; inline metadata lookup and explicit predicate guards avoid runtime metadata storage. general type-valued execution, broader materialization and broader `meta` acceptance remain open.
|
||||||
|
- [ ] remaining gate: general type-valued compile-time execution, broader constant materialization (including address-bearing untagged unions, nonnull data pointers and mutable slices), plus remaining existing-library acceptance for broader `meta` operations. broader imported C-binding support may interleave with later gates.
|
||||||
- [ ] remaining source constructs: multiline strings. boolean and optional-capture conditional expressions, yielding value blocks, `yield`, compound updates and bitwise operators are covered above. examples: `source/parser/parse.hon`, `source/llvm/runtime.hon`.
|
- [ ] remaining source constructs: multiline strings. boolean and optional-capture conditional expressions, yielding value blocks, `yield`, compound updates and bitwise operators are covered above. examples: `source/parser/parse.hon`, `source/llvm/runtime.hon`.
|
||||||
- [ ] standard-library milestone: compile/run a multi-package program exercising existing allocation and IO, including error cleanup; no replacement miniature standard library.
|
- [ ] standard-library milestone: compile/run a multi-package program exercising existing allocation and IO, including error cleanup; no replacement miniature standard library.
|
||||||
- [ ] native driver integration: required C ABI declarations, `process.Init` startup arguments/IO, actual CLI exit statuses, file loading, temporary files and spawning Clang. Honey-generated programs currently accept only parameterless `main` and print its result; Bro's bootstrap startup support is not Honey backend support.
|
- [ ] native driver integration: required C ABI declarations, `process.Init` startup arguments/IO, actual CLI exit statuses, file loading, temporary files and spawning Clang. Honey-generated programs currently accept only parameterless `main` and print its result; Bro's bootstrap startup support is not Honey backend support.
|
||||||
|
|||||||
+15
-5
@@ -220,12 +220,13 @@ NodeData :: union {
|
|||||||
binary_operator BinaryOperator
|
binary_operator BinaryOperator
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file file_local_flag u8 :: 0b0001
|
@hide:file file_local_flag u8 :: 0b00000001
|
||||||
@hide:file foreign_type_flag u8 :: 0b0010
|
@hide:file foreign_type_flag u8 :: 0b00000010
|
||||||
@hide:file variadic_flag u8 :: 0b0100
|
@hide:file variadic_flag u8 :: 0b00000100
|
||||||
@hide:file opaque_flag u8 :: 0b1000
|
@hide:file opaque_flag u8 :: 0b00001000
|
||||||
@hide:file optional_match_flag u8 :: 0b00010000
|
@hide:file optional_match_flag u8 :: 0b00010000
|
||||||
@hide:file compound_assignment_flag u8 :: 0b00100000
|
@hide:file compound_assignment_flag u8 :: 0b00100000
|
||||||
|
@hide:file package_local_flag u8 :: 0b01000000
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
Node :: struct {
|
Node :: struct {
|
||||||
@@ -1208,6 +1209,15 @@ file_local proc(tree @Tree, id NodeId) bool {
|
|||||||
return (tree.storage.nodes.items[usize(id)].flags & file_local_flag) != 0
|
return (tree.storage.nodes.items[usize(id)].flags & file_local_flag) != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
set_package_local proc(builder @mut Builder, id NodeId) void {
|
||||||
|
node :: &builder.storage.nodes.items[usize(id)]
|
||||||
|
node.flags = node.flags | package_local_flag
|
||||||
|
}
|
||||||
|
|
||||||
|
package_local proc(tree @Tree, id NodeId) bool {
|
||||||
|
return (tree.storage.nodes.items[usize(id)].flags & package_local_flag) != 0
|
||||||
|
}
|
||||||
|
|
||||||
set_callable_foreign proc(builder @mut Builder, id NodeId) void {
|
set_callable_foreign proc(builder @mut Builder, id NodeId) void {
|
||||||
node :: &builder.storage.nodes.items[usize(id)]
|
node :: &builder.storage.nodes.items[usize(id)]
|
||||||
node.flags = node.flags | foreign_type_flag
|
node.flags = node.flags | foreign_type_flag
|
||||||
|
|||||||
@@ -765,12 +765,15 @@ dependency_traps_keep_original_spans_and_remain_demand_driven test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
file_local_declarations_are_isolated_from_siblings_and_import_selection test {
|
hidden_declarations_preserve_file_and_package_visibility test {
|
||||||
callers [4][]u8 :: [
|
callers [7][]u8 :: [
|
||||||
"dep :: import \"../dependency\"\nmain proc() int { return dep.answer() }\n",
|
"dep :: import \"../dependency\"\nmain proc() int { return dep.answer() }\n",
|
||||||
"dep :: import \"../dependency\"\nmain proc() int { return dep.sibling() }\n",
|
"dep :: import \"../dependency\"\nmain proc() int { return dep.sibling() }\n",
|
||||||
"dep :: import \"../dependency\"\nmain proc() int { return dep.leak() }\n",
|
"dep :: import \"../dependency\"\nmain proc() int { return dep.leak() }\n",
|
||||||
"dep :: import \"../dependency\"\nmain proc() int { return dep.helper() }\n",
|
"dep :: import \"../dependency\"\nmain proc() int { return dep.helper() }\n",
|
||||||
|
"dep :: import \"../dependency\"\nmain proc() int { return dep.package_helper() }\n",
|
||||||
|
"dep :: import \"../dependency\"\nmain proc() int { return dep.package_value }\n",
|
||||||
|
"dep :: import \"../dependency\"\nmain proc() int { return imported_namespace.package_helper() }\nimported_namespace :: alias dep\n",
|
||||||
]
|
]
|
||||||
for callers |caller, index| {
|
for callers |caller, index| {
|
||||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
@@ -785,6 +788,8 @@ file_local_declarations_are_isolated_from_siblings_and_import_selection test {
|
|||||||
`@hide:file
|
`@hide:file
|
||||||
`helper proc() int { return 42 }
|
`helper proc() int { return 42 }
|
||||||
`@hide:file secret :: 7
|
`@hide:file secret :: 7
|
||||||
|
`@hide package_value :: 40
|
||||||
|
`@hide:package package_helper proc() int { return package_value + 2 }
|
||||||
`answer proc() int { return helper() }
|
`answer proc() int { return helper() }
|
||||||
,
|
,
|
||||||
)
|
)
|
||||||
@@ -792,7 +797,7 @@ file_local_declarations_are_isolated_from_siblings_and_import_selection test {
|
|||||||
second_bytes := try owned_text(
|
second_bytes := try owned_text(
|
||||||
`@hide:file
|
`@hide:file
|
||||||
`helper proc() int { return 99 }
|
`helper proc() int { return 99 }
|
||||||
`sibling proc() int { return helper() }
|
`sibling proc() int { return helper() + package_helper() - 42 }
|
||||||
`leak proc() int { return secret }
|
`leak proc() int { return secret }
|
||||||
,
|
,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ Code :: enum {
|
|||||||
invalid_character
|
invalid_character
|
||||||
float_must_end_with_digit
|
float_must_end_with_digit
|
||||||
unterminated_string
|
unterminated_string
|
||||||
|
unterminated_character
|
||||||
|
invalid_character_literal
|
||||||
unexpected_token
|
unexpected_token
|
||||||
duplicate_name
|
duplicate_name
|
||||||
unknown_name
|
unknown_name
|
||||||
@@ -194,6 +196,8 @@ messages :: $enummap.init(Code, []u8, {
|
|||||||
invalid_character = "invalid character",
|
invalid_character = "invalid character",
|
||||||
float_must_end_with_digit = "float literal must end with a digit",
|
float_must_end_with_digit = "float literal must end with a digit",
|
||||||
unterminated_string = "unterminated string literal",
|
unterminated_string = "unterminated string literal",
|
||||||
|
unterminated_character = "unterminated character literal",
|
||||||
|
invalid_character_literal = "character literal must contain one Unicode code point",
|
||||||
unexpected_token = "unexpected token",
|
unexpected_token = "unexpected token",
|
||||||
duplicate_name = "duplicate name or forbidden shadowing",
|
duplicate_name = "duplicate name or forbidden shadowing",
|
||||||
unknown_name = "unknown name",
|
unknown_name = "unknown name",
|
||||||
|
|||||||
@@ -693,18 +693,21 @@ execute proc(
|
|||||||
return try runtime_trap(diagnostic_store, .invalid_shift_count, instruction.span)
|
return try runtime_trap(diagnostic_store, .invalid_shift_count, instruction.span)
|
||||||
value = shift_integer(instruction.op, left, count)
|
value = shift_integer(instruction.op, left, count)
|
||||||
}
|
}
|
||||||
.add, .subtract, .multiply, .divide: {
|
.add, .subtract, .multiply, .divide, .remainder: {
|
||||||
left :: frames.items[frame_index].values[usize(instruction.left)]
|
left :: frames.items[frame_index].values[usize(instruction.left)]
|
||||||
right :: frames.items[frame_index].values[usize(instruction.right)]
|
right :: frames.items[frame_index].values[usize(instruction.right)]
|
||||||
if (instruction.op == .divide and instruction.type != .float and right.data.unsigned == 0)
|
if ((instruction.op == .divide or instruction.op == .remainder) and instruction.type != .float and right.data.unsigned == 0)
|
||||||
return try runtime_trap(diagnostic_store, .division_by_zero, instruction.span)
|
return try runtime_trap(diagnostic_store, .division_by_zero, instruction.span)
|
||||||
if instruction.type == .float {
|
if instruction.type == .float {
|
||||||
|
if (instruction.op == .remainder and right.data.real == 0)
|
||||||
|
return try runtime_trap(diagnostic_store, .division_by_zero, instruction.span)
|
||||||
result f64 := 0
|
result f64 := 0
|
||||||
match instruction.op {
|
match instruction.op {
|
||||||
.add: result = left.data.real + right.data.real
|
.add: result = left.data.real + right.data.real
|
||||||
.subtract: result = left.data.real - right.data.real
|
.subtract: result = left.data.real - right.data.real
|
||||||
.multiply: result = left.data.real * right.data.real
|
.multiply: result = left.data.real * right.data.real
|
||||||
.divide: result = left.data.real / right.data.real
|
.divide: result = left.data.real / right.data.real
|
||||||
|
.remainder: result = rem!(left.data.real, right.data.real)
|
||||||
else: unreachable
|
else: unreachable
|
||||||
}
|
}
|
||||||
value = ir.Value{ type = .float, data = ir.ValueData{ real = result } }
|
value = ir.Value{ type = .float, data = ir.ValueData{ real = result } }
|
||||||
@@ -1076,6 +1079,7 @@ checked_integer proc(op ir.Op, left i64, right i64) ?i64 {
|
|||||||
if (left == minimum and right == -1) return null
|
if (left == minimum and right == -1) return null
|
||||||
return divtrunc!(left, right)
|
return divtrunc!(left, right)
|
||||||
}
|
}
|
||||||
|
.remainder: return rem!(left, right)
|
||||||
else: unreachable
|
else: unreachable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1117,6 +1121,7 @@ checked_unsigned proc(op ir.Op, left u64, right u64) ?u64 {
|
|||||||
return left * right
|
return left * right
|
||||||
}
|
}
|
||||||
.divide: return divtrunc!(left, right)
|
.divide: return divtrunc!(left, right)
|
||||||
|
.remainder: return rem!(left, right)
|
||||||
else: unreachable
|
else: unreachable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -311,6 +311,36 @@ integer_arithmetic_checks_boundaries_before_host_operations test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
integer_remainder_preserves_dividend_sign_and_minimum_without_overflow test {
|
||||||
|
cases [6]IntegerCase :: [
|
||||||
|
IntegerCase{ op = .remainder, left = -5, right = 3, expected = -2 },
|
||||||
|
IntegerCase{ op = .remainder, left = 5, right = -3, expected = 2 },
|
||||||
|
IntegerCase{ op = .remainder, left = minval!(i64), right = -1, expected = 0 },
|
||||||
|
IntegerCase{ op = .remainder, left = minval!(i64), right = 10, expected = -8 },
|
||||||
|
IntegerCase{ op = .remainder, left = 0, right = -1, expected = 0 },
|
||||||
|
IntegerCase{ op = .remainder, left = 1, right = 0, expected = null },
|
||||||
|
]
|
||||||
|
for (cases) |case| {
|
||||||
|
module := ir.init(mem.c_allocator)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&store)
|
||||||
|
span :: diagnostics.Span{ start = 8, end = 16 }
|
||||||
|
main :: try add_function(&module, &[], 0, .int)
|
||||||
|
module.entry = main
|
||||||
|
try emit(&module, main, integer(0, case.left))
|
||||||
|
try emit(&module, main, integer(1, case.right))
|
||||||
|
try emit(&module, main, ir.Instruction{ op = .remainder, type = .int, destination = ir.ValueId(2), left = ir.ValueId(0), right = ir.ValueId(1), span = span })
|
||||||
|
return_value(&module, main, ir.ValueId(2))
|
||||||
|
outcome := try run(mem.c_allocator, &module, &store)
|
||||||
|
defer deinit(&outcome)
|
||||||
|
if case.expected |expected| {
|
||||||
|
try testing.expect(outcome.trap == null)
|
||||||
|
try testing.expect_equal(expected, outcome.value.data.integer)
|
||||||
|
} else try expect_trap(outcome, &store, .division_by_zero, span)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
integer_negation_traps_only_at_minimum test {
|
integer_negation_traps_only_at_minimum test {
|
||||||
module := ir.init(mem.c_allocator)
|
module := ir.init(mem.c_allocator)
|
||||||
defer ir.deinit(&module)
|
defer ir.deinit(&module)
|
||||||
@@ -1230,7 +1260,7 @@ UnsignedCase :: struct {
|
|||||||
unsigned_arithmetic_and_comparisons_preserve_all_sixty_four_bits test {
|
unsigned_arithmetic_and_comparisons_preserve_all_sixty_four_bits test {
|
||||||
wide_sum u64 :: 9223372036854775815
|
wide_sum u64 :: 9223372036854775815
|
||||||
almost_maximum u64 :: 18446744073709551614
|
almost_maximum u64 :: 18446744073709551614
|
||||||
cases [10]UnsignedCase :: [
|
cases [11]UnsignedCase :: [
|
||||||
UnsignedCase{ op = .add, left = maxval!(u64), right = 0, expected = maxval!(u64) },
|
UnsignedCase{ op = .add, left = maxval!(u64), right = 0, expected = maxval!(u64) },
|
||||||
UnsignedCase{ op = .add, left = maxval!(u64), right = 1, expected = null },
|
UnsignedCase{ op = .add, left = maxval!(u64), right = 1, expected = null },
|
||||||
UnsignedCase{ op = .add, left = 9223372036854775808, right = 7, expected = wide_sum },
|
UnsignedCase{ op = .add, left = 9223372036854775808, right = 7, expected = wide_sum },
|
||||||
@@ -1241,6 +1271,7 @@ unsigned_arithmetic_and_comparisons_preserve_all_sixty_four_bits test {
|
|||||||
UnsignedCase{ op = .multiply, left = maxval!(u64), right = 0, expected = u64(0) },
|
UnsignedCase{ op = .multiply, left = maxval!(u64), right = 0, expected = u64(0) },
|
||||||
UnsignedCase{ op = .multiply, left = 0, right = maxval!(u64), expected = u64(0) },
|
UnsignedCase{ op = .multiply, left = 0, right = maxval!(u64), expected = u64(0) },
|
||||||
UnsignedCase{ op = .multiply, left = 3, right = 14, expected = u64(42) },
|
UnsignedCase{ op = .multiply, left = 3, right = 14, expected = u64(42) },
|
||||||
|
UnsignedCase{ op = .remainder, left = maxval!(u64), right = 10, expected = u64(5) },
|
||||||
]
|
]
|
||||||
for (cases) |case| {
|
for (cases) |case| {
|
||||||
module := ir.init(mem.c_allocator)
|
module := ir.init(mem.c_allocator)
|
||||||
|
|||||||
+4
-1
@@ -162,7 +162,7 @@ Op :: enum {
|
|||||||
bit_not, bit_and, bit_or, bit_xor
|
bit_not, bit_and, bit_or, bit_xor
|
||||||
shift_left, shift_right, shift_left_saturating
|
shift_left, shift_right, shift_left_saturating
|
||||||
add, subtract
|
add, subtract
|
||||||
multiply, divide
|
multiply, divide, remainder
|
||||||
call
|
call
|
||||||
equal, not_equal
|
equal, not_equal
|
||||||
less, less_equal
|
less, less_equal
|
||||||
@@ -184,6 +184,8 @@ Instruction :: struct {
|
|||||||
constant Value = Value{}
|
constant Value = Value{}
|
||||||
arguments_start usize = 0
|
arguments_start usize = 0
|
||||||
arguments_count usize = 0
|
arguments_count usize = 0
|
||||||
|
variadic bool = false
|
||||||
|
fixed_arguments usize = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
TerminatorKind :: enum {
|
TerminatorKind :: enum {
|
||||||
@@ -215,6 +217,7 @@ Function :: struct {
|
|||||||
parameters arraylist.ArrayList(Type)
|
parameters arraylist.ArrayList(Type)
|
||||||
# owned by the module allocator; empty for interpreted/native-defined functions.
|
# owned by the module allocator; empty for interpreted/native-defined functions.
|
||||||
external_name []u8 = ""
|
external_name []u8 = ""
|
||||||
|
variadic bool = false
|
||||||
locals arraylist.ArrayList(Type)
|
locals arraylist.ArrayList(Type)
|
||||||
result Type = .void
|
result Type = .void
|
||||||
result_array_type ArrayId = no_array
|
result_array_type ArrayId = no_array
|
||||||
|
|||||||
+15
-11
@@ -16,6 +16,7 @@ ScanError :: struct {
|
|||||||
ErrorCode :: enum {
|
ErrorCode :: enum {
|
||||||
float_must_end_with_digit,
|
float_must_end_with_digit,
|
||||||
unterminated_string,
|
unterminated_string,
|
||||||
|
unterminated_character,
|
||||||
}
|
}
|
||||||
SourceError :: enum { source_too_large }
|
SourceError :: enum { source_too_large }
|
||||||
|
|
||||||
@@ -153,9 +154,9 @@ scan proc(
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if char == '"' {
|
if (char == '"' or char == '\'') {
|
||||||
start :: cursor
|
start :: cursor
|
||||||
result :: scan_string(start, program) catch |err| {
|
result :: scan_quoted(start, program, char) catch |err| {
|
||||||
id :: try diagnostics.add(
|
id :: try diagnostics.add(
|
||||||
diagnostic_store,
|
diagnostic_store,
|
||||||
diagnostic_code(err.code),
|
diagnostic_code(err.code),
|
||||||
@@ -169,9 +170,10 @@ scan proc(
|
|||||||
cursor = err.end
|
cursor = err.end
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
kind :: if (char == '\'') TokenKind.character else .string
|
||||||
try add_token(state, Token{
|
try add_token(state, Token{
|
||||||
span = make_span(source, start, result.end),
|
span = make_span(source, start, result.end),
|
||||||
kind = .string,
|
kind = kind,
|
||||||
})
|
})
|
||||||
cursor = result.end
|
cursor = result.end
|
||||||
continue
|
continue
|
||||||
@@ -440,19 +442,20 @@ scan_number proc(start usize, program []u8, allow_decimal bool) ScanNumResult !
|
|||||||
ScanStrResult :: struct { end usize }
|
ScanStrResult :: struct { end usize }
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
scan_string proc(start usize, program []u8) ScanStrResult ! ScanError {
|
scan_quoted proc(start usize, program []u8, quote u8) ScanStrResult ! ScanError {
|
||||||
cursor := start + 1 # skip first `"`
|
cursor := start + 1
|
||||||
while cursor < program.len and program[cursor] != '"' and program[cursor] != '\n' : cursor += 1 {
|
while (cursor < program.len and program[cursor] != quote and program[cursor] != '\n') : cursor += 1 {
|
||||||
# escaped quotes do not terminate the string.
|
# an escaped delimiter is content, but a newline still ends an invalid literal.
|
||||||
if (program[cursor] == '\\' and cursor + 1 < program.len) cursor += 1
|
if (program[cursor] == '\\' and cursor + 1 < program.len and program[cursor + 1] != '\n') cursor += 1
|
||||||
}
|
}
|
||||||
if (cursor >= program.len or program[cursor] != '"') {
|
if (cursor >= program.len or program[cursor] != quote) {
|
||||||
|
code :: if (quote == '\'') ErrorCode.unterminated_character else .unterminated_string
|
||||||
return ScanError{
|
return ScanError{
|
||||||
code = .unterminated_string,
|
code = code,
|
||||||
end = cursor,
|
end = cursor,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ScanStrResult{ end = cursor + 1 } # skip last `"`
|
return ScanStrResult{ end = cursor + 1 }
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
@@ -480,6 +483,7 @@ is_digit proc(char u8) bool {
|
|||||||
diagnostic_codes :: $enummap.init(ErrorCode, diagnostics.Code, {
|
diagnostic_codes :: $enummap.init(ErrorCode, diagnostics.Code, {
|
||||||
float_must_end_with_digit = .float_must_end_with_digit,
|
float_must_end_with_digit = .float_must_end_with_digit,
|
||||||
unterminated_string = .unterminated_string,
|
unterminated_string = .unterminated_string,
|
||||||
|
unterminated_character = .unterminated_character,
|
||||||
})
|
})
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
|
|||||||
@@ -3,6 +3,32 @@ import "@std/testing"
|
|||||||
import "@source/diagnostics"
|
import "@source/diagnostics"
|
||||||
import "@source/strpool"
|
import "@source/strpool"
|
||||||
|
|
||||||
|
scans_character_literals_and_preserves_unterminated_line_provenance test {
|
||||||
|
pool := strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&pool)
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
state := init(mem.c_allocator)
|
||||||
|
defer deinit(&state)
|
||||||
|
source :: "'a' '\\n' '\\'' '\\\\' 'é' '\\\nnext :: 1"
|
||||||
|
state.source_id = diagnostics.SourceId(7)
|
||||||
|
try scan(&state, source, &pool, &diagnostic_store)
|
||||||
|
expected [11]TokenKind :: [
|
||||||
|
.character, .character, .character, .character, .character,
|
||||||
|
.invalid, .newline, .ident, .double_colon, .int, .eof,
|
||||||
|
]
|
||||||
|
try testing.expect_equal(expected.len, state.tokens.items.len)
|
||||||
|
for (expected) |kind, index| try testing.expect_equal(kind, state.tokens.items[index].kind)
|
||||||
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
||||||
|
invalid :: state.tokens.items[5]
|
||||||
|
diagnostic :: diagnostics.get(&diagnostic_store, invalid.data.diagnostic)?
|
||||||
|
try testing.expect_equal(diagnostics.Code.unterminated_character, diagnostic.code)
|
||||||
|
try testing.expect_equal(diagnostics.SourceId(7), diagnostic.span.source)
|
||||||
|
try testing.expect_equal(invalid.span.start, diagnostic.span.start)
|
||||||
|
try testing.expect_equal(invalid.span.end, diagnostic.span.end)
|
||||||
|
try testing.expect_equal(invalid.span.end, state.tokens.items[6].span.start)
|
||||||
|
}
|
||||||
|
|
||||||
reports_invalid_input_and_keeps_scanning test {
|
reports_invalid_input_and_keeps_scanning test {
|
||||||
pool := strpool.init(mem.c_allocator)
|
pool := strpool.init(mem.c_allocator)
|
||||||
defer strpool.deinit(&pool)
|
defer strpool.deinit(&pool)
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ TokenKind :: enum {
|
|||||||
undefined
|
undefined
|
||||||
|
|
||||||
# literals
|
# literals
|
||||||
ident, string
|
ident, string, character
|
||||||
int, float
|
int, float
|
||||||
|
|
||||||
# comparison
|
# comparison
|
||||||
@@ -92,7 +92,7 @@ TokenKind :: enum {
|
|||||||
|
|
||||||
render_token proc(token Token, program []u8) void {
|
render_token proc(token Token, program []u8) void {
|
||||||
match token.kind {
|
match token.kind {
|
||||||
.ident, .string, .int, .float:
|
.ident, .string, .character, .int, .float:
|
||||||
debug.print("{}({})\n", {token.kind, program[token.span.start..token.span.end]})
|
debug.print("{}({})\n", {token.kind, program[token.span.start..token.span.end]})
|
||||||
else:
|
else:
|
||||||
debug.print("{}\n", {token.kind})
|
debug.print("{}\n", {token.kind})
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ float_opcodes :: $enummap.init_default(ir.Op, []u8, "", {
|
|||||||
subtract = "fsub",
|
subtract = "fsub",
|
||||||
multiply = "fmul",
|
multiply = "fmul",
|
||||||
divide = "fdiv",
|
divide = "fdiv",
|
||||||
|
remainder = "frem",
|
||||||
})
|
})
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
@@ -329,15 +330,50 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
|
|||||||
for (0..instruction.arguments_count) |index| {
|
for (0..instruction.arguments_count) |index| {
|
||||||
_ = try load(state, block.arguments.items[instruction.arguments_start + index])
|
_ = try load(state, block.arguments.items[instruction.arguments_start + index])
|
||||||
}
|
}
|
||||||
|
# C default promotions apply only after the fixed prefix.
|
||||||
|
first_promotion :: state.next_register
|
||||||
|
if (instruction.variadic) {
|
||||||
|
for (instruction.fixed_arguments..instruction.arguments_count) |index| {
|
||||||
|
operand :: block.arguments.items[instruction.arguments_start + index]
|
||||||
|
kind :: state.values[usize(operand)].kind
|
||||||
|
if (variadic_type(kind) == kind) continue
|
||||||
|
promoted :: fresh(state)
|
||||||
|
opcode []u8 :: if (kind == .bool or ir.unsigned_integer(kind)) "zext" else "sext"
|
||||||
|
try io.print(" %r{} = {s} ", output, {promoted, opcode})
|
||||||
|
try write_type(output, state.module, state.values[usize(operand)])
|
||||||
|
try io.print(" %r{} to i32\n", output, {first_argument + index})
|
||||||
|
}
|
||||||
|
}
|
||||||
if (instruction.type != .void) try io.print(" %r{} = call ", output, {result})
|
if (instruction.type != .void) try io.print(" %r{} = call ", output, {result})
|
||||||
else try io.write_all(output, " call ")
|
else try io.write_all(output, " call ")
|
||||||
try write_abi_result(output, state.module, type)
|
try write_abi_result(output, state.module, type)
|
||||||
|
if (instruction.variadic) {
|
||||||
|
try io.write_all(output, " (")
|
||||||
|
for (0..instruction.fixed_arguments) |index| {
|
||||||
|
if (index != 0) try io.write_all(output, ", ")
|
||||||
|
operand :: block.arguments.items[instruction.arguments_start + index]
|
||||||
|
try write_type(output, state.module, state.values[usize(operand)])
|
||||||
|
}
|
||||||
|
if (instruction.fixed_arguments != 0) try io.write_all(output, ", ")
|
||||||
|
try io.write_all(output, "...)")
|
||||||
|
}
|
||||||
try io.print(" %r{}(", output, {callee})
|
try io.print(" %r{}(", output, {callee})
|
||||||
|
promotion := first_promotion
|
||||||
for (0..instruction.arguments_count) |index| {
|
for (0..instruction.arguments_count) |index| {
|
||||||
if (index != 0) try io.write_all(output, ", ")
|
if (index != 0) try io.write_all(output, ", ")
|
||||||
operand :: block.arguments.items[instruction.arguments_start + index]
|
operand :: block.arguments.items[instruction.arguments_start + index]
|
||||||
try write_abi_parameter(output, state.module, state.values[usize(operand)])
|
argument_type := state.values[usize(operand)]
|
||||||
try io.print(" %r{}", output, {first_argument + index})
|
register := first_argument + index
|
||||||
|
if (instruction.variadic and index >= instruction.fixed_arguments) {
|
||||||
|
kind :: variadic_type(argument_type.kind)
|
||||||
|
if (kind != argument_type.kind) {
|
||||||
|
register = promotion
|
||||||
|
promotion += 1
|
||||||
|
argument_type.kind = kind
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try write_abi_parameter(output, state.module, argument_type)
|
||||||
|
try io.print(" %r{}", output, {register})
|
||||||
}
|
}
|
||||||
try io.write_all(output, ")\n")
|
try io.write_all(output, ")\n")
|
||||||
}
|
}
|
||||||
@@ -372,18 +408,24 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
|
|||||||
try io.print(" %r{} = {s} i{} %r{}, %r{}\n", output, {result, opcode, ir.integer_bits(instruction.type), left, right})
|
try io.print(" %r{} = {s} i{} %r{}, %r{}\n", output, {result, opcode, ir.integer_bits(instruction.type), left, right})
|
||||||
}
|
}
|
||||||
.shift_left, .shift_right, .shift_left_saturating: result = try emit_shift(state, instruction)
|
.shift_left, .shift_right, .shift_left_saturating: result = try emit_shift(state, instruction)
|
||||||
.add, .subtract, .multiply, .divide: {
|
.add, .subtract, .multiply, .divide, .remainder: {
|
||||||
left :: try load(state, instruction.left)
|
left :: try load(state, instruction.left)
|
||||||
right :: try load(state, instruction.right)
|
right :: try load(state, instruction.right)
|
||||||
if (instruction.type == .float) {
|
if (instruction.type == .float) {
|
||||||
|
if (instruction.op == .remainder) {
|
||||||
|
zero :: fresh(state)
|
||||||
|
try io.print(" %r{} = fcmp oeq double %r{}, 0.0\n", output, {zero, right})
|
||||||
|
try check_failure(state, zero, .division_by_zero, instruction.span)
|
||||||
|
}
|
||||||
opcode :: enummap.get(&float_opcodes, instruction.op)?
|
opcode :: enummap.get(&float_opcodes, instruction.op)?
|
||||||
try io.print(" %r{} = {s} double %r{}, %r{}\n", output, {result, opcode, left, right})
|
try io.print(" %r{} = {s} double %r{}, %r{}\n", output, {result, opcode, left, right})
|
||||||
} else if (instruction.op == .divide) {
|
} else if (instruction.op == .divide or instruction.op == .remainder) {
|
||||||
bits :: ir.integer_bits(instruction.type)
|
bits :: ir.integer_bits(instruction.type)
|
||||||
zero :: fresh(state)
|
zero :: fresh(state)
|
||||||
try io.print(" %r{} = icmp eq i{} %r{}, 0\n", output, {zero, bits, right})
|
try io.print(" %r{} = icmp eq i{} %r{}, 0\n", output, {zero, bits, right})
|
||||||
try check_failure(state, zero, .division_by_zero, instruction.span)
|
try check_failure(state, zero, .division_by_zero, instruction.span)
|
||||||
if (!ir.unsigned_integer(instruction.type)) {
|
divisor := right
|
||||||
|
if (!ir.unsigned_integer(instruction.type) and instruction.op == .divide) {
|
||||||
minimum :: fresh(state)
|
minimum :: fresh(state)
|
||||||
negative_one :: fresh(state)
|
negative_one :: fresh(state)
|
||||||
overflow :: fresh(state)
|
overflow :: fresh(state)
|
||||||
@@ -392,8 +434,17 @@ emit_instruction proc(state @mut State, block @ir.Block, instruction ir.Instruct
|
|||||||
try io.print(" %r{} = and i1 %r{}, %r{}\n", output, {overflow, minimum, negative_one})
|
try io.print(" %r{} = and i1 %r{}, %r{}\n", output, {overflow, minimum, negative_one})
|
||||||
try check_failure(state, overflow, .integer_overflow, instruction.span)
|
try check_failure(state, overflow, .integer_overflow, instruction.span)
|
||||||
}
|
}
|
||||||
opcode []u8 :: if (ir.unsigned_integer(instruction.type)) "udiv" else "sdiv"
|
if (!ir.unsigned_integer(instruction.type) and instruction.op == .remainder) {
|
||||||
try io.print(" %r{} = {s} i{} %r{}, %r{}\n", output, {result, opcode, bits, left, right})
|
negative_one :: fresh(state)
|
||||||
|
divisor = fresh(state)
|
||||||
|
try io.print(" %r{} = icmp eq i{} %r{}, -1\n", output, {negative_one, bits, right})
|
||||||
|
# LLVM srem is poison for min/-1; every signed value rem -1 is zero.
|
||||||
|
try io.print(" %r{} = select i1 %r{}, i{} 1, i{} %r{}\n", output, {divisor, negative_one, bits, bits, right})
|
||||||
|
}
|
||||||
|
opcode []u8 := if (ir.unsigned_integer(instruction.type)) "udiv" else "sdiv"
|
||||||
|
if (instruction.op == .remainder)
|
||||||
|
opcode = if (ir.unsigned_integer(instruction.type)) "urem" else "srem"
|
||||||
|
try io.print(" %r{} = {s} i{} %r{}, %r{}\n", output, {result, opcode, bits, left, divisor})
|
||||||
} else {
|
} else {
|
||||||
opcode :: enummap.get(&integer_opcodes, instruction.op)?
|
opcode :: enummap.get(&integer_opcodes, instruction.op)?
|
||||||
bits :: ir.integer_bits(instruction.type)
|
bits :: ir.integer_bits(instruction.type)
|
||||||
@@ -700,3 +751,11 @@ emit_conversion proc(state @mut State, instruction ir.Instruction) usize ! io.Wr
|
|||||||
try io.print(" %r{} = {s} i{} %r{} to i{}\n", output, {result, opcode, source_bits, operand, destination_bits})
|
try io.print(" %r{} = {s} i{} %r{} to i{}\n", output, {result, opcode, source_bits, operand, destination_bits})
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
variadic_type proc(type ir.Type) ir.Type {
|
||||||
|
match type {
|
||||||
|
.bool, .i8, .u8, .i16, .u16: return .i32
|
||||||
|
else: return type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -392,7 +392,7 @@ emit_array_printer proc(output io.Writer, module @ir.Module, id ir.ArrayId) void
|
|||||||
try write_type(output, module, type)
|
try write_type(output, module, type)
|
||||||
try io.write_all(output, "\n store ")
|
try io.write_all(output, "\n store ")
|
||||||
try write_type(output, module, type)
|
try write_type(output, module, type)
|
||||||
try io.write_all(output, " %value, ptr %storage\n call void @out(i32 1, ptr @open, i64 1)\n br label %loop\nloop:\n %index = phi i64 [ 0, %entry ], [ %next, %element ]\n")
|
try io.write_all(output, " %value, ptr %storage\n call void @out(i32 1, ptr @honey.array_open, i64 1)\n br label %loop\nloop:\n %index = phi i64 [ 0, %entry ], [ %next, %element ]\n")
|
||||||
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)
|
||||||
@@ -402,7 +402,7 @@ emit_array_printer proc(output io.Writer, module @ir.Module, id ir.ArrayId) void
|
|||||||
# 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, 0)
|
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 @honey.array_close, i64 1)\n ret void\n}\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
@@ -614,6 +614,10 @@ emit_function proc(state @mut State, function @ir.Function, id usize) void ! io.
|
|||||||
try write_abi_parameter(output, state.module, local_type(function, index))
|
try write_abi_parameter(output, state.module, local_type(function, index))
|
||||||
try io.print(" %p{}", output, {index})
|
try io.print(" %p{}", output, {index})
|
||||||
}
|
}
|
||||||
|
if (function.variadic) {
|
||||||
|
if (function.parameters.items.len != 0) try io.write_all(output, ", ")
|
||||||
|
try io.write_all(output, "...")
|
||||||
|
}
|
||||||
if (function.external_name.len != 0) {
|
if (function.external_name.len != 0) {
|
||||||
try io.write_all(output, ")\n")
|
try io.write_all(output, ")\n")
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -43,13 +43,13 @@ runtime ::
|
|||||||
`@error = private constant [9 x i8] c": error: "
|
`@error = private constant [9 x i8] c": error: "
|
||||||
`@runtime_trap = private constant [16 x i8] c": runtime trap: "
|
`@runtime_trap = private constant [16 x i8] c": runtime trap: "
|
||||||
`@newline = private constant [1 x i8] c"\0A"
|
`@newline = private constant [1 x i8] c"\0A"
|
||||||
`@open = private constant [1 x i8] c"["
|
`@honey.array_open = private constant [1 x i8] c"["
|
||||||
`@close = private constant [1 x i8] c"]"
|
`@honey.array_close = private constant [1 x i8] c"]"
|
||||||
`@record_open = private constant [1 x i8] c"{"
|
`@record_open = private constant [1 x i8] c"{"
|
||||||
`@record_close = 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"
|
`@honey.uninitialized_read = private constant [42 x i8] c"local value was read before initialization"
|
||||||
`@assign = private constant [46 x i8] c"local value was assigned before initialization"
|
`@assign = private constant [46 x i8] c"local value was assigned before initialization"
|
||||||
`@cycle = private constant [27 x i8] c"global initialization cycle"
|
`@cycle = private constant [27 x i8] c"global initialization cycle"
|
||||||
`@overflow = private constant [27 x i8] c"integer arithmetic overflow"
|
`@overflow = private constant [27 x i8] c"integer arithmetic overflow"
|
||||||
@@ -154,7 +154,7 @@ runtime ::
|
|||||||
` %ready = load i1, ptr %flag
|
` %ready = load i1, ptr %flag
|
||||||
` br i1 %ready, label %end, label %fail
|
` br i1 %ready, label %end, label %fail
|
||||||
`fail:
|
`fail:
|
||||||
` %message = select i1 %read, ptr @read, ptr @assign
|
` %message = select i1 %read, ptr @honey.uninitialized_read, ptr @assign
|
||||||
` %length = select i1 %read, i64 42, i64 46
|
` %length = select i1 %read, i64 42, i64 46
|
||||||
` call void @trap(i64 -1, i64 %source, i64 %line, i64 %column, ptr %message, i64 %length)
|
` call void @trap(i64 -1, i64 %source, i64 %line, i64 %column, ptr %message, i64 %length)
|
||||||
` unreachable
|
` unreachable
|
||||||
|
|||||||
+24
-8
@@ -521,6 +521,7 @@ lower_function proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | di
|
|||||||
owned :: try mem.alloc(u8, state.module.allocator, name.len)
|
owned :: try mem.alloc(u8, state.module.allocator, name.len)
|
||||||
memcopy!(owned, name)
|
memcopy!(owned, name)
|
||||||
state.module.functions.items[state.function].external_name = owned
|
state.module.functions.items[state.function].external_name = owned
|
||||||
|
state.module.functions.items[state.function].variadic = ast.callable_variadic(tree, id)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try predeclare_locals(state, declaration.body)
|
try predeclare_locals(state, declaration.body)
|
||||||
@@ -1657,9 +1658,11 @@ convert_value proc(state @mut State, value ir.ValueId, actual, expected typechec
|
|||||||
if (actual.kind == .optional and expected.kind == .optional and actual.array_type != expected.array_type) {
|
if (actual.kind == .optional and expected.kind == .optional and actual.array_type != expected.array_type) {
|
||||||
source :: typecheck.array_type(state.checked, actual.array_type).element
|
source :: typecheck.array_type(state.checked, actual.array_type).element
|
||||||
child :: typecheck.array_type(state.checked, expected.array_type).element
|
child :: typecheck.array_type(state.checked, expected.array_type).element
|
||||||
if (source.kind == .pointer or source.kind == .function) return value
|
nullable :: child.kind == .pointer or child.kind == .function
|
||||||
|
if ((source.kind == .pointer or source.kind == .function) and nullable) return value
|
||||||
|
runtime :: lower_type(state, expected)
|
||||||
shape :: try array_shape(state, expected)
|
shape :: try array_shape(state, expected)
|
||||||
slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), .record, shape))
|
slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), runtime, shape))
|
||||||
present :: try optional_present(state, value, actual, span)
|
present :: try optional_present(state, value, actual, span)
|
||||||
some :: try add_block(state)
|
some :: try add_block(state)
|
||||||
none :: try add_block(state)
|
none :: try add_block(state)
|
||||||
@@ -1670,25 +1673,33 @@ convert_value proc(state @mut State, value ir.ValueId, actual, expected typechec
|
|||||||
state.block = some
|
state.block = some
|
||||||
payload :: try optional_payload(state, value, actual, span)
|
payload :: try optional_payload(state, value, actual, span)
|
||||||
converted :: try convert_value(state, payload, source, child, span)
|
converted :: try convert_value(state, payload, source, child, span)
|
||||||
wrapped :: try record_pair(state, shape, present, converted, span)
|
wrapped := converted
|
||||||
|
if (!nullable) wrapped = try record_pair(state, shape, present, converted, span)
|
||||||
_ = try emit(state, ir.Instruction{ op = .store_local, left = wrapped, slot = slot, span = span })
|
_ = try emit(state, ir.Instruction{ op = .store_local, left = wrapped, slot = slot, span = span })
|
||||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||||
kind = .jump, target = join, span = span,
|
kind = .jump, target = join, span = span,
|
||||||
}
|
}
|
||||||
state.block = none
|
state.block = none
|
||||||
absent :: try zero_value(state, .record, shape, span)
|
absent :: try zero_value(state, runtime, shape, span)
|
||||||
_ = try emit(state, ir.Instruction{ op = .store_local, left = absent, slot = slot, span = span })
|
_ = try emit(state, ir.Instruction{ op = .store_local, left = absent, slot = slot, span = span })
|
||||||
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
state.module.functions.items[state.function].blocks.items[usize(state.block)].terminator = ir.Terminator{
|
||||||
kind = .jump, target = join, span = span,
|
kind = .jump, target = join, span = span,
|
||||||
}
|
}
|
||||||
state.block = join
|
state.block = join
|
||||||
return try emit(state, ir.Instruction{ op = .load_local, type = .record, array_type = shape, slot = slot, span = span })
|
return try emit(state, ir.Instruction{ op = .load_local, type = runtime, array_type = shape, slot = slot, span = span })
|
||||||
}
|
}
|
||||||
|
if (expected.kind == .pointer and actual.kind == .string)
|
||||||
|
return try emit(state, ir.Instruction{ op = .string_pointer, type = .pointer, left = value, span = span })
|
||||||
if (expected.kind == .slice and actual.kind == .string) {
|
if (expected.kind == .slice and actual.kind == .string) {
|
||||||
pointer :: try emit(state, ir.Instruction{ op = .string_pointer, type = .pointer, left = value, span = span })
|
pointer :: try emit(state, ir.Instruction{ op = .string_pointer, type = .pointer, left = value, span = span })
|
||||||
length :: try emit(state, ir.Instruction{ op = .string_length, type = .usize, left = value, span = span })
|
length :: try emit(state, ir.Instruction{ op = .string_length, type = .usize, left = value, span = span })
|
||||||
return try record_pair(state, try array_shape(state, expected), pointer, length, span)
|
return try record_pair(state, try array_shape(state, expected), pointer, length, span)
|
||||||
}
|
}
|
||||||
|
if (expected.kind == .slice and actual.kind == .pointer) {
|
||||||
|
pointer :: typecheck.pointer_type(state.checked, actual.pointer_type)
|
||||||
|
length :: try usize_constant(state, typecheck.array_type(state.checked, pointer.element.array_type).len, span)
|
||||||
|
return try record_pair(state, try array_shape(state, expected), value, length, span)
|
||||||
|
}
|
||||||
if (expected.kind == .slice and actual.kind == .array) {
|
if (expected.kind == .slice and actual.kind == .array) {
|
||||||
source_shape :: try array_shape(state, actual)
|
source_shape :: try array_shape(state, actual)
|
||||||
slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), .array, source_shape))
|
slot :: u32(try ir.add_local(state.module, ir.FunctionId(state.function), .array, source_shape))
|
||||||
@@ -1975,13 +1986,15 @@ lower_builtin proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocError
|
|||||||
else value.data = ir.ValueData{ integer = enummap.get(&ir.integer_minima, type)? }
|
else value.data = ir.ValueData{ integer = enummap.get(&ir.integer_minima, type)? }
|
||||||
return try constant(state, value, span)
|
return try constant(state, value, span)
|
||||||
}
|
}
|
||||||
if (mem.eql(name, "divtrunc")) {
|
if (mem.eql(name, "divtrunc") or mem.eql(name, "rem")) {
|
||||||
left :: try lower_expression(state, ast.call_arg(tree, id, 0))
|
left :: try lower_expression(state, ast.call_arg(tree, id, 0))
|
||||||
if (state.terminated) return ir.no_value
|
if (state.terminated) return ir.no_value
|
||||||
right :: try lower_expression(state, ast.call_arg(tree, id, 1))
|
right :: try lower_expression(state, ast.call_arg(tree, id, 1))
|
||||||
if (state.terminated) return ir.no_value
|
if (state.terminated) return ir.no_value
|
||||||
|
op ir.Op :: if (mem.eql(name, "rem")) .remainder else .divide
|
||||||
return try emit(state, ir.Instruction{
|
return try emit(state, ir.Instruction{
|
||||||
op = .divide, type = lower_type(state, typecheck.type_of(state.checked, id)),
|
op = op,
|
||||||
|
type = lower_type(state, typecheck.type_of(state.checked, id)),
|
||||||
left = left, right = right, span = span,
|
left = left, right = right, span = span,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -2471,8 +2484,9 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
|||||||
count :: ast.call_arg_count(tree, id)
|
count :: ast.call_arg_count(tree, id)
|
||||||
try arraylist.reserve(&state.arguments, scratch_start + count)
|
try arraylist.reserve(&state.arguments, scratch_start + count)
|
||||||
for (0..count) |_| try arraylist.append(&state.arguments, ir.no_value)
|
for (0..count) |_| try arraylist.append(&state.arguments, ir.no_value)
|
||||||
|
signature :: typecheck.type_of(state.checked, ast.call_expr(tree, id).callee).function
|
||||||
|
fixed_arguments :: if (signature != ast.NodeId(maxval!(u32))) ast.function_param_count(tree, signature) else 0
|
||||||
for (0..count) |i| {
|
for (0..count) |i| {
|
||||||
signature :: typecheck.type_of(state.checked, ast.call_expr(tree, id).callee).function
|
|
||||||
expected := typecheck.Type{}
|
expected := typecheck.Type{}
|
||||||
if (signature != ast.NodeId(maxval!(u32)) and i < ast.function_param_count(tree, signature)) expected = typecheck.type_of(state.checked, ast.param(tree, ast.function_param(tree, signature, i)).type)
|
if (signature != ast.NodeId(maxval!(u32)) and i < ast.function_param_count(tree, signature)) expected = typecheck.type_of(state.checked, ast.param(tree, ast.function_param(tree, signature, i)).type)
|
||||||
argument :: try lower_as(state, ast.call_arg(tree, id, i), expected)
|
argument :: try lower_as(state, ast.call_arg(tree, id, i), expected)
|
||||||
@@ -2492,6 +2506,8 @@ lower_expression proc(state @mut State, id ast.NodeId) ir.ValueId ! (mem.AllocEr
|
|||||||
left = callee,
|
left = callee,
|
||||||
arguments_start = start,
|
arguments_start = start,
|
||||||
arguments_count = count,
|
arguments_count = count,
|
||||||
|
variadic = signature != ast.NodeId(maxval!(u32)) and ast.callable_variadic(tree, signature),
|
||||||
|
fixed_arguments = fixed_arguments,
|
||||||
span = span,
|
span = span,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2049,13 +2049,10 @@ keeps_generated_foreign_declarations_demand_driven_and_traps_interpreted_calls t
|
|||||||
try expect_trap(foreign_outcome, &diagnostic_store, .unsupported_foreign_call)
|
try expect_trap(foreign_outcome, &diagnostic_store, .unsupported_foreign_call)
|
||||||
}
|
}
|
||||||
|
|
||||||
diagnoses_demanded_unsupported_foreign_signatures_and_attributes test {
|
diagnoses_demanded_opaque_values_and_incompatible_opaque_pointers test {
|
||||||
sources [5][]u8 :: [
|
sources [2][]u8 :: [
|
||||||
"take c_func(_ c_int, ...) c_int\nmain proc() c_int { return take(1) }\n",
|
|
||||||
"Handle :: opaque\ntake c_func(_ Handle) void\nmain proc() void { take(undefined) }\n",
|
"Handle :: opaque\ntake c_func(_ Handle) void\nmain proc() void { take(undefined) }\n",
|
||||||
"A :: opaque\nB :: opaque\nmain proc() void { a ?*A :: null\n b ?*B :: a }\n",
|
"A :: opaque\nB :: opaque\nmain proc() void { a ?*A :: null\n b ?*B :: a }\n",
|
||||||
"@other:file\nhidden :: 1\nmain proc() int { return hidden }\n",
|
|
||||||
"@hide:package\nhidden :: 1\nmain proc() int { return hidden }\n",
|
|
||||||
]
|
]
|
||||||
for sources |source| {
|
for sources |source| {
|
||||||
diagnostic_store := diagnostics.init(mem.c_allocator)
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
@@ -2087,3 +2084,88 @@ rejects_demanded_unavailable_c_floating_types_without_unknown_names test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
preserves_variadic_call_signatures_and_interpreter_foreign_traps test {
|
||||||
|
sources [2][]u8 :: [
|
||||||
|
"take c_func(_ c_int, ...) c_int\nmain proc() c_int { return take(1) }\n",
|
||||||
|
"take c_func(_ c_int, ...) c_int\nmain proc() c_int { return take(1, true, i8(-2), u8(3), i16(-4), u16(5), 6.5) }\n",
|
||||||
|
]
|
||||||
|
for sources |source| {
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
try testing.expect(module.functions.items[0].variadic)
|
||||||
|
try testing.expect_equal(usize(1), module.functions.items[0].parameters.items.len)
|
||||||
|
entry :: &module.functions.items[usize(module.entry?)]
|
||||||
|
found := false
|
||||||
|
for entry.blocks.items |block| {
|
||||||
|
for block.instructions.items |instruction| {
|
||||||
|
if (instruction.op != .call) continue
|
||||||
|
found = true
|
||||||
|
try testing.expect(instruction.variadic)
|
||||||
|
try testing.expect_equal(usize(1), instruction.fixed_arguments)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try testing.expect(found)
|
||||||
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
|
try expect_trap(outcome, &diagnostic_store, .unsupported_foreign_call)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rejects_variadic_arity_fixed_types_and_non_c_trailing_values test {
|
||||||
|
sources [4][]u8 :: [
|
||||||
|
"take c_func(_ c_int, ...) c_int\nmain proc() c_int { return take() }\n",
|
||||||
|
"take c_func(_ c_int, ...) c_int\nmain proc() c_int { return take(true) }\n",
|
||||||
|
"take c_func(_ c_int, ...) c_int\nmain proc() c_int { return take(1, \"text\") }\n",
|
||||||
|
"take c_func(_ c_int, ...) c_int\nmain proc() c_int { values [2]i64 :: [1, 2]\nreturn take(1, values) }\n",
|
||||||
|
]
|
||||||
|
codes [4]diagnostics.Code :: [
|
||||||
|
.argument_count_mismatch, .type_mismatch, .invalid_operand, .invalid_operand,
|
||||||
|
]
|
||||||
|
for sources |source, index| {
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
|
try expect_trap(outcome, &diagnostic_store, codes[index])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
coerces_strings_to_immutable_byte_pointer_arguments test {
|
||||||
|
sources [3][]u8 :: [
|
||||||
|
"take c_func(_ ?*c_char) void\nmain proc() void { take(\"format\") }\n",
|
||||||
|
"take c_func(_ *u8) void\nmain proc() void { take(\"format\") }\n",
|
||||||
|
"take c_func(_ *i8, ...) void\nmain proc() void { take(\"format\", true) }\n",
|
||||||
|
]
|
||||||
|
for sources |source| {
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
|
try expect_trap(outcome, &diagnostic_store, .unsupported_foreign_call)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rejects_string_arguments_for_mutable_or_nonbyte_pointers test {
|
||||||
|
sources [2][]u8 :: [
|
||||||
|
"take c_func(_ ?*mut c_char) void\nmain proc() void { take(\"format\") }\n",
|
||||||
|
"take c_func(_ *u16) void\nmain proc() void { take(\"format\") }\n",
|
||||||
|
]
|
||||||
|
for sources |source| {
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
module := try compile_owned(source, &diagnostic_store)
|
||||||
|
defer ir.deinit(&module)
|
||||||
|
outcome := try interpreter.run(mem.c_allocator, &module, &diagnostic_store)
|
||||||
|
defer interpreter.deinit(&outcome)
|
||||||
|
try expect_trap(outcome, &diagnostic_store, .type_mismatch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+57
-8
@@ -118,19 +118,27 @@ parse_decl proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
if (next_is(state, .at)) {
|
if (next_is(state, .at)) {
|
||||||
start :: try consume(state, .at)
|
start :: try consume(state, .at)
|
||||||
name :: try consume(state, .ident)
|
name :: try consume(state, .ident)
|
||||||
_ = try consume(state, .colon)
|
file_local := false
|
||||||
qualifier :: try consume(state, .ident)
|
valid := mem.eql(state.builder.source[usize(name.span.start)..usize(name.span.end)], "hide")
|
||||||
|
if (!valid) return .unexpected_token
|
||||||
|
end := name.span.end
|
||||||
|
if (next_is(state, .colon)) {
|
||||||
|
state.next_token += 1
|
||||||
|
qualifier :: try consume(state, .ident)
|
||||||
|
scope :: state.builder.source[usize(qualifier.span.start)..usize(qualifier.span.end)]
|
||||||
|
file_local = mem.eql(scope, "file")
|
||||||
|
valid = valid and (file_local or mem.eql(scope, "package"))
|
||||||
|
end = qualifier.span.end
|
||||||
|
}
|
||||||
skip_newlines(state)
|
skip_newlines(state)
|
||||||
declaration :: try parse_decl(state)
|
declaration :: try parse_decl(state)
|
||||||
if (
|
if (!valid) {
|
||||||
!mem.eql(state.builder.source[usize(name.span.start)..usize(name.span.end)], "hide")
|
span :: ast.Span{ start = start.span.start, end = end, source = start.span.source }
|
||||||
or !mem.eql(state.builder.source[usize(qualifier.span.start)..usize(qualifier.span.end)], "file")
|
|
||||||
) {
|
|
||||||
span :: ast.Span{ start = start.span.start, end = qualifier.span.end, source = start.span.source }
|
|
||||||
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, span)
|
diagnostic :: try diagnostics.add(state.diagnostics, .unexpected_token, span)
|
||||||
return try ast.add_invalid_decl(state.builder, span, diagnostic)
|
return try ast.add_invalid_decl(state.builder, span, diagnostic)
|
||||||
}
|
}
|
||||||
ast.set_file_local(state.builder, declaration)
|
if (file_local) ast.set_file_local(state.builder, declaration)
|
||||||
|
else ast.set_package_local(state.builder, declaration)
|
||||||
return declaration
|
return declaration
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
@@ -494,6 +502,7 @@ parse_if_expr proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
keyword :: try consume(state, .if)
|
keyword :: try consume(state, .if)
|
||||||
condition :: try parse_guard(state)
|
condition :: try parse_guard(state)
|
||||||
capture :: try parse_optional_capture(state)
|
capture :: try parse_optional_capture(state)
|
||||||
|
skip_newlines(state)
|
||||||
then_value :: try parse_expr(state)
|
then_value :: try parse_expr(state)
|
||||||
skip_newlines(state)
|
skip_newlines(state)
|
||||||
if (!next_is(state, .else) or next_n_is(state, 1, .colon)) return .unexpected_token
|
if (!next_is(state, .else) or next_n_is(state, 1, .colon)) return .unexpected_token
|
||||||
@@ -1792,6 +1801,39 @@ brace_starts_block proc(state @State) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
decode_character proc(text []u8) ?u64 {
|
||||||
|
if (text.len < 3) return null
|
||||||
|
contents :: text[1..text.len - 1]
|
||||||
|
if (contents.len == 2 and contents[0] == '\\') {
|
||||||
|
return match contents[1] {
|
||||||
|
'0': u64(0)
|
||||||
|
'n': u64('\n')
|
||||||
|
'r': u64('\r')
|
||||||
|
't': u64('\t')
|
||||||
|
'\\': u64('\\')
|
||||||
|
'\'': u64('\'')
|
||||||
|
else: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
first :: contents[0]
|
||||||
|
if (first < 128) return if (contents.len == 1) u64(first) else null
|
||||||
|
width :: if (first >= 194 and first <= 223) usize(2)
|
||||||
|
else if (first >= 224 and first <= 239) usize(3)
|
||||||
|
else if (first >= 240 and first <= 244) usize(4)
|
||||||
|
else usize(0)
|
||||||
|
if (width == 0 or contents.len != width) return null
|
||||||
|
mask :: if (width == 2) u8(31) else if (width == 3) u8(15) else u8(7)
|
||||||
|
value := u64(first & mask)
|
||||||
|
for (contents[1..]) |byte| {
|
||||||
|
if (byte < 128 or byte > 191) return null
|
||||||
|
value = value * 64 + u64(byte & 63)
|
||||||
|
}
|
||||||
|
if ((width == 3 and value < 2048) or (width == 4 and value < 65536)
|
||||||
|
or (value >= 55296 and value <= 57343) or value > 1114111) return null
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
||||||
token :: current(state)
|
token :: current(state)
|
||||||
@@ -1872,6 +1914,13 @@ parse_primary proc(state @mut State) ast.NodeId ! ParseError {
|
|||||||
state.next_token += 1
|
state.next_token += 1
|
||||||
return try ast.add_literal(state.builder, .int, token.span)
|
return try ast.add_literal(state.builder, .int, token.span)
|
||||||
}
|
}
|
||||||
|
.character: {
|
||||||
|
state.next_token += 1
|
||||||
|
text :: state.builder.source[usize(token.span.start)..usize(token.span.end)]
|
||||||
|
if (decode_character(text)) |value| return try ast.add_integer(state.builder, token.span, value)
|
||||||
|
diagnostic :: try diagnostics.add(state.diagnostics, .invalid_character_literal, token.span)
|
||||||
|
return try ast.add_invalid_expr(state.builder, token.span, diagnostic)
|
||||||
|
}
|
||||||
.float: {
|
.float: {
|
||||||
state.next_token += 1
|
state.next_token += 1
|
||||||
return try ast.add_literal(state.builder, .float, token.span)
|
return try ast.add_literal(state.builder, .float, token.span)
|
||||||
|
|||||||
@@ -8,6 +8,102 @@ import "@source/lexer"
|
|||||||
import "@source/ast"
|
import "@source/ast"
|
||||||
import "@source/ast/renderer"
|
import "@source/ast/renderer"
|
||||||
|
|
||||||
|
parses_characters_as_integer_literals_with_original_spans test {
|
||||||
|
source :: "values :: ['a', '\\0', '\\n', '\\r', '\\t', '\\\\', '\\'', 'é', '😀']"
|
||||||
|
expected [9][]u8 :: ["97", "0", "10", "13", "9", "92", "39", "233", "128512"]
|
||||||
|
strings := strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strings)
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
|
defer lexer.deinit(&scan_state)
|
||||||
|
builder := ast.init_builder(mem.c_allocator, "")
|
||||||
|
defer ast.deinit_builder(&builder)
|
||||||
|
_ = try ast.set_source(&builder, "other.hon", "")
|
||||||
|
source_id :: try ast.set_source(&builder, "characters.hon", source)
|
||||||
|
scan_state.source_id = source_id
|
||||||
|
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
|
||||||
|
root :: try parse_source(&builder, scan_state.tokens.items, &diagnostic_store)
|
||||||
|
tree :: ast.view(&builder, root)
|
||||||
|
values :: ast.statement_decl(&tree, ast.package_decl(&tree, 0)).initializer
|
||||||
|
try testing.expect_equal(expected.len, ast.array_element_count(&tree, values))
|
||||||
|
for (expected) |text, index| {
|
||||||
|
value :: ast.array_element(&tree, values, index)
|
||||||
|
try testing.expect_equal(ast.ExprKind.literal_int, ast.expr_kind(&tree, value))
|
||||||
|
try testing.expect_equal(text, ast.literal_text(&tree, value))
|
||||||
|
span :: ast.node_span(&tree, value)
|
||||||
|
try testing.expect_equal(source_id, span.source)
|
||||||
|
try testing.expect_equal(u8('\''), ast.source_text(&tree, span)[0])
|
||||||
|
try testing.expect_equal(scan_state.tokens.items[3 + index * 2].span.start, span.start)
|
||||||
|
try testing.expect_equal(scan_state.tokens.items[3 + index * 2].span.end, span.end)
|
||||||
|
}
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
}
|
||||||
|
|
||||||
|
diagnoses_malformed_character_values_without_losing_sibling_declarations test {
|
||||||
|
sources [7][]u8 :: [
|
||||||
|
"bad :: ''\nnext :: 1",
|
||||||
|
"bad :: 'ab'\nnext :: 1",
|
||||||
|
"bad :: '\\x41'\nnext :: 1",
|
||||||
|
"bad :: '\\q'\nnext :: 1",
|
||||||
|
"bad :: '\xC0\xAF'\nnext :: 1",
|
||||||
|
"bad :: '\xED\xA0\x80'\nnext :: 1",
|
||||||
|
"bad :: '\xF4\x90\x80\x80'\nnext :: 1",
|
||||||
|
]
|
||||||
|
for (sources) |source| {
|
||||||
|
strings := strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strings)
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
|
defer lexer.deinit(&scan_state)
|
||||||
|
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
|
||||||
|
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||||
|
defer ast.deinit(&tree)
|
||||||
|
try testing.expect_equal(1, diagnostics.count(&diagnostic_store))
|
||||||
|
try testing.expect_equal(2, ast.package_decl_count(&tree))
|
||||||
|
bad :: ast.statement_decl(&tree, ast.package_decl(&tree, 0)).initializer
|
||||||
|
try testing.expect_equal(ast.ExprKind.invalid, ast.expr_kind(&tree, bad))
|
||||||
|
diagnostic :: diagnostics.get(&diagnostic_store, ast.invalid(&tree, bad).diagnostic)?
|
||||||
|
try testing.expect_equal(diagnostics.Code.invalid_character_literal, diagnostic.code)
|
||||||
|
try testing.expect_equal(scan_state.tokens.items[2].span.start, diagnostic.span.start)
|
||||||
|
try testing.expect_equal(scan_state.tokens.items[2].span.end, diagnostic.span.end)
|
||||||
|
try testing.expect_equal("next", ast.source_text(&tree, ast.statement_decl(&tree, ast.package_decl(&tree, 1)).name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parses_multiline_conditional_values_and_hide_scopes test {
|
||||||
|
source ::
|
||||||
|
`@hide
|
||||||
|
`hidden :: if (true)
|
||||||
|
` '0'
|
||||||
|
`else if (false)
|
||||||
|
` 'A'
|
||||||
|
`else
|
||||||
|
` 'a'
|
||||||
|
`@hide:package scoped :: 2
|
||||||
|
`@hide:file local :: 3
|
||||||
|
`visible :: 4
|
||||||
|
strings := strpool.init(mem.c_allocator)
|
||||||
|
defer strpool.deinit(&strings)
|
||||||
|
diagnostic_store := diagnostics.init(mem.c_allocator)
|
||||||
|
defer diagnostics.deinit(&diagnostic_store)
|
||||||
|
scan_state := lexer.init(mem.c_allocator)
|
||||||
|
defer lexer.deinit(&scan_state)
|
||||||
|
try lexer.scan(&scan_state, source, &strings, &diagnostic_store)
|
||||||
|
tree := try parse(mem.c_allocator, scan_state.tokens.items, source, &diagnostic_store)
|
||||||
|
defer ast.deinit(&tree)
|
||||||
|
try testing.expect_equal(0, diagnostics.count(&diagnostic_store))
|
||||||
|
try testing.expect_equal(4, ast.package_decl_count(&tree))
|
||||||
|
for (usize(0)..4) |index| {
|
||||||
|
declaration :: ast.package_decl(&tree, index)
|
||||||
|
try testing.expect_equal(index < 2, ast.package_local(&tree, declaration))
|
||||||
|
try testing.expect_equal(index == 2, ast.file_local(&tree, declaration))
|
||||||
|
}
|
||||||
|
conditional :: ast.statement_decl(&tree, ast.package_decl(&tree, 0)).initializer
|
||||||
|
try testing.expect_equal(ast.ExprKind.match_expr, ast.expr_kind(&tree, conditional))
|
||||||
|
}
|
||||||
|
|
||||||
parses_expression_precedence_through_tree_views test {
|
parses_expression_precedence_through_tree_views test {
|
||||||
strings := strpool.init(mem.c_allocator)
|
strings := strpool.init(mem.c_allocator)
|
||||||
defer strpool.deinit(&strings)
|
defer strpool.deinit(&strings)
|
||||||
|
|||||||
+43
-11
@@ -450,7 +450,8 @@ check_elaborated proc(
|
|||||||
try require_numeric(&state, id, expression.right)
|
try require_numeric(&state, id, expression.right)
|
||||||
}
|
}
|
||||||
.builtin_call: {
|
.builtin_call: {
|
||||||
if (mem.eql(builtin_name(&state, id), "divtrunc") and ast.call_arg_count(tree, id) == 2) {
|
name :: builtin_name(&state, id)
|
||||||
|
if ((mem.eql(name, "divtrunc") or mem.eql(name, "rem")) and ast.call_arg_count(tree, id) == 2) {
|
||||||
try require_numeric(&state, id, ast.call_arg(tree, id, 0))
|
try require_numeric(&state, id, ast.call_arg(tree, id, 0))
|
||||||
try require_numeric(&state, id, ast.call_arg(tree, id, 1))
|
try require_numeric(&state, id, ast.call_arg(tree, id, 1))
|
||||||
}
|
}
|
||||||
@@ -487,7 +488,8 @@ check_elaborated proc(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.builtin_call: {
|
.builtin_call: {
|
||||||
if (mem.eql(builtin_name(&state, id), "divtrunc") and ast.call_arg_count(tree, id) == 2) {
|
name :: builtin_name(&state, id)
|
||||||
|
if ((mem.eql(name, "divtrunc") or mem.eql(name, "rem")) and ast.call_arg_count(tree, id) == 2) {
|
||||||
try numeric_operand(&state, id, ast.call_arg(tree, id, 0))
|
try numeric_operand(&state, id, ast.call_arg(tree, id, 0))
|
||||||
try numeric_operand(&state, id, ast.call_arg(tree, id, 1))
|
try numeric_operand(&state, id, ast.call_arg(tree, id, 1))
|
||||||
}
|
}
|
||||||
@@ -1037,7 +1039,6 @@ collect_function proc(state @mut State, id ast.NodeId) void ! (mem.AllocError |
|
|||||||
if (!ast.function_external(tree, id)) try collect_block(state, function.body, function.return_type, false)
|
if (!ast.function_external(tree, id)) try collect_block(state, function.body, function.return_type, false)
|
||||||
else {
|
else {
|
||||||
if (mem.eql(ast.source_text(tree, function.name), "main")) try fail(state, id, .invalid_entry, function.name)
|
if (mem.eql(ast.source_text(tree, function.name), "main")) try fail(state, id, .invalid_entry, function.name)
|
||||||
if (ast.callable_variadic(tree, id)) try fail(state, id, .invalid_operand, function.span)
|
|
||||||
if (!c_abi_type(state, state.types[usize(function.return_type)])) try fail(state, id, .invalid_operand, function.span)
|
if (!c_abi_type(state, state.types[usize(function.return_type)])) try fail(state, id, .invalid_operand, function.span)
|
||||||
for (0..ast.function_param_count(tree, id)) |i| {
|
for (0..ast.function_param_count(tree, id)) |i| {
|
||||||
parameter :: ast.function_param(tree, id, i)
|
parameter :: ast.function_param(tree, id, i)
|
||||||
@@ -2041,11 +2042,21 @@ assignable_type proc(state @State, actual, expected Type) bool {
|
|||||||
if (actual.kind == .null) return true
|
if (actual.kind == .null) return true
|
||||||
if (actual.kind == .optional) {
|
if (actual.kind == .optional) {
|
||||||
source :: state.arrays.items[usize(actual.array_type)].element
|
source :: state.arrays.items[usize(actual.array_type)].element
|
||||||
if (source.kind != child.kind) return false
|
if (source.kind != child.kind
|
||||||
|
and !(source.kind == .pointer and child.kind == .slice)
|
||||||
|
and !(source.kind == .string and child.kind == .pointer)) return false
|
||||||
return assignable_type(state, source, child)
|
return assignable_type(state, source, child)
|
||||||
}
|
}
|
||||||
return assignable_type(state, actual, child)
|
return assignable_type(state, actual, child)
|
||||||
}
|
}
|
||||||
|
if (actual.kind == .string and expected.kind == .pointer) {
|
||||||
|
pointer :: state.pointers.items[usize(expected.pointer_type)]
|
||||||
|
return (
|
||||||
|
expected.nominal == ast.NodeId(maxval!(u32)) and !pointer.mutable
|
||||||
|
and pointer.element.nominal == ast.NodeId(maxval!(u32))
|
||||||
|
and (pointer.element.kind == .i8 or pointer.element.kind == .u8)
|
||||||
|
)
|
||||||
|
}
|
||||||
if (expected.kind == .slice) {
|
if (expected.kind == .slice) {
|
||||||
b :: state.arrays.items[usize(expected.array_type)]
|
b :: state.arrays.items[usize(expected.array_type)]
|
||||||
if (actual.kind == .string) return !b.mutable and b.element.kind == .u8 and (b.sentinel == null or b.sentinel? == 0)
|
if (actual.kind == .string) return !b.mutable and b.element.kind == .u8 and (b.sentinel == null or b.sentinel? == 0)
|
||||||
@@ -2056,6 +2067,17 @@ assignable_type proc(state @State, actual, expected Type) bool {
|
|||||||
and same_type(state, a.element, b.element)
|
and same_type(state, a.element, b.element)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (actual.kind == .pointer and actual.nominal == ast.NodeId(maxval!(u32))) {
|
||||||
|
pointer :: state.pointers.items[usize(actual.pointer_type)]
|
||||||
|
if (pointer.many or pointer.element.kind != .array
|
||||||
|
or pointer.element.nominal != ast.NodeId(maxval!(u32))) return false
|
||||||
|
a :: state.arrays.items[usize(pointer.element.array_type)]
|
||||||
|
return (
|
||||||
|
(!b.mutable or (pointer.mutable and a.mutable))
|
||||||
|
and (b.sentinel == null or same_sentinel(a.sentinel, b.sentinel))
|
||||||
|
and same_type(state, a.element, b.element)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (actual.kind != .pointer or expected.kind != .pointer or actual.nominal != expected.nominal) return false
|
if (actual.kind != .pointer or expected.kind != .pointer or actual.nominal != expected.nominal) return false
|
||||||
a :: state.pointers.items[usize(actual.pointer_type)]
|
a :: state.pointers.items[usize(actual.pointer_type)]
|
||||||
@@ -2371,12 +2393,21 @@ validate_call proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | dia
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
parameter_count :: ast.function_param_count(tree, signature.function)
|
parameter_count :: ast.function_param_count(tree, signature.function)
|
||||||
if (parameter_count != ast.call_arg_count(tree, id)) {
|
variadic :: ast.callable_variadic(tree, signature.function)
|
||||||
|
if (ast.call_arg_count(tree, id) < parameter_count or (!variadic and parameter_count != ast.call_arg_count(tree, id))) {
|
||||||
diagnostic :: try diagnostics.add(state.diagnostics, .argument_count_mismatch, expression.span)
|
diagnostic :: try diagnostics.add(state.diagnostics, .argument_count_mismatch, expression.span)
|
||||||
if (callee.kind != .invalid) state.types[usize(id)] = invalid_type(diagnostic)
|
if (callee.kind != .invalid) state.types[usize(id)] = invalid_type(diagnostic)
|
||||||
}
|
}
|
||||||
for (0..ast.call_arg_count(tree, id)) |i| {
|
for (0..ast.call_arg_count(tree, id)) |i| {
|
||||||
if (i >= parameter_count) continue
|
if (i >= parameter_count) {
|
||||||
|
if (variadic) {
|
||||||
|
argument :: ast.call_arg(tree, id, i)
|
||||||
|
actual :: state.types[usize(argument)]
|
||||||
|
if (!c_abi_type(state, actual) or actual.kind == .void)
|
||||||
|
try fail(state, argument, .invalid_operand, ast.node_span(tree, argument))
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
parameter :: ast.function_param(tree, signature.function, i)
|
parameter :: ast.function_param(tree, signature.function, i)
|
||||||
try compatible(state, ast.call_arg(tree, id, i), state.types[usize(parameter)])
|
try compatible(state, ast.call_arg(tree, id, i), state.types[usize(parameter)])
|
||||||
}
|
}
|
||||||
@@ -2697,7 +2728,7 @@ check_function_type proc(state @mut State, id ast.NodeId) Type ! (mem.AllocError
|
|||||||
result :: ast.callable_result(state.tree, id)
|
result :: ast.callable_result(state.tree, id)
|
||||||
result_type :: try check_annotation(state, result, true)
|
result_type :: try check_annotation(state, result, true)
|
||||||
if (result_type.kind == .invalid) type = result_type
|
if (result_type.kind == .invalid) type = result_type
|
||||||
if (ast.callable_variadic(state.tree, id) or (ast.callable_foreign(state.tree, id) and !c_abi_type(state, result_type)))
|
if (ast.callable_foreign(state.tree, id) and !c_abi_type(state, result_type))
|
||||||
type = invalid_type(try diagnostics.add(state.diagnostics, .invalid_operand, ast.node_span(state.tree, id)))
|
type = invalid_type(try diagnostics.add(state.diagnostics, .invalid_operand, ast.node_span(state.tree, id)))
|
||||||
try depend(state, result, id)
|
try depend(state, result, id)
|
||||||
for 0..ast.function_param_count(state.tree, id) |i| {
|
for 0..ast.function_param_count(state.tree, id) |i| {
|
||||||
@@ -2764,7 +2795,7 @@ check_container_type proc(state @mut State, id ast.NodeId) Type ! (mem.AllocErro
|
|||||||
@hide:file
|
@hide:file
|
||||||
c_abi_type proc(state @State, type Type) bool {
|
c_abi_type proc(state @State, type Type) bool {
|
||||||
if (type.kind == .optional) return c_abi_pointer(state, state.arrays.items[usize(type.array_type)].element)
|
if (type.kind == .optional) return c_abi_pointer(state, state.arrays.items[usize(type.array_type)].element)
|
||||||
if (type.kind == .function) return ast.callable_foreign(state.tree, type.function) and !ast.callable_variadic(state.tree, type.function)
|
if (type.kind == .function) return ast.callable_foreign(state.tree, type.function)
|
||||||
return integer_kind(type.kind) or type.kind == .float or type.kind == .bool or type.kind == .pointer or type.kind == .void or type.kind == .invalid
|
return integer_kind(type.kind) or type.kind == .float or type.kind == .bool or type.kind == .pointer or type.kind == .void or type.kind == .invalid
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2794,7 +2825,7 @@ collect_builtin proc(state @mut State, id ast.NodeId) void ! (mem.AllocError | d
|
|||||||
}
|
}
|
||||||
if (layout) state.cells[usize(id)].type = Type{ kind = .usize }
|
if (layout) state.cells[usize(id)].type = Type{ kind = .usize }
|
||||||
if (limit and count == 1) state.cells[usize(id)].type = state.types[usize(ast.call_arg(state.tree, id, 0))]
|
if (limit and count == 1) state.cells[usize(id)].type = state.types[usize(ast.call_arg(state.tree, id, 0))]
|
||||||
if (mem.eql(name, "divtrunc")) {
|
if (mem.eql(name, "divtrunc") or mem.eql(name, "rem")) {
|
||||||
state.cells[usize(id)].numeric = true
|
state.cells[usize(id)].numeric = true
|
||||||
state.cells[usize(id)].producer = id
|
state.cells[usize(id)].producer = id
|
||||||
}
|
}
|
||||||
@@ -2872,7 +2903,8 @@ infer_builtin proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.A
|
|||||||
span :: ast.node_span(state.tree, id)
|
span :: ast.node_span(state.tree, id)
|
||||||
layout :: mem.eql(name, "sizeof") or mem.eql(name, "alignof")
|
layout :: mem.eql(name, "sizeof") or mem.eql(name, "alignof")
|
||||||
limit :: mem.eql(name, "maxval") or mem.eql(name, "minval")
|
limit :: mem.eql(name, "maxval") or mem.eql(name, "minval")
|
||||||
division :: mem.eql(name, "divtrunc")
|
remainder :: mem.eql(name, "rem")
|
||||||
|
division :: mem.eql(name, "divtrunc") or remainder
|
||||||
cast :: mem.eql(name, "ptrcast") or mem.eql(name, "constcast")
|
cast :: mem.eql(name, "ptrcast") or mem.eql(name, "constcast")
|
||||||
memory :: mem.eql(name, "memcopy") or mem.eql(name, "memset")
|
memory :: mem.eql(name, "memcopy") or mem.eql(name, "memset")
|
||||||
some :: mem.eql(name, "some")
|
some :: mem.eql(name, "some")
|
||||||
@@ -2892,7 +2924,7 @@ infer_builtin proc(state @mut State, id ast.NodeId, defaults bool) Type ! (mem.A
|
|||||||
left :: try infer_shape(state, ast.call_arg(state.tree, id, 0), defaults)
|
left :: try infer_shape(state, ast.call_arg(state.tree, id, 0), defaults)
|
||||||
right :: try infer_shape(state, ast.call_arg(state.tree, id, 1), defaults)
|
right :: try infer_shape(state, ast.call_arg(state.tree, id, 1), defaults)
|
||||||
if (defaults and left.kind != .invalid and right.kind != .invalid
|
if (defaults and left.kind != .invalid and right.kind != .invalid
|
||||||
and (!integer_kind(left.kind) or !same_type(state, left, right))) try fail(state, id, .invalid_operand, span)
|
and ((!integer_kind(left.kind) and !(remainder and left.kind == .float)) or !same_type(state, left, right))) try fail(state, id, .invalid_operand, span)
|
||||||
return group_type(state, id)
|
return group_type(state, id)
|
||||||
}
|
}
|
||||||
if (some) {
|
if (some) {
|
||||||
|
|||||||
+13
-10
@@ -486,6 +486,17 @@ canonical_binding proc(state @State, original Binding) Binding {
|
|||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@hide:file
|
||||||
|
namespace_member proc(state @State, root, name ast.NodeId) ?Binding {
|
||||||
|
if (ast.expr_kind(state.tree, name) != .identifier) return null
|
||||||
|
namespace :: hashmap.get(&state.namespaces, root)?
|
||||||
|
if (hashmap.get(&namespace.names, ast.name_id(state.tree, name))) |member| {
|
||||||
|
if (member.kind == .declaration and ast.package_local(state.tree, member.data.declaration)) return null
|
||||||
|
return member
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
@hide:file
|
@hide:file
|
||||||
canonicalize_aliases proc(state @mut State) void ! (mem.AllocError | diagnostics.CapacityError) {
|
canonicalize_aliases proc(state @mut State) void ! (mem.AllocError | diagnostics.CapacityError) {
|
||||||
# aliases may forward-reference other aliases or imported namespace members.
|
# aliases may forward-reference other aliases or imported namespace members.
|
||||||
@@ -501,11 +512,7 @@ canonicalize_aliases proc(state @mut State) void ! (mem.AllocError | diagnostics
|
|||||||
expression :: ast.selection_expr(state.tree, id)
|
expression :: ast.selection_expr(state.tree, id)
|
||||||
base :: state.bindings[usize(expression.base)]
|
base :: state.bindings[usize(expression.base)]
|
||||||
if (base.kind != .namespace or state.bindings[usize(id)].kind != .none) continue
|
if (base.kind != .namespace or state.bindings[usize(id)].kind != .none) continue
|
||||||
namespace :: hashmap.get(&state.namespaces, base.data.namespace)?
|
found :: namespace_member(state, base.data.namespace, expression.member)
|
||||||
found :: if (ast.expr_kind(state.tree, expression.member) == .identifier)
|
|
||||||
hashmap.get(&namespace.names, ast.name_id(state.tree, expression.member))
|
|
||||||
else
|
|
||||||
null
|
|
||||||
value :: if (found) |member| canonical_binding(state, member) else Binding{
|
value :: if (found) |member| canonical_binding(state, member) else Binding{
|
||||||
kind = .invalid,
|
kind = .invalid,
|
||||||
data = BindingData{ diagnostic = try diagnostics.add(
|
data = BindingData{ diagnostic = try diagnostics.add(
|
||||||
@@ -614,11 +621,7 @@ resolve_expr proc(
|
|||||||
if (base.kind == .invalid) {
|
if (base.kind == .invalid) {
|
||||||
value = base
|
value = base
|
||||||
} else if (base.kind == .namespace) {
|
} else if (base.kind == .namespace) {
|
||||||
namespace :: hashmap.get(&state.namespaces, base.data.namespace)?
|
found :: namespace_member(state, base.data.namespace, expression.member)
|
||||||
found :: if (ast.expr_kind(tree, expression.member) == .identifier)
|
|
||||||
hashmap.get(&namespace.names, ast.name_id(tree, expression.member))
|
|
||||||
else
|
|
||||||
null
|
|
||||||
if (found) |member| {
|
if (found) |member| {
|
||||||
value = member
|
value = member
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1762,6 +1762,13 @@ branch_constant proc(state @State, id ast.NodeId, depth usize) bool {
|
|||||||
.union_constant, .slice_constant: return true
|
.union_constant, .slice_constant: return true
|
||||||
.typed_constant: return branch_constant(state, ast.typed_constant(&state.tree, id).value, depth + 1)
|
.typed_constant: return branch_constant(state, ast.typed_constant(&state.tree, id).value, depth + 1)
|
||||||
.enum_literal: return true
|
.enum_literal: return true
|
||||||
|
.builtin_call: {
|
||||||
|
name :: ast.literal_text(&state.tree, ast.builtin_call(&state.tree, id).name)
|
||||||
|
if (ast.call_arg_count(&state.tree, id) != 1
|
||||||
|
or (!mem.eql(name, "minval") and !mem.eql(name, "maxval")
|
||||||
|
and !mem.eql(name, "sizeof") and !mem.eql(name, "alignof"))) return false
|
||||||
|
return branch_constant(state, ast.call_arg(&state.tree, id, 0), depth + 1)
|
||||||
|
}
|
||||||
.record_literal: {
|
.record_literal: {
|
||||||
# typed literals may omit defaults; materialized constants are complete
|
# typed literals may omit defaults; materialized constants are complete
|
||||||
# contextual literals inside a typed_constant wrapper.
|
# contextual literals inside a typed_constant wrapper.
|
||||||
@@ -2122,7 +2129,7 @@ specialize_call proc(state @mut State, call, function, expected ast.NodeId) void
|
|||||||
}
|
}
|
||||||
supplied :: ast.call_arg_count(&state.tree, call)
|
supplied :: ast.call_arg_count(&state.tree, call)
|
||||||
explicit :: supplied == count
|
explicit :: supplied == count
|
||||||
if (!explicit and (type_result or supplied != runtime)) {
|
if (!explicit and (type_result or supplied < runtime or supplied > count)) {
|
||||||
try reject(state, call, .argument_count_mismatch)
|
try reject(state, call, .argument_count_mismatch)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -2131,11 +2138,17 @@ specialize_call proc(state @mut State, call, function, expected ast.NodeId) void
|
|||||||
mem.set(arguments, Value{})
|
mem.set(arguments, Value{})
|
||||||
runtime_arguments := arraylist.init(ast.NodeId, state.allocator)
|
runtime_arguments := arraylist.init(ast.NodeId, state.allocator)
|
||||||
defer arraylist.deinit(&runtime_arguments)
|
defer arraylist.deinit(&runtime_arguments)
|
||||||
|
# supplied compile-time arguments form a declaration-order prefix; infer the rest.
|
||||||
|
supplied_comptime :: if (type_result) count else supplied - runtime
|
||||||
|
comptime_position := usize(0)
|
||||||
position := 0
|
position := 0
|
||||||
for (0..count) |i| {
|
for (0..count) |i| {
|
||||||
parameter :: ast.function_param(&state.tree, function, i)
|
parameter :: ast.function_param(&state.tree, function, i)
|
||||||
view :: ast.param(&state.tree, parameter)
|
view :: ast.param(&state.tree, parameter)
|
||||||
if (view.comptime and !explicit) continue
|
if (view.comptime and !type_result) {
|
||||||
|
if (comptime_position >= supplied_comptime) continue
|
||||||
|
comptime_position += 1
|
||||||
|
}
|
||||||
argument :: ast.call_arg(&state.tree, call, position)
|
argument :: ast.call_arg(&state.tree, call, position)
|
||||||
position += 1
|
position += 1
|
||||||
if (type_result or view.comptime) {
|
if (type_result or view.comptime) {
|
||||||
@@ -2288,16 +2301,23 @@ specialize_call proc(state @mut State, call, function, expected ast.NodeId) void
|
|||||||
try reject(state, call, .specialization_cycle)
|
try reject(state, call, .specialization_cycle)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (explicit) {
|
if (type_result or supplied_comptime != 0) {
|
||||||
signature :: state.instances.items[instance].signature
|
signature :: state.instances.items[instance].signature
|
||||||
|
position = 0
|
||||||
|
comptime_position = 0
|
||||||
for (0..count) |i| {
|
for (0..count) |i| {
|
||||||
parameter :: ast.function_param(&state.tree, signature, i)
|
parameter :: ast.function_param(&state.tree, signature, i)
|
||||||
view :: ast.param(&state.tree, parameter)
|
view :: ast.param(&state.tree, parameter)
|
||||||
|
if (view.comptime and !type_result) {
|
||||||
|
if (comptime_position >= supplied_comptime) continue
|
||||||
|
comptime_position += 1
|
||||||
|
}
|
||||||
|
original :: ast.call_arg(&state.tree, call, position)
|
||||||
|
position += 1
|
||||||
if (
|
if (
|
||||||
(!type_result and !view.comptime) or is_type(state, view.type)
|
(!type_result and !view.comptime) or is_type(state, view.type)
|
||||||
or type_record_storage(state, view.type, 0)
|
or type_record_storage(state, view.type, 0)
|
||||||
) continue
|
) continue
|
||||||
original :: ast.call_arg(&state.tree, call, i)
|
|
||||||
constraint :: try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, original), view.type, original)
|
constraint :: try ast.add_typed_constant(state.builder, ast.node_span(&state.tree, original), view.type, original)
|
||||||
try refresh(state)
|
try refresh(state)
|
||||||
try arraylist.append(&state.rewrites.constraints, constraint)
|
try arraylist.append(&state.rewrites.constraints, constraint)
|
||||||
@@ -6481,7 +6501,7 @@ expression_type proc(state @mut State, id ast.NodeId, depth usize) ast.NodeId !
|
|||||||
return try canonical_type(state, ast.call_arg(&state.tree, id, 0))
|
return try canonical_type(state, ast.call_arg(&state.tree, id, 0))
|
||||||
if ((mem.eql(name, "ptrcast") or mem.eql(name, "constcast")) and count == 2)
|
if ((mem.eql(name, "ptrcast") or mem.eql(name, "constcast")) and count == 2)
|
||||||
return try canonical_type(state, ast.call_arg(&state.tree, id, 0))
|
return try canonical_type(state, ast.call_arg(&state.tree, id, 0))
|
||||||
if (mem.eql(name, "divtrunc") and count == 2) {
|
if ((mem.eql(name, "divtrunc") or mem.eql(name, "rem")) and count == 2) {
|
||||||
left :: ast.call_arg(&state.tree, id, 0)
|
left :: ast.call_arg(&state.tree, id, 0)
|
||||||
right :: ast.call_arg(&state.tree, id, 1)
|
right :: ast.call_arg(&state.tree, id, 1)
|
||||||
operand :: if (flexible(state, left)) right else left
|
operand :: if (flexible(state, left)) right else left
|
||||||
|
|||||||
+22
-14
@@ -251,10 +251,13 @@ FormatToken :: struct {
|
|||||||
@hide:file
|
@hide:file
|
||||||
parse_format proc($N usize, $format []u8, $Args type) [N]mut FormatToken {
|
parse_format proc($N usize, $format []u8, $Args type) [N]mut FormatToken {
|
||||||
tokens [N]mut FormatToken := undefined
|
tokens [N]mut FormatToken := undefined
|
||||||
mem.set(FormatToken, tokens[..], FormatToken{ kind = .unused, start = 0, end = 0, field = "" })
|
for (usize(0)..N) |index|
|
||||||
|
tokens[index] = FormatToken{ kind = .unused, start = 0, end = 0, field = "" }
|
||||||
|
|
||||||
field_count := 0
|
# retained metadata makes parsing constant-input execution, including guarded errors.
|
||||||
match typeinfo!(Args) {
|
info :: typeinfo!(Args)
|
||||||
|
field_count := usize(0)
|
||||||
|
match info {
|
||||||
.record |r|: {
|
.record |r|: {
|
||||||
if (!r.is_tuple) compile_error!("io.print arguments must be a tuple")
|
if (!r.is_tuple) compile_error!("io.print arguments must be a tuple")
|
||||||
field_count = r.fields.len
|
field_count = r.fields.len
|
||||||
@@ -262,10 +265,10 @@ parse_format proc($N usize, $format []u8, $Args type) [N]mut FormatToken {
|
|||||||
else: compile_error!("io.print arguments must be a tuple")
|
else: compile_error!("io.print arguments must be a tuple")
|
||||||
}
|
}
|
||||||
|
|
||||||
token_count := 0
|
token_count := usize(0)
|
||||||
argument_count := 0
|
argument_count := usize(0)
|
||||||
literal_start := 0
|
literal_start := usize(0)
|
||||||
cursor := 0
|
cursor := usize(0)
|
||||||
while cursor < format.len {
|
while cursor < format.len {
|
||||||
byte :: format[cursor]
|
byte :: format[cursor]
|
||||||
if byte == '{' {
|
if byte == '{' {
|
||||||
@@ -294,7 +297,7 @@ parse_format proc($N usize, $format []u8, $Args type) [N]mut FormatToken {
|
|||||||
}
|
}
|
||||||
|
|
||||||
kind FormatTokenKind := .default
|
kind FormatTokenKind := .default
|
||||||
width := 2
|
width := usize(2)
|
||||||
if next != '}' {
|
if next != '}' {
|
||||||
if (cursor + 2 >= format.len or format[cursor + 2] != '}') compile_error!("io.print format expects a one-character specifier")
|
if (cursor + 2 >= format.len or format[cursor + 2] != '}') compile_error!("io.print format expects a one-character specifier")
|
||||||
|
|
||||||
@@ -367,7 +370,12 @@ parse_format proc($N usize, $format []u8, $Args type) [N]mut FormatToken {
|
|||||||
@hide:file
|
@hide:file
|
||||||
format_field_name proc($T type, index usize) []u8 {
|
format_field_name proc($T type, index usize) []u8 {
|
||||||
match typeinfo!(T) {
|
match typeinfo!(T) {
|
||||||
.record |r|: return r.fields[index].name
|
.record |r|: {
|
||||||
|
inline for r.fields |field| {
|
||||||
|
if (index == field.index) return field.name
|
||||||
|
}
|
||||||
|
unreachable
|
||||||
|
}
|
||||||
else: compile_error!("io.print arguments must be a tuple")
|
else: compile_error!("io.print arguments must be a tuple")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -402,7 +410,7 @@ write_integer proc(
|
|||||||
} else {
|
} else {
|
||||||
try write_integer_unsigned(output, u64(value), base, uppercase)
|
try write_integer_unsigned(output, u64(value), base, uppercase)
|
||||||
}
|
}
|
||||||
.distinct |backing|: if scalar_or_distinct_type(backing) {
|
.distinct |backing|: if $(scalar_or_distinct_type(backing)) {
|
||||||
try write_integer(output, distinct_value(backing, T, value), base, uppercase)
|
try write_integer(output, distinct_value(backing, T, value), base, uppercase)
|
||||||
} else {
|
} else {
|
||||||
compile_error!("io.print integer format requires an integer argument")
|
compile_error!("io.print integer format requires an integer argument")
|
||||||
@@ -429,7 +437,7 @@ write_float proc($T type, output Writer, value T, scientific bool) void ! WriteE
|
|||||||
if (count < 0 or usize(count) >= buffer.len) return .write_failed
|
if (count < 0 or usize(count) >= buffer.len) return .write_failed
|
||||||
try write_all(output, buffer[0..usize(count)])
|
try write_all(output, buffer[0..usize(count)])
|
||||||
}
|
}
|
||||||
.distinct |backing|: if scalar_or_distinct_type(backing) {
|
.distinct |backing|: if $(scalar_or_distinct_type(backing)) {
|
||||||
try write_float(output, distinct_value(backing, T, value), scientific)
|
try write_float(output, distinct_value(backing, T, value), scientific)
|
||||||
} else {
|
} else {
|
||||||
compile_error!("io.print float format requires a float argument")
|
compile_error!("io.print float format requires a float argument")
|
||||||
@@ -443,7 +451,7 @@ write_decimal proc($T type, output Writer, value T) void ! WriteError {
|
|||||||
match typeinfo!(T) {
|
match typeinfo!(T) {
|
||||||
.integer: try write_integer(output, value, 10, false)
|
.integer: try write_integer(output, value, 10, false)
|
||||||
.float: try write_float(output, value, false)
|
.float: try write_float(output, value, false)
|
||||||
.distinct |backing|: if scalar_or_distinct_type(backing) {
|
.distinct |backing|: if $(scalar_or_distinct_type(backing)) {
|
||||||
try write_decimal(output, distinct_value(backing, T, value))
|
try write_decimal(output, distinct_value(backing, T, value))
|
||||||
} else {
|
} else {
|
||||||
compile_error!("io.print '{d}' requires an integer or float argument")
|
compile_error!("io.print '{d}' requires an integer or float argument")
|
||||||
@@ -460,7 +468,7 @@ write_character proc($T type, output Writer, value T) void ! WriteError {
|
|||||||
buffer [1]u8 :: [u8(value)]
|
buffer [1]u8 :: [u8(value)]
|
||||||
try write_all(output, buffer[..])
|
try write_all(output, buffer[..])
|
||||||
}
|
}
|
||||||
.distinct |backing|: if scalar_or_distinct_type(backing) {
|
.distinct |backing|: if $(scalar_or_distinct_type(backing)) {
|
||||||
try write_character(output, distinct_value(backing, T, value))
|
try write_character(output, distinct_value(backing, T, value))
|
||||||
} else {
|
} else {
|
||||||
compile_error!("io.print '{c}' requires an unsigned integer that fits in u8")
|
compile_error!("io.print '{c}' requires an unsigned integer that fits in u8")
|
||||||
@@ -492,7 +500,7 @@ write_default proc($T type, output Writer, value T) void ! WriteError {
|
|||||||
}
|
}
|
||||||
return .write_failed
|
return .write_failed
|
||||||
}
|
}
|
||||||
.distinct |backing|: if scalar_or_distinct_type(backing) {
|
.distinct |backing|: if $(scalar_or_distinct_type(backing)) {
|
||||||
try write_default(output, distinct_value(backing, T, value))
|
try write_default(output, distinct_value(backing, T, value))
|
||||||
} else {
|
} else {
|
||||||
compile_error!("io.print '{}' does not support this argument type")
|
compile_error!("io.print '{}' does not support this argument type")
|
||||||
|
|||||||
+309
-4
@@ -11,6 +11,115 @@ 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 = {
|
||||||
|
"pointer_array_slice_coercions_borrow_storage_and_preserve_optional_presence": (
|
||||||
|
"""
|
||||||
|
borrow proc(value @mut [2]mut i16) []mut i16 { return value }
|
||||||
|
optional proc(value ?@[2]i16) ?[]i16 { return value }
|
||||||
|
c_string proc(value ?string) ?*u8 { return value }
|
||||||
|
main proc() i16 {
|
||||||
|
values [2]mut i16 := [19, 23]
|
||||||
|
view :: borrow(&values)
|
||||||
|
view[0] = 20
|
||||||
|
if (values[0] != 20 or view.len != 2) return -1
|
||||||
|
immutable [2]i16 :: [19, 23]
|
||||||
|
present :: optional(&immutable)
|
||||||
|
if (present?.len != 2 or present?[0] != 19 or optional(null) != null) return -2
|
||||||
|
text :: c_string("ok")?
|
||||||
|
if (text[0] != 111 or text[2] != 0 or c_string(null) != null) return -3
|
||||||
|
return present?[0] + present?[1]
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"pointer_array_slice_coercion_rejects_readonly_reversal": (
|
||||||
|
"""
|
||||||
|
borrow proc(value @[2]mut i16) []mut i16 { return value }
|
||||||
|
main proc() i16 { values [2]mut i16 := [19, 23]\nreturn borrow(&values)[0] }
|
||||||
|
""", b"", b"return does not match the function result type",
|
||||||
|
),
|
||||||
|
"pointer_array_slice_coercion_rejects_nominal_element_mismatch": (
|
||||||
|
"""
|
||||||
|
Id :: distinct i16
|
||||||
|
borrow proc(value @[2]Id) []i16 { return value }
|
||||||
|
main proc() i16 { values [2]Id :: [Id(19), Id(23)]\nreturn borrow(&values)[0] }
|
||||||
|
""", b"", b"return does not match the function result type",
|
||||||
|
),
|
||||||
|
"partial_comptime_prefix_infers_remaining_types_and_reuses_nominal_results": (
|
||||||
|
"""
|
||||||
|
Box proc($N usize, $T type) type { return struct { values [N]T } }
|
||||||
|
wrap proc($N usize, $T type, value T) Box(N, T) {
|
||||||
|
if (N != 2) compile_error!("expected two elements")
|
||||||
|
return { values = [value, value] }
|
||||||
|
}
|
||||||
|
main proc() i16 {
|
||||||
|
partial :: wrap(usize(2), i16(21))
|
||||||
|
explicit Box(2, i16) :: wrap(usize(2), i16, i16(21))
|
||||||
|
if (partial.values[1] != explicit.values[1]) return -1
|
||||||
|
return partial.values[0] + partial.values[1]
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"partial_comptime_prefix_checks_explicit_value_types": (
|
||||||
|
"""
|
||||||
|
add proc($bias i8, $T type, value T) T { return value + T(bias) }
|
||||||
|
main proc() i16 { return add(i16(2), i16(40)) }
|
||||||
|
""", b"42\n", b"incompatible value type",
|
||||||
|
),
|
||||||
|
"partial_comptime_prefix_still_requires_unresolved_parameters": (
|
||||||
|
"""
|
||||||
|
add proc($bias i8, $T type, value i16) i16 { return value + i16(bias) }
|
||||||
|
main proc() i16 { return add(i8(2), i16(40)) }
|
||||||
|
""", b"", b"cannot infer",
|
||||||
|
),
|
||||||
|
"pure_type_builtins_select_generic_guards_before_compile_errors": (
|
||||||
|
"""
|
||||||
|
checked proc($T type, value T) T {
|
||||||
|
if (minval!(T) == 0 and maxval!(T) == 255 and sizeof!(T) == 1 and alignof!(T) == 1) {
|
||||||
|
return value
|
||||||
|
} else {
|
||||||
|
compile_error!("expected an unsigned byte")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main proc() u8 { return checked(u8(42)) }
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"remainder_preserves_exact_signed_unsigned_float_and_nominal_values": (
|
||||||
|
"""
|
||||||
|
Id :: distinct i16
|
||||||
|
remainder proc(left, right Id) Id { return rem!(left, right) }
|
||||||
|
signs proc(left, right i64) i64 { return rem!(left, right) }
|
||||||
|
real proc(left, right f64) f64 { return rem!(left, right) }
|
||||||
|
constant proc() i16 { return rem!(i16(-42), 23) }
|
||||||
|
main proc() i16 {
|
||||||
|
if (remainder(Id(-42), Id(23)) != Id(-19)) return -1
|
||||||
|
if (signs(minval!(i64), -1) != 0 or signs(minval!(i64), 10) != -8) return -2
|
||||||
|
if (signs(5, -3) != 2 or signs(-5, 3) != -2) return -3
|
||||||
|
if (rem!(maxval!(u64), 10) != 5) return -4
|
||||||
|
if (real(-5.5, 2.0) != -1.5 or real(5.5, -2.0) != 1.5) return -5
|
||||||
|
if ($(constant()) != -19) return -6
|
||||||
|
return 42
|
||||||
|
}
|
||||||
|
""", b"42\n", None,
|
||||||
|
),
|
||||||
|
"remainder_traps_on_runtime_zero": (
|
||||||
|
"calculate proc(zero i16) i16 { return rem!(i16(42), zero) }\n"
|
||||||
|
"main proc() i16 { return calculate(0) }", b"", b"division by zero",
|
||||||
|
),
|
||||||
|
"float_remainder_traps_on_runtime_negative_zero": (
|
||||||
|
"calculate proc(zero f64) f64 { return rem!(f64(42), zero) }\n"
|
||||||
|
"main proc() f64 { return calculate(-0.0) }", b"", b"division by zero",
|
||||||
|
),
|
||||||
|
"remainder_rejects_mismatched_nominal_types": (
|
||||||
|
"Id :: distinct i16\nmain proc() Id { return rem!(Id(42), i16(23)) }",
|
||||||
|
b"", b"operator requires compatible operand types",
|
||||||
|
),
|
||||||
|
"character_literals_preserve_codepoints_escapes_and_constant_values": (
|
||||||
|
r"""
|
||||||
|
constant proc() u32 { return '🙂' }
|
||||||
|
main proc() [8]u32 {
|
||||||
|
return ['A', 'é', $(constant()), '\n', '\r', '\t', '\\', '\'']
|
||||||
|
}
|
||||||
|
""", b"[65, 233, 128578, 10, 13, 9, 92, 39]\n", None,
|
||||||
|
),
|
||||||
"compiletime_only_main_cannot_select_another_runtime_function": (
|
"compiletime_only_main_cannot_select_another_runtime_function": (
|
||||||
"""
|
"""
|
||||||
fallback proc() i16 { return -99 }
|
fallback proc() i16 { return -99 }
|
||||||
@@ -10578,13 +10687,15 @@ main proc() int { return missing }
|
|||||||
emitted = run(str(HONEY), "emit-llvm", str(single))
|
emitted = run(str(HONEY), "emit-llvm", str(single))
|
||||||
assert emitted.returncode == 0 and emitted.stderr == b"", outcome(emitted)
|
assert emitted.returncode == 0 and emitted.stderr == b"", outcome(emitted)
|
||||||
check(single.parent, executable, (b"42\n", b"duplicate name"))
|
check(single.parent, executable, (b"42\n", b"duplicate name"))
|
||||||
put("hidden/a.hon", "@hide:file\nhelper proc() int { return 20 }\nfirst proc() int { return helper() }\n")
|
put("hidden/a.hon", "@hide:file\nhelper proc() int { return 20 }\n@hide\npackage_value :: 19\nfirst proc() int { return helper() }\n")
|
||||||
put("hidden/b.hon", "@hide:file\nhelper proc() int { return 22 }\nsecond proc() int { return helper() }\n")
|
put("hidden/b.hon", "@hide:file\nhelper proc() int { return 22 }\nsecond proc() int { return helper() }\nanswer proc() int { return package_value + 23 }\n")
|
||||||
visibility_cases = (
|
visibility_cases = (
|
||||||
('import "../hidden"\nmain proc() int { return hidden.first() + hidden.second() }\n', b"42\n", None),
|
('import "../hidden"\nmain proc() int { return hidden.first() + hidden.second() }\n', b"42\n", None),
|
||||||
('import "../hidden"\nmain proc() int { return hidden.helper() }\n', b"", b"unknown name"),
|
('import "../hidden"\nmain proc() int { return hidden.helper() }\n', b"", b"unknown name"),
|
||||||
|
('import "../hidden"\nmain proc() int { return hidden.answer() }\n', b"42\n", None),
|
||||||
|
('import "../hidden"\nmain proc() int { return hidden.package_value }\n', b"", b"unknown name"),
|
||||||
|
('import "../hidden"\nspace :: alias hidden\nmain proc() int { return space.package_value }\n', b"", b"unknown name"),
|
||||||
('@hide:file\nprivate proc() int { return 42 }\nmain proc() int { return private() }\n', b"42\n", None),
|
('@hide:file\nprivate proc() int { return 42 }\nmain proc() int { return private() }\n', b"42\n", None),
|
||||||
('@hide:package\nprivate proc() int { return 42 }\nmain proc() int { return private() }\n', b"", b"unexpected token"),
|
|
||||||
)
|
)
|
||||||
sibling.write_text("sibling proc(n int) int { return n }\n")
|
sibling.write_text("sibling proc(n int) int { return n }\n")
|
||||||
for text, stdout, error in visibility_cases:
|
for text, stdout, error in visibility_cases:
|
||||||
@@ -12516,6 +12627,197 @@ def check_hashmap_packages(temporary, executable):
|
|||||||
return 1 + len(invalid_keys), len(native_cases)
|
return 1 + len(invalid_keys), len(native_cases)
|
||||||
|
|
||||||
|
|
||||||
|
def check_io_packages(temporary, executable):
|
||||||
|
project = temporary / "io"
|
||||||
|
project.mkdir()
|
||||||
|
(project / "build.hon").write_text("# project root\n")
|
||||||
|
for package in ("std", "ffi"):
|
||||||
|
(project / package).symlink_to(ROOT / package, target_is_directory=True)
|
||||||
|
source = project / "main.hon"
|
||||||
|
writer = """
|
||||||
|
import "@std/io"
|
||||||
|
State :: struct {
|
||||||
|
bytes []mut u8
|
||||||
|
len usize = 0
|
||||||
|
chunk usize = 3
|
||||||
|
fail_at usize = maxval!(usize)
|
||||||
|
mode u8 = 0
|
||||||
|
}
|
||||||
|
write proc(context ?@mut anyopaque, _ io.Handle, bytes []u8) usize ! io.WriteError {
|
||||||
|
state @mut State :: ptrcast!(context?)
|
||||||
|
if (state.len >= state.fail_at) return .write_failed
|
||||||
|
if (state.mode == 1) return 0
|
||||||
|
if (state.mode == 2) return bytes.len + 1
|
||||||
|
count := bytes.len
|
||||||
|
if (count > state.chunk) count = state.chunk
|
||||||
|
if (count > state.bytes.len - state.len) return .write_failed
|
||||||
|
memcopy!(state.bytes[state.len..state.len + count], bytes[..count])
|
||||||
|
state.len += count
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
output proc(state @mut State) io.Writer {
|
||||||
|
return { context = state, handle = { file_desc = -1 }, write = write }
|
||||||
|
}
|
||||||
|
equal proc(state @State, expected []u8) bool {
|
||||||
|
if (state.len != expected.len) return false
|
||||||
|
for expected |byte, index| { if (state.bytes[index] != byte) return false }
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
cases = (
|
||||||
|
"""
|
||||||
|
Kind :: enum(i16) { negative = -7, first = 3, far = 90 }
|
||||||
|
KindAlias :: alias Kind
|
||||||
|
Inner :: distinct i16
|
||||||
|
Id :: distinct Inner
|
||||||
|
main proc() i16 ! io.WriteError {
|
||||||
|
buffer [512]mut u8 := undefined
|
||||||
|
state State := { bytes = buffer[..] }
|
||||||
|
try io.print("{{{} {d} {b} {o} {x} {X} {c} {s} {}}}",
|
||||||
|
output(&state), {true, i16(-42), u8(5), u16(8), u32(254),
|
||||||
|
u64(254), u8('!'), "ok", Kind.far})
|
||||||
|
if (!equal(&state, "{true -42 101 10 fe FE ! ok .far}")) return -1
|
||||||
|
state.len = 0
|
||||||
|
try io.print("{} {} {} {}", output(&state),
|
||||||
|
{Id(Inner(-42)), false, KindAlias.first, ""})
|
||||||
|
if (!equal(&state, "-42 false .first ")) return -2
|
||||||
|
state.len = 0
|
||||||
|
try io.print("", output(&state), {})
|
||||||
|
if (!equal(&state, "")) return -3
|
||||||
|
try io.print("{{plain}}", output(&state), {})
|
||||||
|
if (!equal(&state, "{plain}")) return -4
|
||||||
|
return 42
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
main proc() i16 ! io.WriteError {
|
||||||
|
buffer [512]mut u8 := undefined
|
||||||
|
state State := { bytes = buffer[..], chunk = 1 }
|
||||||
|
try io.print("{} {} {} {} {}", output(&state),
|
||||||
|
{minval!(i64), maxval!(u64), i8(-128), u8(0), isize(-1)})
|
||||||
|
if (!equal(&state, "-9223372036854775808 18446744073709551615 -128 0 -1")) return -1
|
||||||
|
state.len = 0
|
||||||
|
try io.print("{x} {b} {o}", output(&state), {i16(-255), i8(-5), u8(255)})
|
||||||
|
if (!equal(&state, "-ff -101 377")) return -2
|
||||||
|
return 42
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
main proc() i16 {
|
||||||
|
buffer [64]mut u8 := undefined
|
||||||
|
state State := { bytes = buffer[..], chunk = 2, fail_at = 4 }
|
||||||
|
failed := false
|
||||||
|
io.print("abcdef{}", output(&state), {42}) catch |err| { failed = err == .write_failed }
|
||||||
|
if (!failed or !equal(&state, "abcd")) return -1
|
||||||
|
state.len = 0
|
||||||
|
state.mode = 1
|
||||||
|
failed = false
|
||||||
|
io.print("{}", output(&state), {42}) catch |err| { failed = err == .no_progress }
|
||||||
|
if (!failed or state.len != 0) return -2
|
||||||
|
state.mode = 2
|
||||||
|
failed = false
|
||||||
|
io.print("{}", output(&state), {42}) catch |err| { failed = err == .write_failed }
|
||||||
|
if (!failed or state.len != 0) return -3
|
||||||
|
return 42
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
format proc() []u8 { return "{} {s} {}" }
|
||||||
|
render proc(writer io.Writer, number i16) void ! io.WriteError {
|
||||||
|
bytes [2]u8 :: ['o', 'k']
|
||||||
|
try io.print(format(), writer, {number, bytes, &bytes})
|
||||||
|
}
|
||||||
|
main proc() i16 ! io.WriteError {
|
||||||
|
buffer [64]mut u8 := undefined
|
||||||
|
state State := { bytes = buffer[..] }
|
||||||
|
for (i16(40)..i16(43)) |value| {
|
||||||
|
state.len = 0
|
||||||
|
try render(output(&state), value)
|
||||||
|
if (value == 42 and !equal(&state, "42 ok ok")) return -1
|
||||||
|
}
|
||||||
|
return 42
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
)
|
||||||
|
for text in cases:
|
||||||
|
source.write_text(textwrap.dedent(writer + text).strip() + "\n")
|
||||||
|
check(source, executable, (b"42\n", None))
|
||||||
|
errors = (
|
||||||
|
('"{"', '{}', b"unmatched '{'"),
|
||||||
|
('"}"', '{}', b"unmatched '}'"),
|
||||||
|
('"{q}"', '{42}', b"unknown specifier"),
|
||||||
|
('"{dd}"', '{42}', b"one-character specifier"),
|
||||||
|
('"{}"', '{}', b"argument count does not match the tuple"),
|
||||||
|
('"plain"', '{42}', b"argument count does not match the tuple"),
|
||||||
|
('"{}"', '{value = 42}', b"arguments must be a tuple"),
|
||||||
|
('"{}"', '42', b"arguments must be a tuple"),
|
||||||
|
('"{d}"', '{true}', b"requires an integer or float argument"),
|
||||||
|
('"{b}"', '{true}', b"requires an integer argument"),
|
||||||
|
('"{c}"', '{u16(65)}', b"requires an unsigned integer that fits in u8"),
|
||||||
|
('"{}"', '{{ value = 42 }}', b"does not support this argument type"),
|
||||||
|
)
|
||||||
|
for format_value, args, diagnostic in errors:
|
||||||
|
source.write_text(textwrap.dedent(writer + f"""
|
||||||
|
main proc() i16 ! io.WriteError {{
|
||||||
|
buffer [64]mut u8 := undefined
|
||||||
|
state State := {{ bytes = buffer[..] }}
|
||||||
|
try io.print({format_value}, output(&state), {args})
|
||||||
|
return 42
|
||||||
|
}}
|
||||||
|
""").strip() + "\n")
|
||||||
|
check(source, executable, (b"", diagnostic))
|
||||||
|
source.write_text(textwrap.dedent(writer + """
|
||||||
|
Distance :: distinct f64
|
||||||
|
main proc() i16 ! io.WriteError {
|
||||||
|
buffer [512]mut u8 := undefined
|
||||||
|
state State := { bytes = buffer[..] }
|
||||||
|
try io.print("{} {d} {e}", output(&state), {f64(1.25), Distance(-2.5), f64(0.5)})
|
||||||
|
if (!equal(&state, "1.25 -2.5 5.0000000000000000e-01")) return -1
|
||||||
|
return 42
|
||||||
|
}
|
||||||
|
""").strip() + "\n")
|
||||||
|
interpreted = run(str(HONEY), "run", str(source))
|
||||||
|
assert interpreted.returncode == 1 and interpreted.stdout == b"", outcome(interpreted)
|
||||||
|
assert b"foreign function calls are unsupported by the interpreter" in interpreted.stderr, outcome(interpreted)
|
||||||
|
assert len(interpreted.stderr.splitlines()) == 1, outcome(interpreted)
|
||||||
|
for optimization in ("-O0", "-O2"):
|
||||||
|
built = run(str(HONEY), "build", str(source), "-o", str(executable), optimization)
|
||||||
|
assert outcome(built) == (0, b"", b""), outcome(built)
|
||||||
|
assert outcome(run(str(executable))) == (0, b"42\n", b""), optimization
|
||||||
|
executable.unlink()
|
||||||
|
return len(cases) + len(errors), 1
|
||||||
|
|
||||||
|
|
||||||
|
def check_variadic_libc(temporary, executable):
|
||||||
|
source = temporary / "variadic.hon"
|
||||||
|
source.write_text(textwrap.dedent("""
|
||||||
|
snprintf c_func(buffer ?*mut c_char, size c_ulong, format ?*c_char, ...) c_int
|
||||||
|
main proc() i16 {
|
||||||
|
buffer [256]mut u8 := undefined
|
||||||
|
count :: snprintf(ptrcast!((&buffer).ptr), c_ulong(buffer.len),
|
||||||
|
"%d %d %d %d %d %d %u %lld %llu %.2f %s",
|
||||||
|
true, i8(-128), u8(255), i16(-32768), u16(65535),
|
||||||
|
i32(-2147483648), maxval!(u32), minval!(i64), maxval!(u64),
|
||||||
|
f64(1.25), "ok".ptr)
|
||||||
|
expected :: "1 -128 255 -32768 65535 -2147483648 4294967295 -9223372036854775808 18446744073709551615 1.25 ok"
|
||||||
|
if (count < 0 or usize(count) != expected.len) return -1
|
||||||
|
for expected |byte, index| { if (buffer[index] != byte) return -2 }
|
||||||
|
if (buffer[expected.len] != 0) return -3
|
||||||
|
return 42
|
||||||
|
}
|
||||||
|
""").strip() + "\n")
|
||||||
|
interpreted = run(str(HONEY), "run", str(source))
|
||||||
|
assert interpreted.returncode == 1 and interpreted.stdout == b"", outcome(interpreted)
|
||||||
|
assert b"foreign function calls are unsupported by the interpreter" in interpreted.stderr, outcome(interpreted)
|
||||||
|
assert len(interpreted.stderr.splitlines()) == 1, outcome(interpreted)
|
||||||
|
for optimization in ("-O0", "-O2"):
|
||||||
|
built = run(str(HONEY), "build", str(source), "-o", str(executable), optimization)
|
||||||
|
assert outcome(built) == (0, b"", b""), outcome(built)
|
||||||
|
assert outcome(run(str(executable))) == (0, b"42\n", b""), optimization
|
||||||
|
executable.unlink()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
with tempfile.TemporaryDirectory(prefix="honey native ' ; $") as directory:
|
with tempfile.TemporaryDirectory(prefix="honey native ' ; $") as directory:
|
||||||
temporary = Path(directory)
|
temporary = Path(directory)
|
||||||
@@ -12530,10 +12832,13 @@ def main():
|
|||||||
check(source, executable, (stdout, error))
|
check(source, executable, (stdout, error))
|
||||||
count += 1
|
count += 1
|
||||||
allocator_count = check_allocator_libc(temporary, executable)
|
allocator_count = check_allocator_libc(temporary, executable)
|
||||||
|
variadic_count = check_variadic_libc(temporary, executable)
|
||||||
arraylist_count, arraylist_native_count = check_arraylist_packages(temporary, executable)
|
arraylist_count, arraylist_native_count = check_arraylist_packages(temporary, executable)
|
||||||
count += arraylist_count
|
count += arraylist_count
|
||||||
hashmap_count, hashmap_native_count = check_hashmap_packages(temporary, executable)
|
hashmap_count, hashmap_native_count = check_hashmap_packages(temporary, executable)
|
||||||
count += hashmap_count
|
count += hashmap_count
|
||||||
|
io_count, io_native_count = check_io_packages(temporary, executable)
|
||||||
|
count += io_count
|
||||||
count += check_packages(temporary, executable)
|
count += check_packages(temporary, executable)
|
||||||
count += check_namespace_packages(temporary, executable)
|
count += check_namespace_packages(temporary, executable)
|
||||||
count += check_record_packages(temporary, executable)
|
count += check_record_packages(temporary, executable)
|
||||||
@@ -12560,7 +12865,7 @@ def main():
|
|||||||
assert not executable.exists()
|
assert not executable.exists()
|
||||||
invalid = run(str(HONEY), "build", str(source), "-o", str(executable), "-O3")
|
invalid = run(str(HONEY), "build", str(source), "-o", str(executable), "-O3")
|
||||||
assert invalid.returncode == 2 and b"usage:" in invalid.stderr, outcome(invalid)
|
assert invalid.returncode == 2 and b"usage:" in invalid.stderr, outcome(invalid)
|
||||||
print(f"{count} programs matched interpreter at -O0 and -O2; {allocator_count + arraylist_native_count + hashmap_native_count} native allocator programs, LLVM output and CLI errors passed")
|
print(f"{count} programs matched interpreter at -O0 and -O2; {allocator_count + arraylist_native_count + hashmap_native_count + io_native_count + variadic_count} native allocator/IO programs, LLVM output and CLI errors passed")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user