bytes: make search linear and splits consistent
This commit is contained in:
@@ -1,10 +1,6 @@
|
||||
// Hare port of the in-tree subset; see ref/hare/bytes/.
|
||||
//
|
||||
// Documented divergences from Hare:
|
||||
// - index_slice / rindex_slice use naive O(n·m); Hare specialises
|
||||
// 2/3/4-byte needles and falls back to two_way (Crochemore-Perrin)
|
||||
// for longer (ref/hare/bytes/index.ha:61, ref/hare/bytes/two_way.ha).
|
||||
// Correctness equivalent.
|
||||
// - peektoken dispatches index/rindex by branching on `reverse`
|
||||
// rather than a function-pointer `ifunc` (ref/hare/bytes/tokenize.ha:97).
|
||||
// ww has no fn pointers in scope yet — same pattern as lib/strings
|
||||
@@ -18,7 +14,6 @@
|
||||
|
||||
package bytes;
|
||||
|
||||
import os;
|
||||
import types;
|
||||
|
||||
// done — iteration sentinel returned by nexttoken / peektoken at
|
||||
@@ -48,6 +43,92 @@ export fn equal(a: []u8, b: []u8) bool = {
|
||||
return true;
|
||||
};
|
||||
|
||||
// Maximal suffix and period for the Crochemore-Perrin two-way search.
|
||||
// The signed -1 sentinel is the i32 translation of Hare's SIZE_MAX in
|
||||
// ref/hare/bytes/two_way.ha:68.
|
||||
fn indexmaxsuf(x: []u8, inv: bool) (i32, i32) = {
|
||||
let i: i32 = -1;
|
||||
let j: i32 = 0;
|
||||
let k: i32 = 1;
|
||||
let p: i32 = 1;
|
||||
for (j + k < x.len) {
|
||||
let a: u8 = x[j + k];
|
||||
let b: u8 = x[i + k];
|
||||
if (a == b) {
|
||||
if (k == p) { j += p; k = 1; }
|
||||
else { k += 1; };
|
||||
} else if ((a < b) != inv) {
|
||||
j += k;
|
||||
k = 1;
|
||||
p = j - i;
|
||||
} else {
|
||||
i = j;
|
||||
j += 1;
|
||||
k = 1;
|
||||
p = 1;
|
||||
};
|
||||
};
|
||||
return (i, p);
|
||||
};
|
||||
|
||||
// Allocation-free O(n+m) forward search. This follows
|
||||
// ref/hare/bytes/two_way.ha, with the critical-period test stated directly on
|
||||
// the needle: periodicity is a property of the needle, never the haystack.
|
||||
fn indextwoway(haystack: []u8, needle: []u8) (i32 | void) = {
|
||||
let n: i32 = haystack.len;
|
||||
let m: i32 = needle.len;
|
||||
if (n < m) { return; };
|
||||
if (n == m) {
|
||||
if (equal(haystack, needle)) { return 0; };
|
||||
return;
|
||||
};
|
||||
|
||||
let (i0, p0) = indexmaxsuf(needle, false);
|
||||
let (i1, p1) = indexmaxsuf(needle, true);
|
||||
let ms: i32 = i0;
|
||||
let per: i32 = p0;
|
||||
if (i0 < i1) { ms = i1; per = p1; };
|
||||
|
||||
let periodic: bool = per + ms + 1 <= m;
|
||||
let q: i32 = 0;
|
||||
for (periodic && q <= ms) {
|
||||
if (needle[q] != needle[per + q]) { periodic = false; };
|
||||
q += 1;
|
||||
};
|
||||
|
||||
let mem0: i32 = 0;
|
||||
if (periodic) {
|
||||
mem0 = m - per;
|
||||
} else {
|
||||
let left: i32 = ms + 1;
|
||||
let right: i32 = m - ms - 1;
|
||||
if (left > right) { per = left + 1; }
|
||||
else { per = right + 1; };
|
||||
};
|
||||
|
||||
let off: i32 = 0;
|
||||
let mem: i32 = 0;
|
||||
for (off <= n - m) {
|
||||
let i: i32 = ms + 1;
|
||||
if (mem > i) { i = mem; };
|
||||
for (i < m && needle[i] == haystack[off + i]) { i += 1; };
|
||||
if (i < m) {
|
||||
off += i - ms;
|
||||
mem = 0;
|
||||
continue;
|
||||
};
|
||||
|
||||
i = ms + 1;
|
||||
for (i > mem && needle[i - 1] == haystack[off + i - 1]) {
|
||||
i -= 1;
|
||||
};
|
||||
if (i <= mem) { return off; };
|
||||
off += per;
|
||||
mem = mem0;
|
||||
};
|
||||
return;
|
||||
};
|
||||
|
||||
// ref/hare/bytes/index.ha:6.
|
||||
export fn index(s: []u8, needle: (u8 | []u8)) (i32 | void) = {
|
||||
match (needle) {
|
||||
@@ -61,20 +142,8 @@ export fn index(s: []u8, needle: (u8 | []u8)) (i32 | void) = {
|
||||
};
|
||||
case let sub: []u8 => {
|
||||
if (sub.len == 0) { return 0; };
|
||||
if (sub.len > s.len) { return; };
|
||||
let last: i32 = s.len - sub.len;
|
||||
let i: i32 = 0;
|
||||
for (i <= last) {
|
||||
let j: i32 = 0;
|
||||
let ok: bool = true;
|
||||
for (j < sub.len) {
|
||||
if (s[i + j] != sub[j]) { ok = false; j = sub.len; }
|
||||
else { j += 1; };
|
||||
};
|
||||
if (ok) { return i; };
|
||||
i += 1;
|
||||
};
|
||||
return;
|
||||
if (sub.len == 1) { return index(s, sub[0]); };
|
||||
return indextwoway(s, sub);
|
||||
};
|
||||
};
|
||||
return;
|
||||
@@ -215,8 +284,6 @@ export fn zero(s: []u8) void = {
|
||||
// lifetime. ref/hare/bytes/tokenize.ha:22.
|
||||
export fn tokenize(in: []u8, delim: u8...) tokenizer = {
|
||||
assert(delim.len > 0, "bytes.tokenize called with empty slice");
|
||||
assert((in.len: i64) < types.I64_MAX,
|
||||
"bytes.tokenize: input length exceeds I64_MAX");
|
||||
let t: tokenizer;
|
||||
t.in = in;
|
||||
t.delim = delim;
|
||||
@@ -231,8 +298,6 @@ export fn tokenize(in: []u8, delim: u8...) tokenizer = {
|
||||
// ref/hare/bytes/tokenize.ha:40.
|
||||
export fn rtokenize(in: []u8, delim: u8...) tokenizer = {
|
||||
assert(delim.len > 0, "bytes.rtokenize called with empty slice");
|
||||
assert((in.len: i64) < types.I64_MAX,
|
||||
"bytes.rtokenize: input length exceeds I64_MAX");
|
||||
let t: tokenizer;
|
||||
t.in = in;
|
||||
t.delim = delim;
|
||||
@@ -383,10 +448,12 @@ export fn remainingtokens(s: *tokenizer) []u8 = {
|
||||
export fn splitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
|
||||
assert(delim.len > 0,
|
||||
"bytes.splitn must not be called with an empty delimiter");
|
||||
assert(n >= 0, "bytes.splitn: token limit must not be negative");
|
||||
let toks: [][]u8;
|
||||
toks.ptr = nil: *[]u8;
|
||||
toks.len = 0;
|
||||
toks.cap = 0;
|
||||
if (n == 0) { return toks; };
|
||||
let tok: tokenizer = tokenize(in, delim...);
|
||||
let i: i32 = 0;
|
||||
for (i < n - 1) {
|
||||
@@ -409,27 +476,22 @@ export fn splitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
|
||||
// The trailing slot holds the unconsumed prefix (everything before
|
||||
// the n-th-from-last delim hit).
|
||||
//
|
||||
// When the input has fewer than n tokens, the `done` short-circuit
|
||||
// returns toks UN-reversed (in last-token-first order). Mirrors Hare
|
||||
// at ref/hare/bytes/tokenize.ha:196-199 where the in-place reverse
|
||||
// step is gated behind the n-1 loop running to completion. Only the
|
||||
// "loop ran to completion AND peek saw a remainder" path applies the
|
||||
// reverse; both early-exit paths skip it.
|
||||
//
|
||||
// ref/hare/bytes/tokenize.ha:186.
|
||||
export fn rsplitn(in: []u8, delim: []u8, n: i32) [][]u8 = {
|
||||
assert(delim.len > 0,
|
||||
"bytes.rsplitn called with empty delimiter");
|
||||
assert(n >= 0, "bytes.rsplitn: token limit must not be negative");
|
||||
let toks: [][]u8;
|
||||
toks.ptr = nil: *[]u8;
|
||||
toks.len = 0;
|
||||
toks.cap = 0;
|
||||
if (n == 0) { return toks; };
|
||||
let tok: tokenizer = rtokenize(in, delim...);
|
||||
let i: i32 = 0;
|
||||
for (i < n - 1) {
|
||||
match (nexttoken(&tok)) {
|
||||
case let s: []u8 => { append(toks, s); };
|
||||
case done => { return toks; };
|
||||
case done => { break; };
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user