diff --git a/lib/bytes/bytes.ww b/lib/bytes/bytes.ww index 00419e51..5c5ca654 100644 --- a/lib/bytes/bytes.ww +++ b/lib/bytes/bytes.ww @@ -516,3 +516,57 @@ export fn rsplitn(in: []u8, delim: []u8, n: i32) [][]u8 = { export fn split(in: []u8, delim: []u8) [][]u8 = { return splitn(in, delim, types.I32_MAX); }; + +// cut — split `in` along the first instance of `delim`, returning the +// portion before and the portion after the delimiter as a borrowed +// tuple. When `delim` is absent, the whole input is the first half and +// the second is empty. ref/hare/bytes/tokenize.ha:392. +// +// Delim is spelled (u8 | []u8) to match index/rindex (bytes.ww:57/91); +// the tagged union is an unordered set, so this is the same type as +// Hare's ([]u8 | u8), not a divergence. +export fn cut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = { + let ln: i32 = match (delim) { + case let c: u8 => yield 1i32; + case let sub: []u8 => { + os.assert(sub.len > 0, + "bytes.cut called with empty delimiter"); + yield sub.len; + }; + }; + match (index(in, delim)) { + case let i: i32 => { + let lo: i32 = i + ln; + return (in[0:i], in[lo:in.len]); + }; + case void => { + let empty: []u8; + empty.ptr = nil; empty.len = 0; empty.cap = 0; + return (in, empty); + }; + }; +}; + +// rcut — like [[cut]] but splits along the last instance of `delim`. +// ref/hare/bytes/tokenize.ha:413. +export fn rcut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = { + let ln: i32 = match (delim) { + case let c: u8 => yield 1i32; + case let sub: []u8 => { + os.assert(sub.len > 0, + "bytes.rcut called with empty delimiter"); + yield sub.len; + }; + }; + match (rindex(in, delim)) { + case let i: i32 => { + let lo: i32 = i + ln; + return (in[0:i], in[lo:in.len]); + }; + case void => { + let empty: []u8; + empty.ptr = nil; empty.len = 0; empty.cap = 0; + return (in, empty); + }; + }; +}; diff --git a/lib/bytes/bytestest.ww b/lib/bytes/bytestest.ww index a2d1cc45..349755cc 100644 --- a/lib/bytes/bytestest.ww +++ b/lib/bytes/bytestest.ww @@ -747,6 +747,134 @@ fn expect_tok(toks: [][]u8, i: i32, want: []u8) void = { os.free(t2.ptr: *void, (t2.cap: u64) * 24u64); }; +// ---- cut / rcut ------------------------------------------------------- +// ref/hare/bytes/tokenize.ha:429 (@test fn cut). Borrowed views; each +// half asserted via equal(). Needle order is ww's (u8 | []u8). Rows use +// the destructure form `let (before, after) = cut(...)` to drive the #10 +// over-cap tuple-return (sret) end-to-end. + +@test fn cut_cases() void = { + let z: [1]u8; + let abc: [3]u8; abc[0] = 97u8; abc[1] = 98u8; abc[2] = 99u8; + let ea: [1]u8; ea[0] = 97u8; + let ec: [1]u8; ec[0] = 99u8; + + // ['a','b','c'] along byte 'b' -> ['a'],['c'] + signalled = 1810; + let (b0, a0) = bytes.cut(abc[0:3], 98u8); + if (!bytes.equal(b0, ea[0:1])) { fail(); }; + if (!bytes.equal(a0, ec[0:1])) { fail(); }; + + // same, single-byte []u8 delim -> identical + signalled = 1811; + let nb: [1]u8; nb[0] = 98u8; + let (b1, a1) = bytes.cut(abc[0:3], nb[0:1]); + if (!bytes.equal(b1, ea[0:1])) { fail(); }; + if (!bytes.equal(a1, ec[0:1])) { fail(); }; + + // ['b','c'] along 'b' -> [],['c'] (empty before) + signalled = 1812; + let bc: [2]u8; bc[0] = 98u8; bc[1] = 99u8; + let (b2, a2) = bytes.cut(bc[0:2], 98u8); + if (!bytes.equal(b2, z[0:0])) { fail(); }; + if (!bytes.equal(a2, ec[0:1])) { fail(); }; + + // ['a','b'] along 'b' -> ['a'],[] (empty after) + signalled = 1813; + let ab: [2]u8; ab[0] = 97u8; ab[1] = 98u8; + let (b3, a3) = bytes.cut(ab[0:2], 98u8); + if (!bytes.equal(b3, ea[0:1])) { fail(); }; + if (!bytes.equal(a3, z[0:0])) { fail(); }; + + // delim absent -> (whole input, []) + signalled = 1814; + let (b4, a4) = bytes.cut(abc[0:3], 120u8); + if (!bytes.equal(b4, abc[0:3])) { fail(); }; + if (!bytes.equal(a4, z[0:0])) { fail(); }; + + // empty input -> ([],[]) + signalled = 1815; + let (b5, a5) = bytes.cut(z[0:0], 120u8); + if (!bytes.equal(b5, z[0:0])) { fail(); }; + if (!bytes.equal(a5, z[0:0])) { fail(); }; + + // repeated delim -> FIRST instance: ['a'],['c','b','a'] + signalled = 1816; + let abcba: [5]u8; + abcba[0] = 97u8; abcba[1] = 98u8; abcba[2] = 99u8; + abcba[3] = 98u8; abcba[4] = 97u8; + let ecba: [3]u8; ecba[0] = 99u8; ecba[1] = 98u8; ecba[2] = 97u8; + let (b6, a6) = bytes.cut(abcba[0:5], 98u8); + if (!bytes.equal(b6, ea[0:1])) { fail(); }; + if (!bytes.equal(a6, ecba[0:3])) { fail(); }; + + // 2-byte []u8 delim present — "abc" -> ['a','b'],['c'] + signalled = 1817; + let h: [5]u8; + h[0] = 97u8; h[1] = 98u8; h[2] = 88u8; h[3] = 89u8; h[4] = 99u8; + let xy: [2]u8; xy[0] = 88u8; xy[1] = 89u8; + let eab: [2]u8; eab[0] = 97u8; eab[1] = 98u8; + let (b7, a7) = bytes.cut(h[0:5], xy[0:2]); + if (!bytes.equal(b7, eab[0:2])) { fail(); }; + if (!bytes.equal(a7, ec[0:1])) { fail(); }; + + // 2-byte []u8 delim absent -> (whole, []) + signalled = 1818; + let zz: [2]u8; zz[0] = 9u8; zz[1] = 9u8; + let (b8, a8) = bytes.cut(h[0:5], zz[0:2]); + if (!bytes.equal(b8, h[0:5])) { fail(); }; + if (!bytes.equal(a8, z[0:0])) { fail(); }; +}; + +@test fn rcut_cases() void = { + let z: [1]u8; + let ea: [1]u8; ea[0] = 97u8; + + // ['a','b','c'] along 'b' -> ['a'],['c'] (single instance == cut) + signalled = 1820; + let abc: [3]u8; abc[0] = 97u8; abc[1] = 98u8; abc[2] = 99u8; + let ec: [1]u8; ec[0] = 99u8; + let (b0, a0) = bytes.rcut(abc[0:3], 98u8); + if (!bytes.equal(b0, ea[0:1])) { fail(); }; + if (!bytes.equal(a0, ec[0:1])) { fail(); }; + + // repeated delim -> LAST instance: ['a','b','c'],['a'] + signalled = 1821; + let abcba: [5]u8; + abcba[0] = 97u8; abcba[1] = 98u8; abcba[2] = 99u8; + abcba[3] = 98u8; abcba[4] = 97u8; + let eabc: [3]u8; eabc[0] = 97u8; eabc[1] = 98u8; eabc[2] = 99u8; + let (b1, a1) = bytes.rcut(abcba[0:5], 98u8); + if (!bytes.equal(b1, eabc[0:3])) { fail(); }; + if (!bytes.equal(a1, ea[0:1])) { fail(); }; + + // delim absent -> (whole input, []) + signalled = 1822; + let (b2, a2) = bytes.rcut(abc[0:3], 120u8); + if (!bytes.equal(b2, abc[0:3])) { fail(); }; + if (!bytes.equal(a2, z[0:0])) { fail(); }; + + // 2-byte []u8 delim present, two instances -> cut at LAST. + // "ab" -> ['X','Y','a'],['b'] + signalled = 1823; + let h: [6]u8; + h[0] = 88u8; h[1] = 89u8; h[2] = 97u8; + h[3] = 88u8; h[4] = 89u8; h[5] = 98u8; + let xy: [2]u8; xy[0] = 88u8; xy[1] = 89u8; + let exya: [3]u8; exya[0] = 88u8; exya[1] = 89u8; exya[2] = 97u8; + let eb: [1]u8; eb[0] = 98u8; + let (b3, a3) = bytes.rcut(h[0:6], xy[0:2]); + if (!bytes.equal(b3, exya[0:3])) { fail(); }; + if (!bytes.equal(a3, eb[0:1])) { fail(); }; + + // 2-byte []u8 delim absent -> (whole, []) + signalled = 1824; + let zz: [2]u8; zz[0] = 9u8; zz[1] = 9u8; + let (b4, a4) = bytes.rcut(h[0:6], zz[0:2]); + if (!bytes.equal(b4, h[0:6])) { fail(); }; + if (!bytes.equal(a4, z[0:0])) { fail(); }; +}; + export fn main() i32 = { signalled = 1; equal_cases(); signalled = 2; index_byte_cases(); @@ -766,5 +894,7 @@ export fn main() i32 = { signalled = 16; ltrim_cases(); signalled = 17; rtrim_cases(); signalled = 18; trim_cases(); + signalled = 19; cut_cases(); + signalled = 20; rcut_cases(); return 0; }; diff --git a/selfhost/cmd/w6a/main.combined.ww b/selfhost/cmd/w6a/main.combined.ww index 2d283a5f..6edcbb8a 100644 --- a/selfhost/cmd/w6a/main.combined.ww +++ b/selfhost/cmd/w6a/main.combined.ww @@ -1375,6 +1375,60 @@ export fn split(in: []u8, delim: []u8) [][]u8 = { return splitn(in, delim, types.I32_MAX); }; +// cut — split `in` along the first instance of `delim`, returning the +// portion before and the portion after the delimiter as a borrowed +// tuple. When `delim` is absent, the whole input is the first half and +// the second is empty. ref/hare/bytes/tokenize.ha:392. +// +// Delim is spelled (u8 | []u8) to match index/rindex (bytes.ww:57/91); +// the tagged union is an unordered set, so this is the same type as +// Hare's ([]u8 | u8), not a divergence. +export fn cut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = { + let ln: i32 = match (delim) { + case let c: u8 => yield 1i32; + case let sub: []u8 => { + os.assert(sub.len > 0, + "bytes.cut called with empty delimiter"); + yield sub.len; + }; + }; + match (index(in, delim)) { + case let i: i32 => { + let lo: i32 = i + ln; + return (in[0:i], in[lo:in.len]); + }; + case void => { + let empty: []u8; + empty.ptr = nil; empty.len = 0; empty.cap = 0; + return (in, empty); + }; + }; +}; + +// rcut — like [[cut]] but splits along the last instance of `delim`. +// ref/hare/bytes/tokenize.ha:413. +export fn rcut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = { + let ln: i32 = match (delim) { + case let c: u8 => yield 1i32; + case let sub: []u8 => { + os.assert(sub.len > 0, + "bytes.rcut called with empty delimiter"); + yield sub.len; + }; + }; + match (rindex(in, delim)) { + case let i: i32 => { + let lo: i32 = i + ln; + return (in[0:i], in[lo:in.len]); + }; + case void => { + let empty: []u8; + empty.ptr = nil; empty.len = 0; empty.cap = 0; + return (in, empty); + }; + }; +}; + // encoding/utf8 — UTF-8 encode/decode. Hare port; see // ref/hare/encoding/utf8/{types,rune,encode,decode,decodetable}.ha. // diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 54193988..44ff7b72 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -1375,6 +1375,60 @@ export fn split(in: []u8, delim: []u8) [][]u8 = { return splitn(in, delim, types.I32_MAX); }; +// cut — split `in` along the first instance of `delim`, returning the +// portion before and the portion after the delimiter as a borrowed +// tuple. When `delim` is absent, the whole input is the first half and +// the second is empty. ref/hare/bytes/tokenize.ha:392. +// +// Delim is spelled (u8 | []u8) to match index/rindex (bytes.ww:57/91); +// the tagged union is an unordered set, so this is the same type as +// Hare's ([]u8 | u8), not a divergence. +export fn cut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = { + let ln: i32 = match (delim) { + case let c: u8 => yield 1i32; + case let sub: []u8 => { + os.assert(sub.len > 0, + "bytes.cut called with empty delimiter"); + yield sub.len; + }; + }; + match (index(in, delim)) { + case let i: i32 => { + let lo: i32 = i + ln; + return (in[0:i], in[lo:in.len]); + }; + case void => { + let empty: []u8; + empty.ptr = nil; empty.len = 0; empty.cap = 0; + return (in, empty); + }; + }; +}; + +// rcut — like [[cut]] but splits along the last instance of `delim`. +// ref/hare/bytes/tokenize.ha:413. +export fn rcut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = { + let ln: i32 = match (delim) { + case let c: u8 => yield 1i32; + case let sub: []u8 => { + os.assert(sub.len > 0, + "bytes.rcut called with empty delimiter"); + yield sub.len; + }; + }; + match (rindex(in, delim)) { + case let i: i32 => { + let lo: i32 = i + ln; + return (in[0:i], in[lo:in.len]); + }; + case void => { + let empty: []u8; + empty.ptr = nil; empty.len = 0; empty.cap = 0; + return (in, empty); + }; + }; +}; + // encoding/utf8 — UTF-8 encode/decode. Hare port; see // ref/hare/encoding/utf8/{types,rune,encode,decode,decodetable}.ha. // diff --git a/selfhost/cmd/w6l/main.combined.ww b/selfhost/cmd/w6l/main.combined.ww index 068a7da7..f6dfe10b 100644 --- a/selfhost/cmd/w6l/main.combined.ww +++ b/selfhost/cmd/w6l/main.combined.ww @@ -1484,6 +1484,60 @@ export fn split(in: []u8, delim: []u8) [][]u8 = { return splitn(in, delim, types.I32_MAX); }; +// cut — split `in` along the first instance of `delim`, returning the +// portion before and the portion after the delimiter as a borrowed +// tuple. When `delim` is absent, the whole input is the first half and +// the second is empty. ref/hare/bytes/tokenize.ha:392. +// +// Delim is spelled (u8 | []u8) to match index/rindex (bytes.ww:57/91); +// the tagged union is an unordered set, so this is the same type as +// Hare's ([]u8 | u8), not a divergence. +export fn cut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = { + let ln: i32 = match (delim) { + case let c: u8 => yield 1i32; + case let sub: []u8 => { + os.assert(sub.len > 0, + "bytes.cut called with empty delimiter"); + yield sub.len; + }; + }; + match (index(in, delim)) { + case let i: i32 => { + let lo: i32 = i + ln; + return (in[0:i], in[lo:in.len]); + }; + case void => { + let empty: []u8; + empty.ptr = nil; empty.len = 0; empty.cap = 0; + return (in, empty); + }; + }; +}; + +// rcut — like [[cut]] but splits along the last instance of `delim`. +// ref/hare/bytes/tokenize.ha:413. +export fn rcut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = { + let ln: i32 = match (delim) { + case let c: u8 => yield 1i32; + case let sub: []u8 => { + os.assert(sub.len > 0, + "bytes.rcut called with empty delimiter"); + yield sub.len; + }; + }; + match (rindex(in, delim)) { + case let i: i32 => { + let lo: i32 = i + ln; + return (in[0:i], in[lo:in.len]); + }; + case void => { + let empty: []u8; + empty.ptr = nil; empty.len = 0; empty.cap = 0; + return (in, empty); + }; + }; +}; + // encoding/utf8 — UTF-8 encode/decode. Hare port; see // ref/hare/encoding/utf8/{types,rune,encode,decode,decodetable}.ha. // diff --git a/selfhost/cmd/ww/main.combined.ww b/selfhost/cmd/ww/main.combined.ww index cbfc2f51..ec237884 100644 --- a/selfhost/cmd/ww/main.combined.ww +++ b/selfhost/cmd/ww/main.combined.ww @@ -1375,6 +1375,60 @@ export fn split(in: []u8, delim: []u8) [][]u8 = { return splitn(in, delim, types.I32_MAX); }; +// cut — split `in` along the first instance of `delim`, returning the +// portion before and the portion after the delimiter as a borrowed +// tuple. When `delim` is absent, the whole input is the first half and +// the second is empty. ref/hare/bytes/tokenize.ha:392. +// +// Delim is spelled (u8 | []u8) to match index/rindex (bytes.ww:57/91); +// the tagged union is an unordered set, so this is the same type as +// Hare's ([]u8 | u8), not a divergence. +export fn cut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = { + let ln: i32 = match (delim) { + case let c: u8 => yield 1i32; + case let sub: []u8 => { + os.assert(sub.len > 0, + "bytes.cut called with empty delimiter"); + yield sub.len; + }; + }; + match (index(in, delim)) { + case let i: i32 => { + let lo: i32 = i + ln; + return (in[0:i], in[lo:in.len]); + }; + case void => { + let empty: []u8; + empty.ptr = nil; empty.len = 0; empty.cap = 0; + return (in, empty); + }; + }; +}; + +// rcut — like [[cut]] but splits along the last instance of `delim`. +// ref/hare/bytes/tokenize.ha:413. +export fn rcut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = { + let ln: i32 = match (delim) { + case let c: u8 => yield 1i32; + case let sub: []u8 => { + os.assert(sub.len > 0, + "bytes.rcut called with empty delimiter"); + yield sub.len; + }; + }; + match (rindex(in, delim)) { + case let i: i32 => { + let lo: i32 = i + ln; + return (in[0:i], in[lo:in.len]); + }; + case void => { + let empty: []u8; + empty.ptr = nil; empty.len = 0; empty.cap = 0; + return (in, empty); + }; + }; +}; + // encoding/utf8 — UTF-8 encode/decode. Hare port; see // ref/hare/encoding/utf8/{types,rune,encode,decode,decodetable}.ha. // diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 9ca8cb5f..e39a37cb 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -2869,6 +2869,60 @@ export fn split(in: []u8, delim: []u8) [][]u8 = { return splitn(in, delim, types.I32_MAX); }; +// cut — split `in` along the first instance of `delim`, returning the +// portion before and the portion after the delimiter as a borrowed +// tuple. When `delim` is absent, the whole input is the first half and +// the second is empty. ref/hare/bytes/tokenize.ha:392. +// +// Delim is spelled (u8 | []u8) to match index/rindex (bytes.ww:57/91); +// the tagged union is an unordered set, so this is the same type as +// Hare's ([]u8 | u8), not a divergence. +export fn cut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = { + let ln: i32 = match (delim) { + case let c: u8 => yield 1i32; + case let sub: []u8 => { + os.assert(sub.len > 0, + "bytes.cut called with empty delimiter"); + yield sub.len; + }; + }; + match (index(in, delim)) { + case let i: i32 => { + let lo: i32 = i + ln; + return (in[0:i], in[lo:in.len]); + }; + case void => { + let empty: []u8; + empty.ptr = nil; empty.len = 0; empty.cap = 0; + return (in, empty); + }; + }; +}; + +// rcut — like [[cut]] but splits along the last instance of `delim`. +// ref/hare/bytes/tokenize.ha:413. +export fn rcut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = { + let ln: i32 = match (delim) { + case let c: u8 => yield 1i32; + case let sub: []u8 => { + os.assert(sub.len > 0, + "bytes.rcut called with empty delimiter"); + yield sub.len; + }; + }; + match (rindex(in, delim)) { + case let i: i32 => { + let lo: i32 = i + ln; + return (in[0:i], in[lo:in.len]); + }; + case void => { + let empty: []u8; + empty.ptr = nil; empty.len = 0; empty.cap = 0; + return (in, empty); + }; + }; +}; + // encoding/utf8 — UTF-8 encode/decode. Hare port; see // ref/hare/encoding/utf8/{types,rune,encode,decode,decodetable}.ha. // diff --git a/selfhost/test/smoke.combined.ww b/selfhost/test/smoke.combined.ww index 62f1599e..6721b053 100644 --- a/selfhost/test/smoke.combined.ww +++ b/selfhost/test/smoke.combined.ww @@ -2869,6 +2869,60 @@ export fn split(in: []u8, delim: []u8) [][]u8 = { return splitn(in, delim, types.I32_MAX); }; +// cut — split `in` along the first instance of `delim`, returning the +// portion before and the portion after the delimiter as a borrowed +// tuple. When `delim` is absent, the whole input is the first half and +// the second is empty. ref/hare/bytes/tokenize.ha:392. +// +// Delim is spelled (u8 | []u8) to match index/rindex (bytes.ww:57/91); +// the tagged union is an unordered set, so this is the same type as +// Hare's ([]u8 | u8), not a divergence. +export fn cut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = { + let ln: i32 = match (delim) { + case let c: u8 => yield 1i32; + case let sub: []u8 => { + os.assert(sub.len > 0, + "bytes.cut called with empty delimiter"); + yield sub.len; + }; + }; + match (index(in, delim)) { + case let i: i32 => { + let lo: i32 = i + ln; + return (in[0:i], in[lo:in.len]); + }; + case void => { + let empty: []u8; + empty.ptr = nil; empty.len = 0; empty.cap = 0; + return (in, empty); + }; + }; +}; + +// rcut — like [[cut]] but splits along the last instance of `delim`. +// ref/hare/bytes/tokenize.ha:413. +export fn rcut(in: []u8, delim: (u8 | []u8)) ([]u8, []u8) = { + let ln: i32 = match (delim) { + case let c: u8 => yield 1i32; + case let sub: []u8 => { + os.assert(sub.len > 0, + "bytes.rcut called with empty delimiter"); + yield sub.len; + }; + }; + match (rindex(in, delim)) { + case let i: i32 => { + let lo: i32 = i + ln; + return (in[0:i], in[lo:in.len]); + }; + case void => { + let empty: []u8; + empty.ptr = nil; empty.len = 0; empty.cap = 0; + return (in, empty); + }; + }; +}; + // encoding/utf8 — UTF-8 encode/decode. Hare port; see // ref/hare/encoding/utf8/{types,rune,encode,decode,decodetable}.ha. //