// Formatting + string helpers: the `*_to_string` family, `any_to_string`, // the comptime `format` / `print` pair, and the slice/string allocation // helpers they build on. Consumers never import this file directly — // std.sx re-exports every public name here. #import "modules/std/core.sx"; // --- Slice & string allocation --- cstring :: (size: s64) -> string { raw := context.allocator.alloc_bytes(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_bytes(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; // Extract digits straight from `n` without ever negating it: `0 - n` // overflows for s64::MIN (its magnitude is unrepresentable as a // positive s64). sx `%` truncates toward zero, so `n % 10` keeps n's // sign; take each remainder's absolute value for the digit. tmp := cstring(20); i := 19; v := n; while v != 0 { d := v % 10; if d < 0 { d = 0 - d; } tmp[i] = d + 48; v = v / 10; i -= 1; } if neg { tmp[i] = 45; i -= 1; } substr(tmp, i + 1, 19 - i) } // Unsigned decimal of `n`'s 64 bits — renders the full u64 range // (0 .. 18446744073709551615). Used by `any_to_string` for unsigned // integer values, which an s64-based formatter would misread (e.g. a // u64 all-ones value as -1). uint_to_string :: (n: s64) -> string { if n == 0 { return "0"; } // Long division by 10 across the four unsigned 16-bit limbs, most // significant first. Each step folds the running remainder into the // next limb; the per-step accumulator stays well within s64 // (max 9*65536 + 65535), so signed `/` and `%` are exact. g := decompose_u16x4(n); tmp := cstring(20); i := 19; while g[0] != 0 or g[1] != 0 or g[2] != 0 or g[3] != 0 { rem := 0; k := 0; while k < 4 { acc := rem * 65536 + g[k]; g[k] = acc / 10; rem = acc % 10; k += 1; } tmp[i] = rem + 48; i -= 1; } substr(tmp, i + 1, 19 - i) } 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; } } // Split the 64 bits of `n` into four unsigned 16-bit limbs, most // significant first: [g3, g2, g1, g0]. A negative input is treated as // its two's-complement unsigned bit pattern — each limb is corrected // back into 0..65535 — so callers get correct unsigned arithmetic out // of a signed-only integer type. Shared by the hex and unsigned-decimal // formatters. decompose_u16x4 :: (n: s64) -> [4]s64 { 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; } limbs : [4]s64 = ---; limbs[0] = g3; limbs[1] = g2; limbs[2] = g1; limbs[3] = g0; limbs } int_to_hex_string :: (n: s64) -> string { if n == 0 { return "0"; } g := decompose_u16x4(n); buf := cstring(16); hex_group(buf, 0, g[0]); hex_group(buf, 4, g[1]); hex_group(buf, 8, g[2]); hex_group(buf, 12, g[3]); // 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 } // 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: { if type_is_unsigned(type) { result = uint_to_string(xx val); } else { 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);"; }