richer formatting

This commit is contained in:
2026-07-15 20:38:54 +02:00
parent 1165cfb7c0
commit c4fa8e930f
9 changed files with 807 additions and 99 deletions
+5 -5
View File
@@ -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
+1 -1
View File
@@ -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
+19 -3
View File
@@ -686,8 +686,8 @@
evaluation; runtime-dependent values remain invalid in comptime contexts
- runtime-only behavior is rejected in comptime: external/bodyless `c_func`,
writable globals, and materializing comptime storage pointers/slices as runtime memory
- v1 keeps integer-only `$N` specialization keys; aggregate comptime parameters
and stable aggregate serialization are deferred
- milestone 39 extends specialization keys from integer/type/string values to
recursively stable values while keeping runtime ABI erasure unchanged
27.8 source-defined mutable runtime globals (implemented)
- allow mutable global declarations in Brolang source for process-global runtime
@@ -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
View File
@@ -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)
}
+210 -2
View File
@@ -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
View File
@@ -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)
}
+110
View File
@@ -0,0 +1,110 @@
io :: import "@std/io"
process :: import "@std/process"
debug :: import "@std/debug"
State :: enum {
idle
running
}
Payload :: union(enum) {
count i32
empty void
}
Pair :: struct { i32, bool }
Config :: struct {
enabled bool
ratio f64
label []u8
state State
values [2]i32
pair Pair
maybe ?i32
payload Payload
}
first Config :: Config {
enabled = true,
ratio = 1.5,
label = "bro",
state = .running,
values = [20, 22],
pair = Pair {7, true},
maybe = 9,
payload = Payload {count = 4},
}
same Config :: Config {
enabled = true,
ratio = 1.5,
label = "bro",
state = .running,
values = [20, 22],
pair = Pair {7, true},
maybe = 9,
payload = Payload {count = 4},
}
different Config :: Config {
enabled = false,
ratio = 2.5,
label = "bro",
state = .idle,
values = [21, 21],
pair = Pair {7, false},
maybe = none,
payload = .empty,
}
score func($config Config) i32 {
return config.values[0]
}
Carrier func($config Config) type {
return struct { value i32 }
}
read_carrier func($config Config, carrier Carrier(config)) i32 {
return carrier.value + config.values[0]
}
main func(init process.Init) i32 {
if score(first) != 20 or score(same) != 20 or score(different) != 21 {
return 1
}
carrier Carrier(first) :: Carrier(first) {value = 22}
if read_carrier(carrier) != 42 {
return 2
}
writer io.Writer :: io.Writer {impl = init.io, stream = .stdout}
positive f64 = 1.0
zero f64 = 0.0
infinity f64 :: positive / zero
nan f64 :: zero / zero
io.print(writer, "{} {} {} {} {s} {d} {b} {o} {x} {X} {c} {e} {{}} {d} {b} {} {} {e}\n", {
true,
-42,
1.5,
State.running,
"bro",
2.5,
10,
10,
255,
255,
u8('A'),
1.5,
minval!(i64),
u64(0),
infinity,
nan,
f32(1.5),
}) catch |_| {
return 3
}
debug.print("debug={} {x}\n", {State.idle, 42})
return 0
}
+155 -15
View File
@@ -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
View File
@@ -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