mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-27 05:49:08 +00:00
Str.splitFirst and Str.splitLast
This commit is contained in:
parent
be3800d7fa
commit
eeb271d07f
7 changed files with 135 additions and 3 deletions
|
@ -156,6 +156,7 @@ comptime {
|
||||||
exportStrFn(str.strFromFloatC, "from_float");
|
exportStrFn(str.strFromFloatC, "from_float");
|
||||||
exportStrFn(str.strEqual, "equal");
|
exportStrFn(str.strEqual, "equal");
|
||||||
exportStrFn(str.substringUnsafe, "substring_unsafe");
|
exportStrFn(str.substringUnsafe, "substring_unsafe");
|
||||||
|
exportStrFn(str.getUnsafe, "get_unsafe");
|
||||||
exportStrFn(str.strToUtf8C, "to_utf8");
|
exportStrFn(str.strToUtf8C, "to_utf8");
|
||||||
exportStrFn(str.fromUtf8C, "from_utf8");
|
exportStrFn(str.fromUtf8C, "from_utf8");
|
||||||
exportStrFn(str.fromUtf8RangeC, "from_utf8_range");
|
exportStrFn(str.fromUtf8RangeC, "from_utf8_range");
|
||||||
|
|
|
@ -1194,6 +1194,10 @@ pub fn substringUnsafe(string: RocStr, start: usize, length: usize) callconv(.C)
|
||||||
return RocStr.fromSlice(slice);
|
return RocStr.fromSlice(slice);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn getUnsafe(string: RocStr, index: usize) callconv(.C) u8 {
|
||||||
|
return string.getUnchecked(index);
|
||||||
|
}
|
||||||
|
|
||||||
test "substringUnsafe: start" {
|
test "substringUnsafe: start" {
|
||||||
const str = RocStr.fromSlice("abcdef");
|
const str = RocStr.fromSlice("abcdef");
|
||||||
defer str.deinit();
|
defer str.deinit();
|
||||||
|
|
|
@ -33,6 +33,8 @@ interface Str
|
||||||
toU8,
|
toU8,
|
||||||
toI8,
|
toI8,
|
||||||
toScalars,
|
toScalars,
|
||||||
|
splitFirst,
|
||||||
|
splitLast,
|
||||||
]
|
]
|
||||||
imports [Bool.{ Bool }, Result.{ Result }]
|
imports [Bool.{ Bool }, Result.{ Result }]
|
||||||
|
|
||||||
|
@ -230,3 +232,94 @@ countBytes : Str -> Nat
|
||||||
|
|
||||||
## string slice that does not do bounds checking or utf-8 verification
|
## string slice that does not do bounds checking or utf-8 verification
|
||||||
substringUnsafe : Str, Nat, Nat -> Str
|
substringUnsafe : Str, Nat, Nat -> Str
|
||||||
|
|
||||||
|
## Returns the string before the first occurrence of a delimiter, as well as the
|
||||||
|
## rest of the string after that occurrence. If the delimiter is not found, returns `Err`.
|
||||||
|
##
|
||||||
|
## Str.splitFirst "foo/bar/baz" "/" == Ok { before: "foo", after: "bar/baz" }
|
||||||
|
splitFirst : Str, Str -> Result { before : Str, after : Str } [NotFound]*
|
||||||
|
splitFirst = \haystack, needle ->
|
||||||
|
when firstMatch haystack needle is
|
||||||
|
Some index ->
|
||||||
|
remaining = Str.countBytes haystack - Str.countBytes needle - index
|
||||||
|
|
||||||
|
before = Str.substringUnsafe haystack 0 index
|
||||||
|
after = Str.substringUnsafe haystack (index + Str.countBytes needle) remaining
|
||||||
|
|
||||||
|
Ok { before, after }
|
||||||
|
None ->
|
||||||
|
Err NotFound
|
||||||
|
|
||||||
|
firstMatch : Str, Str -> [Some Nat, None]
|
||||||
|
firstMatch = \haystack, needle ->
|
||||||
|
haystackLength = Str.countBytes haystack
|
||||||
|
needleLength = Str.countBytes needle
|
||||||
|
lastPossible = Num.subSaturated haystackLength needleLength
|
||||||
|
|
||||||
|
firstMatchHelp haystack needle 0 lastPossible
|
||||||
|
|
||||||
|
firstMatchHelp : Str, Str, Nat, Nat -> [Some Nat, None]
|
||||||
|
firstMatchHelp = \haystack, needle, index, lastPossible ->
|
||||||
|
if index < lastPossible then
|
||||||
|
if matchesAt haystack index needle then
|
||||||
|
Some index
|
||||||
|
else
|
||||||
|
firstMatchHelp haystack needle (index + 1) lastPossible
|
||||||
|
else
|
||||||
|
None
|
||||||
|
|
||||||
|
## Returns the string before the last occurrence of a delimiter, as well as the
|
||||||
|
## rest of the string after that occurrence. If the delimiter is not found, returns `Err`.
|
||||||
|
##
|
||||||
|
## Str.splitLast "foo/bar/baz" "/" == Ok { before: "foo/bar", after: "baz" }
|
||||||
|
splitLast : Str, Str -> Result { before : Str, after : Str } [NotFound]*
|
||||||
|
splitLast = \haystack, needle ->
|
||||||
|
when lastMatch haystack needle is
|
||||||
|
Some index ->
|
||||||
|
remaining = Str.countBytes haystack - Str.countBytes needle - index
|
||||||
|
|
||||||
|
before = Str.substringUnsafe haystack 0 index
|
||||||
|
after = Str.substringUnsafe haystack (index + Str.countBytes needle) remaining
|
||||||
|
|
||||||
|
Ok { before, after }
|
||||||
|
None ->
|
||||||
|
Err NotFound
|
||||||
|
|
||||||
|
lastMatch : Str, Str -> [Some Nat, None]
|
||||||
|
lastMatch = \haystack, needle ->
|
||||||
|
haystackLength = Str.countBytes haystack
|
||||||
|
needleLength = Str.countBytes needle
|
||||||
|
lastPossibleIndex = Num.subSaturated haystackLength (needleLength + 1)
|
||||||
|
|
||||||
|
lastMatchHelp haystack needle lastPossibleIndex
|
||||||
|
|
||||||
|
lastMatchHelp : Str, Str, Nat -> [Some Nat, None]
|
||||||
|
lastMatchHelp = \haystack, needle, index ->
|
||||||
|
if matchesAt haystack index needle then
|
||||||
|
Some index
|
||||||
|
else
|
||||||
|
when Num.subChecked index 1 is
|
||||||
|
Ok nextIndex ->
|
||||||
|
lastMatchHelp haystack needle nextIndex
|
||||||
|
Err _ ->
|
||||||
|
None
|
||||||
|
|
||||||
|
min = \x, y -> if x < y then x else y
|
||||||
|
|
||||||
|
matchesAt : Str, Nat, Str -> Bool
|
||||||
|
matchesAt = \haystack, haystackIndex, needle ->
|
||||||
|
haystackLength = Str.countBytes haystack
|
||||||
|
needleLength = Str.countBytes needle
|
||||||
|
endIndex = min (haystackIndex + needleLength) haystackLength
|
||||||
|
|
||||||
|
matchesAtHelp haystack haystackIndex needle 0 endIndex
|
||||||
|
|
||||||
|
matchesAtHelp : Str, Nat, Str, Nat, Nat -> Bool
|
||||||
|
matchesAtHelp = \haystack, haystackIndex, needle, needleIndex, endIndex ->
|
||||||
|
if haystackIndex < endIndex then
|
||||||
|
if Str.getUnsafe haystack haystackIndex == Str.getUnsafe needle needleIndex then
|
||||||
|
matchesAtHelp haystack (haystackIndex + 1) needle (needleIndex + 1) endIndex
|
||||||
|
else
|
||||||
|
False
|
||||||
|
else
|
||||||
|
True
|
||||||
|
|
|
@ -1734,7 +1734,7 @@ fn str_count_bytes(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
|
|
||||||
/// Str.substringUnsafe : Str, Nat, Nat -> Nat
|
/// Str.substringUnsafe : Str, Nat, Nat -> Nat
|
||||||
fn str_substring_unsafe(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
fn str_substring_unsafe(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
lowlevel_1(symbol, LowLevel::StrSubstringUnsafe, var_store)
|
lowlevel_3(symbol, LowLevel::StrSubstringUnsafe, var_store)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Str.fromUtf8 : List U8 -> Result Str [BadUtf8 { byteIndex : Nat, problem : Utf8Problem } }]*
|
/// Str.fromUtf8 : List U8 -> Result Str [BadUtf8 { byteIndex : Nat, problem : Utf8Problem } }]*
|
||||||
|
|
|
@ -1193,6 +1193,8 @@ define_builtins! {
|
||||||
35 STR_GET_UNSAFE: "getUnsafe"
|
35 STR_GET_UNSAFE: "getUnsafe"
|
||||||
36 STR_COUNT_BYTES: "countBytes"
|
36 STR_COUNT_BYTES: "countBytes"
|
||||||
37 STR_SUBSTRING_UNSAFE: "substringUnsafe"
|
37 STR_SUBSTRING_UNSAFE: "substringUnsafe"
|
||||||
|
38 STR_SPLIT_FIRST: "splitFirst"
|
||||||
|
39 STR_SPLIT_LAST: "splitLast"
|
||||||
}
|
}
|
||||||
5 LIST: "List" => {
|
5 LIST: "List" => {
|
||||||
0 LIST_LIST: "List" imported // the List.List type alias
|
0 LIST_LIST: "List" imported // the List.List type alias
|
||||||
|
|
|
@ -895,7 +895,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
|
||||||
StrGetUnsafe | ListGetUnsafe => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
StrGetUnsafe | ListGetUnsafe => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
||||||
ListConcat => arena.alloc_slice_copy(&[owned, owned]),
|
ListConcat => arena.alloc_slice_copy(&[owned, owned]),
|
||||||
StrConcat => arena.alloc_slice_copy(&[owned, borrowed]),
|
StrConcat => arena.alloc_slice_copy(&[owned, borrowed]),
|
||||||
StrSubstringUnsafe => arena.alloc_slice_copy(&[owned]),
|
StrSubstringUnsafe => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
|
||||||
StrTrim => arena.alloc_slice_copy(&[owned]),
|
StrTrim => arena.alloc_slice_copy(&[owned]),
|
||||||
StrTrimLeft => arena.alloc_slice_copy(&[owned]),
|
StrTrimLeft => arena.alloc_slice_copy(&[owned]),
|
||||||
StrTrimRight => arena.alloc_slice_copy(&[owned]),
|
StrTrimRight => arena.alloc_slice_copy(&[owned]),
|
||||||
|
|
|
@ -1677,7 +1677,7 @@ fn to_scalar_3_byte() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn to_scalar_4_byte() {
|
fn to_scalar_4_byte() {
|
||||||
// from https://design215.com/toolbox/utf8-4byte-characters.php
|
// from https://design215.com/toolbox/utf8-4byte-characters.php
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
|
@ -1700,3 +1700,35 @@ fn to_scalar_4_byte() {
|
||||||
RocList<u32>
|
RocList<u32>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
|
fn str_split_first() {
|
||||||
|
// Str.splitFirst "foo/bar/baz" "/" == Ok { before: "foo", after: "bar/baz" }
|
||||||
|
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
Str.splitFirst "foo/bar/baz" "/"
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
RocResult::ok((RocStr::from("bar/baz"), RocStr::from("foo"))),
|
||||||
|
RocResult<(RocStr, RocStr), ()>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
|
fn str_split_last() {
|
||||||
|
// Str.splitFirst "foo/bar/baz" "/" == Ok { before: "foo", after: "bar/baz" }
|
||||||
|
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
Str.splitLast"foo/bar/baz" "/"
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
RocResult::ok((RocStr::from("baz"), RocStr::from("foo/bar"))),
|
||||||
|
RocResult<(RocStr, RocStr), ()>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue