reflection foundation, tuples, debug.print

This commit is contained in:
2026-07-14 23:45:30 +02:00
parent 267947e79d
commit 0b2055d64b
24 changed files with 126598 additions and 115394 deletions
+9 -6
View File
@@ -150,11 +150,12 @@ 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 type/integer/string parameters may appear anywhere, are erased from the runtime ABI, and may be omitted when uniquely recoverable from runtime arguments or the immediate expected result; `_` 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 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 reflection and heterogeneous static expansion without runtime metadata; 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 +174,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 currently supports sequential `{s}` / `{d}` plus `{{` / `}}`, 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 +184,18 @@ 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, 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
- Integer, type, and immutable byte-string comptime parameters may be interleaved with runtime parameters, are erased from the ABI, and specialize from explicit or uniquely inferred arguments
- 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
+23 -10
View File
@@ -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,9 +847,22 @@
- `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)
+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
+476 -2
View File
@@ -7,20 +7,24 @@ import "../symbol"
import "../types"
import "base:intrinsics"
import "core:fmt"
import "core:math"
import "core:mem"
import "core:strings"
COMPTIME_EVAL_QUOTA :: 100_000
Comptime_Value_Kind :: enum u8 {
Integer,
Type,
String,
}
Comptime_Value :: struct {
name: symbol.Id,
type: types.Type,
value: i128,
text: string,
kind: Comptime_Value_Kind,
}
@@ -57,6 +61,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 +84,8 @@ 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) {
return false
}
}
@@ -337,8 +351,22 @@ 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)
}
}
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
}
@@ -543,6 +571,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
@@ -881,6 +915,50 @@ 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
if types.is_concrete_integer(value_type) || types.is_enum(value_type, store) {
return ct_add_value(state, Ct_Value{kind=.Integer, type=value_type}), true
}
if types.is_bool(value_type) {
return ct_add_value(state, Ct_Value{kind=.Bool, type=value_type}), true
}
if types.is_float(value_type, state.checker.target) {
return ct_add_value(state, Ct_Value{kind=.Float, type=value_type}), true
}
item, ok := types.node(store, value_type)
if !ok {
return INVALID_CT_VALUE, false
}
if 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 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 INVALID_CT_VALUE, false
}
ct_eval_expr :: proc(
state: ^Ct_State,
expr_id: ast.Expr_Id,
@@ -921,6 +999,16 @@ ct_eval_expr :: proc(
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 {
@@ -1164,7 +1252,12 @@ ct_eval_expr :: proc(
return value, ct_flow(.Normal), true
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 +1330,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))
}
@@ -1502,6 +1635,30 @@ ct_eval_field_value :: proc(state: ^Ct_State, base_id: Ct_Value_Id, name: symbol
return children[index], ct_flow(.Normal), true
}
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)
return value, ct_flow(.Normal), value_ok
}
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 children[field_index], ct_flow(.Normal), true
}
ct_eval_index_value :: proc(state: ^Ct_State, base_id: Ct_Value_Id, index: int, span: source.Span) -> (Ct_Value_Id, Ct_Flow, bool) {
checker := state.checker
if base_id == INVALID_CT_VALUE || int(base_id) >= len(state.values) {
@@ -2045,6 +2202,179 @@ 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")
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(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 != "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 +2387,49 @@ 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))
}
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))
@@ -2234,6 +2607,107 @@ 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_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'")
+24 -2
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)
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,
+175 -8
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 {
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,16 +2472,40 @@ 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 {
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)
}
}
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")
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 {
@@ -2359,6 +2516,7 @@ parse_record_body :: proc(
}
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
+275 -13
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,239 @@ 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, "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), "tuple=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, "{x}", {}) 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 := [7]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")
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")
}
testing.expect(t, loaded)
for present in found {
testing.expect(t, present)
}
}
@(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_33_rejects_an_incompatible_runtime_write_declaration :: proc(t: ^testing.T) {
text := `write c_func(_ c_int, _ c_int, _ c_ulong) c_long
@@ -2931,10 +3170,10 @@ main func() void {
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_bad_type = found_bad_type || strings.contains(message, "requires type, a concrete integer type, or immutable []u8")
found_assignment = found_assignment || strings.contains(message, "cannot assign comptime parameter 'N'")
found_address = found_address || strings.contains(message, "'&' requires an addressable location")
}
@@ -2996,7 +3235,7 @@ main func() void {
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'")
if strings.contains(message, "cannot infer comptime parameter 'T'") {
unrecoverable += 1
}
@@ -3008,19 +3247,38 @@ main func() void {
}
@(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 +3292,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
}
+87
View File
@@ -0,0 +1,87 @@
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(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 |_| {}
}
+177
View File
@@ -1,4 +1,5 @@
c :: import "@ffi/c"
meta :: import "@std/meta"
ReadError :: enum {
read_failed
@@ -74,6 +75,182 @@ write_all func(writer Writer, bytes []u8) void ! WriteError {
return
}
hide write_decimal_signed func(writer Writer, value i64) void ! WriteError {
buffer [21]mut u8 = undefined
end usize = buffer.len
current i64 = value
while true {
digit_value i64 :: rem!(current, 10)
digit u8 = 0
if digit_value < 0 {
digit = u8(-digit_value)
} else {
digit = u8(digit_value)
}
end -= 1
buffer[end] = '0' + digit
current = divtrunc!(current, 10)
if current == 0 {
break
}
}
if value < 0 {
end -= 1
buffer[end] = '-'
}
try write_all(writer, buffer[end..])
return
}
hide write_decimal_unsigned func(writer Writer, value u64) void ! WriteError {
buffer [21]mut u8 = undefined
end usize = buffer.len
current u64 = value
while true {
digit u8 :: u8(rem!(current, 10))
end -= 1
buffer[end] = '0' + digit
current = divtrunc!(current, 10)
if current == 0 {
break
}
}
try write_all(writer, buffer[end..])
return
}
hide FormatTokenKind :: enum {
unused
literal
string
decimal
}
hide FormatToken :: struct {
kind FormatTokenKind
start usize
end usize
arg usize
}
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, arg = 0}
}
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, arg = 0}
token_count += 1
}
next :: format[cursor + 1]
if next == '{' {
tokens[token_count] = FormatToken {kind = .literal, start = cursor, end = cursor + 1, arg = 0}
token_count += 1
cursor += 2
literal_start = cursor
continue
}
if cursor + 2 >= format.len or format[cursor + 2] != '}' {
compile_error!("io.print format expects '{s}' or '{d}'")
}
kind FormatTokenKind = .unused
if next == 's' {
kind = .string
} else if next == 'd' {
kind = .decimal
} else {
compile_error!("io.print format has an unknown specifier")
}
tokens[token_count] = FormatToken {kind = kind, start = 0, end = 0, arg = argument_count}
token_count += 1
argument_count += 1
cursor += 3
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, arg = 0}
token_count += 1
}
tokens[token_count] = FormatToken {kind = .literal, start = cursor, end = cursor + 1, arg = 0}
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, arg = 0}
}
if argument_count != field_count {
compile_error!("io.print format argument count does not match the tuple")
}
return tokens
}
print func(
writer Writer,
$format []u8,
$Args type,
args Args,
) void ! WriteError {
match typeinfo!(Args) {
.record |record|: inline for $parse_format(format.len, format, Args) |token| {
if (token.kind == .literal) {
try write_all(writer, format[token.start..token.end])
}
if (token.kind == .string or token.kind == .decimal) {
inline for record.fields |field| {
if (field.index == token.arg) {
if (token.kind == .string) {
try write_all(writer, field!(args, field.name))
} else {
match typeinfo!(field.type) {
.integer: {
if minval!(field.type) < 0 {
try write_decimal_signed(writer, i64(field!(args, field.name)))
} else {
try write_decimal_unsigned(writer, u64(field!(args, field.name)))
}
}
else: compile_error!("io.print '{d}' requires an integer argument")
}
}
}
}
}
}
else: compile_error!("io.print arguments must be a tuple")
}
return
}
hide system_read func(_ ?*mut anyopaque, stream ReadStream, buffer []mut u8) usize ! ReadError {
request usize = buffer.len
maximum usize :: usize(maxval!(c_long))
+37
View File
@@ -0,0 +1,37 @@
Layout :: enum {
auto
c
}
FieldInfo :: struct {
name []u8
type type
index usize
}
RecordInfo :: struct {
name ?[]u8
fields []FieldInfo
is_tuple bool
layout Layout
}
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 void
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"
+182
View File
@@ -656,6 +656,9 @@
]
},
"record_field": {
"type": "CHOICE",
"members": [
{
"type": "SEQ",
"members": [
{
@@ -685,6 +688,25 @@
}
]
},
{
"type": "FIELD",
"name": "type",
"content": {
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "type"
},
{
"type": "SYMBOL",
"name": "struct_type"
}
]
}
}
]
},
"enum_body": {
"type": "SEQ",
"members": [
@@ -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": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "identifier"
},
{
"type": "SYMBOL",
"name": "integer"
}
]
}
}
]
@@ -3707,10 +3754,19 @@
"name": "_newline"
}
},
{
"type": "CHOICE",
"members": [
{
"type": "SYMBOL",
"name": "field_initializer"
},
{
"type": "SYMBOL",
"name": "expression"
}
]
},
{
"type": "REPEAT",
"content": {
@@ -3734,9 +3790,18 @@
"name": "_newline"
}
},
{
"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))))))))))