lib/strings+test: port replace per Hare
ref/hare/strings/replace.ha:46-66. Two-pass byte scan: pass 1 counts non-overlapping needle hits via bytes.hasprefix, pass 2 allocs the result []u8 at exact size and copies chunks + replacement. Single nomem propagation site at the alloc — ww's append builtin aborts on OOM (#11), so the per-chunk append(...)? form Hare uses is not available; the exact-size single alloc is equivalent in spec. total==0 returns {nil,0} to dodge rt_alloc(0) per #47. Empty needle is intentionally ungated and loops forever — that's Hare's behavior at ref/hare/strings/replace.ha:31 (i += len(needle) is 0; hasprefix("") always matches). Hare-faithful divergence, documented at the site. multireplace deferred to #49 — its (str, str) variadic gather hits #39 in variadic-param position. Filed and blocked accordingly.
This commit is contained in:
@@ -2743,6 +2743,62 @@ export fn lpad(s: str, p: rune, maxlen: i32) str = {
|
||||
return r;
|
||||
};
|
||||
|
||||
// replace — fresh allocation of `s` with every non-overlapping
|
||||
// occurrence of `needle` replaced by `target`. Caller releases with
|
||||
// `os.free(r.ptr, r.len: u64)`. ref/hare/strings/replace.ha:8 (#4).
|
||||
//
|
||||
// Hare delegates to [[multireplace]] with a single pair; ww has no
|
||||
// `(str, str)` variadic shape today (#39), so this is a standalone
|
||||
// two-pass implementation: pass 1 counts matches to size the result,
|
||||
// pass 2 copies chunks and `target` into a single fresh buffer.
|
||||
// Single nomem path (the `alloc([], total)?`) preserves Hare's
|
||||
// signature without a per-write `append(...)?` (ww's append builtin
|
||||
// aborts on OOM, #11). Empty `needle` would hasprefix-match every
|
||||
// position with a zero stride — same infinite loop Hare exhibits at
|
||||
// ref/hare/strings/replace.ha:31; not gated.
|
||||
export fn replace(s: str, needle: str, target: str) (str | nomem) = {
|
||||
let sb: []u8 = toutf8(s);
|
||||
let nb: []u8 = toutf8(needle);
|
||||
let tb: []u8 = toutf8(target);
|
||||
let count: i32 = 0;
|
||||
let i: i32 = 0;
|
||||
for (i < sb.len) {
|
||||
if (bytes.hasprefix(sb[i:sb.len], nb)) {
|
||||
count += 1;
|
||||
i += nb.len;
|
||||
} else {
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
let total: i32 = sb.len + count * (tb.len - nb.len);
|
||||
if (total == 0) {
|
||||
let r: str;
|
||||
r.ptr = nil;
|
||||
r.len = 0;
|
||||
return r;
|
||||
};
|
||||
let res: []u8 = alloc([], total)?;
|
||||
let off: i32 = 0;
|
||||
i = 0;
|
||||
for (i < sb.len) {
|
||||
if (bytes.hasprefix(sb[i:sb.len], nb)) {
|
||||
let j: i32 = 0;
|
||||
for (j < tb.len) {
|
||||
res.ptr[off + j] = tb.ptr[j];
|
||||
j += 1;
|
||||
};
|
||||
off += tb.len;
|
||||
i += nb.len;
|
||||
} else {
|
||||
res.ptr[off] = sb.ptr[i];
|
||||
off += 1;
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
res.len = total;
|
||||
return fromutf8_unsafe(res);
|
||||
};
|
||||
|
||||
// rpad — right-pad `s` with `p` rune until the result reaches `maxlen`
|
||||
// bytes. Symmetric with [[lpad]]. ref/hare/strings/pad.ha:39.
|
||||
export fn rpad(s: str, p: rune, maxlen: i32) str = {
|
||||
|
||||
@@ -2743,6 +2743,62 @@ export fn lpad(s: str, p: rune, maxlen: i32) str = {
|
||||
return r;
|
||||
};
|
||||
|
||||
// replace — fresh allocation of `s` with every non-overlapping
|
||||
// occurrence of `needle` replaced by `target`. Caller releases with
|
||||
// `os.free(r.ptr, r.len: u64)`. ref/hare/strings/replace.ha:8 (#4).
|
||||
//
|
||||
// Hare delegates to [[multireplace]] with a single pair; ww has no
|
||||
// `(str, str)` variadic shape today (#39), so this is a standalone
|
||||
// two-pass implementation: pass 1 counts matches to size the result,
|
||||
// pass 2 copies chunks and `target` into a single fresh buffer.
|
||||
// Single nomem path (the `alloc([], total)?`) preserves Hare's
|
||||
// signature without a per-write `append(...)?` (ww's append builtin
|
||||
// aborts on OOM, #11). Empty `needle` would hasprefix-match every
|
||||
// position with a zero stride — same infinite loop Hare exhibits at
|
||||
// ref/hare/strings/replace.ha:31; not gated.
|
||||
export fn replace(s: str, needle: str, target: str) (str | nomem) = {
|
||||
let sb: []u8 = toutf8(s);
|
||||
let nb: []u8 = toutf8(needle);
|
||||
let tb: []u8 = toutf8(target);
|
||||
let count: i32 = 0;
|
||||
let i: i32 = 0;
|
||||
for (i < sb.len) {
|
||||
if (bytes.hasprefix(sb[i:sb.len], nb)) {
|
||||
count += 1;
|
||||
i += nb.len;
|
||||
} else {
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
let total: i32 = sb.len + count * (tb.len - nb.len);
|
||||
if (total == 0) {
|
||||
let r: str;
|
||||
r.ptr = nil;
|
||||
r.len = 0;
|
||||
return r;
|
||||
};
|
||||
let res: []u8 = alloc([], total)?;
|
||||
let off: i32 = 0;
|
||||
i = 0;
|
||||
for (i < sb.len) {
|
||||
if (bytes.hasprefix(sb[i:sb.len], nb)) {
|
||||
let j: i32 = 0;
|
||||
for (j < tb.len) {
|
||||
res.ptr[off + j] = tb.ptr[j];
|
||||
j += 1;
|
||||
};
|
||||
off += tb.len;
|
||||
i += nb.len;
|
||||
} else {
|
||||
res.ptr[off] = sb.ptr[i];
|
||||
off += 1;
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
res.len = total;
|
||||
return fromutf8_unsafe(res);
|
||||
};
|
||||
|
||||
// rpad — right-pad `s` with `p` rune until the result reaches `maxlen`
|
||||
// bytes. Symmetric with [[lpad]]. ref/hare/strings/pad.ha:39.
|
||||
export fn rpad(s: str, p: rune, maxlen: i32) str = {
|
||||
|
||||
Reference in New Issue
Block a user