Previously, `t : Type = f64` stored a boxed string carrying the literal
name "f64"; comparisons and `type_of`/`type_name` round-trips lost the
underlying TypeId. This switches `Type` to a runtime-representable Any
pair: `{ tag = .any.index() (meta-marker), value = TypeId.index() }`.
Mechanism:
- `const_type` emits a 16-byte Any aggregate via insertvalue.
- `TypeId.any` advertises 16 bytes / 8-byte alignment so structs that
embed `t: Type` size correctly under verifySizes.
- `lowerBinaryOp` folds `==`/`!=` between static type-refs to a
`const_bool`, and decomposes runtime Any-vs-Any compares via
`unbox_any` so LLVM doesn't see icmp on aggregates.
- `lowerMatch`'s `is_type_match` path unboxes Any-typed subjects to
the i64 type tag before the switch, so `case type:` etc. fire.
- `lowerRuntimeDispatchCall` (used by `case T: ... cast(t) val`) does
the same unbox for the type-tag arg.
- `type_of(val: Any)` rebuilds an Any with `{.any, tag_of(val)}` so
the result is itself a `Type` value, not a bare i64.
- `buildPackSliceValue` stops re-boxing const_type — the value is
already canonical Any.
- `__sx_type_names` now indexes by TypeId across the whole table
using the new `types.formatTypeName` (structural names for `*T`,
`[]T`, `[N]T`, `?T`, `Vector(N,T)`, function/closure/tuple) so
runtime `type_name(t)` works for compound types.
- `interp.zig`'s comptime `type_name` accepts either the bare
`.type_tag` Value or the Any-boxed aggregate it now sees.
- `scanDecls` registers `Vec4 :: Vector(4, f32)` style aliases in
`type_alias_map` (before the `fn_ast_map` check; `Vector` IS a
`#builtin` fn). Lets `Vec4` in expression position lower as
`const_type(<vector tid>)`.
- `isStaticTypeArg` becomes scope-aware: a name shadowed by a runtime
local is not static. `isStaticTypeRef` is the symmetric helper for
the eq fold.
- `inferExprType` returns `.any` for bare type names (identifier and
type_expr) so pack arg types are correct.
Side effect: `print("{}", Vec4)` now prints the structural name
`Vector(4,f32)` rather than the alias literal `Vec4` — 12-meta's
expectation updated. Aliases stay pointer-equal to their target
(`Vec4 == Vector(4, f32)` is true).
Tests:
- examples/189-type-all-interactions.sx: 12-section comprehensive
coverage — literal `==`, `type_of(value) == T`, `Type` var storage,
`type_name` (static + runtime), printing Type values, generic
dispatch via `$T: Type`, `identity($T, val)`, `Wrap($T)`, reflection
builtins (`size_of`, `align_of`, `field_count`, `type_eq`),
`..$args` pack walking, `Type` in struct field, compound type
literals (`*Point`, `[4]s32`, `[]bool`, `?f64`).
- examples/12-meta.sx: expected output updated to reflect structural
name for the Vec4 alias path.
- ffi-objc-call-06-sret-return.ir: regenerated to absorb the new
type-name strings now emitted globally.
223/223 examples pass.
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: result = type_name(val);
|
|
}
|
|
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) -> string {
|
|
#insert build_format(fmt);
|
|
#insert "result;";
|
|
}
|
|
|
|
print :: ($fmt: string, ..$args) {
|
|
#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;
|
|
}
|
|
} |