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; error_tag_name :: (e: $T) -> string #builtin; // Call-site location, synthesized by the `#caller_location` directive when it // is a parameter's default value (ERR E4.1b). `process.exit` / `assert` use it // to report where they were invoked. Source_Location :: struct { file: string; line: s32; col: s32; func: string; } 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 error_set: { tagid : u32 = xx val; result = error_tag_name(tagid); } 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 "return 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; } }