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:
@@ -204,7 +204,7 @@ bundle_main :: () -> bool {
|
||||
// buffer for libc / `[:0]u8` callees. Allocated from
|
||||
// `context.allocator` like the rest of the bundling stage.
|
||||
str_to_cstr :: (s: string) -> [:0]u8 {
|
||||
buf := cstring(s.len);
|
||||
buf := alloc_string(s.len);
|
||||
memcpy(buf.ptr, s.ptr, s.len);
|
||||
buf
|
||||
}
|
||||
@@ -927,7 +927,7 @@ MANIFEST, pkg_esc, lib_esc, lib_esc, lib_esc)
|
||||
|
||||
// `co/swipelab/sxchess/SxApp` → `co.swipelab.sxchess.SxApp`.
|
||||
slash_to_dot :: (path: string) -> string {
|
||||
buf := cstring(path.len);
|
||||
buf := alloc_string(path.len);
|
||||
i := 0;
|
||||
while i < path.len {
|
||||
c := path[i];
|
||||
|
||||
@@ -69,7 +69,10 @@ optional_to_string :: fmt.optional_to_string;
|
||||
concat :: fmt.concat;
|
||||
substr :: fmt.substr;
|
||||
path_join :: fmt.path_join;
|
||||
cstring :: fmt.cstring;
|
||||
alloc_string :: fmt.alloc_string;
|
||||
cstring_len :: fmt.cstring_len;
|
||||
from_cstring :: fmt.from_cstring;
|
||||
to_cstring :: fmt.to_cstring;
|
||||
alloc_slice :: fmt.alloc_slice;
|
||||
|
||||
// fmt internals, re-exported only because they were always part of the
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -130,7 +130,7 @@ read_file :: (path: [:0]u8) -> ?string {
|
||||
size := lseek(fd, 0, SEEK_END);
|
||||
if size < 0 { close(fd); return null; }
|
||||
lseek(fd, 0, SEEK_SET);
|
||||
buf := cstring(size);
|
||||
buf := alloc_string(size);
|
||||
n := read(fd, buf.ptr, xx size);
|
||||
close(fd);
|
||||
if cast(i64) n != size { return null; }
|
||||
@@ -191,7 +191,7 @@ create_dir_all :: (path: [:0]u8) -> bool {
|
||||
last -= 1;
|
||||
}
|
||||
if last > 0 {
|
||||
parent := cstring(last);
|
||||
parent := alloc_string(last);
|
||||
memcpy(parent.ptr, path.ptr, last);
|
||||
if !create_dir_all(parent) { return false; }
|
||||
}
|
||||
@@ -211,7 +211,7 @@ copy_file :: (src: [:0]u8, dst: [:0]u8) -> bool {
|
||||
return false;
|
||||
}
|
||||
ok := true;
|
||||
buf := cstring(65536);
|
||||
buf := alloc_string(65536);
|
||||
loop := true;
|
||||
while loop {
|
||||
n := read(src_fd, buf.ptr, 65536);
|
||||
|
||||
@@ -24,8 +24,7 @@ popen :: (cmd: [:0]u8, mode: [:0]u8) -> *void #foreign libc;
|
||||
pclose :: (stream: *void) -> i32 #foreign libc;
|
||||
fread :: (ptr: [*]u8, size: usize, nmemb: usize, stream: *void) -> usize #foreign libc;
|
||||
feof :: (stream: *void) -> i32 #foreign libc;
|
||||
getenv :: (name: [:0]u8) -> *u8 #foreign libc;
|
||||
strlen :: (s: *u8) -> usize #foreign libc;
|
||||
getenv :: (name: cstring) -> ?cstring #foreign libc;
|
||||
system :: (cmd: [:0]u8) -> i32 #foreign libc;
|
||||
|
||||
// ── Public types ─────────────────────────────────────────────────────
|
||||
@@ -53,7 +52,7 @@ run :: (cmd: [:0]u8) -> ?ProcessResult {
|
||||
if cast(i64) f == 0 { return null; }
|
||||
|
||||
out := "";
|
||||
buf := cstring(4096);
|
||||
buf := alloc_string(4096);
|
||||
loop := true;
|
||||
while loop {
|
||||
n := fread(buf.ptr, 1, 4096, f);
|
||||
@@ -84,14 +83,11 @@ run :: (cmd: [:0]u8) -> ?ProcessResult {
|
||||
// Read an environment variable. Returns null if unset; an empty
|
||||
// string if set to "".
|
||||
env :: (name: [:0]u8) -> ?string {
|
||||
p := getenv(name);
|
||||
addr : i64 = xx p;
|
||||
if addr == 0 { return null; }
|
||||
n := strlen(p);
|
||||
if n == 0 { return ""; }
|
||||
buf := cstring(cast(i64) n);
|
||||
memcpy(buf.ptr, xx p, cast(i64) n);
|
||||
buf
|
||||
p := getenv(to_cstring(name));
|
||||
if p == null { return null; }
|
||||
v := from_cstring(p!);
|
||||
if v.len == 0 { return ""; }
|
||||
substr(v, 0, v.len)
|
||||
}
|
||||
|
||||
// Locate an executable by walking `$PATH`. Returns the absolute path
|
||||
@@ -103,7 +99,7 @@ find_executable :: (name: [:0]u8) -> ?string {
|
||||
// (executable names like `codesign`, `javac`, `aapt2`).
|
||||
cmd := concat("command -v ", name);
|
||||
// Need null-terminated for popen.
|
||||
cmd_z := cstring(cmd.len);
|
||||
cmd_z := alloc_string(cmd.len);
|
||||
memcpy(cmd_z.ptr, cmd.ptr, cmd.len);
|
||||
if r := run(cmd_z) {
|
||||
if r.exit_code != 0 { return null; }
|
||||
|
||||
Reference in New Issue
Block a user