lang: introduce cstring — the C-boundary string (Odin model)

cstring is ONE pointer to a null-terminated u8 buffer, C's char*: thin
(8 bytes, no length; cstring_len walks to the terminator), crossing
#foreign boundaries verbatim in both directions, with ?cstring as the
nullable case lowering to the same bare pointer (null = absent).

Conversion discipline mirrors Odin: a string LITERAL coerces implicitly
(its bytes are terminated constants); any other string is rejected with
a diagnostic naming to_cstring (it may be an unterminated view); and
cstring never coerces to string implicitly — from_cstring(c) is the
explicit zero-copy view, pricing the strlen.

Plumbing: TypeId/TypeInfo builtin slot 18 (first_user 19), name
classifiers, size/align/name tables, LLVM ptr lowering, the ?T pointer
niche, the xx pointer ladder, the literal-gated coercion plan
(isConstString + data_ptr), and the reserved-spelling set. std gains
cstring_len/from_cstring/to_cstring (fmt.sx, re-exported); the old
cstring(size) allocator helper is renamed alloc_string everywhere;
getenv migrates to (name: cstring) -> ?cstring as the canonical user
and env() drops its manual strlen/memcpy.

Pinned: examples/1222 (FFI both directions, literal coercion,
?cstring null paths, round trip) and examples/1173 (both coercion
diagnostics); FAIL pre-feature. The alloc_string rename + getenv
signature shift the .ir snapshots — regenerated. zig build test
426/426; run_examples 604/604.

Spec: reserved spelling + cstring section + C-interop rows.
This commit is contained in:
agra
2026-06-12 14:50:53 +03:00
parent d88bdd7242
commit 1d17b0abcf
58 changed files with 26437 additions and 25257 deletions

View File

@@ -6,7 +6,7 @@
// --- Slice & string allocation ---
cstring :: (size: i64) -> string {
alloc_string :: (size: i64) -> string {
raw := context.allocator.alloc_bytes(size + 1);
memset(raw, 0, size + 1);
s : string = ---;
@@ -31,7 +31,7 @@ int_to_string :: (n: i64) -> string {
// overflows for i64::MIN (its magnitude is unrepresentable as a
// positive i64). sx `%` truncates toward zero, so `n % 10` keeps n's
// sign; take each remainder's absolute value for the digit.
tmp := cstring(20);
tmp := alloc_string(20);
i := 19;
v := n;
while v != 0 {
@@ -56,7 +56,7 @@ uint_to_string :: (n: i64) -> string {
// next limb; the per-step accumulator stays well within i64
// (max 9*65536 + 65535), so signed `/` and `%` are exact.
g := decompose_u16x4(n);
tmp := cstring(20);
tmp := alloc_string(20);
i := 19;
while g[0] != 0 or g[1] != 0 or g[2] != 0 or g[3] != 0 {
rem := 0;
@@ -89,7 +89,7 @@ float_to_string :: (f: f64) -> string {
fl := fstr.len;
prefix := if neg then 1 else 0;
total := prefix + il + 1 + 6;
buf := cstring(total);
buf := alloc_string(total);
pos := 0;
if neg { buf[0] = 45; pos = 1; }
memcpy(@buf[pos], istr.ptr, il);
@@ -144,7 +144,7 @@ int_to_hex_string :: (n: i64) -> string {
if n == 0 { return "0"; }
g := decompose_u16x4(n);
buf := cstring(16);
buf := alloc_string(16);
hex_group(buf, 0, g[0]);
hex_group(buf, 4, g[1]);
hex_group(buf, 8, g[2]);
@@ -162,18 +162,48 @@ int_to_hex_string :: (n: i64) -> string {
concat :: (a: string, b: string) -> string {
al := a.len;
bl := b.len;
buf := cstring(al + bl);
buf := alloc_string(al + bl);
memcpy(buf.ptr, a.ptr, al);
memcpy(@buf[al], b.ptr, bl);
buf
}
substr :: (s: string, start: i64, len: i64) -> string {
buf := cstring(len);
buf := alloc_string(len);
memcpy(buf.ptr, @s[start], len);
buf
}
// ── cstring: the C-boundary string ────────────────────────────────────
// `cstring` is ONE pointer to a null-terminated u8 buffer — C's `char *`.
// It carries no length (`cstring_len` walks to the terminator) and
// crosses `#foreign` boundaries verbatim in both directions; `?cstring`
// is the nullable case (null pointer = absent). String LITERALS coerce
// to `cstring` implicitly — their bytes are terminated constants; every
// other `string` must materialize through `to_cstring`.
// Byte length of `c` (strlen — O(n), walks to the terminator).
cstring_len :: (c: cstring) -> i64 {
p : [*]u8 = xx c;
n := 0;
while p[n] != 0 { n += 1; }
n
}
// A zero-copy string VIEW over `c`'s bytes ({ptr, strlen}). The view
// shares C's buffer — `substr` it if it must outlive the source.
from_cstring :: (c: cstring) -> string {
p : [*]u8 = xx c;
string.{ ptr = p, len = cstring_len(c) }
}
// An owned, terminated copy of `s` as a `cstring`.
to_cstring :: (s: string) -> cstring {
z := alloc_string(s.len);
memcpy(z.ptr, s.ptr, s.len);
xx z.ptr
}
// 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