richer formatting
This commit is contained in:
+5
-5
@@ -150,12 +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
|
||||
- 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
|
||||
- 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; `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 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
|
||||
- `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
|
||||
@@ -174,7 +175,7 @@ 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, 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
|
||||
- `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`
|
||||
|
||||
@@ -190,12 +191,11 @@ as `math.divfloor(a, b)` resolve to ordinary functions.
|
||||
|
||||
## PLANNED / DEFERRED
|
||||
|
||||
- aggregate comptime parameters and stable aggregate specialization keys
|
||||
- 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, reflection payloads beyond records, 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
|
||||
|
||||
@@ -203,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, 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
|
||||
- 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
|
||||
|
||||
@@ -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
|
||||
@@ -867,7 +867,23 @@
|
||||
38. place every intrinsic behind direct unqualified `name!(...)` syntax, freeing the bare names for
|
||||
user functions (implemented)
|
||||
|
||||
39. aggregate comptime parameters and richer formatting
|
||||
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
|
||||
|
||||
|
||||
+137
-26
@@ -232,6 +232,8 @@ Checker :: struct {
|
||||
current_comptime_values: []Comptime_Value,
|
||||
static_state: Ct_State,
|
||||
static_bindings: [dynamic]Static_Binding,
|
||||
comptime_keys: [dynamic]string,
|
||||
comptime_static_values: [dynamic]Ct_Value_Id,
|
||||
inline_context: [dynamic]Inline_Expansion,
|
||||
type_factories: [dynamic]Type_Factory_Entry,
|
||||
generated_types: [dynamic]Generated_Type_Entry,
|
||||
@@ -528,7 +530,14 @@ static_field_value :: proc(checker: ^Checker, base, name: symbol.Id) -> (Ct_Valu
|
||||
if !ok || binding.value == INVALID_CT_VALUE || int(binding.value) >= len(checker.static_state.values) {
|
||||
return {}, false
|
||||
}
|
||||
value := checker.static_state.values[binding.value]
|
||||
return persistent_field_value(checker, binding.value, name)
|
||||
}
|
||||
|
||||
persistent_field_value :: proc(checker: ^Checker, root: Ct_Value_Id, name: symbol.Id) -> (Ct_Value, bool) {
|
||||
if root == INVALID_CT_VALUE || int(root) >= len(checker.static_state.values) {
|
||||
return {}, false
|
||||
}
|
||||
value := checker.static_state.values[root]
|
||||
index, _, found := find_struct_field(checker, value.type, name)
|
||||
children := ct_child_slice(&checker.static_state, value)
|
||||
if !found || index < 0 || index >= len(children) || children[index] == INVALID_CT_VALUE ||
|
||||
@@ -1705,7 +1714,9 @@ explicit_comptime_argument_valid :: proc(
|
||||
if types.is_concrete_integer(type_from_syntax(checker, param.type, function.pkg, function.file)) {
|
||||
return eval_integer_constant_in_context(checker, arg, pkg, file).kind == .Value
|
||||
}
|
||||
return false
|
||||
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
|
||||
_, ok := eval_static_comptime_value(checker, param.name, arg, declared, pkg, file)
|
||||
return ok
|
||||
}
|
||||
|
||||
search_call_mappings :: proc(search: ^Call_Mapping_Search, param_index, source_index: int) {
|
||||
@@ -1966,13 +1977,15 @@ bind_inferred_comptime :: proc(
|
||||
if matches {
|
||||
if existing.kind == .Type {
|
||||
matches = types.equal(types.resolve_alias(existing.type, &checker.module.types), types.resolve_alias(value.type, &checker.module.types))
|
||||
} else if existing.kind == .Static {
|
||||
matches = existing.fingerprint == value.fingerprint && existing.key == value.key && types.equal(existing.type, value.type)
|
||||
} else {
|
||||
matches = existing.value == value.value && types.equal(existing.type, value.type)
|
||||
}
|
||||
}
|
||||
if !matches && diagnose {
|
||||
left := type_label(checker, existing.type) if existing.kind == .Type else fmt.aprintf("%d", existing.value, allocator=checker.allocator)
|
||||
right := type_label(checker, value.type) if value.kind == .Type else fmt.aprintf("%d", value.value, allocator=checker.allocator)
|
||||
left := type_label(checker, existing.type) if existing.kind == .Type else fmt.aprintf("<comptime value>", allocator=checker.allocator) if existing.kind == .Static else fmt.aprintf("%d", existing.value, allocator=checker.allocator)
|
||||
right := type_label(checker, value.type) if value.kind == .Type else fmt.aprintf("<comptime value>", allocator=checker.allocator) if value.kind == .Static else fmt.aprintf("%d", value.value, allocator=checker.allocator)
|
||||
source.addf(checker.diagnostics, span, "conflicting inference for comptime parameter '%s': %s and %s", symbol_text(checker, name), left, right)
|
||||
if existing.kind != .Type {
|
||||
delete(left, checker.allocator)
|
||||
@@ -2221,9 +2234,12 @@ infer_call_comptime_values :: proc(
|
||||
} else if is_comptime_string_param(checker, param, function) {
|
||||
values[ordinal].kind = .String
|
||||
values[ordinal].type = type_from_syntax(checker, param.type, function.pkg, function.file)
|
||||
} else {
|
||||
} else if types.is_concrete_integer(type_from_syntax(checker, param.type, function.pkg, function.file)) {
|
||||
values[ordinal].kind = .Integer
|
||||
values[ordinal].type = type_from_syntax(checker, param.type, function.pkg, function.file)
|
||||
} else {
|
||||
values[ordinal].kind = .Static
|
||||
values[ordinal].type = type_from_syntax(checker, param.type, function.pkg, function.file)
|
||||
}
|
||||
ordinal += 1
|
||||
}
|
||||
@@ -2287,7 +2303,7 @@ infer_call_comptime_values :: proc(
|
||||
kind=.String,
|
||||
}
|
||||
bound[binding_index] = true
|
||||
} else {
|
||||
} else if types.is_concrete_integer(type_from_syntax(checker, param.type, function.pkg, function.file)) {
|
||||
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
|
||||
constant := eval_integer_constant_in_context(checker, arg_id, pkg, file)
|
||||
if constant.kind != .Value {
|
||||
@@ -2321,6 +2337,32 @@ infer_call_comptime_values :: proc(
|
||||
}
|
||||
values[binding_index] = Comptime_Value{name=param.name, type=declared, value=constant.value, kind=.Integer}
|
||||
bound[binding_index] = true
|
||||
} else {
|
||||
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
|
||||
available: [dynamic]Comptime_Value
|
||||
available.allocator = checker.allocator
|
||||
append(&available, ..checker.current_comptime_values)
|
||||
for prior, prior_index in values[:binding_index] {
|
||||
if bound[prior_index] {
|
||||
append(&available, prior)
|
||||
}
|
||||
}
|
||||
value, value_ok := eval_static_comptime_value(
|
||||
checker, param.name, arg_id, declared, pkg, file, available[:], diagnose,
|
||||
)
|
||||
delete(available)
|
||||
if !value_ok {
|
||||
if failure != nil && len(failure^) == 0 {
|
||||
failure^ = fmt.aprintf(
|
||||
"argument %d for comptime parameter '%s' has no stable comptime identity",
|
||||
source_index+1, symbol_text(checker, param.name), allocator=checker.allocator,
|
||||
)
|
||||
}
|
||||
matched = false
|
||||
continue
|
||||
}
|
||||
values[binding_index] = value
|
||||
bound[binding_index] = true
|
||||
}
|
||||
}
|
||||
all_bound := true
|
||||
@@ -2768,18 +2810,7 @@ collect_comptime_values :: proc(
|
||||
continue
|
||||
}
|
||||
declared := type_from_syntax(checker, param.type, function.pkg, function.file)
|
||||
if !types.is_concrete_integer(declared) {
|
||||
if diagnose {
|
||||
source.addf(
|
||||
checker.diagnostics,
|
||||
param.span,
|
||||
"comptime parameter '%s' requires a concrete integer type",
|
||||
symbol_text(checker, param.name),
|
||||
)
|
||||
}
|
||||
ok = false
|
||||
continue
|
||||
}
|
||||
if types.is_concrete_integer(declared) {
|
||||
constant := Constant{kind = .Not_Constant}
|
||||
if index < len(args) {
|
||||
constant = eval_integer_constant_in_context(checker, args[index], pkg, file, values=extra_values)
|
||||
@@ -2810,6 +2841,25 @@ collect_comptime_values :: proc(
|
||||
continue
|
||||
}
|
||||
append(&values, Comptime_Value{name=param.name, type=declared, value=constant.value})
|
||||
continue
|
||||
}
|
||||
if index >= len(args) || args[index] == ast.INVALID_EXPR {
|
||||
ok = false
|
||||
continue
|
||||
}
|
||||
available: [dynamic]Comptime_Value
|
||||
available.allocator = checker.allocator
|
||||
append(&available, ..extra_values)
|
||||
append(&available, ..values[:])
|
||||
value, value_ok := eval_static_comptime_value(
|
||||
checker, param.name, args[index], declared, pkg, file, available[:], diagnose,
|
||||
)
|
||||
delete(available)
|
||||
if !value_ok {
|
||||
ok = false
|
||||
continue
|
||||
}
|
||||
append(&values, value)
|
||||
}
|
||||
if !ok {
|
||||
delete(values)
|
||||
@@ -3111,12 +3161,11 @@ validate_declarations :: proc(checker: ^Checker) {
|
||||
)
|
||||
}
|
||||
if !is_type_metatype_syntax(checker, param.type) &&
|
||||
!types.is_concrete_integer(param_type) &&
|
||||
!is_comptime_string_param(checker, param, function) {
|
||||
!is_runtime_type(checker, param_type) && param_type != types.RANGE {
|
||||
checker.template_diagnostics[function_id] = source.addf(
|
||||
checker.diagnostics,
|
||||
param.span,
|
||||
"comptime parameter '%s' requires type, a concrete integer type, or immutable []u8",
|
||||
"comptime parameter '%s' requires type or a concrete value type",
|
||||
symbol_text(checker, param.name),
|
||||
)
|
||||
}
|
||||
@@ -3503,9 +3552,10 @@ validate_meta_schema :: proc(checker: ^Checker) {
|
||||
}
|
||||
field_info := find(checker, meta_package, "FieldInfo")
|
||||
record_info := find(checker, meta_package, "RecordInfo")
|
||||
enum_info := find(checker, meta_package, "EnumInfo")
|
||||
type_info := find(checker, meta_package, "TypeInfo")
|
||||
layout := find(checker, meta_package, "Layout")
|
||||
valid := types.is_valid(field_info) && types.is_valid(record_info) &&
|
||||
valid := types.is_valid(field_info) && types.is_valid(record_info) && types.is_valid(enum_info) &&
|
||||
types.is_valid(type_info) && types.is_valid(layout)
|
||||
layout_item, layout_ok := types.node(&checker.module.types, layout)
|
||||
layout_members := types.enum_members_for(&checker.module.types, layout)
|
||||
@@ -3516,8 +3566,10 @@ validate_meta_schema :: proc(checker: ^Checker) {
|
||||
}
|
||||
field_item, field_ok := types.node(&checker.module.types, field_info)
|
||||
record_item, record_ok := types.node(&checker.module.types, record_info)
|
||||
enum_item, enum_ok := types.node(&checker.module.types, enum_info)
|
||||
valid = valid && field_ok && field_item.kind == .Struct && !field_item.tuple && !field_item.c_layout &&
|
||||
record_ok && record_item.kind == .Struct && !record_item.tuple && !record_item.c_layout
|
||||
record_ok && record_item.kind == .Struct && !record_item.tuple && !record_item.c_layout &&
|
||||
enum_ok && enum_item.kind == .Struct && !enum_item.tuple && !enum_item.c_layout
|
||||
field_fields := types.fields_for(&checker.module.types, field_info)
|
||||
valid = valid && len(field_fields) == 3
|
||||
if valid {
|
||||
@@ -3528,6 +3580,13 @@ validate_meta_schema :: proc(checker: ^Checker) {
|
||||
symbol_text(checker, symbol.Id(field_fields[2].name)) == "index" &&
|
||||
types.equal(field_fields[2].type, types.USIZE)
|
||||
}
|
||||
enum_fields := types.fields_for(&checker.module.types, enum_info)
|
||||
valid = valid && len(enum_fields) == 1
|
||||
if valid {
|
||||
fields_item, fields_ok := types.node(&checker.module.types, enum_fields[0].type)
|
||||
valid = symbol_text(checker, symbol.Id(enum_fields[0].name)) == "fields" && fields_ok &&
|
||||
fields_item.kind == .Slice && !fields_item.mutable && types.equal(fields_item.child, field_info)
|
||||
}
|
||||
record_fields := types.fields_for(&checker.module.types, record_info)
|
||||
valid = valid && len(record_fields) == 4
|
||||
if valid {
|
||||
@@ -3553,7 +3612,8 @@ validate_meta_schema :: proc(checker: ^Checker) {
|
||||
field := type_fields[index]
|
||||
if symbol_text(checker, symbol.Id(field.name)) != tag ||
|
||||
(tag == "record" && !types.equal(field.type, record_info)) ||
|
||||
(tag != "record" && !types.is_void(field.type)) {
|
||||
(tag == "enum" && !types.equal(field.type, enum_info)) ||
|
||||
(tag != "record" && tag != "enum" && !types.is_void(field.type)) {
|
||||
valid = false
|
||||
break
|
||||
}
|
||||
@@ -3623,7 +3683,7 @@ validate_type_nodes :: proc(checker: ^Checker) {
|
||||
}
|
||||
item_name := symbol_text(checker, symbol.Id(item.name))
|
||||
comptime_meta := meta_package &&
|
||||
(item_name == "FieldInfo" || item_name == "RecordInfo" || item_name == "TypeInfo")
|
||||
(item_name == "FieldInfo" || item_name == "RecordInfo" || item_name == "EnumInfo" || item_name == "TypeInfo")
|
||||
if item.c_layout && !item.opaque && item.field_count == 0 {
|
||||
source.add(
|
||||
checker.diagnostics,
|
||||
@@ -4263,6 +4323,12 @@ infer_expr :: proc(
|
||||
}
|
||||
if !types.is_valid(last) && symbol.is_valid(expr.qualifier) &&
|
||||
find_import(checker, file, expr.qualifier) == ast.INVALID_IMPORT {
|
||||
if value, ok := current_comptime_value(checker, expr.qualifier);
|
||||
ok && value.kind == .Static {
|
||||
if _, field, found := find_struct_field(checker, value.type, expr.name); found {
|
||||
last = field.type
|
||||
}
|
||||
}
|
||||
if value, ok := current_static_binding(checker, expr.qualifier); ok {
|
||||
if _, field, found := find_struct_field(checker, value.type, expr.name); found {
|
||||
last = field.type
|
||||
@@ -4298,7 +4364,7 @@ infer_expr :: proc(
|
||||
if !types.is_valid(last) {
|
||||
if !symbol.is_valid(expr.qualifier) {
|
||||
if value, ok := current_comptime_value(checker, expr.name); ok {
|
||||
if value.kind == .Integer || value.kind == .String {
|
||||
if value.kind == .Integer || value.kind == .String || value.kind == .Static {
|
||||
last = value.type
|
||||
}
|
||||
}
|
||||
@@ -4385,6 +4451,13 @@ infer_expr :: proc(
|
||||
continue
|
||||
}
|
||||
if field_expr, handled := field_intrinsic_expr(checker, expr, pkg, file); handled {
|
||||
if enum_type, ok := resolve_type_argument(checker, field_expr.left, pkg, file);
|
||||
ok && types.is_enum(enum_type, &checker.module.types) {
|
||||
_, member_ok := find_enum_member(checker, enum_type, field_expr.name)
|
||||
last = enum_type if member_ok else types.INVALID
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
base_type := infer_nested_expr(
|
||||
checker, field_expr.left, locals, pkg, file, demanded, local_types,
|
||||
)
|
||||
@@ -6360,6 +6433,15 @@ build_qualified_value_field :: proc(
|
||||
value, ok := build_field_from_value(checker, expr, base, local.type)
|
||||
return value, true, ok
|
||||
}
|
||||
if value, ok := current_comptime_value(checker, expr.qualifier);
|
||||
ok && value.kind == .Static && value.static_value != INVALID_CT_VALUE &&
|
||||
int(value.static_value) < len(checker.static_state.values) {
|
||||
field_value, field_ok := persistent_field_value(checker, value.static_value, expr.name)
|
||||
if !field_ok {
|
||||
return hir.INVALID_EXPR, true, false
|
||||
}
|
||||
return build_static_value(checker, field_value, expr.span, types.INVALID), true, true
|
||||
}
|
||||
if global := find_global(checker, expr.qualifier, pkg, file); global != ast.INVALID_GLOBAL {
|
||||
base := build_global_reference(checker, global, expr.span, global_reads)
|
||||
value, ok := build_field_from_value(checker, expr, base, checker.global_types[global])
|
||||
@@ -7482,6 +7564,15 @@ build_expr :: proc(
|
||||
})
|
||||
}
|
||||
}
|
||||
if last == hir.INVALID_EXPR && symbol.is_valid(expr.qualifier) {
|
||||
if value, ok := current_comptime_value(checker, expr.qualifier);
|
||||
ok && value.kind == .Static && value.static_value != INVALID_CT_VALUE &&
|
||||
int(value.static_value) < len(checker.static_state.values) {
|
||||
if field_value, found := persistent_field_value(checker, value.static_value, expr.name); found {
|
||||
last = build_static_value(checker, field_value, expr.span, frame.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
if last == hir.INVALID_EXPR && symbol.is_valid(expr.qualifier) {
|
||||
if value, ok := static_field_value(checker, expr.qualifier, expr.name); ok {
|
||||
last = build_static_value(checker, value, expr.span, frame.expected)
|
||||
@@ -7558,6 +7649,11 @@ build_expr :: proc(
|
||||
checker, constant_expr, locals, global_reads, calls,
|
||||
frame.expected, pkg, file,
|
||||
)
|
||||
} else if value.kind == .Static && value.static_value != INVALID_CT_VALUE &&
|
||||
int(value.static_value) < len(checker.static_state.values) {
|
||||
last = build_static_value(
|
||||
checker, checker.static_state.values[value.static_value], expr.span, frame.expected,
|
||||
)
|
||||
} else {
|
||||
id := source.addf(
|
||||
checker.diagnostics,
|
||||
@@ -7628,6 +7724,12 @@ build_expr :: proc(
|
||||
continue
|
||||
}
|
||||
if field_expr, handled := field_intrinsic_expr(checker, expr, pkg, file); handled {
|
||||
if enum_type, ok := resolve_type_argument(checker, field_expr.left, pkg, file);
|
||||
ok && types.is_enum(enum_type, &checker.module.types) {
|
||||
last = enum_member_hir(checker, enum_type, field_expr.name, field_expr.span)
|
||||
_ = pop(&stack)
|
||||
continue
|
||||
}
|
||||
base := build_nested_expr(
|
||||
checker, field_expr.left, locals, global_reads, calls,
|
||||
types.INVALID, pkg, file,
|
||||
@@ -8315,6 +8417,8 @@ make_link_name :: proc(checker: ^Checker, id: Spec_Id) -> string {
|
||||
strings.write_byte(&builder, hex[byte>>4])
|
||||
strings.write_byte(&builder, hex[byte&0xf])
|
||||
}
|
||||
} else if value.kind == .Static {
|
||||
fmt.sbprintf(&builder, "__ca%d_%016x", len(value.key), value.fingerprint)
|
||||
} else {
|
||||
strings.write_string(&builder, "__cv")
|
||||
if value.value < 0 {
|
||||
@@ -12070,6 +12174,8 @@ check :: proc(
|
||||
checker.type_factory_origins.allocator = allocator
|
||||
checker.call_resolutions.allocator = allocator
|
||||
checker.static_bindings.allocator = allocator
|
||||
checker.comptime_keys.allocator = allocator
|
||||
checker.comptime_static_values.allocator = allocator
|
||||
checker.inline_context.allocator = allocator
|
||||
checker.static_state = ct_state_make(&checker, 0, ast.INVALID_FILE)
|
||||
build_symbol_indexes(&checker)
|
||||
@@ -12146,6 +12252,11 @@ check :: proc(
|
||||
delete(checker.call_resolutions)
|
||||
ct_state_destroy(&checker.static_state)
|
||||
delete(checker.static_bindings)
|
||||
for key in checker.comptime_keys {
|
||||
delete(key, allocator)
|
||||
}
|
||||
delete(checker.comptime_keys)
|
||||
delete(checker.comptime_static_values)
|
||||
delete(checker.inline_context)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import "../types"
|
||||
import "base:intrinsics"
|
||||
|
||||
import "core:fmt"
|
||||
import "core:hash"
|
||||
import "core:math"
|
||||
import "core:mem"
|
||||
import "core:strings"
|
||||
@@ -18,6 +19,7 @@ Comptime_Value_Kind :: enum u8 {
|
||||
Integer,
|
||||
Type,
|
||||
String,
|
||||
Static,
|
||||
}
|
||||
|
||||
Comptime_Value :: struct {
|
||||
@@ -25,6 +27,9 @@ Comptime_Value :: struct {
|
||||
type: types.Type,
|
||||
value: i128,
|
||||
text: string,
|
||||
static_value: Ct_Value_Id,
|
||||
key: string,
|
||||
fingerprint: u64,
|
||||
kind: Comptime_Value_Kind,
|
||||
}
|
||||
|
||||
@@ -85,7 +90,8 @@ comptime_values_equal :: proc(left, right: []Comptime_Value) -> bool {
|
||||
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 == .String && value.text != other.text) {
|
||||
(value.kind == .String && value.text != other.text) ||
|
||||
(value.kind == .Static && (value.fingerprint != other.fingerprint || value.key != other.key)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -362,6 +368,9 @@ ct_state_make :: proc(
|
||||
}
|
||||
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 {
|
||||
@@ -2312,9 +2321,10 @@ ct_typeinfo_value :: proc(state: ^Ct_State, target: types.Type, span: source.Spa
|
||||
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(layout_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",
|
||||
@@ -2352,6 +2362,31 @@ ct_typeinfo_value :: proc(state: ^Ct_State, target: types.Type, span: source.Spa
|
||||
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{
|
||||
@@ -2437,6 +2472,22 @@ ct_eval_call_expr :: proc(state: ^Ct_State, expr: ast.Expr, expected: types.Type
|
||||
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
|
||||
@@ -2753,6 +2804,163 @@ store_static_binding :: proc(checker: ^Checker, source: ^Ct_State, id: Ct_Value_
|
||||
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'")
|
||||
|
||||
+126
-7
@@ -2461,7 +2461,10 @@ main func(init process.Init) void {
|
||||
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, "{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 |_| {}
|
||||
}
|
||||
`
|
||||
@@ -2481,16 +2484,19 @@ main func(init process.Init) void {
|
||||
hir_module := checker.check(&ast_module, &diagnostics, &symbols)
|
||||
defer hir.destroy_module(&hir_module)
|
||||
|
||||
found := [7]bool{}
|
||||
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")
|
||||
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 {
|
||||
@@ -2498,6 +2504,121 @@ main func(init process.Init) void {
|
||||
}
|
||||
}
|
||||
|
||||
@(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 {
|
||||
@@ -3327,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()
|
||||
@@ -3359,7 +3481,6 @@ 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 {
|
||||
@@ -3369,7 +3490,6 @@ main func() void {
|
||||
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 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")
|
||||
}
|
||||
@@ -3379,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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
+155
-15
@@ -75,12 +75,12 @@ 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
|
||||
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, 10)
|
||||
digit_value i64 :: rem!(current, i64(base))
|
||||
digit u8 = 0
|
||||
if digit_value < 0 {
|
||||
digit = u8(-digit_value)
|
||||
@@ -88,8 +88,14 @@ hide write_decimal_signed func(writer Writer, value i64) void ! WriteError {
|
||||
digit = u8(digit_value)
|
||||
}
|
||||
end -= 1
|
||||
if digit < 10 {
|
||||
buffer[end] = '0' + digit
|
||||
current = divtrunc!(current, 10)
|
||||
} else if uppercase {
|
||||
buffer[end] = 'A' + digit - 10
|
||||
} else {
|
||||
buffer[end] = 'a' + digit - 10
|
||||
}
|
||||
current = divtrunc!(current, i64(base))
|
||||
if current == 0 {
|
||||
break
|
||||
}
|
||||
@@ -102,15 +108,21 @@ hide write_decimal_signed func(writer Writer, value i64) void ! WriteError {
|
||||
return
|
||||
}
|
||||
|
||||
hide write_decimal_unsigned func(writer Writer, value u64) void ! WriteError {
|
||||
buffer [21]mut u8 = undefined
|
||||
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, 10))
|
||||
digit u8 :: u8(rem!(current, base))
|
||||
end -= 1
|
||||
if digit < 10 {
|
||||
buffer[end] = '0' + digit
|
||||
current = divtrunc!(current, 10)
|
||||
} else if uppercase {
|
||||
buffer[end] = 'A' + digit - 10
|
||||
} else {
|
||||
buffer[end] = 'a' + digit - 10
|
||||
}
|
||||
current = divtrunc!(current, base)
|
||||
if current == 0 {
|
||||
break
|
||||
}
|
||||
@@ -122,8 +134,15 @@ hide write_decimal_unsigned func(writer Writer, value u64) void ! WriteError {
|
||||
hide FormatTokenKind :: enum {
|
||||
unused
|
||||
literal
|
||||
default
|
||||
string
|
||||
decimal
|
||||
binary
|
||||
octal
|
||||
hex_lower
|
||||
hex_upper
|
||||
character
|
||||
scientific
|
||||
}
|
||||
|
||||
hide FormatToken :: struct {
|
||||
@@ -171,17 +190,33 @@ hide parse_format func($N usize, $format []u8, $Args type) [N]mut FormatToken {
|
||||
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 '{s}' or '{d}'")
|
||||
compile_error!("io.print format expects a one-character specifier")
|
||||
}
|
||||
kind FormatTokenKind = .unused
|
||||
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")
|
||||
}
|
||||
@@ -193,7 +228,7 @@ hide parse_format func($N usize, $format []u8, $Args type) [N]mut FormatToken {
|
||||
}
|
||||
token_count += 1
|
||||
argument_count += 1
|
||||
cursor += 3
|
||||
cursor += width
|
||||
literal_start = cursor
|
||||
continue
|
||||
}
|
||||
@@ -229,14 +264,91 @@ hide format_field_name func($T type, index usize) []u8 {
|
||||
}
|
||||
}
|
||||
|
||||
hide write_decimal func(writer Writer, $T type, value T) void ! WriteError {
|
||||
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_decimal_signed(writer, i64(value))
|
||||
try write_integer_signed(writer, i64(value), base, uppercase)
|
||||
} else {
|
||||
try write_decimal_unsigned(writer, u64(value))
|
||||
try write_integer_unsigned(writer, u64(value), base, uppercase)
|
||||
}
|
||||
else: compile_error!("io.print '{d}' requires an integer argument")
|
||||
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
|
||||
}
|
||||
@@ -259,7 +371,35 @@ print func(
|
||||
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
|
||||
}
|
||||
|
||||
+5
-1
@@ -16,6 +16,10 @@ RecordInfo :: struct {
|
||||
layout Layout
|
||||
}
|
||||
|
||||
EnumInfo :: struct {
|
||||
fields []FieldInfo
|
||||
}
|
||||
|
||||
TypeInfo :: union(enum) {
|
||||
invalid void
|
||||
void void
|
||||
@@ -29,7 +33,7 @@ TypeInfo :: union(enum) {
|
||||
range void
|
||||
optional void
|
||||
function void
|
||||
enum void
|
||||
enum EnumInfo
|
||||
record RecordInfo
|
||||
union void
|
||||
fallible void
|
||||
|
||||
Reference in New Issue
Block a user