Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 36 additions & 19 deletions ndc_stdlib/src/sequence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -585,22 +585,31 @@ mod inner {
))
}

/// Returns all prefixes of a sequence, each as a list.
fn string_prefixes(text: &str) -> Value {
Value::list(
text.chars()
.scan(String::new(), |acc, c| {
acc.push(c);
Some(Value::string(acc.clone()))
})
.collect(),
)
}

/// Returns all prefixes of the string as a list of strings.
#[function(name = "prefixes", return_type = Vec<String>)]
pub fn prefixes_string(text: &str) -> Value {
string_prefixes(text)
}

/// Returns all prefixes of a sequence, each as a list; for strings, returns string prefixes.
#[function(return_type = Vec<_>)]
pub fn prefixes(seq: SeqValue) -> anyhow::Result<Value> {
// Special case for String — produce string prefixes instead of lists of chars.
// A sequence-typed caller can still supply a string.
if let Value::Object(ref obj) = seq
&& let Object::String(s) = obj.as_ref()
{
return Ok(Value::list(
s.borrow()
.chars()
.scan(String::new(), |acc, c| {
acc.push(c);
Some(Value::string(acc.clone()))
})
.collect(),
));
return Ok(string_prefixes(&s.borrow()));
}
Ok(Value::list(
seq.try_into_iter()
Expand All @@ -613,20 +622,28 @@ mod inner {
))
}

fn string_suffixes(text: &str) -> Value {
Value::list(
text.char_indices()
.map(|(i, _)| Value::string(text[i..].to_string()))
.collect(),
)
}

/// Returns all suffixes of the string as a list of strings.
#[function(name = "suffixes", return_type = Vec<String>)]
pub fn suffixes_string(text: &str) -> Value {
string_suffixes(text)
}

/// Returns all suffixes of a sequence, each as a list; for strings, returns all trailing substrings.
#[function(return_type = Vec<_>)]
pub fn suffixes(seq: SeqValue) -> anyhow::Result<Value> {
// Special case for String — produce string suffixes instead of lists of chars.
// A sequence-typed caller can still supply a string.
if let Value::Object(ref obj) = seq
&& let Object::String(s) = obj.as_ref()
{
let borrowed = s.borrow();
return Ok(Value::list(
borrowed
.char_indices()
.map(|(i, _)| Value::string(borrowed[i..].to_string()))
.collect(),
));
return Ok(string_suffixes(&s.borrow()));
}
let out: Vec<Value> = seq
.try_into_iter()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// These annotations require the String overloads to infer List<String>.
let prefixes: List<String> = "aé🦀".prefixes();
let suffixes: List<String> = suffixes("aé🦀");
assert_eq(prefixes, ["a", "aé", "aé🦀"]);
assert_eq(suffixes, ["aé🦀", "é🦀", "🦀"]);

let empty_prefixes: List<String> = "".prefixes();
let empty_suffixes: List<String> = "".suffixes();
assert_eq(empty_prefixes, []);
assert_eq(empty_suffixes, []);

// Prefixes and suffixes still include the entire input, but no empty string.
assert_eq("x".prefixes(), ["x"]);
assert_eq("x".suffixes(), ["x"]);
assert_eq("abc".prefixes(), ["a", "ab", "abc"]);
assert_eq("abc".suffixes(), ["abc", "bc", "c"]);
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// The general overload must retain string behavior when the concrete kind is hidden.
let text: Sequence<String> = "aé🦀";
assert_eq(text.prefixes(), ["a", "aé", "aé🦀"]);
assert_eq(text.suffixes(), ["aé🦀", "é🦀", "🦀"]);
let empty_text: Sequence<Any> = "";
assert_eq(empty_text.prefixes(), []);
assert_eq(empty_text.suffixes(), []);

// Untyped parameters exercise runtime overload dispatch for both kinds.
fn prefixes_of(value) => value.prefixes();
fn suffixes_of(value) => value.suffixes();
assert_eq(prefixes_of("ab"), ["a", "ab"]);
assert_eq(suffixes_of("ab"), ["ab", "b"]);
assert_eq(prefixes_of([1, 2]), [[1], [1, 2]]);
assert_eq(suffixes_of([1, 2]), [[1, 2], [2]]);

// Other sequences continue producing lists of lists.
assert_eq([1, 2, 3].prefixes(), [[1], [1, 2], [1, 2, 3]]);
assert_eq([1, 2, 3].suffixes(), [[1, 2, 3], [2, 3], [3]]);
assert_eq((1, "a").prefixes(), [[1], [1, "a"]]);
assert_eq((1, "a").suffixes(), [[1, "a"], ["a"]]);
assert_eq((1..4).prefixes(), [[1], [1, 2], [1, 2, 3]]);
assert_eq((1..4).suffixes(), [[1, 2, 3], [2, 3], [3]]);
assert_eq([].prefixes(), []);
assert_eq([].suffixes(), []);
Loading