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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user