Closes the optional-through-Any gap that test 178 pinned. Stdlib (`library/modules/std.sx`): - New `optional_to_string :: (o: $T) -> string` returns `"null"` when the optional is None, otherwise recurses through `any_to_string` on the unwrapped inner value. Per-shape monomorphisation re-emits this for each concrete `?T`. - `any_to_string` grows a `case optional:` arm that dispatches through `cast(type) val` (same shape as `case struct:` etc.). The cast picks up the dynamic optional type from the Any tag. Compiler (`src/ir/lower.zig`): - `resolveTypeCategoryTags` recognises "optional" as a dynamic category, scanning the TypeTable for `info == .optional`. The type-switch dispatch then routes any ?T tag into the optional arm. IR snapshots regenerated where the optional addition shifted constant pool / string numbering: 142, ffi-objc-call-06, ffi-objc-dsl-07. 218/218 (test 178 included). The variadic auto-unwrap in `packVariadicCallArgs` stays in place — direct `print(opt)` calls still flow through it. The new arm closes the gap for struct fields, slice elements, and any other path that boxes an optional before stringifying.
453 lines
13 KiB
Plaintext
453 lines
13 KiB
Plaintext
Vector :: ($N: int, $T: Type) -> Type #builtin;
|
|
out :: (str: string) -> void #builtin;
|
|
// sqrt :: (x: $T) -> T #builtin;
|
|
// sin :: (x: $T) -> T #builtin;
|
|
// cos :: (x: $T) -> T #builtin;
|
|
size_of :: ($T: Type) -> s64 #builtin;
|
|
align_of :: ($T: Type) -> s64 #builtin;
|
|
// Low-level libc bindings, used by allocator implementations to avoid
|
|
// recursing through `context.allocator`.
|
|
libc_malloc :: (size: s64) -> *void #foreign libc "malloc";
|
|
libc_free :: (ptr: *void) -> void #foreign libc "free";
|
|
|
|
malloc :: (size: s64) -> *void #foreign libc "malloc";
|
|
memcpy :: (dst: *void, src: *void, size: s64) -> *void #foreign libc "memcpy";
|
|
memset :: (dst: *void, val: s64, size: s64) -> void #foreign libc "memset";
|
|
free :: (ptr: *void) -> void #foreign libc "free";
|
|
type_of :: (val: $T) -> Type #builtin;
|
|
type_name :: ($T: Type) -> string #builtin;
|
|
field_count :: ($T: Type) -> s64 #builtin;
|
|
field_name :: ($T: Type, idx: s64) -> string #builtin;
|
|
field_value :: (s: $T, idx: s64) -> Any #builtin;
|
|
is_flags :: ($T: Type) -> bool #builtin;
|
|
field_value_int :: ($T: Type, idx: s64) -> s64 #builtin;
|
|
field_index :: ($T: Type, val: T) -> s64 #builtin;
|
|
string :: []u8 #builtin;
|
|
|
|
#import "allocators.sx";
|
|
|
|
// --- Context ---
|
|
|
|
Context :: struct {
|
|
allocator: Allocator;
|
|
data: *void;
|
|
}
|
|
|
|
// --- Slice & string allocation ---
|
|
|
|
cstring :: (size: s64) -> string {
|
|
raw := context.allocator.alloc(size + 1);
|
|
memset(raw, 0, size + 1);
|
|
s : string = ---;
|
|
s.ptr = xx raw;
|
|
s.len = size;
|
|
s;
|
|
}
|
|
|
|
alloc_slice :: ($T: Type, count: s64) -> []T {
|
|
raw := context.allocator.alloc(count * size_of(T));
|
|
memset(raw, 0, count * size_of(T));
|
|
s : []T = ---;
|
|
s.ptr = xx raw;
|
|
s.len = count;
|
|
s;
|
|
}
|
|
|
|
int_to_string :: (n: s64) -> string {
|
|
if n == 0 { return "0"; }
|
|
neg := n < 0;
|
|
v := if neg then 0 - n else n;
|
|
// Single pass: fill digits backwards into temp string, then substr
|
|
tmp := cstring(20);
|
|
i := 19;
|
|
while v > 0 {
|
|
tmp[i] = (v % 10) + 48;
|
|
v = v / 10;
|
|
i -= 1;
|
|
}
|
|
if neg { tmp[i] = 45; i -= 1; }
|
|
substr(tmp, i + 1, 20 - i - 1);
|
|
}
|
|
|
|
bool_to_string :: (b: bool) -> string {
|
|
if b then "true" else "false";
|
|
}
|
|
|
|
float_to_string :: (f: f64) -> string {
|
|
neg := f < 0.0;
|
|
v := if neg then 0.0 - f else f;
|
|
int_part := cast(s64) v;
|
|
frac := cast(s64) ((v - cast(f64) int_part) * 1000000.0);
|
|
if frac < 0 { frac = 0 - frac; }
|
|
istr := int_to_string(int_part);
|
|
fstr := int_to_string(frac);
|
|
il := istr.len;
|
|
fl := fstr.len;
|
|
prefix := if neg then 1 else 0;
|
|
total := prefix + il + 1 + 6;
|
|
buf := cstring(total);
|
|
pos := 0;
|
|
if neg { buf[0] = 45; pos = 1; }
|
|
memcpy(@buf[pos], istr.ptr, il);
|
|
pos = pos + il;
|
|
buf[pos] = 46;
|
|
pos += 1;
|
|
pad := 6 - fl;
|
|
memset(@buf[pos], 48, pad);
|
|
pos = pos + pad;
|
|
memcpy(@buf[pos], fstr.ptr, fl);
|
|
buf;
|
|
}
|
|
|
|
hex_group :: (buf: string, offset: s64, val: s64) {
|
|
i := offset + 3;
|
|
v := val;
|
|
while i >= offset {
|
|
d := v % 16;
|
|
buf[i] = if d < 10 then d + 48 else d - 10 + 97;
|
|
v = v / 16;
|
|
i -= 1;
|
|
}
|
|
}
|
|
|
|
int_to_hex_string :: (n: s64) -> string {
|
|
if n == 0 { return "0"; }
|
|
|
|
// Split into four 16-bit groups for correct unsigned treatment
|
|
g0 := n % 65536;
|
|
if g0 < 0 { g0 = g0 + 65536; }
|
|
r1 := (n - g0) / 65536;
|
|
g1 := r1 % 65536;
|
|
if g1 < 0 { g1 = g1 + 65536; }
|
|
r2 := (r1 - g1) / 65536;
|
|
g2 := r2 % 65536;
|
|
if g2 < 0 { g2 = g2 + 65536; }
|
|
r3 := (r2 - g2) / 65536;
|
|
g3 := r3 % 65536;
|
|
if g3 < 0 { g3 = g3 + 65536; }
|
|
|
|
buf := cstring(16);
|
|
hex_group(buf, 0, g3);
|
|
hex_group(buf, 4, g2);
|
|
hex_group(buf, 8, g1);
|
|
hex_group(buf, 12, g0);
|
|
|
|
// Skip leading zeros (keep at least 1 digit)
|
|
start := 0;
|
|
while start < 15 {
|
|
if buf[start] != 48 { break; }
|
|
start += 1;
|
|
}
|
|
substr(buf, start, 16 - start);
|
|
}
|
|
|
|
concat :: (a: string, b: string) -> string {
|
|
al := a.len;
|
|
bl := b.len;
|
|
buf := cstring(al + bl);
|
|
memcpy(buf.ptr, a.ptr, al);
|
|
memcpy(@buf[al], b.ptr, bl);
|
|
buf;
|
|
}
|
|
|
|
substr :: (s: string, start: s64, len: s64) -> string {
|
|
buf := cstring(len);
|
|
memcpy(buf.ptr, @s[start], len);
|
|
buf;
|
|
}
|
|
|
|
// Replace XML special characters with their entity references. Used
|
|
// when emitting Info.plist / AndroidManifest content from sx values
|
|
// that may contain user-supplied text (bundle id, app name, etc).
|
|
xml_escape :: (s: string) -> string {
|
|
result := "";
|
|
i := 0;
|
|
seg_start := 0;
|
|
while i < s.len {
|
|
c := s[i];
|
|
// 38='&', 60='<', 62='>', 34='"', 39='\''
|
|
ent := "";
|
|
if c == 38 { ent = "&"; }
|
|
if c == 60 { ent = "<"; }
|
|
if c == 62 { ent = ">"; }
|
|
if c == 34 { ent = """; }
|
|
if c == 39 { ent = "'"; }
|
|
if ent.len > 0 {
|
|
if i > seg_start {
|
|
result = concat(result, substr(s, seg_start, i - seg_start));
|
|
}
|
|
result = concat(result, ent);
|
|
seg_start = i + 1;
|
|
}
|
|
i += 1;
|
|
}
|
|
if seg_start < s.len {
|
|
result = concat(result, substr(s, seg_start, s.len - seg_start));
|
|
}
|
|
result;
|
|
}
|
|
|
|
// Join path components with the POSIX separator ('/'). Skips empty
|
|
// components and collapses duplicate separators at component
|
|
// boundaries. Used for bundle paths where Apple .app and Android APK
|
|
// both expect POSIX-style paths.
|
|
path_join :: (..parts: []string) -> string {
|
|
result := "";
|
|
i := 0;
|
|
while i < parts.len {
|
|
p := parts[i];
|
|
if p.len > 0 {
|
|
if result.len > 0 {
|
|
tail := result[result.len - 1];
|
|
head := p[0];
|
|
if tail == 47 {
|
|
if head == 47 {
|
|
p = substr(p, 1, p.len - 1);
|
|
}
|
|
} else {
|
|
if head != 47 {
|
|
result = concat(result, "/");
|
|
}
|
|
}
|
|
}
|
|
result = concat(result, p);
|
|
}
|
|
i += 1;
|
|
}
|
|
result;
|
|
}
|
|
|
|
struct_to_string :: (s: $T) -> string {
|
|
result := concat(type_name(T), "{");
|
|
i := 0;
|
|
while i < field_count(T) {
|
|
if i > 0 { result = concat(result, ", "); }
|
|
result = concat(result, field_name(T, i));
|
|
result = concat(result, ": ");
|
|
result = concat(result, any_to_string(field_value(s, i)));
|
|
i += 1;
|
|
}
|
|
concat(result, "}");
|
|
}
|
|
|
|
vector_to_string :: (v: $T) -> string {
|
|
result := "[";
|
|
i := 0;
|
|
while i < field_count(T) {
|
|
if i > 0 { result = concat(result, ", "); }
|
|
result = concat(result, any_to_string(field_value(v, i)));
|
|
i += 1;
|
|
}
|
|
concat(result, "]");
|
|
}
|
|
|
|
array_to_string :: (a: $T) -> string {
|
|
result := "[";
|
|
i := 0;
|
|
while i < field_count(T) {
|
|
if i > 0 { result = concat(result, ", "); }
|
|
result = concat(result, any_to_string(field_value(a, i)));
|
|
i += 1;
|
|
}
|
|
concat(result, "]");
|
|
}
|
|
|
|
slice_to_string :: (items: []$T) -> string {
|
|
result := "[";
|
|
i := 0;
|
|
while i < items.len {
|
|
if i > 0 { result = concat(result, ", "); }
|
|
result = concat(result, any_to_string(field_value(items, i)));
|
|
i += 1;
|
|
}
|
|
concat(result, "]");
|
|
}
|
|
|
|
pointer_to_string :: (p: $T) -> string {
|
|
addr : s64 = xx p;
|
|
if addr == 0 { "null"; } else {
|
|
concat(type_name(T), concat("@0x", int_to_hex_string(addr)));
|
|
}
|
|
}
|
|
|
|
flags_to_string :: (val: $T) -> string {
|
|
v := cast(s64) val;
|
|
result := "";
|
|
i := 0;
|
|
while i < field_count(T) {
|
|
fv := field_value_int(T, i);
|
|
if v & fv {
|
|
if result.len > 0 { result = concat(result, " | "); }
|
|
result = concat(result, concat(".", field_name(T, i)));
|
|
}
|
|
i += 1;
|
|
}
|
|
if result.len == 0 { result = "0"; }
|
|
result;
|
|
}
|
|
|
|
enum_to_string :: (u: $T) -> string {
|
|
if is_flags(T) { return flags_to_string(u); }
|
|
idx := field_index(T, u);
|
|
result := concat(".", field_name(T, idx));
|
|
payload := field_value(u, idx);
|
|
pstr := any_to_string(payload);
|
|
if pstr.len > 0 {
|
|
result = concat(result, concat("(", concat(pstr, ")")));
|
|
}
|
|
result;
|
|
}
|
|
|
|
optional_to_string :: (o: $T) -> string {
|
|
if o == null { return "null"; }
|
|
return any_to_string(o!);
|
|
}
|
|
|
|
any_to_string :: (val: Any) -> string {
|
|
result := "<?>";
|
|
type := type_of(val);
|
|
if type == {
|
|
case void: result = "";
|
|
case int: result = int_to_string(xx val);
|
|
case string: { s : string = xx val; result = s; }
|
|
case bool: result = bool_to_string(xx val);
|
|
case float: result = float_to_string(xx val);
|
|
case struct: result = struct_to_string(cast(type) val);
|
|
case enum: result = enum_to_string(cast(type) val);
|
|
case vector: result = vector_to_string(cast(type) val);
|
|
case array: result = array_to_string(cast(type) val);
|
|
case slice: result = slice_to_string(cast(type) val);
|
|
case pointer: result = pointer_to_string(cast(type) val);
|
|
case optional: result = optional_to_string(cast(type) val);
|
|
case type: { s : string = xx val; result = s; }
|
|
}
|
|
result;
|
|
}
|
|
|
|
build_format :: (fmt: string) -> string {
|
|
code := "result := \"\"; ";
|
|
seg_start := 0;
|
|
i := 0;
|
|
arg_idx := 0;
|
|
while i < fmt.len {
|
|
if fmt[i] == 123 {
|
|
if i + 1 < fmt.len {
|
|
if fmt[i + 1] == 125 {
|
|
if i > seg_start {
|
|
code = concat(code, "result = concat(result, substr(fmt, ");
|
|
code = concat(code, int_to_string(seg_start));
|
|
code = concat(code, ", ");
|
|
code = concat(code, int_to_string(i - seg_start));
|
|
code = concat(code, ")); ");
|
|
}
|
|
code = concat(code, "result = concat(result, any_to_string(args[");
|
|
code = concat(code, int_to_string(arg_idx));
|
|
code = concat(code, "])); ");
|
|
arg_idx += 1;
|
|
i += 2;
|
|
seg_start = i;
|
|
} else if fmt[i + 1] == 123 {
|
|
code = concat(code, "result = concat(result, substr(fmt, ");
|
|
code = concat(code, int_to_string(seg_start));
|
|
code = concat(code, ", ");
|
|
code = concat(code, int_to_string(i - seg_start + 1));
|
|
code = concat(code, ")); ");
|
|
i += 2;
|
|
seg_start = i;
|
|
} else {
|
|
i += 1;
|
|
}
|
|
} else {
|
|
i += 1;
|
|
}
|
|
} else if fmt[i] == 125 {
|
|
if i + 1 < fmt.len {
|
|
if fmt[i + 1] == 125 {
|
|
code = concat(code, "result = concat(result, substr(fmt, ");
|
|
code = concat(code, int_to_string(seg_start));
|
|
code = concat(code, ", ");
|
|
code = concat(code, int_to_string(i - seg_start + 1));
|
|
code = concat(code, ")); ");
|
|
i += 2;
|
|
seg_start = i;
|
|
} else {
|
|
i += 1;
|
|
}
|
|
} else {
|
|
i += 1;
|
|
}
|
|
} else {
|
|
i += 1;
|
|
}
|
|
}
|
|
if seg_start < fmt.len {
|
|
code = concat(code, "result = concat(result, substr(fmt, ");
|
|
code = concat(code, int_to_string(seg_start));
|
|
code = concat(code, ", ");
|
|
code = concat(code, int_to_string(fmt.len - seg_start));
|
|
code = concat(code, ")); ");
|
|
}
|
|
code;
|
|
}
|
|
|
|
format :: ($fmt: string, ..args: []Any) -> string {
|
|
#insert build_format(fmt);
|
|
#insert "result;";
|
|
}
|
|
|
|
print :: ($fmt: string, ..args: []Any) {
|
|
#insert build_format(fmt);
|
|
#insert "out(result);";
|
|
}
|
|
|
|
// User-space `xx` extension. `xx val : T` where the built-in conversion
|
|
// ladder makes no progress falls through to an `impl Into(T) for Source`
|
|
// lookup; the compiler monomorphises `convert` for the (Source, T) pair
|
|
// and emits a direct call. Compile-time only — no vtable, no runtime
|
|
// dispatch.
|
|
Into :: protocol(Target: Type) {
|
|
convert :: () -> Target;
|
|
}
|
|
|
|
List :: struct ($T: Type) {
|
|
items: [*]T = null;
|
|
len: s64 = 0;
|
|
cap: s64 = 0;
|
|
|
|
append :: (list: *List(T), item: T, alloc: Allocator = context.allocator) {
|
|
if list.len >= list.cap {
|
|
new_cap := if list.cap == 0 then 4 else list.cap * 2;
|
|
new_items : [*]T = xx alloc.alloc(new_cap * size_of(T));
|
|
if list.len > 0 {
|
|
memcpy(new_items, list.items, list.len * size_of(T));
|
|
alloc.dealloc(list.items);
|
|
}
|
|
list.items = new_items;
|
|
list.cap = new_cap;
|
|
}
|
|
list.items[list.len] = item;
|
|
list.len += 1;
|
|
}
|
|
|
|
ensure_capacity :: (list: *List(T), n: s64, alloc: Allocator = context.allocator) {
|
|
if list.cap >= n { return; }
|
|
new_cap := if list.cap == 0 then 4 else list.cap;
|
|
while new_cap < n { new_cap = new_cap * 2; }
|
|
new_items : [*]T = xx alloc.alloc(new_cap * size_of(T));
|
|
if list.len > 0 {
|
|
memcpy(new_items, list.items, list.len * size_of(T));
|
|
alloc.dealloc(list.items);
|
|
}
|
|
list.items = new_items;
|
|
list.cap = new_cap;
|
|
}
|
|
|
|
deinit :: (list: *List(T), alloc: Allocator = context.allocator) {
|
|
if list.items != null {
|
|
alloc.dealloc(list.items);
|
|
}
|
|
list.items = null;
|
|
list.len = 0;
|
|
list.cap = 0;
|
|
}
|
|
} |