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.
|
||||
|
||||
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
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
`$(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.
|
||||
|
||||
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.
|
||||
|
||||
`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.
|
||||
|
||||
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/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.
|
||||
|
||||
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 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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
`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.
|
||||
|
||||
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.
|
||||
|
||||
Reference in New Issue
Block a user