lib: collapse the manual rt_ensure append workarounds onto the fixed builtin (#34 follow-up)

shlex.appendstr, getopt.appendoption, bytes.appendslice and
strings.appendstr existed only because the append builtin stored the
first 8 bytes of the element; each carried its own @symbol("rt_ensure")
bind and a grow-then-store-through-*T body, with comments promising to
"collapse in one go when the append builtin is fixed". The previous
commit fixed the builtin; this removes all four helpers and their
rt_ensure binds and spells every call site as plain append().

Bonus correctness: getopt's appendoption passed a hardcoded membsz of
24, stale since the str 24B redesign made option {rune, str} 32B — the
manual growth under-allocated past 6 options while &opts.ptr[i] strode
32 (latent OOB). The builtin derives membsz from the type table
(probe: MOVQ $32, SI), closing that drift by construction.
This commit is contained in:
2026-06-04 02:22:25 +09:00
parent faade48513
commit 70fa9e2264
9 changed files with 66 additions and 354 deletions

View File

@@ -386,28 +386,6 @@ export fn remaining_tokens(s: *tokenizer) []u8 = {
return s.in; return s.in;
}; };
// rt_ensure is the runtime slice-growth helper invoked by the
// `append(s, v)` builtin. We bind it directly because the builtin's
// expansion stores only 8 bytes of the new element (cgen emits a
// single MOVQ), losing the .len/.cap fields of a []u8 element (24B).
// Mirrors the same workaround in lib/shlex.shlex (appendstr, 16B) and
// lib/getopt.getopt (appendoption, 24B); collapses in one go when the
// append builtin learns to store the full element width.
@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void;
// appendslice — grow `*slice` by one and store `item` (24B). Mirror
// of [[shlex.appendstr]] / [[getopt.appendoption]]. Bypasses the
// `append` builtin's first-8B-only-store gap for a slice-element.
fn appendslice(slice: *[][]u8, item: []u8) void = {
let newlen: i32 = slice.len + 1;
slice.len = newlen;
rtensure(slice: *void, 24u64);
let dst: *[]u8 = &slice.ptr[newlen - 1];
dst.ptr = item.ptr;
dst.len = item.len;
dst.cap = item.cap;
};
// splitn — split `in` on any byte in `delim`, returning up to `n` // splitn — split `in` on any byte in `delim`, returning up to `n`
// tokens via forward iteration. The trailing slot (when more than // tokens via forward iteration. The trailing slot (when more than
// `n - 1` tokens exist) holds the unconsumed remainder. // `n - 1` tokens exist) holds the unconsumed remainder.
@@ -432,7 +410,7 @@ export fn splitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
let i: i32 = 0; let i: i32 = 0;
for (i < n - 1) { for (i < n - 1) {
match (next_token(&tok)) { match (next_token(&tok)) {
case let s: []u8 => { appendslice(&toks, s); }; case let s: []u8 => { append(toks, s); };
case done => { return toks; }; case done => { return toks; };
}; };
i += 1; i += 1;
@@ -441,7 +419,7 @@ export fn splitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
case done => void; case done => void;
case let pk: []u8 => { case let pk: []u8 => {
let r: []u8 = remaining_tokens(&tok); let r: []u8 = remaining_tokens(&tok);
appendslice(&toks, r); append(toks, r);
}; };
}; };
return toks; return toks;
@@ -470,7 +448,7 @@ export fn rsplitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
let i: i32 = 0; let i: i32 = 0;
for (i < n - 1) { for (i < n - 1) {
match (next_token(&tok)) { match (next_token(&tok)) {
case let s: []u8 => { appendslice(&toks, s); }; case let s: []u8 => { append(toks, s); };
case done => { return toks; }; case done => { return toks; };
}; };
i += 1; i += 1;
@@ -479,7 +457,7 @@ export fn rsplitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
case done => void; case done => void;
case let pk: []u8 => { case let pk: []u8 => {
let r: []u8 = remaining_tokens(&tok); let r: []u8 = remaining_tokens(&tok);
appendslice(&toks, r); append(toks, r);
}; };
}; };

View File

@@ -97,14 +97,6 @@ import encoding.utf8;
import os; import os;
import strings; import strings;
// rt_ensure is the runtime slice-growth helper invoked by the
// `append(s, v)` builtin. We bind it directly because the builtin's
// expansion stores only 8 bytes of the new element (cgen emits a
// single MOVQ), losing the `value: str` half of an [[option]].
// [[appendoption]] grows manually and stores both fields via *option.
// No public stdlib facade exposes rt_ensure, hence the direct @symbol.
@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void;
// helpkind — which slot of [[help]] is meaningful. Hare's getopt // helpkind — which slot of [[help]] is meaningful. Hare's getopt
// also has a subcmd_help variant; not shipped here (see file header). // also has a subcmd_help variant; not shipped here (see file header).
export type helpkind = enum i32 { export type helpkind = enum i32 {
@@ -205,18 +197,6 @@ export type command = struct {
helpcap: i32, helpcap: i32,
}; };
// appendoption — grow `*opts` by one slot and store `(flag, value)`.
// Avoids the `append(opts, pair)` builtin: cgen lowers that to a
// MOVQ-of-the-first-8-bytes, which drops the `value: str` half.
fn appendoption(opts: *[]option, flag: rune, value: str) void = {
let newlen: i32 = opts.len + 1;
opts.len = newlen;
rtensure(opts: *void, 24u64);
let dst: *option = &opts.ptr[newlen - 1];
dst.flag = flag;
dst.value = value;
};
// findflag — lookup the kind of `r` in `hs`. Returns void if absent. // findflag — lookup the kind of `r` in `hs`. Returns void if absent.
// //
// Reads go through `&hs[i]` rather than `hs[i].field` directly: the // Reads go through `&hs[i]` rather than `hs[i].field` directly: the
@@ -307,7 +287,7 @@ export fn tryparse(out: *command, argv: []str, help: []help) (void | error) = {
}; };
case let k: helpkind => { case let k: helpkind => {
if (k == helpkind.FLAG) { if (k == helpkind.FLAG) {
appendoption(&opts, r, ""); append(opts, option { flag = r, value = "" });
bi += 1; bi += 1;
} else { } else {
// PARAM: glued value, or next argv slot. // PARAM: glued value, or next argv slot.
@@ -325,7 +305,7 @@ export fn tryparse(out: *command, argv: []str, help: []help) (void | error) = {
case utf8.invalid => case utf8.invalid =>
abort("getopt: malformed argv UTF-8"); abort("getopt: malformed argv UTF-8");
}; };
appendoption(&opts, r, v); append(opts, option { flag = r, value = v });
advanced = true; advanced = true;
} else { } else {
if (i + 1 >= argv.len) { if (i + 1 >= argv.len) {
@@ -340,7 +320,7 @@ export fn tryparse(out: *command, argv: []str, help: []help) (void | error) = {
return e; return e;
}; };
i += 1; i += 1;
appendoption(&opts, r, argv[i]); append(opts, option { flag = r, value = argv[i] });
advanced = true; advanced = true;
}; };
}; };

View File

@@ -30,7 +30,7 @@
// //
// - Drop nomem: ww os.alloc has no recoverable failure path (OOM // - Drop nomem: ww os.alloc has no recoverable failure path (OOM
// yields a poisonous pointer that faults on deref; see lib/os.ww // yields a poisonous pointer that faults on deref; see lib/os.ww
// comment). Same precedent as strings.dup, getopt.appendoption. // comment). Same precedent as strings.dup.
// Hare's (...|syntaxerr|nomem) collapses to (...|syntaxerr). // Hare's (...|syntaxerr|nomem) collapses to (...|syntaxerr).
// //
// - Byte-wise iteration via i32 cursor instead of Hare's // - Byte-wise iteration via i32 cursor instead of Hare's
@@ -55,15 +55,6 @@
// "write one byte" path goes through `io.write(s, buf[0:1])` // "write one byte" path goes through `io.write(s, buf[0:1])`
// against a stack `[N]u8`. Same shape as fmt's rune-arm. // against a stack `[N]u8`. Same shape as fmt's rune-arm.
// //
// - `[]str` grown via direct rt_ensure rather than the `append`
// builtin: cgen lowers `append(slice, element)` to a single MOVQ
// of the first 8 bytes, which drops the `len` half of a 16B str
// element. [[appendstr]] writes both halves through `*str`.
// Same gap, same workaround, same comment shape as
// [[getopt.appendoption]] (lib/getopt/getopt.ww:96-106) — kept
// greppable across modules. When the append-builtin is fixed,
// all three (getopt, shlex, anywhere else) collapse in one go.
//
// - Internal [[dupstr]] inlined rather than `use strings;`: keeps // - Internal [[dupstr]] inlined rather than `use strings;`: keeps
// the dep surface small for a 6-line helper. shlex doesn't reach // the dep surface small for a 6-line helper. shlex doesn't reach
// for any other strings:: routine, and callers free split()'s // for any other strings:: routine, and callers free split()'s
@@ -94,13 +85,6 @@ import memio;
import os; import os;
import strings; import strings;
// rt_ensure is the runtime slice-growth helper invoked by the
// `append(s, v)` builtin. We bind it directly because the builtin's
// expansion stores only 8 bytes of the new element (cgen emits a
// single MOVQ), losing the `len` half of a `str` (16B). No public
// stdlib facade exposes it, hence the direct @symbol.
@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void;
// syntaxerr — the input wasn't a valid shell-tokenizable string // syntaxerr — the input wasn't a valid shell-tokenizable string
// (unterminated quote / bare trailing backslash). Mirrors Hare's // (unterminated quote / bare trailing backslash). Mirrors Hare's
// shlex::syntaxerr; a `!void` so it can ride a tagged-union return // shlex::syntaxerr; a `!void` so it can ride a tagged-union return
@@ -127,18 +111,6 @@ fn dupstr(s: str) str = {
return strings.frombytes(buf); return strings.frombytes(buf);
}; };
// appendstr — grow `*slice` by one and store `item` (16B). Bypasses
// the `append` builtin's first-8B-only store gap; mirror of
// [[getopt.appendoption]] for the `[]option` case.
fn appendstr(slice: *[]str, item: str) void = {
let newlen: i32 = slice.len + 1;
slice.len = newlen;
rtensure(slice: *void, size(str): u64);
let dst: *str = &slice.ptr[newlen - 1];
dst.ptr = item.ptr;
dst.len = item.len;
};
// freepartial — drop a partially built [[split]] result on the // freepartial — drop a partially built [[split]] result on the
// syntaxerr path. Hare uses a `defer if (!ok)` guard; ww has no defer, // syntaxerr path. Hare uses a `defer if (!ok)` guard; ww has no defer,
// so we hand-roll the cleanup at every error return. Mirror of // so we hand-roll the cleanup at every error return. Mirror of
@@ -271,7 +243,7 @@ export fn split(in: str) ([]str | syntaxerr) = {
if (!first) { if (!first) {
let view: str = memio.string(&st); let view: str = memio.string(&st);
let owned: str = dupstr(view); let owned: str = dupstr(view);
appendstr(&slice, owned); append(slice, owned);
memio.reset(&st); memio.reset(&st);
}; };
dirty = false; dirty = false;
@@ -315,7 +287,7 @@ export fn split(in: str) ([]str | syntaxerr) = {
if (dirty) { if (dirty) {
let view: str = memio.string(&st); let view: str = memio.string(&st);
let owned: str = dupstr(view); let owned: str = dupstr(view);
appendstr(&slice, owned); append(slice, owned);
}; };
let _c = io.close(snk); let _c = io.close(snk);

View File

@@ -88,7 +88,7 @@ export fn dup(s: str) str = {
// so the only nomem propagation point is the initial slice alloc. // so the only nomem propagation point is the initial slice alloc.
// With no inner failure path, the rollback is structurally a no-op // With no inner failure path, the rollback is structurally a no-op
// and is omitted; it returns once dup graduates to `(str | nomem)` // and is omitted; it returns once dup graduates to `(str | nomem)`
// (#46). The pre-allocated slice has `cap == s.len`, so appendstr's // (#46). The pre-allocated slice has `cap == s.len`, so append's
// rt_ensure call never reaches the grow branch. // rt_ensure call never reaches the grow branch.
// //
// Empty input bypasses the alloc: rt_malloc(0) is an mmap of 0 bytes // Empty input bypasses the alloc: rt_malloc(0) is an mmap of 0 bytes
@@ -107,7 +107,7 @@ export fn dupall(s: []str) ([]str | nomem) = {
let newsl: []str = alloc([], s.len)?; let newsl: []str = alloc([], s.len)?;
let i: i32 = 0; let i: i32 = 0;
for (i < s.len) { for (i < s.len) {
appendstr(&newsl, dup(s[i])); append(newsl, dup(s[i]));
i += 1; i += 1;
}; };
return newsl; return newsl;
@@ -733,24 +733,6 @@ export fn rcut(in: str, delim: str) (str, str) = {
return (frombytes(a), frombytes(b)); return (frombytes(a), frombytes(b));
}; };
// rt_ensure is the runtime slice-growth helper invoked by the
// `append(s, v)` builtin. Direct bind for the same reason as
// lib/shlex.shlex (appendstr, 16B): the builtin's expansion stores
// only 8B of the new element, losing the `.len` half of a `str`.
@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void;
// appendstr — grow `*slice` by one and store `item` (16B). Mirror of
// lib/shlex.shlex appendstr. Collapses when the append builtin learns
// to store the full element width.
fn appendstr(slice: *[]str, item: str) void = {
let newlen: i32 = slice.len + 1;
slice.len = newlen;
rtensure(slice: *void, size(str): u64);
let dst: *str = &slice.ptr[newlen - 1];
dst.ptr = item.ptr;
dst.len = item.len;
};
// splitn — split `in` on any byte in `delim`, returning up to `n` // splitn — split `in` on any byte in `delim`, returning up to `n`
// tokens via forward iteration. The trailing slot (when more than // tokens via forward iteration. The trailing slot (when more than
// `n - 1` tokens exist) holds the unconsumed remainder. Strings // `n - 1` tokens exist) holds the unconsumed remainder. Strings
@@ -773,7 +755,7 @@ export fn splitn(in: str, delim: str, n: i32) []str = {
let i: i32 = 0; let i: i32 = 0;
for (i < n - 1) { for (i < n - 1) {
match (next_token(&tok)) { match (next_token(&tok)) {
case let s: str => { appendstr(&toks, s); }; case let s: str => { append(toks, s); };
case bytes.done => { return toks; }; case bytes.done => { return toks; };
}; };
i += 1; i += 1;
@@ -782,7 +764,7 @@ export fn splitn(in: str, delim: str, n: i32) []str = {
case bytes.done => void; case bytes.done => void;
case let pk: str => { case let pk: str => {
let r: str = remaining_tokens(&tok); let r: str = remaining_tokens(&tok);
appendstr(&toks, r); append(toks, r);
}; };
}; };
return toks; return toks;
@@ -807,7 +789,7 @@ export fn rsplitn(in: str, delim: str, n: i32) []str = {
let i: i32 = 0; let i: i32 = 0;
for (i < n - 1) { for (i < n - 1) {
match (next_token(&tok)) { match (next_token(&tok)) {
case let s: str => { appendstr(&toks, s); }; case let s: str => { append(toks, s); };
case bytes.done => { return toks; }; case bytes.done => { return toks; };
}; };
i += 1; i += 1;
@@ -816,7 +798,7 @@ export fn rsplitn(in: str, delim: str, n: i32) []str = {
case bytes.done => void; case bytes.done => void;
case let pk: str => { case let pk: str => {
let r: str = remaining_tokens(&tok); let r: str = remaining_tokens(&tok);
appendstr(&toks, r); append(toks, r);
}; };
}; };

View File

@@ -1244,28 +1244,6 @@ export fn remaining_tokens(s: *tokenizer) []u8 = {
return s.in; return s.in;
}; };
// rt_ensure is the runtime slice-growth helper invoked by the
// `append(s, v)` builtin. We bind it directly because the builtin's
// expansion stores only 8 bytes of the new element (cgen emits a
// single MOVQ), losing the .len/.cap fields of a []u8 element (24B).
// Mirrors the same workaround in lib/shlex.shlex (appendstr, 16B) and
// lib/getopt.getopt (appendoption, 24B); collapses in one go when the
// append builtin learns to store the full element width.
@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void;
// appendslice — grow `*slice` by one and store `item` (24B). Mirror
// of [[shlex.appendstr]] / [[getopt.appendoption]]. Bypasses the
// `append` builtin's first-8B-only-store gap for a slice-element.
fn appendslice(slice: *[][]u8, item: []u8) void = {
let newlen: i32 = slice.len + 1;
slice.len = newlen;
rtensure(slice: *void, 24u64);
let dst: *[]u8 = &slice.ptr[newlen - 1];
dst.ptr = item.ptr;
dst.len = item.len;
dst.cap = item.cap;
};
// splitn — split `in` on any byte in `delim`, returning up to `n` // splitn — split `in` on any byte in `delim`, returning up to `n`
// tokens via forward iteration. The trailing slot (when more than // tokens via forward iteration. The trailing slot (when more than
// `n - 1` tokens exist) holds the unconsumed remainder. // `n - 1` tokens exist) holds the unconsumed remainder.
@@ -1290,7 +1268,7 @@ export fn splitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
let i: i32 = 0; let i: i32 = 0;
for (i < n - 1) { for (i < n - 1) {
match (next_token(&tok)) { match (next_token(&tok)) {
case let s: []u8 => { appendslice(&toks, s); }; case let s: []u8 => { append(toks, s); };
case done => { return toks; }; case done => { return toks; };
}; };
i += 1; i += 1;
@@ -1299,7 +1277,7 @@ export fn splitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
case done => void; case done => void;
case let pk: []u8 => { case let pk: []u8 => {
let r: []u8 = remaining_tokens(&tok); let r: []u8 = remaining_tokens(&tok);
appendslice(&toks, r); append(toks, r);
}; };
}; };
return toks; return toks;
@@ -1328,7 +1306,7 @@ export fn rsplitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
let i: i32 = 0; let i: i32 = 0;
for (i < n - 1) { for (i < n - 1) {
match (next_token(&tok)) { match (next_token(&tok)) {
case let s: []u8 => { appendslice(&toks, s); }; case let s: []u8 => { append(toks, s); };
case done => { return toks; }; case done => { return toks; };
}; };
i += 1; i += 1;
@@ -1337,7 +1315,7 @@ export fn rsplitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
case done => void; case done => void;
case let pk: []u8 => { case let pk: []u8 => {
let r: []u8 = remaining_tokens(&tok); let r: []u8 = remaining_tokens(&tok);
appendslice(&toks, r); append(toks, r);
}; };
}; };
@@ -1955,7 +1933,7 @@ export fn dup(s: str) str = {
// so the only nomem propagation point is the initial slice alloc. // so the only nomem propagation point is the initial slice alloc.
// With no inner failure path, the rollback is structurally a no-op // With no inner failure path, the rollback is structurally a no-op
// and is omitted; it returns once dup graduates to `(str | nomem)` // and is omitted; it returns once dup graduates to `(str | nomem)`
// (#46). The pre-allocated slice has `cap == s.len`, so appendstr's // (#46). The pre-allocated slice has `cap == s.len`, so append's
// rt_ensure call never reaches the grow branch. // rt_ensure call never reaches the grow branch.
// //
// Empty input bypasses the alloc: rt_malloc(0) is an mmap of 0 bytes // Empty input bypasses the alloc: rt_malloc(0) is an mmap of 0 bytes
@@ -1974,7 +1952,7 @@ export fn dupall(s: []str) ([]str | nomem) = {
let newsl: []str = alloc([], s.len)?; let newsl: []str = alloc([], s.len)?;
let i: i32 = 0; let i: i32 = 0;
for (i < s.len) { for (i < s.len) {
appendstr(&newsl, dup(s[i])); append(newsl, dup(s[i]));
i += 1; i += 1;
}; };
return newsl; return newsl;
@@ -2600,24 +2578,6 @@ export fn rcut(in: str, delim: str) (str, str) = {
return (frombytes(a), frombytes(b)); return (frombytes(a), frombytes(b));
}; };
// rt_ensure is the runtime slice-growth helper invoked by the
// `append(s, v)` builtin. Direct bind for the same reason as
// lib/shlex.shlex (appendstr, 16B): the builtin's expansion stores
// only 8B of the new element, losing the `.len` half of a `str`.
@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void;
// appendstr — grow `*slice` by one and store `item` (16B). Mirror of
// lib/shlex.shlex appendstr. Collapses when the append builtin learns
// to store the full element width.
fn appendstr(slice: *[]str, item: str) void = {
let newlen: i32 = slice.len + 1;
slice.len = newlen;
rtensure(slice: *void, size(str): u64);
let dst: *str = &slice.ptr[newlen - 1];
dst.ptr = item.ptr;
dst.len = item.len;
};
// splitn — split `in` on any byte in `delim`, returning up to `n` // splitn — split `in` on any byte in `delim`, returning up to `n`
// tokens via forward iteration. The trailing slot (when more than // tokens via forward iteration. The trailing slot (when more than
// `n - 1` tokens exist) holds the unconsumed remainder. Strings // `n - 1` tokens exist) holds the unconsumed remainder. Strings
@@ -2640,7 +2600,7 @@ export fn splitn(in: str, delim: str, n: i32) []str = {
let i: i32 = 0; let i: i32 = 0;
for (i < n - 1) { for (i < n - 1) {
match (next_token(&tok)) { match (next_token(&tok)) {
case let s: str => { appendstr(&toks, s); }; case let s: str => { append(toks, s); };
case bytes.done => { return toks; }; case bytes.done => { return toks; };
}; };
i += 1; i += 1;
@@ -2649,7 +2609,7 @@ export fn splitn(in: str, delim: str, n: i32) []str = {
case bytes.done => void; case bytes.done => void;
case let pk: str => { case let pk: str => {
let r: str = remaining_tokens(&tok); let r: str = remaining_tokens(&tok);
appendstr(&toks, r); append(toks, r);
}; };
}; };
return toks; return toks;
@@ -2674,7 +2634,7 @@ export fn rsplitn(in: str, delim: str, n: i32) []str = {
let i: i32 = 0; let i: i32 = 0;
for (i < n - 1) { for (i < n - 1) {
match (next_token(&tok)) { match (next_token(&tok)) {
case let s: str => { appendstr(&toks, s); }; case let s: str => { append(toks, s); };
case bytes.done => { return toks; }; case bytes.done => { return toks; };
}; };
i += 1; i += 1;
@@ -2683,7 +2643,7 @@ export fn rsplitn(in: str, delim: str, n: i32) []str = {
case bytes.done => void; case bytes.done => void;
case let pk: str => { case let pk: str => {
let r: str = remaining_tokens(&tok); let r: str = remaining_tokens(&tok);
appendstr(&toks, r); append(toks, r);
}; };
}; };

View File

@@ -1244,28 +1244,6 @@ export fn remaining_tokens(s: *tokenizer) []u8 = {
return s.in; return s.in;
}; };
// rt_ensure is the runtime slice-growth helper invoked by the
// `append(s, v)` builtin. We bind it directly because the builtin's
// expansion stores only 8 bytes of the new element (cgen emits a
// single MOVQ), losing the .len/.cap fields of a []u8 element (24B).
// Mirrors the same workaround in lib/shlex.shlex (appendstr, 16B) and
// lib/getopt.getopt (appendoption, 24B); collapses in one go when the
// append builtin learns to store the full element width.
@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void;
// appendslice — grow `*slice` by one and store `item` (24B). Mirror
// of [[shlex.appendstr]] / [[getopt.appendoption]]. Bypasses the
// `append` builtin's first-8B-only-store gap for a slice-element.
fn appendslice(slice: *[][]u8, item: []u8) void = {
let newlen: i32 = slice.len + 1;
slice.len = newlen;
rtensure(slice: *void, 24u64);
let dst: *[]u8 = &slice.ptr[newlen - 1];
dst.ptr = item.ptr;
dst.len = item.len;
dst.cap = item.cap;
};
// splitn — split `in` on any byte in `delim`, returning up to `n` // splitn — split `in` on any byte in `delim`, returning up to `n`
// tokens via forward iteration. The trailing slot (when more than // tokens via forward iteration. The trailing slot (when more than
// `n - 1` tokens exist) holds the unconsumed remainder. // `n - 1` tokens exist) holds the unconsumed remainder.
@@ -1290,7 +1268,7 @@ export fn splitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
let i: i32 = 0; let i: i32 = 0;
for (i < n - 1) { for (i < n - 1) {
match (next_token(&tok)) { match (next_token(&tok)) {
case let s: []u8 => { appendslice(&toks, s); }; case let s: []u8 => { append(toks, s); };
case done => { return toks; }; case done => { return toks; };
}; };
i += 1; i += 1;
@@ -1299,7 +1277,7 @@ export fn splitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
case done => void; case done => void;
case let pk: []u8 => { case let pk: []u8 => {
let r: []u8 = remaining_tokens(&tok); let r: []u8 = remaining_tokens(&tok);
appendslice(&toks, r); append(toks, r);
}; };
}; };
return toks; return toks;
@@ -1328,7 +1306,7 @@ export fn rsplitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
let i: i32 = 0; let i: i32 = 0;
for (i < n - 1) { for (i < n - 1) {
match (next_token(&tok)) { match (next_token(&tok)) {
case let s: []u8 => { appendslice(&toks, s); }; case let s: []u8 => { append(toks, s); };
case done => { return toks; }; case done => { return toks; };
}; };
i += 1; i += 1;
@@ -1337,7 +1315,7 @@ export fn rsplitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
case done => void; case done => void;
case let pk: []u8 => { case let pk: []u8 => {
let r: []u8 = remaining_tokens(&tok); let r: []u8 = remaining_tokens(&tok);
appendslice(&toks, r); append(toks, r);
}; };
}; };
@@ -1955,7 +1933,7 @@ export fn dup(s: str) str = {
// so the only nomem propagation point is the initial slice alloc. // so the only nomem propagation point is the initial slice alloc.
// With no inner failure path, the rollback is structurally a no-op // With no inner failure path, the rollback is structurally a no-op
// and is omitted; it returns once dup graduates to `(str | nomem)` // and is omitted; it returns once dup graduates to `(str | nomem)`
// (#46). The pre-allocated slice has `cap == s.len`, so appendstr's // (#46). The pre-allocated slice has `cap == s.len`, so append's
// rt_ensure call never reaches the grow branch. // rt_ensure call never reaches the grow branch.
// //
// Empty input bypasses the alloc: rt_malloc(0) is an mmap of 0 bytes // Empty input bypasses the alloc: rt_malloc(0) is an mmap of 0 bytes
@@ -1974,7 +1952,7 @@ export fn dupall(s: []str) ([]str | nomem) = {
let newsl: []str = alloc([], s.len)?; let newsl: []str = alloc([], s.len)?;
let i: i32 = 0; let i: i32 = 0;
for (i < s.len) { for (i < s.len) {
appendstr(&newsl, dup(s[i])); append(newsl, dup(s[i]));
i += 1; i += 1;
}; };
return newsl; return newsl;
@@ -2600,24 +2578,6 @@ export fn rcut(in: str, delim: str) (str, str) = {
return (frombytes(a), frombytes(b)); return (frombytes(a), frombytes(b));
}; };
// rt_ensure is the runtime slice-growth helper invoked by the
// `append(s, v)` builtin. Direct bind for the same reason as
// lib/shlex.shlex (appendstr, 16B): the builtin's expansion stores
// only 8B of the new element, losing the `.len` half of a `str`.
@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void;
// appendstr — grow `*slice` by one and store `item` (16B). Mirror of
// lib/shlex.shlex appendstr. Collapses when the append builtin learns
// to store the full element width.
fn appendstr(slice: *[]str, item: str) void = {
let newlen: i32 = slice.len + 1;
slice.len = newlen;
rtensure(slice: *void, size(str): u64);
let dst: *str = &slice.ptr[newlen - 1];
dst.ptr = item.ptr;
dst.len = item.len;
};
// splitn — split `in` on any byte in `delim`, returning up to `n` // splitn — split `in` on any byte in `delim`, returning up to `n`
// tokens via forward iteration. The trailing slot (when more than // tokens via forward iteration. The trailing slot (when more than
// `n - 1` tokens exist) holds the unconsumed remainder. Strings // `n - 1` tokens exist) holds the unconsumed remainder. Strings
@@ -2640,7 +2600,7 @@ export fn splitn(in: str, delim: str, n: i32) []str = {
let i: i32 = 0; let i: i32 = 0;
for (i < n - 1) { for (i < n - 1) {
match (next_token(&tok)) { match (next_token(&tok)) {
case let s: str => { appendstr(&toks, s); }; case let s: str => { append(toks, s); };
case bytes.done => { return toks; }; case bytes.done => { return toks; };
}; };
i += 1; i += 1;
@@ -2649,7 +2609,7 @@ export fn splitn(in: str, delim: str, n: i32) []str = {
case bytes.done => void; case bytes.done => void;
case let pk: str => { case let pk: str => {
let r: str = remaining_tokens(&tok); let r: str = remaining_tokens(&tok);
appendstr(&toks, r); append(toks, r);
}; };
}; };
return toks; return toks;
@@ -2674,7 +2634,7 @@ export fn rsplitn(in: str, delim: str, n: i32) []str = {
let i: i32 = 0; let i: i32 = 0;
for (i < n - 1) { for (i < n - 1) {
match (next_token(&tok)) { match (next_token(&tok)) {
case let s: str => { appendstr(&toks, s); }; case let s: str => { append(toks, s); };
case bytes.done => { return toks; }; case bytes.done => { return toks; };
}; };
i += 1; i += 1;
@@ -2683,7 +2643,7 @@ export fn rsplitn(in: str, delim: str, n: i32) []str = {
case bytes.done => void; case bytes.done => void;
case let pk: str => { case let pk: str => {
let r: str = remaining_tokens(&tok); let r: str = remaining_tokens(&tok);
appendstr(&toks, r); append(toks, r);
}; };
}; };

View File

@@ -1244,28 +1244,6 @@ export fn remaining_tokens(s: *tokenizer) []u8 = {
return s.in; return s.in;
}; };
// rt_ensure is the runtime slice-growth helper invoked by the
// `append(s, v)` builtin. We bind it directly because the builtin's
// expansion stores only 8 bytes of the new element (cgen emits a
// single MOVQ), losing the .len/.cap fields of a []u8 element (24B).
// Mirrors the same workaround in lib/shlex.shlex (appendstr, 16B) and
// lib/getopt.getopt (appendoption, 24B); collapses in one go when the
// append builtin learns to store the full element width.
@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void;
// appendslice — grow `*slice` by one and store `item` (24B). Mirror
// of [[shlex.appendstr]] / [[getopt.appendoption]]. Bypasses the
// `append` builtin's first-8B-only-store gap for a slice-element.
fn appendslice(slice: *[][]u8, item: []u8) void = {
let newlen: i32 = slice.len + 1;
slice.len = newlen;
rtensure(slice: *void, 24u64);
let dst: *[]u8 = &slice.ptr[newlen - 1];
dst.ptr = item.ptr;
dst.len = item.len;
dst.cap = item.cap;
};
// splitn — split `in` on any byte in `delim`, returning up to `n` // splitn — split `in` on any byte in `delim`, returning up to `n`
// tokens via forward iteration. The trailing slot (when more than // tokens via forward iteration. The trailing slot (when more than
// `n - 1` tokens exist) holds the unconsumed remainder. // `n - 1` tokens exist) holds the unconsumed remainder.
@@ -1290,7 +1268,7 @@ export fn splitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
let i: i32 = 0; let i: i32 = 0;
for (i < n - 1) { for (i < n - 1) {
match (next_token(&tok)) { match (next_token(&tok)) {
case let s: []u8 => { appendslice(&toks, s); }; case let s: []u8 => { append(toks, s); };
case done => { return toks; }; case done => { return toks; };
}; };
i += 1; i += 1;
@@ -1299,7 +1277,7 @@ export fn splitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
case done => void; case done => void;
case let pk: []u8 => { case let pk: []u8 => {
let r: []u8 = remaining_tokens(&tok); let r: []u8 = remaining_tokens(&tok);
appendslice(&toks, r); append(toks, r);
}; };
}; };
return toks; return toks;
@@ -1328,7 +1306,7 @@ export fn rsplitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
let i: i32 = 0; let i: i32 = 0;
for (i < n - 1) { for (i < n - 1) {
match (next_token(&tok)) { match (next_token(&tok)) {
case let s: []u8 => { appendslice(&toks, s); }; case let s: []u8 => { append(toks, s); };
case done => { return toks; }; case done => { return toks; };
}; };
i += 1; i += 1;
@@ -1337,7 +1315,7 @@ export fn rsplitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
case done => void; case done => void;
case let pk: []u8 => { case let pk: []u8 => {
let r: []u8 = remaining_tokens(&tok); let r: []u8 = remaining_tokens(&tok);
appendslice(&toks, r); append(toks, r);
}; };
}; };
@@ -1955,7 +1933,7 @@ export fn dup(s: str) str = {
// so the only nomem propagation point is the initial slice alloc. // so the only nomem propagation point is the initial slice alloc.
// With no inner failure path, the rollback is structurally a no-op // With no inner failure path, the rollback is structurally a no-op
// and is omitted; it returns once dup graduates to `(str | nomem)` // and is omitted; it returns once dup graduates to `(str | nomem)`
// (#46). The pre-allocated slice has `cap == s.len`, so appendstr's // (#46). The pre-allocated slice has `cap == s.len`, so append's
// rt_ensure call never reaches the grow branch. // rt_ensure call never reaches the grow branch.
// //
// Empty input bypasses the alloc: rt_malloc(0) is an mmap of 0 bytes // Empty input bypasses the alloc: rt_malloc(0) is an mmap of 0 bytes
@@ -1974,7 +1952,7 @@ export fn dupall(s: []str) ([]str | nomem) = {
let newsl: []str = alloc([], s.len)?; let newsl: []str = alloc([], s.len)?;
let i: i32 = 0; let i: i32 = 0;
for (i < s.len) { for (i < s.len) {
appendstr(&newsl, dup(s[i])); append(newsl, dup(s[i]));
i += 1; i += 1;
}; };
return newsl; return newsl;
@@ -2600,24 +2578,6 @@ export fn rcut(in: str, delim: str) (str, str) = {
return (frombytes(a), frombytes(b)); return (frombytes(a), frombytes(b));
}; };
// rt_ensure is the runtime slice-growth helper invoked by the
// `append(s, v)` builtin. Direct bind for the same reason as
// lib/shlex.shlex (appendstr, 16B): the builtin's expansion stores
// only 8B of the new element, losing the `.len` half of a `str`.
@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void;
// appendstr — grow `*slice` by one and store `item` (16B). Mirror of
// lib/shlex.shlex appendstr. Collapses when the append builtin learns
// to store the full element width.
fn appendstr(slice: *[]str, item: str) void = {
let newlen: i32 = slice.len + 1;
slice.len = newlen;
rtensure(slice: *void, size(str): u64);
let dst: *str = &slice.ptr[newlen - 1];
dst.ptr = item.ptr;
dst.len = item.len;
};
// splitn — split `in` on any byte in `delim`, returning up to `n` // splitn — split `in` on any byte in `delim`, returning up to `n`
// tokens via forward iteration. The trailing slot (when more than // tokens via forward iteration. The trailing slot (when more than
// `n - 1` tokens exist) holds the unconsumed remainder. Strings // `n - 1` tokens exist) holds the unconsumed remainder. Strings
@@ -2640,7 +2600,7 @@ export fn splitn(in: str, delim: str, n: i32) []str = {
let i: i32 = 0; let i: i32 = 0;
for (i < n - 1) { for (i < n - 1) {
match (next_token(&tok)) { match (next_token(&tok)) {
case let s: str => { appendstr(&toks, s); }; case let s: str => { append(toks, s); };
case bytes.done => { return toks; }; case bytes.done => { return toks; };
}; };
i += 1; i += 1;
@@ -2649,7 +2609,7 @@ export fn splitn(in: str, delim: str, n: i32) []str = {
case bytes.done => void; case bytes.done => void;
case let pk: str => { case let pk: str => {
let r: str = remaining_tokens(&tok); let r: str = remaining_tokens(&tok);
appendstr(&toks, r); append(toks, r);
}; };
}; };
return toks; return toks;
@@ -2674,7 +2634,7 @@ export fn rsplitn(in: str, delim: str, n: i32) []str = {
let i: i32 = 0; let i: i32 = 0;
for (i < n - 1) { for (i < n - 1) {
match (next_token(&tok)) { match (next_token(&tok)) {
case let s: str => { appendstr(&toks, s); }; case let s: str => { append(toks, s); };
case bytes.done => { return toks; }; case bytes.done => { return toks; };
}; };
i += 1; i += 1;
@@ -2683,7 +2643,7 @@ export fn rsplitn(in: str, delim: str, n: i32) []str = {
case bytes.done => void; case bytes.done => void;
case let pk: str => { case let pk: str => {
let r: str = remaining_tokens(&tok); let r: str = remaining_tokens(&tok);
appendstr(&toks, r); append(toks, r);
}; };
}; };

View File

@@ -1244,28 +1244,6 @@ export fn remaining_tokens(s: *tokenizer) []u8 = {
return s.in; return s.in;
}; };
// rt_ensure is the runtime slice-growth helper invoked by the
// `append(s, v)` builtin. We bind it directly because the builtin's
// expansion stores only 8 bytes of the new element (cgen emits a
// single MOVQ), losing the .len/.cap fields of a []u8 element (24B).
// Mirrors the same workaround in lib/shlex.shlex (appendstr, 16B) and
// lib/getopt.getopt (appendoption, 24B); collapses in one go when the
// append builtin learns to store the full element width.
@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void;
// appendslice — grow `*slice` by one and store `item` (24B). Mirror
// of [[shlex.appendstr]] / [[getopt.appendoption]]. Bypasses the
// `append` builtin's first-8B-only-store gap for a slice-element.
fn appendslice(slice: *[][]u8, item: []u8) void = {
let newlen: i32 = slice.len + 1;
slice.len = newlen;
rtensure(slice: *void, 24u64);
let dst: *[]u8 = &slice.ptr[newlen - 1];
dst.ptr = item.ptr;
dst.len = item.len;
dst.cap = item.cap;
};
// splitn — split `in` on any byte in `delim`, returning up to `n` // splitn — split `in` on any byte in `delim`, returning up to `n`
// tokens via forward iteration. The trailing slot (when more than // tokens via forward iteration. The trailing slot (when more than
// `n - 1` tokens exist) holds the unconsumed remainder. // `n - 1` tokens exist) holds the unconsumed remainder.
@@ -1290,7 +1268,7 @@ export fn splitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
let i: i32 = 0; let i: i32 = 0;
for (i < n - 1) { for (i < n - 1) {
match (next_token(&tok)) { match (next_token(&tok)) {
case let s: []u8 => { appendslice(&toks, s); }; case let s: []u8 => { append(toks, s); };
case done => { return toks; }; case done => { return toks; };
}; };
i += 1; i += 1;
@@ -1299,7 +1277,7 @@ export fn splitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
case done => void; case done => void;
case let pk: []u8 => { case let pk: []u8 => {
let r: []u8 = remaining_tokens(&tok); let r: []u8 = remaining_tokens(&tok);
appendslice(&toks, r); append(toks, r);
}; };
}; };
return toks; return toks;
@@ -1328,7 +1306,7 @@ export fn rsplitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
let i: i32 = 0; let i: i32 = 0;
for (i < n - 1) { for (i < n - 1) {
match (next_token(&tok)) { match (next_token(&tok)) {
case let s: []u8 => { appendslice(&toks, s); }; case let s: []u8 => { append(toks, s); };
case done => { return toks; }; case done => { return toks; };
}; };
i += 1; i += 1;
@@ -1337,7 +1315,7 @@ export fn rsplitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
case done => void; case done => void;
case let pk: []u8 => { case let pk: []u8 => {
let r: []u8 = remaining_tokens(&tok); let r: []u8 = remaining_tokens(&tok);
appendslice(&toks, r); append(toks, r);
}; };
}; };
@@ -1955,7 +1933,7 @@ export fn dup(s: str) str = {
// so the only nomem propagation point is the initial slice alloc. // so the only nomem propagation point is the initial slice alloc.
// With no inner failure path, the rollback is structurally a no-op // With no inner failure path, the rollback is structurally a no-op
// and is omitted; it returns once dup graduates to `(str | nomem)` // and is omitted; it returns once dup graduates to `(str | nomem)`
// (#46). The pre-allocated slice has `cap == s.len`, so appendstr's // (#46). The pre-allocated slice has `cap == s.len`, so append's
// rt_ensure call never reaches the grow branch. // rt_ensure call never reaches the grow branch.
// //
// Empty input bypasses the alloc: rt_malloc(0) is an mmap of 0 bytes // Empty input bypasses the alloc: rt_malloc(0) is an mmap of 0 bytes
@@ -1974,7 +1952,7 @@ export fn dupall(s: []str) ([]str | nomem) = {
let newsl: []str = alloc([], s.len)?; let newsl: []str = alloc([], s.len)?;
let i: i32 = 0; let i: i32 = 0;
for (i < s.len) { for (i < s.len) {
appendstr(&newsl, dup(s[i])); append(newsl, dup(s[i]));
i += 1; i += 1;
}; };
return newsl; return newsl;
@@ -2600,24 +2578,6 @@ export fn rcut(in: str, delim: str) (str, str) = {
return (frombytes(a), frombytes(b)); return (frombytes(a), frombytes(b));
}; };
// rt_ensure is the runtime slice-growth helper invoked by the
// `append(s, v)` builtin. Direct bind for the same reason as
// lib/shlex.shlex (appendstr, 16B): the builtin's expansion stores
// only 8B of the new element, losing the `.len` half of a `str`.
@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void;
// appendstr — grow `*slice` by one and store `item` (16B). Mirror of
// lib/shlex.shlex appendstr. Collapses when the append builtin learns
// to store the full element width.
fn appendstr(slice: *[]str, item: str) void = {
let newlen: i32 = slice.len + 1;
slice.len = newlen;
rtensure(slice: *void, size(str): u64);
let dst: *str = &slice.ptr[newlen - 1];
dst.ptr = item.ptr;
dst.len = item.len;
};
// splitn — split `in` on any byte in `delim`, returning up to `n` // splitn — split `in` on any byte in `delim`, returning up to `n`
// tokens via forward iteration. The trailing slot (when more than // tokens via forward iteration. The trailing slot (when more than
// `n - 1` tokens exist) holds the unconsumed remainder. Strings // `n - 1` tokens exist) holds the unconsumed remainder. Strings
@@ -2640,7 +2600,7 @@ export fn splitn(in: str, delim: str, n: i32) []str = {
let i: i32 = 0; let i: i32 = 0;
for (i < n - 1) { for (i < n - 1) {
match (next_token(&tok)) { match (next_token(&tok)) {
case let s: str => { appendstr(&toks, s); }; case let s: str => { append(toks, s); };
case bytes.done => { return toks; }; case bytes.done => { return toks; };
}; };
i += 1; i += 1;
@@ -2649,7 +2609,7 @@ export fn splitn(in: str, delim: str, n: i32) []str = {
case bytes.done => void; case bytes.done => void;
case let pk: str => { case let pk: str => {
let r: str = remaining_tokens(&tok); let r: str = remaining_tokens(&tok);
appendstr(&toks, r); append(toks, r);
}; };
}; };
return toks; return toks;
@@ -2674,7 +2634,7 @@ export fn rsplitn(in: str, delim: str, n: i32) []str = {
let i: i32 = 0; let i: i32 = 0;
for (i < n - 1) { for (i < n - 1) {
match (next_token(&tok)) { match (next_token(&tok)) {
case let s: str => { appendstr(&toks, s); }; case let s: str => { append(toks, s); };
case bytes.done => { return toks; }; case bytes.done => { return toks; };
}; };
i += 1; i += 1;
@@ -2683,7 +2643,7 @@ export fn rsplitn(in: str, delim: str, n: i32) []str = {
case bytes.done => void; case bytes.done => void;
case let pk: str => { case let pk: str => {
let r: str = remaining_tokens(&tok); let r: str = remaining_tokens(&tok);
appendstr(&toks, r); append(toks, r);
}; };
}; };

View File

@@ -2738,28 +2738,6 @@ export fn remaining_tokens(s: *tokenizer) []u8 = {
return s.in; return s.in;
}; };
// rt_ensure is the runtime slice-growth helper invoked by the
// `append(s, v)` builtin. We bind it directly because the builtin's
// expansion stores only 8 bytes of the new element (cgen emits a
// single MOVQ), losing the .len/.cap fields of a []u8 element (24B).
// Mirrors the same workaround in lib/shlex.shlex (appendstr, 16B) and
// lib/getopt.getopt (appendoption, 24B); collapses in one go when the
// append builtin learns to store the full element width.
@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void;
// appendslice — grow `*slice` by one and store `item` (24B). Mirror
// of [[shlex.appendstr]] / [[getopt.appendoption]]. Bypasses the
// `append` builtin's first-8B-only-store gap for a slice-element.
fn appendslice(slice: *[][]u8, item: []u8) void = {
let newlen: i32 = slice.len + 1;
slice.len = newlen;
rtensure(slice: *void, 24u64);
let dst: *[]u8 = &slice.ptr[newlen - 1];
dst.ptr = item.ptr;
dst.len = item.len;
dst.cap = item.cap;
};
// splitn — split `in` on any byte in `delim`, returning up to `n` // splitn — split `in` on any byte in `delim`, returning up to `n`
// tokens via forward iteration. The trailing slot (when more than // tokens via forward iteration. The trailing slot (when more than
// `n - 1` tokens exist) holds the unconsumed remainder. // `n - 1` tokens exist) holds the unconsumed remainder.
@@ -2784,7 +2762,7 @@ export fn splitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
let i: i32 = 0; let i: i32 = 0;
for (i < n - 1) { for (i < n - 1) {
match (next_token(&tok)) { match (next_token(&tok)) {
case let s: []u8 => { appendslice(&toks, s); }; case let s: []u8 => { append(toks, s); };
case done => { return toks; }; case done => { return toks; };
}; };
i += 1; i += 1;
@@ -2793,7 +2771,7 @@ export fn splitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
case done => void; case done => void;
case let pk: []u8 => { case let pk: []u8 => {
let r: []u8 = remaining_tokens(&tok); let r: []u8 = remaining_tokens(&tok);
appendslice(&toks, r); append(toks, r);
}; };
}; };
return toks; return toks;
@@ -2822,7 +2800,7 @@ export fn rsplitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
let i: i32 = 0; let i: i32 = 0;
for (i < n - 1) { for (i < n - 1) {
match (next_token(&tok)) { match (next_token(&tok)) {
case let s: []u8 => { appendslice(&toks, s); }; case let s: []u8 => { append(toks, s); };
case done => { return toks; }; case done => { return toks; };
}; };
i += 1; i += 1;
@@ -2831,7 +2809,7 @@ export fn rsplitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
case done => void; case done => void;
case let pk: []u8 => { case let pk: []u8 => {
let r: []u8 = remaining_tokens(&tok); let r: []u8 = remaining_tokens(&tok);
appendslice(&toks, r); append(toks, r);
}; };
}; };
@@ -3472,7 +3450,7 @@ export fn dup(s: str) str = {
// so the only nomem propagation point is the initial slice alloc. // so the only nomem propagation point is the initial slice alloc.
// With no inner failure path, the rollback is structurally a no-op // With no inner failure path, the rollback is structurally a no-op
// and is omitted; it returns once dup graduates to `(str | nomem)` // and is omitted; it returns once dup graduates to `(str | nomem)`
// (#46). The pre-allocated slice has `cap == s.len`, so appendstr's // (#46). The pre-allocated slice has `cap == s.len`, so append's
// rt_ensure call never reaches the grow branch. // rt_ensure call never reaches the grow branch.
// //
// Empty input bypasses the alloc: rt_malloc(0) is an mmap of 0 bytes // Empty input bypasses the alloc: rt_malloc(0) is an mmap of 0 bytes
@@ -3491,7 +3469,7 @@ export fn dupall(s: []str) ([]str | nomem) = {
let newsl: []str = alloc([], s.len)?; let newsl: []str = alloc([], s.len)?;
let i: i32 = 0; let i: i32 = 0;
for (i < s.len) { for (i < s.len) {
appendstr(&newsl, dup(s[i])); append(newsl, dup(s[i]));
i += 1; i += 1;
}; };
return newsl; return newsl;
@@ -4117,24 +4095,6 @@ export fn rcut(in: str, delim: str) (str, str) = {
return (frombytes(a), frombytes(b)); return (frombytes(a), frombytes(b));
}; };
// rt_ensure is the runtime slice-growth helper invoked by the
// `append(s, v)` builtin. Direct bind for the same reason as
// lib/shlex.shlex (appendstr, 16B): the builtin's expansion stores
// only 8B of the new element, losing the `.len` half of a `str`.
@symbol("rt_ensure") fn rtensure(s: *void, membsz: u64) void;
// appendstr — grow `*slice` by one and store `item` (16B). Mirror of
// lib/shlex.shlex appendstr. Collapses when the append builtin learns
// to store the full element width.
fn appendstr(slice: *[]str, item: str) void = {
let newlen: i32 = slice.len + 1;
slice.len = newlen;
rtensure(slice: *void, size(str): u64);
let dst: *str = &slice.ptr[newlen - 1];
dst.ptr = item.ptr;
dst.len = item.len;
};
// splitn — split `in` on any byte in `delim`, returning up to `n` // splitn — split `in` on any byte in `delim`, returning up to `n`
// tokens via forward iteration. The trailing slot (when more than // tokens via forward iteration. The trailing slot (when more than
// `n - 1` tokens exist) holds the unconsumed remainder. Strings // `n - 1` tokens exist) holds the unconsumed remainder. Strings
@@ -4157,7 +4117,7 @@ export fn splitn(in: str, delim: str, n: i32) []str = {
let i: i32 = 0; let i: i32 = 0;
for (i < n - 1) { for (i < n - 1) {
match (next_token(&tok)) { match (next_token(&tok)) {
case let s: str => { appendstr(&toks, s); }; case let s: str => { append(toks, s); };
case bytes.done => { return toks; }; case bytes.done => { return toks; };
}; };
i += 1; i += 1;
@@ -4166,7 +4126,7 @@ export fn splitn(in: str, delim: str, n: i32) []str = {
case bytes.done => void; case bytes.done => void;
case let pk: str => { case let pk: str => {
let r: str = remaining_tokens(&tok); let r: str = remaining_tokens(&tok);
appendstr(&toks, r); append(toks, r);
}; };
}; };
return toks; return toks;
@@ -4191,7 +4151,7 @@ export fn rsplitn(in: str, delim: str, n: i32) []str = {
let i: i32 = 0; let i: i32 = 0;
for (i < n - 1) { for (i < n - 1) {
match (next_token(&tok)) { match (next_token(&tok)) {
case let s: str => { appendstr(&toks, s); }; case let s: str => { append(toks, s); };
case bytes.done => { return toks; }; case bytes.done => { return toks; };
}; };
i += 1; i += 1;
@@ -4200,7 +4160,7 @@ export fn rsplitn(in: str, delim: str, n: i32) []str = {
case bytes.done => void; case bytes.done => void;
case let pk: str => { case let pk: str => {
let r: str = remaining_tokens(&tok); let r: str = remaining_tokens(&tok);
appendstr(&toks, r); append(toks, r);
}; };
}; };