Files
sx/library/modules/std.sx
agra b69a2ea29c mem: Step 8 — delete context global from std.sx
The `context : Context = ---;` global in `library/modules/std.sx` had
no remaining readers — all `context.X` lookups in user code resolve
through `current_ctx_ref` (Step 5), `push Context.{...}` uses an alloca
slot (Step 6), and `allocViaContext` sources from the lowering's
current ref. `emitDefaultContextInit` (the only writer) was already
removed in Step 5.

`inferExprType` for the `context` identifier now returns the registered
`Context` type when implicit-ctx is enabled, mirroring the lowering's
identifier-handling fast path. Without this, `context.allocator` would
type as `s64` (the fallback) and the field access would fail.

11 JNI/ObjC IR snapshots regen — the `@context` LLVM global is gone
from each.

152/152 example tests pass.
2026-05-25 09:13:36 +03:00

425 lines
12 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 = "&amp;"; }
if c == 60 { ent = "&lt;"; }
if c == 62 { ent = "&gt;"; }
if c == 34 { ent = "&quot;"; }
if c == 39 { ent = "&apos;"; }
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;
}
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 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) {
if list.len >= list.cap {
new_cap := if list.cap == 0 then 4 else list.cap * 2;
new_items : [*]T = xx context.allocator.alloc(new_cap * size_of(T));
if list.len > 0 {
memcpy(new_items, list.items, list.len * size_of(T));
context.allocator.dealloc(list.items);
}
list.items = new_items;
list.cap = new_cap;
}
list.items[list.len] = item;
list.len += 1;
}
}