Compare commits

...

3 Commits

Author SHA1 Message Date
hl-valdemar c4fa8e930f richer formatting 2026-07-15 21:10:54 +02:00
hl-valdemar 1165cfb7c0 fix comptime specialization, implement io.print 2026-07-15 19:52:14 +02:00
hl-valdemar 0b2055d64b reflection foundation, tuples, debug.print 2026-07-15 19:50:24 +02:00
25 changed files with 127942 additions and 115452 deletions
+11 -8
View File
@@ -150,11 +150,13 @@ as `math.divfloor(a, b)` resolve to ordinary functions.
- demand-monomorphized Brolang and C-ABI functions
- integer comptime value parameters such as `make_array func($N usize) [N]u8`, specialized by value and omitted from the runtime ABI
- explicit comptime type parameters such as `max func($T type, a, b T) T`, specialized by type and omitted from the runtime ABI
- leading comptime type/integer parameters may be omitted when uniquely recoverable from runtime argument types or the immediate expected result; explicit calls remain valid
- comptime parameters must form one leading prefix before all runtime parameters
- comptime parameters may appear anywhere, are erased from the runtime ABI, and accept recursively stable booleans, integers, floats, types, immutable bytes, enums, fixed arrays, records/tuples, optionals, and tagged unions; equal structural values share specializations, while pointers, functions, general slices, fallibles, ranges, untagged unions, and undefined values have no stable comptime identity
- comptime parameters may be omitted when uniquely recoverable from runtime arguments, the immediate expected result, or exact type-factory provenance; `_` is an explicit inference hole
- forced typed comptime expressions such as `$sum(1, 2)`, `$Point { x = 1, y = 2 }`, and comptime value blocks such as `${ yield 4 }`
- comptime execution for bodyful Brolang functions with mutable locals, loops, `defer`, `match`, `try`/`catch`, pointer/slice storage mutation, pointer captures, and calls through comptime-known function values
- comptime execution for bodyful Brolang functions with mutable locals, loops, `defer`, `match`, `try`/`catch`, pointer/slice storage mutation, pointer captures, and calls through comptime-known function values; `undefined` storage may be initialized at comptime, but remaining poison cannot be observed
- comptime type factories such as `Box func($T type) type { return struct { value T } }`; calls like `Box(i32)` are concrete nominal types and may appear anywhere a type is expected
- tuple types are unnamed-field structs (`struct { i32, []u8 }`), tuple values use `{1, "bro"}` / `{1,}` / `{}`, and fields use canonical numeric names such as `.0`
- `typeinfo!`, `field!`, `compile_error!`, and semantic `inline for` provide compile-time record and enum reflection and heterogeneous static expansion without runtime metadata; enum reflection exposes declaration-ordered fields, reflected aggregates remain persistent compile-time values, and inline-loop `break` / `continue` must be selected entirely at comptime
- bodyful `c_func` definitions and bodyless `c_func` declarations with exact external symbol names
- concrete-only C signatures, C variadic declarations/calls, and C default argument promotions
- native function pointer values and types with `@func(...) R`, fallible `@func(...) R ! E`, optional `?@func(...) R`, and non-variadic native indirect calls
@@ -173,7 +175,9 @@ as `math.divfloor(a, b)` resolve to ordinary functions.
- root `std` re-exports `ArrayList(T)` while its operations remain in `std/arraylist`
- `std/mem` generic slice equality, allocator contract with raw byte operations, typed `empty` / `alloc` / `realloc` / `free`, overflow checks, zero-sized-type support, and failure-preserving reallocation
- `std/arraylist` generic `ArrayList(T)` with direct `items` slice access, explicit capacity, allocator ownership, fallible reserve/append, clear, and deinit
- `std/io` explicit `Io` capabilities, `Reader`/`Writer` stream values, one-shot `read`/`write`, and allocation-free `write_all`
- `std/io` explicit `Io` capabilities, `Reader`/`Writer` stream values, allocation-free `write_all`, and comptime-expanded writer-first `print`; formatting supports natural `{}`, byte `{s}`, decimal `{d}`, integer `{b}` / `{o}` / `{x}` / `{X}`, byte-character `{c}`, scientific float `{e}`, and `{{` / `}}`, with malformed formats and incompatible tuple fields rejected at comptime
- entry points are either `main func() ...` or `main func(init process.Init) ...`; `std/process.Init` carries startup capabilities, currently only `io`, while the system provider remains hidden inside `std/io`
- `std/debug.print` is an allocation-free, failure-ignoring stderr escape hatch independent of `process.Init`
### compiler behavior
@@ -181,18 +185,17 @@ as `math.divfloor(a, b)` resolve to ordinary functions.
- lazy semantic checking of demanded function specializations
- static, eager runtime, mutable runtime, and deferred problematic globals with cycle diagnostics
- demand-driven LLVM declarations for referenced foreign functions
- root `main` may be parameterless or accept the canonical `@std/io Io`; the injected form is called through a synthesized no-argument C entry point
- root `main` may be parameterless or accept canonical `@std/process Init`; the generated C entry point obtains the hidden system I/O provider and constructs the init value
- replaceable dynamically loaded libclang C-import backend
- C-header import caching by canonical path, target, include paths, and defines
## PLANNED / DEFERRED
- aggregate comptime parameters and stable aggregate specialization keys
- tuples and native Brolang variadic functions
- native Brolang variadic functions
- exporting Brolang functions to C and broader target-specific C ABI lowering
- non-plain C record layouts such as bitfields, packed records, flexible arrays, qualified fields, and C variadic record arguments
- arenas, pools, build-mode heap policy, and escaping-allocation diagnostics
- recursive type factories, type reflection, and type-producing unions/enums
- recursive type factories, reflection payloads beyond records/enums, and type-producing unions/enums
- broader Zig-style pointer/result casts beyond V1 `ptrcast!(T, ptr)`
- sum-type ABI/layout polish, including dynamic tag-width shrinking, all-void channel collapse, and cross-module global-id determinism
- backed/C enum composition and must-consume fallible linting
+6 -5
View File
@@ -14,12 +14,13 @@ pair that implementation with a stream; `main func() ...` remains valid.
```bro
io :: import "@std/io"
process :: import "@std/process"
main func(system io.Io) void {
io.write_all(io.Writer {
impl = system,
main func(init process.Init) void {
io.print(io.Writer {
impl = init.io,
stream = .stdout,
}, "hello\n") catch |_| {
}, "hello {s} {d}\n", {"bro", 37}) catch |_| {
return
}
}
@@ -202,7 +203,7 @@ Current prototype features:
- Plain imported C structs/unions, fixed arrays, and C function pointer typedefs, including keyed literals, field access, callbacks, and Apple Silicon by-value ABI lowering
- Qualified imported globals and functions with package-aware symbol mangling
- Demand-monomorphized Brolang and C-ABI functions
- Integer and type comptime parameters (`func($N usize) [N]u8`, `func($T type, value T) T`) specialized by explicit or uniquely inferred leading comptime arguments
- Recursively stable comptime values—including booleans, integers, floats, types, immutable bytes, enums, fixed arrays, records/tuples, optionals, and tagged unions—may be interleaved with runtime parameters, are erased from the ABI, and specialize from explicit arguments or exact inference provenance
- Forced typed comptime expressions (`$sum(1, 2)`, `$Point { x = 1, y = 2 }`) and comptime value blocks (`${ yield 4 }`)
- Zig-style comptime type factories returning anonymous native structs (`Box func($T type) type`, used as `Box(i32)`)
- Comptime execution for bodyful Brolang functions with mutable locals, loops, `defer`/`errdefer`, `match`, `try`/`catch`, pointer/slice storage mutation, pointer captures, and calls through comptime-known function values
+43 -12
View File
@@ -686,8 +686,8 @@
evaluation; runtime-dependent values remain invalid in comptime contexts
- runtime-only behavior is rejected in comptime: external/bodyless `c_func`,
writable globals, and materializing comptime storage pointers/slices as runtime memory
- v1 keeps integer-only `$N` specialization keys; aggregate comptime parameters
and stable aggregate serialization are deferred
- milestone 39 extends specialization keys from integer/type/string values to
recursively stable values while keeping runtime ABI erasure unchanged
27.8 source-defined mutable runtime globals (implemented)
- allow mutable global declarations in Brolang source for process-global runtime
@@ -777,17 +777,17 @@
- keep functions package-scoped and keep the explicit form valid; this preserves simple name
resolution and gives ambiguous calls an escape hatch
31.5. inferred leading comptime parameters (implemented)
- a native call may omit its complete leading `$T type` / integer comptime prefix when every
31.5. inferred comptime parameters (implemented)
- comptime parameters may appear anywhere and are erased while preserving runtime parameter order
- a native call may omit `$T type`, integer, or immutable byte-string comptime arguments when every
value is uniquely recoverable from runtime argument types and/or the immediate expected result
- inference structurally matches direct type parameters, pointers/slices/arrays/optionals/
fallibles/functions, direct array counts, and canonical generated type-factory provenance;
forwarding/non-invertible factories keep the explicit spelling
- concrete evidence is exact; contextual numeric constants are weak evidence and are rebuilt with
the resolved parameter type before ordinary coercion
- calls are all-explicit or all-inferred (no partial prefix omission); unconstrained/conflicting
values diagnose with the explicit call as the escape hatch
- comptime parameters must form one leading prefix before every runtime parameter
- `_` explicitly leaves one comptime argument to inference; exactly one complete argument mapping
must succeed, with missing and ambiguous mappings diagnosed
- the existing specialization/HIR/LLVM ABI is unchanged; `std/mem` and `std/arraylist` now use the
inferred form where their arguments or result provide enough information
@@ -809,8 +809,8 @@
- migrated `std/mem`, `std/arraylist`, and the compound-assignment example to `divtrunc!`
33. explicit I/O provider (implemented)
- `main` may take one canonical `@std/io Io`; parameterless entry points remain valid
- the compiler supplies a `hide system` macOS provider through an external no-argument C wrapper
- `main` may take one canonical `@std/process Init`; parameterless entry points remain valid
- the compiler supplies a `hide system` macOS provider and constructs `Init` in the external C wrapper
- readers and writers pair an explicit provider with `stdin`, `stdout`, or `stderr`
- `read` and `write` validate provider counts; `write_all` handles partial writes and no progress
- the system provider uses unbuffered libc `read`/`write`, retries interruption, and allocates nothing
@@ -847,13 +847,44 @@
- `hide` is reserved for named top-level declarations and is rejected on imports, locals,
parameters, fields, and anonymous declarations
37. add a debug package in `std` that provides debugging utilities
- add `debug.print` function making use of `std/io` to print values to the console
- this may either require native variadic arguments or a tuple value to like zig's approach (consider pros and cons)
37. tuples, reflection, process init, and printing (implemented)
- tuples are unnamed-field structs with structural anonymous values, nominal named declarations,
brace literals, numeric fields, and no runtime metadata
- `@std/meta`, `typeinfo!`, `field!`, `compile_error!`, specialization-time branches, and semantic
`inline for` use checker-owned persistent compile-time values for aggregate-first reflection and
heterogeneous static expansion; inline-loop control is recursively resolved at comptime
- interleaved comptime parameters use semantic candidate resolution, immutable byte values specialize
by contents, and all comptime parameters remain erased from the runtime ABI
- `io.print(writer, format, args)` validates and expands `{s}` / `{d}` formatting at comptime,
is implemented in ordinary `@std/io` code, performs no allocation or runtime parsing, and
propagates the first write error
- `debug.print` reuses formatting through an independent stderr backend, ignores failures, and adds
no newline
- entrypoint validation accepts only parameterless `main` or canonical `main(process.Init)`, validates
the hidden `@std/io.system` provider, and injects the provider through the generated C entrypoint;
native variadics and additional startup data remain deferred
38. place every intrinsic behind direct unqualified `name!(...)` syntax, freeing the bare names for
user functions (implemented)
39. stable comptime values and richer formatting (implemented)
- comptime parameters accept booleans, integers, floats, types, immutable bytes,
enums, fixed arrays, records/tuples, optionals, and tagged unions recursively
- canonical specialization keys include deterministic type identity, exact float bits,
byte contents, ordered aggregate children, optional state, and active union variants;
FNV-1a fingerprints accelerate lookup while exact key comparison handles collisions
- equal structural values reuse specializations and stable emitted names, distinct values
specialize separately, aggregate inference uses exact type-factory provenance, and all
comptime parameters remain erased from the runtime ABI
- undefined values, pointers, functions, general slices, fallibles, ranges, and untagged
unions diagnose that they have no stable comptime identity
- `@std/meta.TypeInfo.enum` carries declaration-ordered `EnumInfo.fields`, enabling enum
formatting through `field!` without runtime reflection metadata
- `io.print` and `debug.print` retain their APIs and expand `{}`, `{s}`, `{d}`, `{b}`,
`{o}`, `{x}`, `{X}`, `{c}`, and `{e}` at comptime; `{{` and `}}` remain escapes
- integer output uses one base-aware 65-byte stack buffer; float output uses fixed-buffer
libc `snprintf` with 32-bit and 64-bit general/scientific precision and propagates failure
## A word on unchecked casts
For casts that bypass safety checks, Honey provides builtin functions:
+3
View File
@@ -72,6 +72,7 @@ Expr_Kind :: enum u8 {
Array,
None,
Undefined,
Inference_Hole,
Type,
Name,
Enum_Literal,
@@ -121,6 +122,7 @@ Expr :: struct {
diagnostic: source.Diagnostic_Id,
parenthesized: bool,
intrinsic: bool,
tuple: bool,
kind: Expr_Kind,
}
@@ -170,6 +172,7 @@ Stmt :: struct {
immutable: bool,
value_control_flow: bool,
pointer_capture: bool,
inline: bool,
error_only: bool,
// Assignments store the lvalue in `target`, the right-hand side in `expr`,
// and the source operator in `assignment_op`. `Set` is ordinary `=`;
File diff suppressed because it is too large Load Diff
+742 -15
View File
@@ -7,21 +7,30 @@ import "../symbol"
import "../types"
import "base:intrinsics"
import "core:fmt"
import "core:hash"
import "core:math"
import "core:mem"
import "core:strings"
COMPTIME_EVAL_QUOTA :: 100_000
Comptime_Value_Kind :: enum u8 {
Integer,
Type,
String,
Static,
}
Comptime_Value :: struct {
name: symbol.Id,
type: types.Type,
value: i128,
kind: Comptime_Value_Kind,
name: symbol.Id,
type: types.Type,
value: i128,
text: string,
static_value: Ct_Value_Id,
key: string,
fingerprint: u64,
kind: Comptime_Value_Kind,
}
Constant_Kind :: enum {
@@ -57,6 +66,15 @@ current_comptime_value :: proc(checker: ^Checker, name: symbol.Id) -> (Comptime_
return find_comptime_value(checker.current_comptime_values, name)
}
current_static_binding :: proc(checker: ^Checker, name: symbol.Id) -> (Static_Binding, bool) {
for index := len(checker.static_bindings)-1; index >= 0; index -= 1 {
if checker.static_bindings[index].name == name {
return checker.static_bindings[index], true
}
}
return {}, false
}
current_comptime_type :: proc(checker: ^Checker, name: symbol.Id) -> (types.Type, bool) {
if value, ok := current_comptime_value(checker, name); ok && value.kind == .Type {
return value.type, true
@@ -71,7 +89,9 @@ comptime_values_equal :: proc(left, right: []Comptime_Value) -> bool {
for value, index in left {
other := right[index]
if value.name != other.name || value.kind != other.kind || !types.equal(value.type, other.type) ||
(value.kind == .Integer && value.value != other.value) {
(value.kind == .Integer && value.value != other.value) ||
(value.kind == .String && value.text != other.text) ||
(value.kind == .Static && (value.fingerprint != other.fingerprint || value.key != other.key)) {
return false
}
}
@@ -196,6 +216,7 @@ ct_place_id :: proc(index: int) -> Ct_Place_Id {
Ct_Value_Kind :: enum u8 {
Invalid,
Void,
Undefined,
Integer,
Float,
Bool,
@@ -337,8 +358,25 @@ ct_state_make :: proc(
} else if value.kind == .Type {
id := ct_add_value(&state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(value.type)})
ct_bind_value(&state, value.name, types.INVALID, id, false)
} else if value.kind == .String {
string_id := u64(0)
for text, index in checker.ast_module.strings {
if text == value.text {
string_id = u64(index)
break
}
}
id := ct_add_value(&state, Ct_Value{kind=.String, type=value.type, index=string_id})
ct_bind_value(&state, value.name, value.type, id, false)
} else if value.kind == .Static {
id := ct_clone_graph(&state, &checker.static_state, value.static_value)
ct_bind_value(&state, value.name, value.type, id, false)
}
}
for binding in checker.static_bindings {
id := ct_clone_graph(&state, &checker.static_state, binding.value)
ct_bind_value(&state, binding.name, binding.type, id, false)
}
return state
}
@@ -432,6 +470,34 @@ ct_child_slice :: proc(state: ^Ct_State, value: Ct_Value) -> []Ct_Value_Id {
return state.children[start:end]
}
ct_value_contains_undefined :: proc(state: ^Ct_State, id: Ct_Value_Id, depth := 0) -> bool {
if depth > 64 || id == INVALID_CT_VALUE || int(id) >= len(state.values) {
return false
}
value := state.values[id]
if value.kind == .Undefined {
return true
}
for child in ct_child_slice(state, value) {
if ct_value_contains_undefined(state, child, depth+1) {
return true
}
}
return false
}
ct_observe_value :: proc(state: ^Ct_State, id: Ct_Value_Id, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
if id == INVALID_CT_VALUE || int(id) >= len(state.values) {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
if state.values[id].kind == .Undefined {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
state, .Not_Comptime, span, "cannot read an undefined value at comptime",
)
}
return id, ct_flow(.Normal), true
}
ct_fail :: proc(state: ^Ct_State, kind: Ct_Error_Kind, span: source.Span, message: string) -> bool {
if state.error == .None {
state.error = kind
@@ -543,6 +609,12 @@ ct_coerce_value :: proc(state: ^Ct_State, id: Ct_Value_Id, expected: types.Type,
return id, true
}
store := &state.checker.module.types
if value.kind == .String {
if item, ok := types.node(store, expected); ok && item.kind == .Slice && !item.mutable && item.child == types.U8 {
value.type = expected
return ct_add_value(state, value), true
}
}
if value.kind == .Pointer && types.can_weaken_pointer(value.type, expected, store) {
value.type = expected
return ct_add_value(state, value), true
@@ -761,6 +833,11 @@ ct_materialize_value :: proc(
}
value := state.values[materialized]
#partial switch value.kind {
case .Undefined:
if state.diagnostic == source.INVALID_DIAGNOSTIC {
state.diagnostic = source.add(checker.diagnostics, span, "cannot materialize an undefined comptime value")
}
return invalid_hir_expr(checker, span, state.diagnostic, value.type)
case .Integer:
if types.is_enum(value.type, &checker.module.types) {
int_value := i64(value.integer) if value.integer < 0 else transmute(i64)u64(value.integer)
@@ -881,6 +958,38 @@ ct_materialize_value :: proc(
return invalid_hir_expr(checker, span, source.INVALID_DIAGNOSTIC, value.type)
}
ct_undefined_value :: proc(state: ^Ct_State, value_type: types.Type, depth := 0) -> (Ct_Value_Id, bool) {
if depth > 64 || !types.is_valid(value_type) {
return INVALID_CT_VALUE, false
}
store := &state.checker.module.types
item, ok := types.node(store, value_type)
if ok && item.kind == .Array {
children := make([]Ct_Value_Id, int(item.count), state.checker.allocator)
defer delete(children, state.checker.allocator)
for &child in children {
child, ok = ct_undefined_value(state, item.child, depth+1)
if !ok { return INVALID_CT_VALUE, false }
}
start := u32(len(state.children))
append(&state.children, ..children)
return ct_add_value(state, Ct_Value{kind=.Array, type=value_type, start=start, count=u32(len(children))}), true
}
if ok && item.kind == .Struct && !item.opaque {
fields := types.fields_for(store, value_type)
children := make([]Ct_Value_Id, len(fields), state.checker.allocator)
defer delete(children, state.checker.allocator)
for field, index in fields {
children[index], ok = ct_undefined_value(state, field.type, depth+1)
if !ok { return INVALID_CT_VALUE, false }
}
start := u32(len(state.children))
append(&state.children, ..children)
return ct_add_value(state, Ct_Value{kind=.Struct, type=value_type, start=start, count=u32(len(children))}), true
}
return ct_add_value(state, Ct_Value{kind=.Undefined, type=value_type}), true
}
ct_eval_expr :: proc(
state: ^Ct_State,
expr_id: ast.Expr_Id,
@@ -914,13 +1023,23 @@ ct_eval_expr :: proc(
case .Name:
if !symbol.is_valid(expr.qualifier) {
if index, ok := ct_find_binding_index(state, expr.name); ok {
return ct_binding_value(state, index), ct_flow(.Normal), true
return ct_observe_value(state, ct_binding_value(state, index), expr.span)
}
if value, ok := current_comptime_value(checker, expr.name); ok {
if value.kind == .Integer {
id := ct_add_value(state, Ct_Value{kind=.Integer, type=value.type, integer=value.value})
return id, ct_flow(.Normal), true
}
if value.kind == .String {
string_id := u64(0)
for text, index in checker.ast_module.strings {
if text == value.text {
string_id = u64(index)
break
}
}
return ct_add_value(state, Ct_Value{kind=.String, type=value.type, index=string_id}), ct_flow(.Normal), true
}
return ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(value.type)}), ct_flow(.Normal), true
}
} else if find_import(checker, state.file, expr.qualifier) == ast.INVALID_IMPORT {
@@ -1025,7 +1144,10 @@ ct_eval_expr :: proc(
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime slice index out of bounds")
}
value, value_ok := ct_place_get(state, place)
return value, ct_flow(.Normal), value_ok
if !value_ok {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
return ct_observe_value(state, value, expr.span)
}
if base.kind == .Pointer {
pointer_item, pointer_ok := types.node(store, base.type)
@@ -1036,7 +1158,10 @@ ct_eval_expr :: proc(
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime pointer index out of bounds")
}
value, value_ok := ct_place_get(state, place)
return value, ct_flow(.Normal), value_ok
if !value_ok {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
return ct_observe_value(state, value, expr.span)
}
if array_item, array_ok := types.node(store, pointer_item.child); array_ok && array_item.kind == .Array {
base_place, _, _ := ct_pointer_place(state, base)
@@ -1045,7 +1170,10 @@ ct_eval_expr :: proc(
}
place := ct_extend_place(state, base_place, Ct_Path_Elem{kind=.Index, index=u32(index_value)}, array_item.child, pointer_item.mutable && array_item.mutable)
value, value_ok := ct_place_get(state, place)
return value, ct_flow(.Normal), value_ok
if !value_ok {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
return ct_observe_value(state, value, expr.span)
}
}
}
@@ -1161,10 +1289,15 @@ ct_eval_expr :: proc(
if !value_ok {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "comptime pointer no longer points to live storage")
}
return value, ct_flow(.Normal), true
return ct_observe_value(state, value, expr.span)
case .Slice:
return ct_eval_slice_expr(state, expr, depth+1)
case .Undefined, .Keyed:
case .Undefined:
if value, ok := ct_undefined_value(state, expected); ok {
return value, ct_flow(.Normal), true
}
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "undefined comptime value requires a concrete scalar or aggregate type")
case .Keyed:
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "expression cannot be evaluated at comptime")
}
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "expression cannot be evaluated at comptime")
@@ -1237,9 +1370,49 @@ ct_eval_struct_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Ty
target_pkg, available := expr_package(checker, expr, state.pkg, state.file, false)
struct_type = types.find_named(store, u32(target_pkg), u32(expr.name), file=u32(expr_lookup_file(expr, state.file))) if available else types.INVALID
struct_type = types.resolve_alias(struct_type, store)
} else if expr.tuple {
struct_type = types.INVALID
} else {
struct_type = types.resolve_alias(expected, store)
}
if expr.tuple {
resolved := types.is_valid(struct_type)
fields := types.fields_for(store, struct_type) if resolved else nil
if resolved {
item, item_ok := types.node(store, struct_type)
if !item_ok || item.kind != .Struct || !item.tuple || len(fields) != len(expr.args) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "invalid tuple construction")
}
}
values := make([]Ct_Value_Id, len(expr.args), checker.allocator)
inferred_fields := make([]types.Field, len(expr.args), checker.allocator)
defer {
delete(values, checker.allocator)
delete(inferred_fields, checker.allocator)
}
for arg, index in expr.args {
expected_field := fields[index].type if resolved else types.INVALID
value, flow, ok := ct_eval_expr(state, arg, expected_field, depth+1)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
if resolved {
value, ok = ct_coerce_value(state, value, expected_field, checker.ast_module.exprs[arg].span)
if !ok {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
} else {
inferred_fields[index].type = state.values[value].type
}
values[index] = value
}
if !resolved {
struct_type = types.struct_anonymous(store, inferred_fields, true)
}
start := u32(len(state.children))
append(&state.children, ..values)
return ct_add_value(state, Ct_Value{kind=.Struct, type=struct_type, start=start, count=u32(len(values))}), ct_flow(.Normal), true
}
if !types.is_record(struct_type, store) || types.is_opaque_struct(struct_type, store) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown or opaque record type '%s'", symbol_text(checker, expr.name))
}
@@ -1478,7 +1651,7 @@ ct_eval_field_value :: proc(state: ^Ct_State, base_id: Ct_Value_Id, name: symbol
}
field_place := ct_extend_place(state, place, Ct_Path_Elem{kind=.Field, index=u32(index)}, field.type, false)
if value, value_ok := ct_place_get(state, field_place); value_ok {
return value, ct_flow(.Normal), true
return ct_observe_value(state, value, span)
}
}
}
@@ -1494,12 +1667,39 @@ ct_eval_field_value :: proc(state: ^Ct_State, base_id: Ct_Value_Id, name: symbol
if int(base.active) != index || len(children) == 0 || children[0] == INVALID_CT_VALUE {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, span, "variant '%s' has no payload to read", field_name)
}
return children[0], ct_flow(.Normal), true
return ct_observe_value(state, children[0], span)
}
if index < 0 || index >= len(children) || types.is_void(field.type) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, span, "field '%s' has no value", field_name)
}
return children[index], ct_flow(.Normal), true
return ct_observe_value(state, children[index], span)
}
ct_eval_tuple_field_value :: proc(state: ^Ct_State, base_id: Ct_Value_Id, index: u64, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
if base_id == INVALID_CT_VALUE || int(base_id) >= len(state.values) {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
base := state.values[base_id]
base_type := base.type
if base.kind == .Pointer {
place, child, _ := ct_pointer_place(state, base)
field_index, field, ok := find_tuple_field(state.checker, child, index)
if place == INVALID_CT_PLACE || !ok {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "tuple field index is out of bounds")
}
field_place := ct_extend_place(state, place, Ct_Path_Elem{kind=.Field, index=u32(field_index)}, field.type, false)
value, value_ok := ct_place_get(state, field_place)
if !value_ok {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
return ct_observe_value(state, value, span)
}
field_index, _, ok := find_tuple_field(state.checker, base_type, index)
children := ct_child_slice(state, base)
if !ok || field_index < 0 || field_index >= len(children) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "tuple field index is out of bounds")
}
return ct_observe_value(state, children[field_index], span)
}
ct_eval_index_value :: proc(state: ^Ct_State, base_id: Ct_Value_Id, index: int, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
@@ -1513,7 +1713,7 @@ ct_eval_index_value :: proc(state: ^Ct_State, base_id: Ct_Value_Id, index: int,
if index < 0 || index >= len(children) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime array index out of bounds")
}
return children[index], ct_flow(.Normal), true
return ct_observe_value(state, children[index], span)
}
if base.kind == .String {
if base.index >= u64(len(checker.ast_module.strings)) || index < 0 || index >= len(checker.ast_module.strings[base.index]) {
@@ -2045,6 +2245,205 @@ ct_scalar_cast :: proc(state: ^Ct_State, id: Ct_Value_Id, target: types.Type, sp
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "scalar cast requires numeric scalar types")
}
intern_comptime_string :: proc(checker: ^Checker, text: string) -> u64 {
string_id := u64(len(checker.ast_module.strings))
for value, index in checker.ast_module.strings {
if value == text {
string_id = u64(index)
break
}
}
if string_id == u64(len(checker.ast_module.strings)) {
append(&checker.ast_module.strings, strings.clone(text, checker.ast_module.allocator))
append(&checker.module.strings, strings.clone(text, checker.allocator))
}
return string_id
}
ct_reflection_string :: proc(state: ^Ct_State, text: string) -> Ct_Value_Id {
checker := state.checker
string_id := intern_comptime_string(checker, text)
return ct_add_value(state, Ct_Value{
kind=.String,
type=types.slice(&checker.module.types, types.U8, false),
index=string_id,
})
}
ct_value_bytes :: proc(state: ^Ct_State, id: Ct_Value_Id) -> (string, bool) {
if id == INVALID_CT_VALUE || int(id) >= len(state.values) {
return "", false
}
value := state.values[id]
if value.kind == .String {
if value.index < u64(len(state.checker.ast_module.strings)) {
return state.checker.ast_module.strings[value.index], true
}
return "", false
}
item, ok := types.container(value.type, &state.checker.module.types)
if !ok || item.child != types.U8 || item.mutable ||
(value.kind != .Array && value.kind != .Slice) {
return "", false
}
count := int(value.count)
bytes := make([]u8, count, state.checker.allocator)
defer delete(bytes, state.checker.allocator)
for index in 0..<count {
child := INVALID_CT_VALUE
if value.kind == .Array {
children := ct_child_slice(state, value)
if index >= len(children) { return "", false }
child = children[index]
} else {
place, _, place_ok := ct_slice_element_place(state, value, index)
if !place_ok { return "", false }
child, place_ok = ct_place_get(state, place)
if !place_ok { return "", false }
}
integer, integer_ok := ct_integer_value(state, child)
if !integer_ok || integer < 0 || integer > 255 { return "", false }
bytes[index] = u8(integer)
}
string_id := intern_comptime_string(state.checker, string(bytes))
return state.checker.ast_module.strings[string_id], true
}
ct_struct_value :: proc(state: ^Ct_State, value_type: types.Type, children: []Ct_Value_Id) -> Ct_Value_Id {
start := u32(len(state.children))
append(&state.children, ..children)
return ct_add_value(state, Ct_Value{kind=.Struct, type=value_type, start=start, count=u32(len(children))})
}
ct_typeinfo_value :: proc(state: ^Ct_State, target: types.Type, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
checker := state.checker
store := &checker.module.types
typeinfo_type := std_named_type(checker, "@std/meta", "TypeInfo")
fieldinfo_type := std_named_type(checker, "@std/meta", "FieldInfo")
recordinfo_type := std_named_type(checker, "@std/meta", "RecordInfo")
enuminfo_type := std_named_type(checker, "@std/meta", "EnumInfo")
layout_type := std_named_type(checker, "@std/meta", "Layout")
if !types.is_valid(typeinfo_type) || !types.is_valid(fieldinfo_type) ||
!types.is_valid(recordinfo_type) || !types.is_valid(enuminfo_type) || !types.is_valid(layout_type) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
state, .Not_Comptime, span,
"typeinfo! requires importing @std/meta",
)
}
resolved := types.resolve_alias(target, store)
item, item_ok := types.node(store, resolved)
tag := "invalid"
if !item_ok {
if types.is_void(resolved) { tag = "void" }
else if types.is_anyopaque(resolved) { tag = "anyopaque" }
else if types.is_bool(resolved) { tag = "bool" }
else if types.is_concrete_integer(resolved) { tag = "integer" }
else if types.is_float(resolved, checker.target) { tag = "float" }
} else {
#partial switch item.kind {
case .Array: tag = "array"
case .Pointer: tag = "pointer"
case .Slice: tag = "slice"
case .Range: tag = "range"
case .Optional: tag = "optional"
case .Function: tag = "function"
case .Enum: tag = "enum"
case .Struct: tag = "record"
case .Union: tag = "union"
case .Fallible: tag = "fallible"
case .Distinct: tag = "distinct"
case .Alias:
tag = "invalid"
case:
tag = "invalid"
}
}
variant_index, _, variant_ok := find_struct_field(checker, typeinfo_type, symbol.intern(checker.symbols, tag))
if !variant_ok {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "@std/meta TypeInfo is malformed")
}
if tag == "enum" {
members := types.enum_members_for(store, resolved)
field_values := make([]Ct_Value_Id, len(members), checker.allocator)
defer delete(field_values, checker.allocator)
for member, index in members {
name_value := ct_reflection_string(state, symbol_text(checker, symbol.Id(member.name)))
type_value := ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(resolved)})
index_value := ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(index)})
field_values[index] = ct_struct_value(
state, fieldinfo_type, []Ct_Value_Id{name_value, type_value, index_value},
)
}
fields_start := u32(len(state.children))
append(&state.children, ..field_values)
fields_type := types.array(store, fieldinfo_type, u64(len(field_values)), false)
fields_value := ct_add_value(state, Ct_Value{
kind=.Array, type=fields_type, start=fields_start, count=u32(len(field_values)),
})
enum_value := ct_struct_value(state, enuminfo_type, []Ct_Value_Id{fields_value})
payload_start := u32(len(state.children))
append(&state.children, enum_value)
return ct_add_value(state, Ct_Value{
kind=.Struct, type=typeinfo_type, start=payload_start, count=1, active=i64(variant_index),
}), ct_flow(.Normal), true
}
if tag != "record" {
start := u32(len(state.children))
return ct_add_value(state, Ct_Value{
kind=.Struct, type=typeinfo_type, start=start, count=0, active=i64(variant_index),
}), ct_flow(.Normal), true
}
fields := types.fields_for(store, resolved)
field_values := make([]Ct_Value_Id, len(fields), checker.allocator)
defer delete(field_values, checker.allocator)
for field, index in fields {
name := ""
if item.tuple {
name = fmt.aprintf("%d", index, allocator=checker.allocator)
} else {
name = symbol_text(checker, symbol.Id(field.name))
}
name_value := ct_reflection_string(state, name)
if item.tuple {
delete(name, checker.allocator)
}
type_value := ct_add_value(state, Ct_Value{kind=.Type, type=types.INVALID, index=u64(field.type)})
index_value := ct_add_value(state, Ct_Value{kind=.Integer, type=types.USIZE, integer=i128(index)})
children := []Ct_Value_Id{name_value, type_value, index_value}
field_values[index] = ct_struct_value(state, fieldinfo_type, children)
}
fields_start := u32(len(state.children))
append(&state.children, ..field_values)
fields_type := types.array(store, fieldinfo_type, u64(len(field_values)), false)
fields_value := ct_add_value(state, Ct_Value{
kind=.Array, type=fields_type, start=fields_start, count=u32(len(field_values)),
})
name_optional_type := types.optional(store, types.slice(store, types.U8, false))
record_name := ct_add_value(state, Ct_Value{kind=.None, type=name_optional_type})
if item.name != 0 {
name_value := ct_reflection_string(state, symbol_text(checker, symbol.Id(item.name)))
name_start := u32(len(state.children))
append(&state.children, name_value)
record_name = ct_add_value(state, Ct_Value{
kind=.Optional_Some, type=name_optional_type, start=name_start, count=1,
})
}
tuple_value := ct_add_value(state, Ct_Value{kind=.Bool, type=types.BOOL, integer=1 if item.tuple else 0})
layout_name := symbol.intern(checker.symbols, "c" if item.c_layout else "auto")
layout_member, layout_ok := find_enum_member(checker, layout_type, layout_name)
if !layout_ok {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "@std/meta Layout is malformed")
}
layout_value := ct_add_value(state, Ct_Value{kind=.Integer, type=layout_type, integer=layout_member.value})
record_children := []Ct_Value_Id{record_name, fields_value, tuple_value, layout_value}
record_value := ct_struct_value(state, recordinfo_type, record_children)
payload_start := u32(len(state.children))
append(&state.children, record_value)
return ct_add_value(state, Ct_Value{
kind=.Struct, type=typeinfo_type, start=payload_start, count=1, active=i64(variant_index),
}), ct_flow(.Normal), true
}
ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) {
checker := state.checker
if expr.left != ast.INVALID_EXPR {
@@ -2057,6 +2456,65 @@ ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type
}
return ct_eval_template_call(state, ast.Function_Id(u32(state.values[callee].index)), expr.args, expr.span, expected, depth+1)
}
if is_intrinsic_call(checker, expr, "compile_error") {
message := "compile_error! requires one comptime string argument"
if len(expr.args) == 1 {
value, flow, ok := ct_eval_expr(state, expr.args[0], types.INVALID, depth+1)
if ok && flow.kind == .Normal {
if text, text_ok := ct_value_bytes(state, value); text_ok {
message = text
}
}
}
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, message)
}
if is_intrinsic_call(checker, expr, "field") {
if len(expr.args) != 2 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "field! expects 2 arguments, got %d", len(expr.args))
}
if enum_type, ok := resolve_type_argument(checker, expr.args[0], state.pkg, state.file);
ok && types.is_enum(enum_type, &checker.module.types) {
name_value, name_flow, name_ok := ct_eval_expr(state, expr.args[1], types.INVALID, depth+1)
if !name_ok || name_flow.kind != .Normal {
return INVALID_CT_VALUE, name_flow, name_ok
}
name, text_ok := ct_value_bytes(state, name_value)
if !text_ok {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "field! name must be a comptime immutable byte string")
}
member, member_ok := find_enum_member(checker, enum_type, symbol.intern(checker.symbols, name))
if !member_ok {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "unknown enum member '%s'", name)
}
return ct_add_value(state, Ct_Value{kind=.Integer, type=enum_type, integer=member.value}), ct_flow(.Normal), true
}
base, flow, ok := ct_eval_expr(state, expr.args[0], types.INVALID, depth+1)
if !ok || flow.kind != .Normal {
return INVALID_CT_VALUE, flow, ok
}
name_value, name_flow, name_ok := ct_eval_expr(state, expr.args[1], types.INVALID, depth+1)
if !name_ok || name_flow.kind != .Normal {
return INVALID_CT_VALUE, name_flow, name_ok
}
name, text_ok := ct_value_bytes(state, name_value)
if !text_ok {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "field! name must be a comptime immutable byte string")
}
if index, numeric := canonical_decimal_index(name); numeric {
return ct_eval_tuple_field_value(state, base, index, expr.span)
}
return ct_eval_field_value(state, base, symbol.intern(checker.symbols, name), expr.span)
}
if is_intrinsic_call(checker, expr, "typeinfo") {
if len(expr.args) != 1 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "typeinfo! expects 1 argument, got %d", len(expr.args))
}
target, ok := resolve_type_argument(checker, expr.args[0], state.pkg, state.file)
if !ok {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "typeinfo! argument must be a type")
}
return ct_typeinfo_value(state, target, expr.span)
}
if builtin := type_builtin_call(checker, expr); builtin != .None {
if len(expr.args) != 1 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, expr.span, "%s! expects 1 argument, got %d", symbol_text(checker, expr.name), len(expr.args))
@@ -2173,6 +2631,12 @@ ct_eval_template_call :: proc(
if !ok {
return INVALID_CT_VALUE, ct_flow(.Normal), false
}
if ct_value_contains_undefined(state, value) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
state, .Not_Comptime, checker.ast_module.exprs[args[index]].span,
"cannot pass an undefined value at comptime",
)
}
append(&runtime_values, value)
append(&runtime_types, param_type)
append(&runtime_names, param.name)
@@ -2210,6 +2674,11 @@ ct_eval_template_call :: proc(
return INVALID_CT_VALUE, ct_flow(.Normal), ct_failf(state, .Not_Comptime, span, "comptime function '%s' did not return a value", symbol_text(checker, function.name))
}
result := flow.value
if ct_value_contains_undefined(state, result) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(
state, .Not_Comptime, span, "comptime function returned an undefined value",
)
}
if ct_value_references_dead_storage(state, result) {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, span, "comptime function returned a pointer to expired storage")
}
@@ -2234,6 +2703,264 @@ ct_clone_value :: proc(dst, src: ^Ct_State, id: Ct_Value_Id) -> Ct_Value_Id {
return ct_add_value(dst, value)
}
Ct_Clone_Context :: struct {
dst, src: ^Ct_State,
values: []Ct_Value_Id,
cells: []Ct_Cell_Id,
places: []Ct_Place_Id,
}
ct_clone_graph_value :: proc(ctx: ^Ct_Clone_Context, id: Ct_Value_Id) -> Ct_Value_Id {
if id == INVALID_CT_VALUE || int(id) >= len(ctx.src.values) {
return INVALID_CT_VALUE
}
if ctx.values[id] != INVALID_CT_VALUE {
return ctx.values[id]
}
value := ctx.src.values[id]
children := ct_child_slice(ctx.src, value)
value.start = 0
value.count = 0
dst_id := ct_add_value(ctx.dst, value)
ctx.values[id] = dst_id
if len(children) > 0 {
cloned := make([]Ct_Value_Id, len(children), ctx.dst.checker.allocator)
defer delete(cloned, ctx.dst.checker.allocator)
for child, index in children {
cloned[index] = ct_clone_graph_value(ctx, child)
}
start := u32(len(ctx.dst.children))
append(&ctx.dst.children, ..cloned)
ctx.dst.values[dst_id].start = start
ctx.dst.values[dst_id].count = u32(len(children))
}
if value.kind == .Pointer || value.kind == .Slice {
ctx.dst.values[dst_id].index = u64(ct_clone_graph_place(ctx, Ct_Place_Id(value.index)))
}
return dst_id
}
ct_clone_graph_cell :: proc(ctx: ^Ct_Clone_Context, id: Ct_Cell_Id) -> Ct_Cell_Id {
if id == INVALID_CT_CELL || int(id) >= len(ctx.src.cells) {
return INVALID_CT_CELL
}
if ctx.cells[id] != INVALID_CT_CELL {
return ctx.cells[id]
}
cell := ctx.src.cells[id]
cell.value = INVALID_CT_VALUE
dst_id := ct_cell_id(len(ctx.dst.cells))
append(&ctx.dst.cells, cell)
ctx.cells[id] = dst_id
ctx.dst.cells[dst_id].value = ct_clone_graph_value(ctx, ctx.src.cells[id].value)
return dst_id
}
ct_clone_graph_place :: proc(ctx: ^Ct_Clone_Context, id: Ct_Place_Id) -> Ct_Place_Id {
if id == INVALID_CT_PLACE || int(id) >= len(ctx.src.places) {
return INVALID_CT_PLACE
}
if ctx.places[id] != INVALID_CT_PLACE {
return ctx.places[id]
}
place := ctx.src.places[id]
path := ct_place_path(ctx.src, place)
place.start = u32(len(ctx.dst.paths))
place.count = u32(len(path))
append(&ctx.dst.paths, ..path)
place.cell = INVALID_CT_CELL
dst_id := Ct_Place_Id(len(ctx.dst.places))
append(&ctx.dst.places, place)
ctx.places[id] = dst_id
ctx.dst.places[dst_id].cell = ct_clone_graph_cell(ctx, ctx.src.places[id].cell)
return dst_id
}
ct_clone_graph :: proc(dst, src: ^Ct_State, id: Ct_Value_Id) -> Ct_Value_Id {
ctx := Ct_Clone_Context{
dst=dst,
src=src,
values=make([]Ct_Value_Id, len(src.values), dst.checker.allocator),
cells=make([]Ct_Cell_Id, len(src.cells), dst.checker.allocator),
places=make([]Ct_Place_Id, len(src.places), dst.checker.allocator),
}
defer {
delete(ctx.values, dst.checker.allocator)
delete(ctx.cells, dst.checker.allocator)
delete(ctx.places, dst.checker.allocator)
}
for &value in ctx.values { value = INVALID_CT_VALUE }
for &cell in ctx.cells { cell = INVALID_CT_CELL }
for &place in ctx.places { place = INVALID_CT_PLACE }
return ct_clone_graph_value(&ctx, id)
}
store_static_binding :: proc(checker: ^Checker, source: ^Ct_State, id: Ct_Value_Id, name: symbol.Id) -> Static_Binding {
value := ct_clone_graph(&checker.static_state, source, id)
value_type := types.INVALID
if value != INVALID_CT_VALUE && int(value) < len(checker.static_state.values) {
value_type = checker.static_state.values[value].type
}
return Static_Binding{name=name, type=value_type, value=value}
}
ct_write_comptime_key :: proc(state: ^Ct_State, id: Ct_Value_Id, builder: ^strings.Builder, depth := 0) -> bool {
if depth > 256 || id == INVALID_CT_VALUE || int(id) >= len(state.values) {
return false
}
value := state.values[id]
fmt.sbprintf(builder, "t%d:", value.type)
#partial switch value.kind {
case .Integer:
fmt.sbprintf(builder, "i%d;", value.integer)
return true
case .Float:
if types.bits(value.type, state.checker.target) == 32 {
fmt.sbprintf(builder, "f%08x;", transmute(u32)f32(value.float))
} else {
fmt.sbprintf(builder, "f%016x;", transmute(u64)value.float)
}
return true
case .Bool:
fmt.sbprintf(builder, "b%d;", value.integer)
return true
case .Type:
fmt.sbprintf(builder, "y%d;", value.index)
return true
case .String:
if value.index >= u64(len(state.checker.ast_module.strings)) {
return false
}
text := state.checker.ast_module.strings[value.index]
fmt.sbprintf(builder, "s%d:", len(text))
hex := "0123456789abcdef"
for byte in transmute([]byte)text {
strings.write_byte(builder, hex[byte>>4])
strings.write_byte(builder, hex[byte&0xf])
}
strings.write_byte(builder, ';')
return true
case .Slice:
item, item_ok := types.container(value.type, &state.checker.module.types)
if !item_ok || item.kind != .Slice || item.mutable || item.child != types.U8 {
return false
}
text, text_ok := ct_value_bytes(state, id)
if !text_ok {
return false
}
fmt.sbprintf(builder, "s%d:", len(text))
hex := "0123456789abcdef"
for byte in transmute([]byte)text {
strings.write_byte(builder, hex[byte>>4])
strings.write_byte(builder, hex[byte&0xf])
}
strings.write_byte(builder, ';')
return true
case .Array:
children := ct_child_slice(state, value)
fmt.sbprintf(builder, "a%d[", len(children))
for child in children {
if !ct_write_comptime_key(state, child, builder, depth+1) {
return false
}
}
strings.write_string(builder, "];")
return true
case .Struct:
if types.is_union(value.type, &state.checker.module.types) &&
!types.is_tagged_union(value.type, &state.checker.module.types) {
return false
}
children := ct_child_slice(state, value)
fmt.sbprintf(builder, "r%d:%d[", value.active, len(children))
for child in children {
if child != INVALID_CT_VALUE && !ct_write_comptime_key(state, child, builder, depth+1) {
return false
}
}
strings.write_string(builder, "];")
return true
case .None:
strings.write_string(builder, "n;")
return true
case .Optional_Some:
children := ct_child_slice(state, value)
if len(children) != 1 || !ct_write_comptime_key(state, children[0], builder, depth+1) {
return false
}
strings.write_string(builder, "o;")
return true
case .Invalid, .Void, .Undefined, .Range, .Pointer, .Function, .Fallible:
return false
}
return false
}
eval_static_comptime_value :: proc(
checker: ^Checker,
name: symbol.Id,
expr: ast.Expr_Id,
declared: types.Type,
pkg: ast.Package_Id,
file: ast.File_Id,
values: []Comptime_Value = nil,
diagnose := false,
) -> (Comptime_Value, bool) {
state := ct_state_make(checker, pkg, file, values=values, diagnose=false)
defer ct_state_destroy(&state)
id, flow, ok := ct_eval_expr(&state, expr, declared, 0)
if ok && flow.kind == .Normal {
id, ok = ct_coerce_value(&state, id, declared, checker.ast_module.exprs[expr].span)
}
if !ok || flow.kind != .Normal || ct_value_contains_undefined(&state, id) {
if diagnose {
source.add(
checker.diagnostics,
checker.ast_module.exprs[expr].span,
"comptime argument has no stable comptime identity",
)
}
return {}, false
}
builder := strings.builder_make(checker.allocator)
defer strings.builder_destroy(&builder)
if !ct_write_comptime_key(&state, id, &builder) {
if diagnose {
source.add(
checker.diagnostics,
checker.ast_module.exprs[expr].span,
"comptime argument has no stable comptime identity",
)
}
return {}, false
}
key_view := strings.to_string(builder)
static_value := INVALID_CT_VALUE
key := ""
for existing, index in checker.comptime_keys {
if existing == key_view {
key = existing
static_value = checker.comptime_static_values[index]
break
}
}
if static_value == INVALID_CT_VALUE {
key = strings.clone(key_view, checker.allocator)
static_value = ct_clone_graph(&checker.static_state, &state, id)
append(&checker.comptime_keys, key)
append(&checker.comptime_static_values, static_value)
}
return Comptime_Value{
name=name,
type=declared,
static_value=static_value,
key=key,
fingerprint=hash.fnv64a(transmute([]byte)key),
kind=.Static,
}, true
}
ct_eval_try_expr :: proc(state: ^Ct_State, expr: ast.Expr, depth: int) -> (Ct_Value_Id, Ct_Flow, bool) {
if state.defer_depth > 0 {
return INVALID_CT_VALUE, ct_flow(.Normal), ct_fail(state, .Not_Comptime, expr.span, "cannot 'try' inside a 'defer'")
+25 -3
View File
@@ -630,13 +630,18 @@ lower_compound_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instructi
lower_expr :: proc(state: ^State, expr_id: hir.Expr_Id) -> ir.Instruction_Id {
stack := state.expr_stack
state.expr_stack = nil
clear_dynamic_array(&stack)
defer {
for frame in stack {
delete(frame.args, state.allocator)
}
clear_dynamic_array(&stack)
state.expr_stack = stack
if state.expr_stack == nil {
state.expr_stack = stack
} else {
delete(stack)
}
}
append(&stack, Lower_Expr_Frame{expr=expr_id})
last := ir.INVALID_INSTRUCTION
@@ -1656,12 +1661,29 @@ append_injected_main :: proc(module: ^ir.Module, hir_module: ^hir.Module, alloca
b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
main_function := &hir_module.functions[main_index]
param_index, param_ok := hir.index(main_function.params[0], hir.INVALID_LOCAL, len(main_function.locals))
if !param_ok {
return
}
init_args := make([]ir.Instruction_Id, 1, allocator)
init_args[0] = provider_call
init_value := ir.instruction_id(len(instructions))
append(&instructions, ir.Instruction{
op=.Aggregate,
type=main_function.locals[param_index].type,
args=init_args,
target=ir.INVALID_REF,
a=ir.INVALID_INSTRUCTION,
b=ir.INVALID_INSTRUCTION,
diagnostic=source.INVALID_DIAGNOSTIC,
})
args := make([]ir.Instruction_Id, 1, allocator)
args[0] = provider_call
args[0] = init_value
main_call := ir.instruction_id(len(instructions))
append(&instructions, ir.Instruction{
op=.Call,
type=hir_module.functions[main_index].result,
type=main_function.result,
args=args,
target=ir.function_ref(ir.Function_Id(main_index)),
a=ir.INVALID_INSTRUCTION,
+186 -19
View File
@@ -497,7 +497,17 @@ parse_call_args :: proc(parser: ^Parser, nesting: int) -> ([]ast.Expr_Id, token.
args.allocator = parser.module.allocator
skip_newlines(parser)
for current(parser).kind != .Right_Paren && current(parser).kind != .Eof {
append(&args, parse_expression_bp(parser, 0, nesting+1))
if hole, ok := allow(parser, .Underscore); ok {
append(&args, add_expr(parser, ast.Expr{
kind=.Inference_Hole,
span=hole.span,
left=ast.INVALID_EXPR,
right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
}))
} else {
append(&args, parse_expression_bp(parser, 0, nesting+1))
}
skip_newlines(parser)
if _, ok := allow(parser, .Comma); ok {
skip_newlines(parser)
@@ -612,6 +622,74 @@ parse_keyed_initializers :: proc(
return args[:], right_brace
}
parse_positional_initializers :: proc(
parser: ^Parser,
left_brace: token.Token,
nesting: int,
close_message: string,
) -> ([]ast.Expr_Id, token.Token) {
parser.delimiter_depth += 1
defer parser.delimiter_depth -= 1
args: [dynamic]ast.Expr_Id
args.allocator = parser.module.allocator
skip_newlines(parser)
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
append(&args, parse_expression_bp(parser, 0, nesting+1))
skip_newlines(parser)
if _, ok := allow(parser, .Comma); ok {
skip_newlines(parser)
continue
}
break
}
right_brace, ok := allow(parser, .Right_Brace)
if !ok {
source.add(parser.diagnostics, current(parser).span, close_message)
right_brace = left_brace
}
return args[:], right_brace
}
brace_starts_tuple :: proc(parser: ^Parser) -> bool {
if current(parser).kind != .Left_Brace {
return false
}
depth := 0
for cursor := parser.cursor; cursor < len(parser.tokens.items); cursor += 1 {
#partial switch parser.tokens.items[cursor].kind {
case .Left_Brace, .Left_Paren, .Left_Bracket:
depth += 1
case .Right_Brace, .Right_Paren, .Right_Bracket:
depth -= 1
if depth == 0 {
return cursor == parser.cursor+1
}
case .Comma:
if depth == 1 {
return true
}
case .Eof:
return false
case:
}
}
return false
}
parse_tuple_literal :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
left_brace := advance(parser)
args, right_brace := parse_positional_initializers(parser, left_brace, nesting, "expected '}' after tuple literal")
return add_expr(parser, ast.Expr{
kind=.Struct_Literal,
span=span_from(left_brace.span, right_brace.span),
args=args,
tuple=true,
left=ast.INVALID_EXPR,
right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
parse_struct_literal :: proc(
parser: ^Parser,
qualifier: symbol.Id,
@@ -619,13 +697,24 @@ parse_struct_literal :: proc(
nesting: int,
) -> ast.Expr_Id {
left_brace := advance(parser)
args, right_brace := parse_keyed_initializers(parser, left_brace, nesting, "expected '}' after struct literal")
skip_newlines(parser)
keyed_start := (current(parser).kind == .Identifier || token.is_keyword(current(parser).kind)) &&
(peek(parser).kind == .Equal || peek(parser).kind == .Right_Brace || token.is_keyword(current(parser).kind))
positional := current(parser).kind == .Right_Brace || !keyed_start
args: []ast.Expr_Id
right_brace: token.Token
if positional {
args, right_brace = parse_positional_initializers(parser, left_brace, nesting, "expected '}' after tuple literal")
} else {
args, right_brace = parse_keyed_initializers(parser, left_brace, nesting, "expected '}' after struct literal")
}
return add_expr(parser, ast.Expr{
kind=.Struct_Literal,
span=source.Span{file=name.span.file, start=first.span.start, end=right_brace.span.end},
qualifier=qualifier,
name=name.symbol,
args=args[:],
tuple=positional,
left=ast.INVALID_EXPR,
right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
@@ -636,7 +725,8 @@ parse_anonymous_struct_type_expr :: proc(parser: ^Parser) -> ast.Expr_Id {
start := advance(parser)
fields: [dynamic]types.Field
fields.allocator = parser.module.allocator
if !parse_record_body(parser, &fields, "expected '{' after anonymous struct type") {
tuple := false
if !parse_record_body(parser, &fields, "expected '{' after anonymous struct type", tuple_result=&tuple) {
delete(fields)
return invalid_expr(parser, start.span, "invalid anonymous struct type")
}
@@ -649,6 +739,7 @@ parse_anonymous_struct_type_expr :: proc(parser: ^Parser) -> ast.Expr_Id {
kind=.Anonymous_Struct_Type,
span=span_from(start.span, end.span),
integer=u64(field_start)<<32 | u64(field_count),
tuple=tuple,
left=ast.INVALID_EXPR,
right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
@@ -837,6 +928,8 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
})
}
return parse_array_literal(parser, nesting)
case .Left_Brace:
return parse_tuple_literal(parser, nesting)
case .Dot:
start := advance(parser)
member, member_ok := parse_member_name(parser)
@@ -901,7 +994,8 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
first := advance(parser)
name := first
qualifier := symbol.INVALID
if _, ok := allow(parser, .Dot); ok {
if current(parser).kind == .Dot && peek(parser).kind != .Integer {
advance(parser)
member, member_ok := parse_member_name(parser)
if !member_ok {
return invalid_expr(parser, current(parser).span, "expected a package member after '.'")
@@ -914,11 +1008,22 @@ parse_primary :: proc(parser: ^Parser, nesting: int) -> ast.Expr_Id {
call := parse_call(parser, qualifier, first, name, nesting, intrinsic)
if !intrinsic && current(parser).kind == .Left_Brace && !(parser.no_struct_literal && parser.delimiter_depth == 0) {
left_brace := advance(parser)
args, right_brace := parse_keyed_initializers(parser, left_brace, nesting, "expected '}' after struct literal")
skip_newlines(parser)
keyed_start := (current(parser).kind == .Identifier || token.is_keyword(current(parser).kind)) &&
(peek(parser).kind == .Equal || peek(parser).kind == .Right_Brace || token.is_keyword(current(parser).kind))
positional := current(parser).kind == .Right_Brace || !keyed_start
args: []ast.Expr_Id
right_brace: token.Token
if positional {
args, right_brace = parse_positional_initializers(parser, left_brace, nesting, "expected '}' after tuple literal")
} else {
args, right_brace = parse_keyed_initializers(parser, left_brace, nesting, "expected '}' after struct literal")
}
return add_expr(parser, ast.Expr{
kind=.Struct_Literal,
span=span_from(parser.module.exprs[call].span, right_brace.span),
args=args,
tuple=positional,
left=call,
right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
@@ -1113,6 +1218,26 @@ parse_expression_bp :: proc(parser: ^Parser, minimum_binding_power, nesting: int
}
if current(parser).kind == .Dot {
advance(parser)
if current(parser).kind == .Integer {
field := advance(parser)
text := token_text(parser, field)
index, index_ok := parse_integer_magnitude(text)
left_expr := parser.module.exprs[left]
if !index_ok || (len(text) > 1 && text[0] == '0') {
left = invalid_expr(parser, field.span, "tuple field indices must be canonical decimal integers")
} else {
left = add_expr(parser, ast.Expr{
kind=.Field,
span=span_from(left_expr.span, field.span),
name=symbol.INVALID,
integer=index,
left=left,
right=ast.INVALID_EXPR,
diagnostic=source.INVALID_DIAGNOSTIC,
})
}
continue
}
field, field_ok := parse_member_name(parser)
if !field_ok {
left = invalid_expr(parser, field.span, "expected a field name after '.'")
@@ -1544,6 +1669,14 @@ parse_value_control_flow :: proc(parser: ^Parser) -> (ast.Stmt_Id, bool) {
}
parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
if current(parser).kind == .Identifier && token_text(parser, current(parser)) == "inline" &&
peek(parser).kind == .Keyword_For {
start := advance(parser)
id := parse_for(parser)
parser.module.statements[id].inline = true
parser.module.statements[id].span = span_from(start.span, parser.module.statements[id].span)
return id
}
if current(parser).kind == .Keyword_Return {
return parse_return(parser)
}
@@ -1623,7 +1756,7 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
}
// A `{` on the right is a value block: parse its statements now; the
// checker turns its final `yield` into the declared/assigned value.
if current(parser).kind == .Left_Brace {
if current(parser).kind == .Left_Brace && !brace_starts_tuple(parser) {
brace := current(parser)
body := parse_block(parser)
id := ast.stmt_id(len(parser.module.statements))
@@ -1697,7 +1830,7 @@ parse_statement :: proc(parser: ^Parser) -> ast.Stmt_Id {
return id
}
// A value block assigned to a complex target (`a[i] = { ... }`, `p.f = { ... }`).
if current(parser).kind == .Left_Brace {
if current(parser).kind == .Left_Brace && !brace_starts_tuple(parser) {
brace := current(parser)
body := parse_block(parser)
id := ast.stmt_id(len(parser.module.statements))
@@ -2339,26 +2472,51 @@ parse_record_body :: proc(
expected_open: string,
allow_anonymous_struct_payload := false,
allow_keyword_names := false,
tuple_result: ^bool = nil,
) -> bool {
if _, ok := allow(parser, .Left_Brace); !ok {
source.add(parser.diagnostics, current(parser).span, expected_open)
return false
}
skip_newlines(parser)
mode := 0 // 0 unknown, 1 named, 2 tuple
for current(parser).kind != .Right_Brace && current(parser).kind != .Eof {
field_name, field_ok := parse_member_name(parser, allow_keyword_names)
if !field_ok {
source.add(parser.diagnostics, current(parser).span, "expected a struct field name")
for current(parser).kind != .Newline &&
current(parser).kind != .Right_Brace &&
current(parser).kind != .Eof {
advance(parser)
unnamed := false
if !allow_keyword_names {
if current(parser).kind == .Identifier {
next := peek(parser).kind
unnamed = next == .Comma || next == .Newline || next == .Right_Brace ||
next == .Dot || next == .Left_Paren
} else {
unnamed = is_type_token(current(parser).kind)
}
skip_newlines(parser)
continue
}
field_type := parse_record_field_type(parser, allow_anonymous_struct_payload)
append(fields, types.Field{name=u32(field_name.symbol), type=field_type})
if unnamed {
if mode == 1 {
source.add(parser.diagnostics, current(parser).span, "struct fields cannot mix named and unnamed forms")
}
mode = 2
field_type := parse_type(parser)
append(fields, types.Field{name=0, type=field_type})
} else {
if mode == 2 {
source.add(parser.diagnostics, current(parser).span, "struct fields cannot mix named and unnamed forms")
}
mode = 1
field_name, field_ok := parse_member_name(parser, allow_keyword_names)
if !field_ok {
source.add(parser.diagnostics, current(parser).span, "expected a struct field name or tuple element type")
for current(parser).kind != .Newline &&
current(parser).kind != .Right_Brace &&
current(parser).kind != .Eof {
advance(parser)
}
skip_newlines(parser)
continue
}
field_type := parse_record_field_type(parser, allow_anonymous_struct_payload)
append(fields, types.Field{name=u32(field_name.symbol), type=field_type})
}
if _, ok := allow(parser, .Comma); ok {
skip_newlines(parser)
continue
@@ -2368,6 +2526,9 @@ parse_record_body :: proc(
if _, ok := allow(parser, .Right_Brace); !ok {
source.add(parser.diagnostics, current(parser).span, "expected '}' after struct fields")
}
if tuple_result != nil {
tuple_result^ = mode == 2
}
return true
}
@@ -2459,17 +2620,23 @@ parse_struct :: proc(parser: ^Parser, name: token.Token, c_layout, file_hidden:
fields.allocator = parser.module.allocator
defer delete(fields)
allow_anonymous_struct_payload := is_union && (inferred_tag || types.is_valid(declared_tag))
tuple := false
_ = parse_record_body(
parser,
&fields,
"expected '{' after struct fields",
allow_anonymous_struct_payload,
allow_anonymous_struct_payload,
&tuple,
)
if tuple && (c_layout || is_union) {
source.add(parser.diagnostics, start.span, "unnamed fields are only supported by native structs")
tuple = false
}
if is_union && (inferred_tag || types.is_valid(declared_tag)) {
tag = synthesize_union_tag(parser, fields[:])
}
if !types.define_record(&parser.module.type_store, id, fields[:], c_layout, false, is_union, tag=tag, declared_tag=declared_tag) {
if !types.define_record(&parser.module.type_store, id, fields[:], c_layout, false, is_union, tag=tag, declared_tag=declared_tag, tuple=tuple) {
source.addf(parser.diagnostics, name.span, "duplicate type declaration '%s'", token_text(parser, name))
}
_ = finish_statement(parser)
+8 -3
View File
@@ -101,6 +101,7 @@ Node :: struct {
c_abi: bool,
variadic: bool,
c_layout: bool,
tuple: bool,
opaque: bool,
declared: bool,
file_hidden: bool,
@@ -273,6 +274,7 @@ define_record :: proc(
explicit_alignment: u32 = 0,
tag: Type = INVALID,
declared_tag: Type = INVALID,
tuple := false,
) -> bool {
existing, ok := node(store, id)
if !ok || (existing.kind != .Named && existing.kind != .Struct && existing.kind != .Union) ||
@@ -282,6 +284,7 @@ define_record :: proc(
index := int(id-DYNAMIC_START)
store.nodes[index].kind = .Union if is_union else .Struct
store.nodes[index].c_layout = c_layout
store.nodes[index].tuple = tuple
store.nodes[index].opaque = opaque
store.nodes[index].declared = true
store.nodes[index].explicit_size = explicit_size
@@ -342,10 +345,10 @@ anonymous_struct_fields_equal :: proc(store: ^Store, item: Node, fields: []Field
return true
}
struct_anonymous :: proc(store: ^Store, fields: []Field) -> Type {
struct_anonymous :: proc(store: ^Store, fields: []Field, tuple := false) -> Type {
for existing, index in store.nodes {
if existing.kind == .Struct && existing.name == 0 && existing.declared &&
!existing.c_layout && !existing.opaque &&
!existing.c_layout && existing.tuple == tuple && !existing.opaque &&
anonymous_struct_fields_equal(store, existing, fields) {
return DYNAMIC_START+Type(index)
}
@@ -356,13 +359,14 @@ struct_anonymous :: proc(store: ^Store, fields: []Field) -> Type {
kind=.Struct,
field_start=start,
field_count=u32(len(fields)),
tuple=tuple,
declared=true,
})
}
// Generated structs are nominal per comptime type-expression specialization.
// The checker owns canonicalization; this routine deliberately creates a fresh node.
struct_generated :: proc(store: ^Store, fields: []Field) -> Type {
struct_generated :: proc(store: ^Store, fields: []Field, tuple := false) -> Type {
start := u32(len(store.fields))
append(&store.fields, ..fields)
id := DYNAMIC_START+Type(len(store.nodes))
@@ -370,6 +374,7 @@ struct_generated :: proc(store: ^Store, fields: []Field) -> Type {
kind=.Struct,
field_start=start,
field_count=u32(len(fields)),
tuple=tuple,
declared=true,
})
return id
+600 -16
View File
@@ -2258,7 +2258,12 @@ milestone_33_injects_explicit_io_provider_and_runs_std_io :: proc(t: ^testing.T)
defer delete(stdout)
defer delete(stderr)
testing.expect_value(t, state.exit_code, 0)
testing.expect_value(t, string(stdout), "io-ok\n")
testing.expect_value(t, string(stdout),
"io-ok 37 {bro}\n" +
"bounds=-128/255 -32768/65535 -2147483648/4294967295 -9223372036854775808/18446744073709551615 " +
"-9223372036854775808/18446744073709551615 -128/127 -128/255 -32768/65535 -2147483648/4294967295 " +
"-9223372036854775808/18446744073709551615 -9223372036854775808/18446744073709551615 0/0 1/1 -1/1\n",
)
}
@(test)
@@ -2266,6 +2271,7 @@ milestone_33_rejects_non_io_and_extra_main_parameters :: proc(t: ^testing.T) {
cases := [?]string{
"main func(value i32) void {}\n",
"main func(left, right i32) void {}\n",
"main func($value i32) void {}\n",
}
for text in cases {
source_file := source.Source{path="test.bro", text=text}
@@ -2279,7 +2285,7 @@ milestone_33_rejects_non_io_and_extra_main_parameters :: proc(t: ^testing.T) {
for diagnostic in diagnostics.items {
found = found || strings.contains(
diagnostic.message,
"take no parameters or one @std/io Io",
"take no parameters or one @std/process Init",
)
}
testing.expect(t, found)
@@ -2292,6 +2298,526 @@ milestone_33_rejects_non_io_and_extra_main_parameters :: proc(t: ^testing.T) {
}
}
@(test)
milestone_37_rejects_direct_io_main_parameter :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-direct-io-main"
main_path := "/tmp/brolang-test-direct-io-main/main.bro"
text := `io :: import "@std/io"
main func(system io.Io) void {
_ = system
}
`
_ = os2.remove_all(directory)
defer _ = os2.remove_all(directory)
testing.expect(t, os.make_directory(directory) == nil)
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
sources := source.init_store()
defer source.destroy_store(&sources)
diagnostics := source.init_store_diagnostics(&sources)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
ast_module, loaded := loader.load(directory, &sources, &diagnostics, &symbols, project_root_path=".")
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
found := false
for diagnostic in diagnostics.items {
found = found || strings.contains(
diagnostic.message,
"take no parameters or one @std/process Init",
)
}
testing.expect(t, loaded)
testing.expect(t, found)
}
@(test)
milestone_37_validates_process_init_and_hidden_system_provider :: proc(t: ^testing.T) {
Case :: struct {
io_source: string,
process_source: string,
message: string,
}
cases := []Case{
{
io_source=`Io :: struct { value i32 }
system func() Io { return Io {value = 0} }
`,
process_source=`io :: import "@std/io"
Init :: struct { io io.Io }
`,
message="does not provide the required 'hide system func() Io'",
},
{
io_source=`Io :: struct { value i32 }
hide system func() Io { return Io {value = 0} }
`,
process_source=`io :: import "@std/io"
Init :: struct { io io.Io, extra i32 }
`,
message="Init must be an auto-layout record containing exactly 'io io.Io'",
},
}
for test_case, index in cases {
root := fmt.tprintf("/tmp/brolang-test-process-schema-%d", index)
app := fmt.tprintf("%s/app", root)
io_dir := fmt.tprintf("%s/std/io", root)
process_dir := fmt.tprintf("%s/std/process", root)
main_source: string = `process :: import "@std/process"
main func(init process.Init) void { _ = init }
`
_ = os2.remove_all(root)
defer _ = os2.remove_all(root)
testing.expect(t, os2.make_directory_all(app) == nil)
testing.expect(t, os2.make_directory_all(io_dir) == nil)
testing.expect(t, os2.make_directory_all(process_dir) == nil)
testing.expect(t, os.write_entire_file(
fmt.tprintf("%s/main.bro", app), transmute([]byte)main_source,
))
testing.expect(t, os.write_entire_file(
fmt.tprintf("%s/io.bro", io_dir), transmute([]byte)test_case.io_source,
))
testing.expect(t, os.write_entire_file(
fmt.tprintf("%s/process.bro", process_dir), transmute([]byte)test_case.process_source,
))
sources := source.init_store()
diagnostics := source.init_store_diagnostics(&sources)
symbols := symbol.init_table()
ast_module, loaded := loader.load(app, &sources, &diagnostics, &symbols, project_root_path=root)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
found := false
for diagnostic in diagnostics.items {
found = found || strings.contains(diagnostic.message, test_case.message)
}
testing.expect(t, loaded)
testing.expect(t, found)
hir.destroy_module(&hir_module)
ast.destroy_module(&ast_module)
symbol.destroy_table(&symbols)
source.destroy_diagnostics(&diagnostics)
source.destroy_store(&sources)
}
}
@(test)
milestone_37_tuples_reflection_inline_for_and_debug_print_compile_and_run :: proc(t: ^testing.T) {
sources := source.init_store()
defer source.destroy_store(&sources)
diagnostics := source.init_store_diagnostics(&sources)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
ast_module, loaded := loader.load(
"examples/programs/tuples", &sources, &diagnostics, &symbols, project_root_path=".",
)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
ir_module := lower.lower(&hir_module)
defer ir.destroy_module(&ir_module)
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
defer delete(llvm_text)
testing.expect(t, loaded)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect(t, !strings.contains(llvm_text, "FormatToken"))
testing.expect(t, !strings.contains(llvm_text, "parse_format"))
testing.expect(t, !strings.contains(llvm_text, "format_field_name"))
testing.expect(t, !strings.contains(llvm_text, "FieldInfo"))
testing.expect(t, !strings.contains(llvm_text, "RecordInfo"))
output := "/tmp/brolang-test-tuples"
defer _ = os.remove(output)
status := compiler_core.compile_package(
"examples/programs/tuples", output, nil, target.DEFAULT, cimport.Options{}, ".",
)
testing.expect_value(t, status, 0)
state, stdout, stderr, _ := os2.process_exec(
os2.Process_Desc{command=[]string{output}}, context.allocator,
)
defer delete(stdout)
defer delete(stderr)
testing.expect_value(t, state.exit_code, 0)
testing.expect_value(t, string(stdout), "")
testing.expect_value(t, string(stderr), "hello!\ntuple=40/bro, limits=-9223372036854775808/18446744073709551615")
}
@(test)
milestone_37_format_errors_are_reported_at_comptime :: proc(t: ^testing.T) {
directory := "/tmp/brolang-test-format-errors"
main_path := "/tmp/brolang-test-format-errors/main.bro"
text := `io :: import "@std/io"
process :: import "@std/process"
main func(init process.Init) void {
writer io.Writer :: io.Writer {impl = init.io, stream = .stdout}
stored :: typeinfo!(i32)
_ = stored
io.print(writer, "", 1) catch |_| {}
io.print(writer, "{s}", {1,}) catch |_| {}
io.print(writer, "{d}", {"bro",}) catch |_| {}
io.print(writer, "{d}{d}", {1,}) catch |_| {}
io.print(writer, "{d}", {1, 2}) catch |_| {}
io.print(writer, "{q}", {}) catch |_| {}
io.print(writer, "{b}", {1.5,}) catch |_| {}
io.print(writer, "{e}", {1,}) catch |_| {}
io.print(writer, "{c}", {i16(65),}) catch |_| {}
io.print(writer, "}", {}) catch |_| {}
}
`
_ = os2.remove_all(directory)
defer _ = os2.remove_all(directory)
testing.expect(t, os.make_directory(directory) == nil)
testing.expect(t, os.write_entire_file(main_path, transmute([]byte)text))
sources := source.init_store()
defer source.destroy_store(&sources)
diagnostics := source.init_store_diagnostics(&sources)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
ast_module, loaded := loader.load(directory, &sources, &diagnostics, &symbols, project_root_path=".")
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
found := [10]bool{}
for diagnostic in diagnostics.items {
message := diagnostic.message
found[0] = found[0] || strings.contains(message, "arguments must be a tuple")
found[1] = found[1] || strings.contains(message, "cannot implicitly convert i8 to []u8")
found[2] = found[2] || strings.contains(message, "'{d}' requires an integer or float")
found[3] = found[3] || strings.contains(message, "argument count does not match")
found[4] = found[4] || strings.contains(message, "unknown specifier")
found[5] = found[5] || strings.contains(message, "unmatched '}'")
found[6] = found[6] || strings.contains(message, "compile-time-only metadata")
found[7] = found[7] || strings.contains(message, "integer format requires an integer argument")
found[8] = found[8] || strings.contains(message, "float format requires a float argument")
found[9] = found[9] || strings.contains(message, "'{c}' requires an unsigned integer that fits in u8")
}
testing.expect(t, loaded)
for present in found {
testing.expect(t, present)
}
}
@(test)
milestone_39_stable_values_and_richer_formatting_compile_and_run :: proc(t: ^testing.T) {
stable_names: [dynamic]string
defer {
for name in stable_names {
delete(name)
}
delete(stable_names)
}
for pass := 0; pass < 2; pass += 1 {
sources := source.init_store()
diagnostics := source.init_store_diagnostics(&sources)
symbols := symbol.init_table()
ast_module, loaded := loader.load(
"examples/programs/milestone_39", &sources, &diagnostics, &symbols, project_root_path=".",
)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
ir_module := lower.lower(&hir_module)
llvm_text := llvm.emit(&ir_module, &diagnostics, &symbols)
score_count := 0
for function in hir_module.functions {
if strings.contains(function.link_name, "bro__p0__score__ca") {
if pass == 0 {
append(&stable_names, strings.clone(function.link_name))
} else {
testing.expect_value(t, function.link_name, stable_names[score_count])
}
score_count += 1
}
}
testing.expect(t, loaded)
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, score_count, 2)
testing.expect_value(t, count_substring_occurrences(llvm_text, "define internal fastcc i32 @bro__p0__read_carrier__"), 1)
testing.expect(t, !strings.contains(llvm_text, "FormatToken"))
testing.expect(t, !strings.contains(llvm_text, "parse_format"))
testing.expect(t, !strings.contains(llvm_text, "FieldInfo"))
testing.expect(t, !strings.contains(llvm_text, "EnumInfo"))
delete(llvm_text)
ir.destroy_module(&ir_module)
hir.destroy_module(&hir_module)
ast.destroy_module(&ast_module)
symbol.destroy_table(&symbols)
source.destroy_diagnostics(&diagnostics)
source.destroy_store(&sources)
}
output := "/tmp/brolang-test-milestone-39"
defer _ = os.remove(output)
status := compiler_core.compile_package(
"examples/programs/milestone_39", output, nil, target.DEFAULT, cimport.Options{}, ".",
)
testing.expect_value(t, status, 0)
state, stdout, stderr, _ := os2.process_exec(
os2.Process_Desc{command=[]string{output}}, context.allocator,
)
defer delete(stdout)
defer delete(stderr)
testing.expect_value(t, state.exit_code, 0)
testing.expect_value(
t,
string(stdout),
"true -42 1.5 .running bro 2.5 1010 12 ff FF A 1.5000000000000000e+00 {} -9223372036854775808 0 inf nan 1.50000000e+00\n",
)
testing.expect_value(t, string(stderr), "debug=.idle 2a\n")
}
@(test)
milestone_39_rejects_values_without_stable_identity :: proc(t: ^testing.T) {
text := `BadUnion :: union { number i32, flag bool }
Config :: struct { value i32 }
identity func() i32 { return 1 }
reject_pointer func($value @i32) void {}
reject_function func($value @func() i32) void {}
reject_slice func($value []i32) void {}
reject_range func($value range) void {}
reject_union func($value BadUnion) void {}
reject_undefined func($value Config) void {}
stored i32 :: 1
items [2]i32 :: [1, 2]
main func() void {
reject_pointer(&stored)
reject_function(identity)
reject_slice(items[..])
reject_range(0..3)
reject_union(BadUnion {number = 1})
reject_undefined(Config {value = undefined})
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
found := 0
for diagnostic in diagnostics.items {
if strings.contains(diagnostic.message, "comptime argument has no stable comptime identity") {
found += 1
}
}
testing.expect(t, found >= 6)
}
@(test)
milestone_37_inline_loop_control_must_be_statically_resolvable :: proc(t: ^testing.T) {
text := `main func() void {
total i32 = 0
inline for {1, 2} |value| {
if total == 0 {
break
}
total += value
}
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
found := false
for diagnostic in diagnostics.items {
found = found || strings.contains(
diagnostic.message,
"break or continue targeting an inline loop must be compile-time-resolvable",
)
}
testing.expect(t, found)
}
@(test)
milestone_37_comptime_undefined_aggregates_support_full_initialization :: proc(t: ^testing.T) {
text := `Token :: struct {
text []u8
count usize
}
Partial :: struct { initialized i32, text []u8 }
make_tokens func() [2]mut Token {
tokens [2]mut Token = undefined
tokens[0] = Token {text = "a", count = 1}
tokens[1].text = "bro"
tokens[1].count = 3
return tokens
}
read_initialized_sibling func() i32 {
value Partial = undefined
value.initialized = 42
return value.initialized
}
answer :: $read_initialized_sibling()
main func() i32 {
total usize = 0
inline for make_tokens() |token| {
total += token.text.len + token.count
}
if answer != 42 or total != 8 {
return 1
}
return 0
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
testing.expect_value(t, len(diagnostics.items), 0)
}
@(test)
milestone_37_comptime_undefined_values_cannot_be_observed :: proc(t: ^testing.T) {
text := `Bad :: struct { value i32, text []u8 }
read_scalar func() i32 {
value i32 = undefined
return value
}
return_partial func() Bad {
value Bad = undefined
value.value = 1
return value
}
take_bad func(value Bad) i32 {
return value.value
}
pass_partial func() i32 {
value Bad = undefined
value.value = 1
return take_bad(value)
}
bad_scalar :: $read_scalar()
bad_record :: $return_partial()
bad_argument :: $pass_partial()
main func() void {}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
found_read := false
found_return := false
found_pass := false
for diagnostic in diagnostics.items {
found_read = found_read || strings.contains(diagnostic.message, "cannot read an undefined value at comptime")
found_return = found_return || strings.contains(diagnostic.message, "comptime function returned an undefined value")
found_pass = found_pass || strings.contains(diagnostic.message, "cannot pass an undefined value at comptime")
}
testing.expect(t, found_read)
testing.expect(t, found_return)
testing.expect(t, found_pass)
}
@(test)
milestone_37_inline_expansions_keep_distinct_call_resolutions :: proc(t: ^testing.T) {
text := `identity func($T type, value T) T {
return value
}
main func() i32 {
total i64 = 0
inline for {{i8(1), i16(2)}, {i32(3), i64(4)}} |row| {
inline for row |value| {
total += i64(identity(value))
}
}
return i32(total - 10)
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
identity_symbol := symbol.intern(&symbols, "identity")
specializations := 0
for function in hir_module.functions {
specializations += 1 if function.name == identity_symbol else 0
}
testing.expect_value(t, len(diagnostics.items), 0)
testing.expect_value(t, specializations, 4)
}
@(test)
milestone_37_inline_control_prunes_inference_after_static_exit :: proc(t: ^testing.T) {
text := `take_i8 func(value i8) void { _ = value }
main func() void {
inline for {i8(1), "skip"} |value, index| {
if index == 1 {
continue
}
take_i8(value)
}
inline for {i8(1), "stop"} |value, index| {
if index == 1 {
break
}
take_i8(value)
}
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
testing.expect_value(t, len(diagnostics.items), 0)
}
@(test)
milestone_33_rejects_an_incompatible_runtime_write_declaration :: proc(t: ^testing.T) {
text := `write c_func(_ c_int, _ c_int, _ c_ulong) c_long
@@ -2638,6 +3164,36 @@ main func() void {
testing.expect(t, found_function)
}
@(test)
generic_parameter_usage_is_source_based :: proc(t: ^testing.T) {
text := `choose func($N usize, used, unused i32) i32 {
if N > 0 {
return used
}
return 0
}
main func() void {
_ = choose(0, 1, 2)
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
defer source.destroy_diagnostics(&diagnostics)
symbols := symbol.init_table()
defer symbol.destroy_table(&symbols)
stream := lexer.lex(&source_file, &diagnostics, &symbols)
defer delete(stream.items)
ast_module := parser.parse(&stream, &source_file, &diagnostics)
defer ast.destroy_module(&ast_module)
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
testing.expect_value(t, len(diagnostics.items), 1)
testing.expect_value(t, diagnostics.items[0].severity, source.Severity.Warning)
testing.expect(t, strings.contains(diagnostics.items[0].message, "unused parameter 'unused'"))
testing.expect(t, !strings.contains(diagnostics.items[0].message, "unused parameter 'used'"))
}
@(test)
recursive_specialization_reaches_a_fixed_point :: proc(t: ^testing.T) {
text := `a func(value int) i32 {
@@ -2892,12 +3448,13 @@ comptime_value_params_diagnose_invalid_uses :: proc(t: ^testing.T) {
tiny func($N u8) i32 {
return N
}
bad_type func($T bool) void {}
good_bool func($T bool) void {}
bad_use func($N usize) void {
N = 1
_ = &N
}
main func() void {
good_bool(true)
x usize = 4
_ = make(x)
_ = make()
@@ -2924,17 +3481,15 @@ main func() void {
found_extra := false
found_negative := false
found_range := false
found_bad_type := false
found_assignment := false
found_address := false
for diagnostic in diagnostics.items {
message := diagnostic.message
found_runtime_arg = found_runtime_arg || strings.contains(message, "must be a compile-time integer expression")
found_missing = found_missing || strings.contains(message, "cannot infer comptime parameter 'N'")
found_extra = found_extra || strings.contains(message, "expects 1 arguments with explicit comptime parameters or 0 with inferred comptime parameters, got 2")
found_extra = found_extra || strings.contains(message, "has no unique complete argument mapping")
found_negative = found_negative || strings.contains(message, "integer constant -1 does not fit in usize")
found_range = found_range || strings.contains(message, "integer constant 300 does not fit in u8")
found_bad_type = found_bad_type || strings.contains(message, "requires a concrete integer type")
found_assignment = found_assignment || strings.contains(message, "cannot assign comptime parameter 'N'")
found_address = found_address || strings.contains(message, "'&' requires an addressable location")
}
@@ -2944,7 +3499,6 @@ main func() void {
testing.expect(t, found_extra)
testing.expect(t, found_negative)
testing.expect(t, found_range)
testing.expect(t, found_bad_type)
testing.expect(t, found_assignment)
testing.expect(t, found_address)
}
@@ -2965,6 +3519,7 @@ unknown func($T type) T { value T = undefined; return value }
partial func($T type, $N usize, value T) T { return value }
use_ignored func($T type, value Ignored(T)) i32 { return value }
use_alias func($T type, value BoxAlias(T)) T { return value.value }
mapping_fail func($A usize, value i32, $B usize) void {}
main func() void {
a i32 :: 1
b u32 :: 2
@@ -2974,6 +3529,7 @@ main func() void {
_ = use_ignored(a)
box Box(i32) :: Box(i32) { value = 1 }
_ = use_alias(box)
mapping_fail(true, false)
}
`
source_file := source.Source{path="test.bro", text=text}
@@ -2991,12 +3547,16 @@ main func() void {
found_conflict := false
found_unknown := false
found_partial := false
found_candidate_failures := false
unrecoverable := 0
for diagnostic in diagnostics.items {
message := diagnostic.message
found_conflict = found_conflict || strings.contains(message, "conflicting inference for comptime parameter 'T': i32 and u32")
found_unknown = found_unknown || strings.contains(message, "cannot infer comptime parameter 'T'")
found_partial = found_partial || strings.contains(message, "expects 3 arguments with explicit comptime parameters or 1 with inferred comptime parameters, got 2")
found_partial = found_partial || strings.contains(message, "cannot infer comptime parameter 'N'")
found_candidate_failures = found_candidate_failures ||
strings.contains(message, "candidate 1: cannot infer comptime parameter 'B'") &&
strings.contains(message, "candidate 2: cannot infer comptime parameter 'A'")
if strings.contains(message, "cannot infer comptime parameter 'T'") {
unrecoverable += 1
}
@@ -3004,23 +3564,43 @@ main func() void {
testing.expect(t, found_conflict)
testing.expect(t, found_unknown)
testing.expect(t, found_partial)
testing.expect(t, found_candidate_failures)
testing.expect(t, unrecoverable >= 3)
}
@(test)
comptime_params_must_form_leading_prefix :: proc(t: ^testing.T) {
comptime_params_may_be_interleaved_and_are_erased_from_the_abi :: proc(t: ^testing.T) {
text := `valid func($T type, $N usize, value T) [N]T {
result [N]T = undefined
_ = value
return result
}
runtime_first func(value i32, $T type, $N usize) T { return value }
runtime_first func(value T, $T type, $N usize) T { return value }
split func($T type, value T, $N usize) [N]T {
result [N]T = undefined
_ = value
return result
}
main func() void {}
from_result func($T type) T {
value T = undefined
return value
}
choose_mapping func($A usize, value i32, $B usize) [A]u8 {
result [A]u8 = undefined
_ = value
_ = B
return result
}
main func() void {
_ = runtime_first(42, i32, 4)
_ = runtime_first(42, _, 4)
_ = split(i32, 42, 4)
_ = split(42, 4)
value i32 = from_result()
chosen [1]u8 :: choose_mapping(7, 2)
_ = value
_ = chosen
}
`
source_file := source.Source{path="test.bro", text=text}
diagnostics := source.init_diagnostics(&source_file)
@@ -3034,13 +3614,17 @@ main func() void {}
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
defer hir.destroy_module(&hir_module)
count := 0
for diagnostic in diagnostics.items {
if strings.contains(diagnostic.message, "comptime parameters must form a leading parameter prefix") {
count += 1
for function in hir_module.functions {
name := symbol.resolve(&symbols, function.name)
if name == "runtime_first" || name == "split" {
testing.expect_value(t, len(function.params), 1)
} else if name == "from_result" {
testing.expect_value(t, len(function.params), 0)
} else if name == "choose_mapping" {
testing.expect_value(t, len(function.params), 1)
}
}
testing.expect_value(t, count, 2)
testing.expect_value(t, len(diagnostics.items), 0)
}
@(test)
+28 -5
View File
@@ -1,4 +1,5 @@
io :: import "@std/io"
process :: import "@std/process"
hide read_ok func(_ ?*mut anyopaque, _ io.ReadStream, buffer []mut u8) usize ! io.ReadError {
if buffer.len == 0 {
@@ -83,7 +84,7 @@ rejects_bad_read func() bool {
}
rejects_no_progress func() bool {
io.write_all(writer_for(&write_none_vtable), "x") catch |err| {
io.print(writer_for(&write_none_vtable), "{s}", {"x",}) catch |err| {
return err == .no_progress
}
return false
@@ -96,7 +97,8 @@ rejects_bad_write func() bool {
return false
}
main func(system std.Io) i32 {
main func(init process.Init) i32 {
system io.Io :: init.io
buffer [2]mut u8 = [0, 0]
count usize :: io.read(reader_for(&ok_vtable), buffer[..]) catch 0
if count != 2 or buffer[0] != 'o' or buffer[1] != 'k' {
@@ -108,17 +110,38 @@ main func(system std.Io) i32 {
if eof != 0 or empty_read != 0 or empty_write != 0 {
return 5
}
io.write_all(writer_for(&ok_vtable), "partial") catch |_| {
io.print(writer_for(&ok_vtable), "{s}{d}", {"partial", 37}) catch |_| {
return 2
}
if !rejects_bad_read() or !rejects_no_progress() or !rejects_bad_write() {
return 3
}
io.write_all(io.Writer {
io.print(io.Writer {
impl = system,
stream = .stdout,
}, "io-ok\n") catch |_| {
}, "io-ok {d} {{bro}}\n", {37,}) catch |_| {
return 4
}
io.print(io.Writer {
impl = system,
stream = .stdout,
}, "bounds={d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d} {d}/{d}\n", {
minval!(i8), maxval!(u8),
minval!(i16), maxval!(u16),
minval!(i32), maxval!(u32),
minval!(i64), maxval!(u64),
minval!(isize), maxval!(usize),
minval!(c_char), maxval!(c_char),
minval!(c_schar), maxval!(c_uchar),
minval!(c_short), maxval!(c_ushort),
minval!(c_int), maxval!(c_uint),
minval!(c_long), maxval!(c_ulong),
minval!(c_longlong), maxval!(c_ulonglong),
0, 0,
1, 1,
-1, 1,
}) catch |_| {
return 6
}
return 0
}
+110
View File
@@ -0,0 +1,110 @@
io :: import "@std/io"
process :: import "@std/process"
debug :: import "@std/debug"
State :: enum {
idle
running
}
Payload :: union(enum) {
count i32
empty void
}
Pair :: struct { i32, bool }
Config :: struct {
enabled bool
ratio f64
label []u8
state State
values [2]i32
pair Pair
maybe ?i32
payload Payload
}
first Config :: Config {
enabled = true,
ratio = 1.5,
label = "bro",
state = .running,
values = [20, 22],
pair = Pair {7, true},
maybe = 9,
payload = Payload {count = 4},
}
same Config :: Config {
enabled = true,
ratio = 1.5,
label = "bro",
state = .running,
values = [20, 22],
pair = Pair {7, true},
maybe = 9,
payload = Payload {count = 4},
}
different Config :: Config {
enabled = false,
ratio = 2.5,
label = "bro",
state = .idle,
values = [21, 21],
pair = Pair {7, false},
maybe = none,
payload = .empty,
}
score func($config Config) i32 {
return config.values[0]
}
Carrier func($config Config) type {
return struct { value i32 }
}
read_carrier func($config Config, carrier Carrier(config)) i32 {
return carrier.value + config.values[0]
}
main func(init process.Init) i32 {
if score(first) != 20 or score(same) != 20 or score(different) != 21 {
return 1
}
carrier Carrier(first) :: Carrier(first) {value = 22}
if read_carrier(carrier) != 42 {
return 2
}
writer io.Writer :: io.Writer {impl = init.io, stream = .stdout}
positive f64 = 1.0
zero f64 = 0.0
infinity f64 :: positive / zero
nan f64 :: zero / zero
io.print(writer, "{} {} {} {} {s} {d} {b} {o} {x} {X} {c} {e} {{}} {d} {b} {} {} {e}\n", {
true,
-42,
1.5,
State.running,
"bro",
2.5,
10,
10,
255,
255,
u8('A'),
1.5,
minval!(i64),
u64(0),
infinity,
nan,
f32(1.5),
}) catch |_| {
return 3
}
debug.print("debug={} {x}\n", {State.idle, 42})
return 0
}
+88
View File
@@ -0,0 +1,88 @@
debug :: import "@std/debug"
meta :: import "@std/meta"
Numbers :: struct { i8, i16, i32 }
Row :: struct { value i32 }
format func() []u8 {
return "tuple={d}/{s}, limits={d}/{d}"
}
sum func($T type, value T) i32 {
total i32 = 0
match typeinfo!(T) {
.record |record|: inline for record.fields |field| {
total += i32(field!(value, field.name))
}
else: compile_error!("sum requires a record")
}
return total
}
static_control func($T type, value T) i32 {
total i32 = 0
match typeinfo!(T) {
.record |record|: inline for record.fields |field| {
{
if field.index == 1 {
continue
}
total += i32(field!(value, field.name))
match typeinfo!(field.type) {
.integer: {
if field.index == 2 {
break
}
}
else: _ = 0
}
}
total += 100
}
else: compile_error!("static_control requires a record")
}
return total
}
row_value func(row Row) i32 {
return row.value
}
static_aggregates func() i32 {
total i32 = 0
inline for {Row {value = 2}, Row {value = 40}} |row| {
total += row_value(row)
}
return total
}
main func() i32 {
numbers Numbers = Numbers {1, 2, 39}
singleton :: {42,}
empty :: {}
block_value :: {
yield 42
}
_ = empty
if singleton.0 != block_value {
return 2
}
if sum(Numbers, numbers) != 42 {
return 1
}
if static_control(Numbers, numbers) != 140 {
return 3
}
if static_aggregates() != 42 {
return 4
}
field!(&numbers, "2") += 1
debug.print("hello!\n", {})
debug.print(format(), {
numbers.2,
"bro",
minval!(i64),
maxval!(u64),
})
return 0
}
+1 -1
View File
@@ -9,5 +9,5 @@ languages = ["languages/brolang"]
[grammars.brolang]
repository = "file:///Users/valdemar/Developer/Personal/Languages/brolang"
rev = "ba171a2d5e248fa24f5f4ee960e21056893f72e7"
rev = "03f5445509575fb72226e8b4bbf230d7905b7b8e"
path = "tree-sitter-brolang"
+3
View File
@@ -41,6 +41,7 @@
(call_expression function: (expression (identifier) @function))
(call_expression function: (expression (field_expression field: (identifier) @function)))
(field_expression field: (identifier) @property)
(field_expression field: (integer) @property)
(field_initializer name: (identifier) @property)
(keyed_field_initializer name: (identifier) @property)
(record_field name: (identifier) @property)
@@ -60,6 +61,7 @@
"distinct"
"alias"
"import"
"hide"
"return"
"try"
"catch"
@@ -70,6 +72,7 @@
"if"
"while"
"for"
"inline"
"break"
"continue"
"defer"
+36
View File
@@ -0,0 +1,36 @@
c :: import "@ffi/c"
io :: import "@std/io"
hide write func(_ ?*mut anyopaque, _ io.WriteStream, bytes []u8) usize ! io.WriteError {
request usize = bytes.len
maximum usize :: usize(maxval!(c_long))
if request > maximum {
request = maximum
}
while true {
count c_long :: c.write(2, bytes.ptr, c_ulong(request))
if count >= 0 {
return usize(count)
}
if c.__error()^ != 4 {
return .write_failed
}
}
}
hide read func(_ ?*mut anyopaque, _ io.ReadStream, _ []mut u8) usize ! io.ReadError {
return .read_failed
}
hide vtable io.IoVTable :: io.IoVTable {
read = read,
write = write,
}
print func($format []u8, $Args type, args Args) void {
writer io.Writer :: io.Writer {
impl = io.Io {context = none, vtable = &vtable},
stream = .stderr,
}
io.print(writer, format, Args, args) catch |_| {}
}
+330
View File
@@ -1,4 +1,5 @@
c :: import "@ffi/c"
meta :: import "@std/meta"
ReadError :: enum {
read_failed
@@ -74,6 +75,335 @@ write_all func(writer Writer, bytes []u8) void ! WriteError {
return
}
hide write_integer_signed func(writer Writer, value i64, base u64, uppercase bool) void ! WriteError {
buffer [65]mut u8 = undefined
end usize = buffer.len
current i64 = value
while true {
digit_value i64 :: rem!(current, i64(base))
digit u8 = 0
if digit_value < 0 {
digit = u8(-digit_value)
} else {
digit = u8(digit_value)
}
end -= 1
if digit < 10 {
buffer[end] = '0' + digit
} else if uppercase {
buffer[end] = 'A' + digit - 10
} else {
buffer[end] = 'a' + digit - 10
}
current = divtrunc!(current, i64(base))
if current == 0 {
break
}
}
if value < 0 {
end -= 1
buffer[end] = '-'
}
try write_all(writer, buffer[end..])
return
}
hide write_integer_unsigned func(writer Writer, value u64, base u64, uppercase bool) void ! WriteError {
buffer [65]mut u8 = undefined
end usize = buffer.len
current u64 = value
while true {
digit u8 :: u8(rem!(current, base))
end -= 1
if digit < 10 {
buffer[end] = '0' + digit
} else if uppercase {
buffer[end] = 'A' + digit - 10
} else {
buffer[end] = 'a' + digit - 10
}
current = divtrunc!(current, base)
if current == 0 {
break
}
}
try write_all(writer, buffer[end..])
return
}
hide FormatTokenKind :: enum {
unused
literal
default
string
decimal
binary
octal
hex_lower
hex_upper
character
scientific
}
hide FormatToken :: struct {
kind FormatTokenKind
start usize
end usize
field []u8
}
hide parse_format func($N usize, $format []u8, $Args type) [N]mut FormatToken {
tokens [N]mut FormatToken = undefined
for (usize(0))..format.len |index| {
tokens[index] = FormatToken {kind = .unused, start = 0, end = 0, field = ""}
}
field_count usize = 0
match typeinfo!(Args) {
.record |record|: {
if !record.is_tuple {
compile_error!("io.print arguments must be a tuple")
}
field_count = record.fields.len
}
else: compile_error!("io.print arguments must be a tuple")
}
token_count usize = 0
argument_count usize = 0
literal_start usize = 0
cursor usize = 0
while cursor < format.len {
byte :: format[cursor]
if byte == '{' {
if cursor + 1 >= format.len {
compile_error!("io.print format has an unmatched '{'")
}
if cursor > literal_start {
tokens[token_count] = FormatToken {kind = .literal, start = literal_start, end = cursor, field = ""}
token_count += 1
}
next :: format[cursor + 1]
if next == '{' {
tokens[token_count] = FormatToken {kind = .literal, start = cursor, end = cursor + 1, field = ""}
token_count += 1
cursor += 2
literal_start = cursor
continue
}
kind FormatTokenKind = .default
width usize = 2
if next != '}' {
if cursor + 2 >= format.len or format[cursor + 2] != '}' {
compile_error!("io.print format expects a one-character specifier")
}
width = 3
if next == 's' {
kind = .string
} else if next == 'd' {
kind = .decimal
} else if next == 'b' {
kind = .binary
} else if next == 'o' {
kind = .octal
} else if next == 'x' {
kind = .hex_lower
} else if next == 'X' {
kind = .hex_upper
} else if next == 'c' {
kind = .character
} else if next == 'e' {
kind = .scientific
} else {
compile_error!("io.print format has an unknown specifier")
}
}
if argument_count >= field_count {
compile_error!("io.print format argument count does not match the tuple")
}
tokens[token_count] = FormatToken {
kind = kind,
start = 0,
end = 0,
field = format_field_name(Args, argument_count),
}
token_count += 1
argument_count += 1
cursor += width
literal_start = cursor
continue
}
if byte == '}' {
if cursor + 1 >= format.len or format[cursor + 1] != '}' {
compile_error!("io.print format has an unmatched '}'")
}
if cursor > literal_start {
tokens[token_count] = FormatToken {kind = .literal, start = literal_start, end = cursor, field = ""}
token_count += 1
}
tokens[token_count] = FormatToken {kind = .literal, start = cursor, end = cursor + 1, field = ""}
token_count += 1
cursor += 2
literal_start = cursor
continue
}
cursor += 1
}
if literal_start < format.len {
tokens[token_count] = FormatToken {kind = .literal, start = literal_start, end = format.len, field = ""}
}
if argument_count != field_count {
compile_error!("io.print format argument count does not match the tuple")
}
return tokens
}
hide format_field_name func($T type, index usize) []u8 {
match typeinfo!(T) {
.record |record|: return record.fields[index].name
else: compile_error!("io.print arguments must be a tuple")
}
}
hide write_integer func(writer Writer, $T type, value T, base u64, uppercase bool) void ! WriteError {
match typeinfo!(T) {
.integer: if minval!(T) < 0 {
try write_integer_signed(writer, i64(value), base, uppercase)
} else {
try write_integer_unsigned(writer, u64(value), base, uppercase)
}
else: compile_error!("io.print integer format requires an integer argument")
}
return
}
# ponytail: libc keeps float formatting small; replace it with a native shortest-roundtrip writer if locale independence matters.
hide write_float func(writer Writer, $T type, value T, scientific bool) void ! WriteError {
match typeinfo!(T) {
.float: {
buffer [64]mut u8 = undefined
count c_int = 0
if sizeof!(T) == 4 {
if scientific {
count = c.snprintf(ptrcast!(c_char, (&buffer).ptr), c_ulong(buffer.len), "%.8e", value)
} else {
count = c.snprintf(ptrcast!(c_char, (&buffer).ptr), c_ulong(buffer.len), "%.9g", value)
}
} else if scientific {
count = c.snprintf(ptrcast!(c_char, (&buffer).ptr), c_ulong(buffer.len), "%.16e", value)
} else {
count = c.snprintf(ptrcast!(c_char, (&buffer).ptr), c_ulong(buffer.len), "%.17g", value)
}
if count < 0 or usize(count) >= buffer.len {
return .write_failed
}
try write_all(writer, buffer[0..usize(count)])
}
else: compile_error!("io.print float format requires a float argument")
}
return
}
hide write_decimal func(writer Writer, $T type, value T) void ! WriteError {
match typeinfo!(T) {
.integer: try write_integer(writer, value, 10, false)
.float: try write_float(writer, value, false)
else: compile_error!("io.print '{d}' requires an integer or float argument")
}
return
}
hide write_character func(writer Writer, $T type, value T) void ! WriteError {
match typeinfo!(T) {
.integer: {
if minval!(T) < 0 or maxval!(T) > 255 {
compile_error!("io.print '{c}' requires an unsigned integer that fits in u8")
}
buffer [1]u8 = [u8(value)]
try write_all(writer, buffer[..])
}
else: compile_error!("io.print '{c}' requires an unsigned integer that fits in u8")
}
return
}
hide write_default func(writer Writer, $T type, value T) void ! WriteError {
match typeinfo!(T) {
.bool: if value {
try write_all(writer, "true")
} else {
try write_all(writer, "false")
}
.integer: try write_integer(writer, value, 10, false)
.float: try write_float(writer, value, false)
.array: try write_all(writer, value)
.pointer: try write_all(writer, value)
.slice: try write_all(writer, value)
.enum |enum_info|: {
inline for enum_info.fields |field| {
if value == field!(T, field.name) {
try write_all(writer, ".")
try write_all(writer, field.name)
return
}
}
return .write_failed
}
else: compile_error!("io.print '{}' does not support this argument type")
}
return
}
print func(
writer Writer,
$format []u8,
$Args type,
args Args,
) void ! WriteError {
inline for parse_format(format.len, format, Args) |token| {
if (token.kind == .unused) {
break
}
if (token.kind == .literal) {
try write_all(writer, format[token.start..token.end])
continue
}
if (token.kind == .string) {
try write_all(writer, field!(args, token.field))
continue
}
if (token.kind == .default) {
try write_default(writer, field!(args, token.field))
continue
}
if (token.kind == .decimal) {
try write_decimal(writer, field!(args, token.field))
continue
}
if (token.kind == .binary) {
try write_integer(writer, field!(args, token.field), 2, false)
continue
}
if (token.kind == .octal) {
try write_integer(writer, field!(args, token.field), 8, false)
continue
}
if (token.kind == .hex_lower) {
try write_integer(writer, field!(args, token.field), 16, false)
continue
}
if (token.kind == .hex_upper) {
try write_integer(writer, field!(args, token.field), 16, true)
continue
}
if (token.kind == .character) {
try write_character(writer, field!(args, token.field))
continue
}
try write_float(writer, field!(args, token.field), true)
}
return
}
hide system_read func(_ ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize ! ReadError {
request usize = buffer.len
maximum usize :: usize(maxval!(c_long))
+41
View File
@@ -0,0 +1,41 @@
Layout :: enum {
auto
c
}
FieldInfo :: struct {
name []u8
type type
index usize
}
RecordInfo :: struct {
name ?[]u8
fields []FieldInfo
is_tuple bool
layout Layout
}
EnumInfo :: struct {
fields []FieldInfo
}
TypeInfo :: union(enum) {
invalid void
void void
anyopaque void
bool void
integer void
float void
array void
pointer void
slice void
range void
optional void
function void
enum EnumInfo
record RecordInfo
union void
fallible void
distinct void
}
+5
View File
@@ -0,0 +1,5 @@
io :: import "@std/io"
Init :: struct {
io io.Io
}
+20 -5
View File
@@ -30,6 +30,9 @@ module.exports = grammar({
[$.array_type, $.expression],
[$.array_type, $.array_literal],
[$.expression_statement, $.parenthesized_expression],
[$.block, $.tuple_literal],
[$.field_initializer, $.expression],
[$.tuple_literal],
],
rules: {
@@ -124,8 +127,8 @@ module.exports = grammar({
'}',
),
record_field: $ => seq(
field('name', $.identifier),
record_field: $ => choice(
seq(field('name', $.identifier), field('type', choice($.type, $.struct_type))),
field('type', choice($.type, $.struct_type)),
),
@@ -336,6 +339,7 @@ module.exports = grammar({
),
for_statement: $ => seq(
optional('inline'),
'for',
repeat($._newline),
field('iterable', $.expression),
@@ -393,6 +397,7 @@ module.exports = grammar({
$.slice_expression,
$.postfix_expression,
$.struct_literal,
$.tuple_literal,
$.comptime_block,
$.function_literal,
$.struct_type,
@@ -442,7 +447,7 @@ module.exports = grammar({
field_expression: $ => prec.left(PREC.POSTFIX, seq(
field('value', $.expression),
'.',
field('field', $.identifier),
field('field', choice($.identifier, $.integer)),
)),
intrinsic_call_expression: $ => prec(PREC.POSTFIX, seq(
@@ -495,8 +500,8 @@ module.exports = grammar({
repeat($._newline),
seq(
repeat($._newline),
$.field_initializer,
repeat(seq(repeat($._newline), ',', repeat($._newline), $.field_initializer)),
choice($.field_initializer, $.expression),
repeat(seq(repeat($._newline), ',', repeat($._newline), choice($.field_initializer, $.expression))),
optional(seq(repeat($._newline), ',')),
repeat($._newline),
),
@@ -509,6 +514,16 @@ module.exports = grammar({
optional(seq('=', repeat($._newline), field('value', $.expression))),
),
tuple_literal: $ => prec(1, choice(
seq('{', repeat($._newline), '}'),
seq(
'{', repeat($._newline), $.expression,
',', repeat($._newline),
repeat(seq($.expression, ',', repeat($._newline))),
optional($.expression), repeat($._newline), '}',
),
)),
enum_literal: $ => seq(
'.',
field('name', $.identifier),
@@ -41,6 +41,7 @@
(call_expression function: (expression (identifier) @function))
(call_expression function: (expression (field_expression field: (identifier) @function)))
(field_expression field: (identifier) @property)
(field_expression field: (integer) @property)
(field_initializer name: (identifier) @property)
(keyed_field_initializer name: (identifier) @property)
(record_field name: (identifier) @property)
@@ -71,6 +72,7 @@
"if"
"while"
"for"
"inline"
"break"
"continue"
"defer"
+195 -13
View File
@@ -656,15 +656,37 @@
]
},
"record_field": {
"type": "SEQ",
"type": "CHOICE",
"members": [
{
"type": "FIELD",
"name": "name",
"content": {
"type": "SYMBOL",
"name": "identifier"
}
"type": "SEQ",
"members": [
{
"type": "FIELD",
"name": "name",
"content": {
"type": "SYMBOL",
"name": "identifier"
}
},
{
"type": "FIELD",
"name": "type",
"content": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "type"
},
{
"type": "SYMBOL",
"name": "struct_type"
}
]
}
}
]
},
{
"type": "FIELD",
@@ -2375,6 +2397,18 @@
"for_statement": {
"type": "SEQ",
"members": [
{
"type": "CHOICE",
"members": [
{
"type": "STRING",
"value": "inline"
},
{
"type": "BLANK"
}
]
},
{
"type": "STRING",
"value": "for"
@@ -2768,6 +2802,10 @@
"type": "SYMBOL",
"name": "struct_literal"
},
{
"type": "SYMBOL",
"name": "tuple_literal"
},
{
"type": "SYMBOL",
"name": "comptime_block"
@@ -3316,8 +3354,17 @@
"type": "FIELD",
"name": "field",
"content": {
"type": "SYMBOL",
"name": "identifier"
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "identifier"
},
{
"type": "SYMBOL",
"name": "integer"
}
]
}
}
]
@@ -3708,8 +3755,17 @@
}
},
{
"type": "SYMBOL",
"name": "field_initializer"
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "field_initializer"
},
{
"type": "SYMBOL",
"name": "expression"
}
]
},
{
"type": "REPEAT",
@@ -3735,8 +3791,17 @@
}
},
{
"type": "SYMBOL",
"name": "field_initializer"
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "field_initializer"
},
{
"type": "SYMBOL",
"name": "expression"
}
]
}
]
}
@@ -3827,6 +3892,112 @@
}
]
},
"tuple_literal": {
"type": "PREC",
"value": 1,
"content": {
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "{"
},
{
"type": "REPEAT",
"content": {
"type": "SYMBOL",
"name": "_newline"
}
},
{
"type": "STRING",
"value": "}"
}
]
},
{
"type": "SEQ",
"members": [
{
"type": "STRING",
"value": "{"
},
{
"type": "REPEAT",
"content": {
"type": "SYMBOL",
"name": "_newline"
}
},
{
"type": "SYMBOL",
"name": "expression"
},
{
"type": "STRING",
"value": ","
},
{
"type": "REPEAT",
"content": {
"type": "SYMBOL",
"name": "_newline"
}
},
{
"type": "REPEAT",
"content": {
"type": "SEQ",
"members": [
{
"type": "SYMBOL",
"name": "expression"
},
{
"type": "STRING",
"value": ","
},
{
"type": "REPEAT",
"content": {
"type": "SYMBOL",
"name": "_newline"
}
}
]
}
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "expression"
},
{
"type": "BLANK"
}
]
},
{
"type": "REPEAT",
"content": {
"type": "SYMBOL",
"name": "_newline"
}
},
{
"type": "STRING",
"value": "}"
}
]
}
]
}
},
"enum_literal": {
"type": "SEQ",
"members": [
@@ -4436,6 +4607,17 @@
[
"expression_statement",
"parenthesized_expression"
],
[
"block",
"tuple_literal"
],
[
"field_initializer",
"expression"
],
[
"tuple_literal"
]
],
"precedences": [],
+32 -1
View File
@@ -770,6 +770,10 @@
"type": "struct_type",
"named": true
},
{
"type": "tuple_literal",
"named": true
},
{
"type": "unary_expression",
"named": true
@@ -807,6 +811,10 @@
{
"type": "identifier",
"named": true
},
{
"type": "integer",
"named": true
}
]
},
@@ -1306,6 +1314,10 @@
"multiple": true,
"required": false,
"types": [
{
"type": "expression",
"named": true
},
{
"type": "field_initializer",
"named": true
@@ -1677,7 +1689,7 @@
"fields": {
"name": {
"multiple": false,
"required": true,
"required": false,
"types": [
{
"type": "identifier",
@@ -1950,6 +1962,21 @@
]
}
},
{
"type": "tuple_literal",
"named": true,
"fields": {},
"children": {
"multiple": true,
"required": false,
"types": [
{
"type": "expression",
"named": true
}
]
}
},
{
"type": "type",
"named": true,
@@ -2608,6 +2635,10 @@
"type": "import",
"named": false
},
{
"type": "inline",
"named": false
},
{
"type": "int",
"named": false
+123242 -115100
View File
File diff suppressed because it is too large Load Diff
@@ -277,3 +277,91 @@ choose func() i32 {
(return_statement
(expression
(identifier)))))))
==================
Tuples and inline for
==================
Pair :: struct { i32, []u8 }
main func() void {
pair Pair = Pair {42, "bro"}
singleton :: {1,}
empty :: {}
_ = pair.0
inline for singleton |value| {
_ = value
}
}
---
(source_file
(type_declaration
(identifier)
(struct_type
(record_body
(record_field
(type
(builtin_type)))
(record_field
(type
(array_type
(type
(builtin_type))))))))
(function_declaration
(identifier)
(parameter_list)
(type
(builtin_type))
(block
(statement
(variable_declaration
(identifier)
(type
(named_type
(qualified_identifier
(identifier))))
(expression
(struct_literal
(qualified_identifier
(identifier))
(initializer_list
(expression
(integer))
(expression
(string
(string_content))))))))
(statement
(constant_declaration
(identifier)
(expression
(tuple_literal
(expression
(integer))))))
(statement
(constant_declaration
(identifier)
(expression
(tuple_literal))))
(statement
(assignment_statement
(expression
(sink))
(expression
(field_expression
(expression
(identifier))
(integer)))))
(statement
(for_statement
(expression
(identifier))
(identifier)
(block
(statement
(assignment_statement
(expression
(sink))
(expression
(identifier))))))))))