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
7 changes: 7 additions & 0 deletions ndc_stdlib/src/cmp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ mod inner {
}
}

/// Stops the program with the error `message`. It never returns, so it
/// can end a branch whose other branches give a value.
#[function(signature = "(String) -> Never")]
pub fn panic(message: &str) -> anyhow::Result<Value> {
Err(anyhow!(message.to_string()))
}

/// Produces an error if the arguments aren't equal to each other.
#[function(signature = "<A, B>(A, B)")]
pub fn assert_eq(left: Value, right: Value) -> anyhow::Result<()> {
Expand Down
39 changes: 27 additions & 12 deletions ndc_stdlib/src/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,23 @@ mod inner {
list.windows(subsequence.len()).contains(&subsequence)
}

/// Returns the starting index of the first occurrence of `subsequence` in `list`, or unit if not found.
#[function(signature = "<T>(List<T>, List<T>) -> Any")]
pub fn find_subsequence(list: &[Value], subsequence: &[Value]) -> Value {
let result = list
.windows(subsequence.len())
.enumerate()
.find(|(_, seq)| *seq == subsequence)
.map(|(idx, _)| idx);
if let Some(result) = result {
Value::Int(result as i64)
} else {
Value::unit()
/// Returns the starting index of the first occurrence of `subsequence` in `list`, or produces an error if there is none.
#[function(signature = "<T>(List<T>, List<T>) -> Int")]
pub fn find_subsequence(list: &[Value], subsequence: &[Value]) -> anyhow::Result<Value> {
subsequence_start(list, subsequence)
.map(|idx| Value::Int(idx as i64))
.ok_or_else(|| anyhow!("the list doesn't contain the subsequence"))
}

/// Returns the starting index of the first occurrence of `subsequence` in `list`, or `None` if there is none.
#[function(
name = "find_subsequence?",
signature = "<T>(List<T>, List<T>) -> Option<Int>"
)]
pub fn maybe_find_subsequence(list: &[Value], subsequence: &[Value]) -> Value {
match subsequence_start(list, subsequence) {
None => Value::None,
Some(idx) => Value::Object(Rc::new(Object::Some(Value::Int(idx as i64)))),
}
}

Expand Down Expand Up @@ -390,3 +395,13 @@ pub mod ops {
env.declare_global_fn(native);
}
}

/// Where `subsequence` first starts in `list`, if it's in it.
fn subsequence_start(list: &[Value], subsequence: &[Value]) -> Option<usize> {
// An empty subsequence starts anywhere, and `windows` can't be empty.
if subsequence.is_empty() {
return Some(0);
}
list.windows(subsequence.len())
.position(|window| window == subsequence)
}
10 changes: 5 additions & 5 deletions ndc_stdlib/src/sequence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -603,8 +603,8 @@ mod inner {
default.call(&[]).map_err(|e| anyhow!(e))
}

/// Returns the `k` sized combinations of the given sequence `seq` as a lazy iterator of tuples.
#[function(signature = "<T>(Iterable<T>, Int) -> Iterator<Any>")]
/// Returns the `k` sized combinations of the given sequence `seq` as a lazy iterator of lists.
#[function(signature = "<T>(Iterable<T>, Int) -> Iterator<List<T>>")]
pub fn combinations(seq: SeqValue, k: usize) -> anyhow::Result<Value> {
let iter = CombinationsIter::new(seq, k)
.ok_or_else(|| anyhow!("combinations requires a sequence"))?;
Expand All @@ -618,14 +618,14 @@ mod inner {
Ok(Value::iterator(iter.into_shared()))
}

/// Returns the `k` sized permutations of the given sequence `seq` as a list of tuples.
#[function(signature = "<T>(Iterable<T>, Int) -> List<Any>")]
/// Returns the `k` sized permutations of the given sequence `seq` as a list of lists.
#[function(signature = "<T>(Iterable<T>, Int) -> List<List<T>>")]
pub fn permutations(seq: SeqValue, k: usize) -> anyhow::Result<Value> {
Ok(Value::list(
seq.try_into_iter()
.ok_or_else(|| anyhow!("permutations requires a sequence"))?
.permutations(k)
.map(Value::tuple)
.map(Value::list)
.collect::<Vec<Value>>(),
))
}
Expand Down
4 changes: 2 additions & 2 deletions ndc_vm/src/iterator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ impl VmIterator for CombinationsIter {
if k == 0 {
return if self.first {
self.first = false;
Some(Value::tuple(vec![]))
Some(Value::list(vec![]))
} else {
None
};
Expand Down Expand Up @@ -504,7 +504,7 @@ impl VmIterator for CombinationsIter {
}

// All indices are in-bounds: direct indexing is safe.
Some(Value::tuple(
Some(Value::list(
self.indices
.iter()
.map(|&i| self.buffer[i].clone())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// `panic` stops the program and never returns, so it can end a branch whose
// other branch gives a value.
// expect-error: no such thing
fn pick(c) {
if c { 1 } else { panic("no such thing") }
}
let n: Int = pick(true);
print(n + pick(false));
2 changes: 1 addition & 1 deletion tests/functional/programs/008_iterators/005_take.ndc
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ assert_eq([1, 2].take(10).list(), [1, 2]);
assert_eq([1, 2, 3].take(0).list(), []);

// Chained: take from a combinations iterator.
assert_eq(combinations([1, 2, 3, 4], 2).take(3).list(), [(1, 2), (1, 3), (1, 4)]);
assert_eq(combinations([1, 2, 3, 4], 2).take(3).list(), [[1, 2], [1, 3], [1, 4]]);
8 changes: 4 additions & 4 deletions tests/functional/programs/603_stdlib_seq/011_combinations.ndc
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
// Basic 2-combinations of a list.
assert_eq(combinations([1, 2, 3], 2).list(), [(1, 2), (1, 3), (2, 3)]);
assert_eq(combinations([1, 2, 3], 2).list(), [[1, 2], [1, 3], [2, 3]]);

// k=0 yields exactly one empty tuple.
assert_eq(combinations([1, 2], 0).list(), [()]);
assert_eq(combinations([1, 2], 0).list(), [[]]);

// k > length yields nothing.
assert_eq(combinations([1, 2], 5).list(), []);

// Works on a tuple source.
assert_eq(combinations((10, 20, 30), 2).list(), [(10, 20), (10, 30), (20, 30)]);
assert_eq(combinations((10, 20, 30), 2).list(), [[10, 20], [10, 30], [20, 30]]);

// Works on a map source (order is unspecified, test length only).
assert_eq(combinations(%{"a": 1, "b": 2, "c": 3}, 2).list().len(), 3);

// Lazy: take only the first combination from a large range iterator — the pool
// is never fully materialised.
assert_eq(combinations(1..100, 2).take(1).list(), [(1, 2)]);
assert_eq(combinations(1..100, 2).take(1).list(), [[1, 2]]);
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Combinations and permutations are lists of the elements, so what they hold
// is typed.
// expect-output: [3,4,5] [3,4,3,5,4,5]
print([a + b for [a, b] in [1, 2, 3].combinations(2)], [p.sum() for p in [1, 2, 3].permutations(2)]);
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// `find_subsequence` gives where a subsequence starts, and `find_subsequence?`
// gives `None` when it isn't there.
// expect-output: 2 Some(0) None
print([1, 2, 3, 4].find_subsequence([3, 4]), [1, 2].find_subsequence?([1]), [1, 2].find_subsequence?([5]));
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,23 @@
// some entirely. Fix: pull `indices[k-1] + 1` before computing pivot.

// Eager (list) reference — correct for both pre- and post-fix.
assert_eq([1, 2, 3, 4].combinations(2).list(), [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]);
assert_eq([1, 2, 3, 4].combinations(2).list(), [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]);

// Lazy (iterator) source — this is the path the bug lived in.
assert_eq(
[1, 2, 3, 4].enumerate().combinations(2).list(),
[
((0, 1), (1, 2)),
((0, 1), (2, 3)),
((0, 1), (3, 4)),
((1, 2), (2, 3)),
((1, 2), (3, 4)),
((2, 3), (3, 4))
[(0, 1), (1, 2)],
[(0, 1), (2, 3)],
[(0, 1), (3, 4)],
[(1, 2), (2, 3)],
[(1, 2), (3, 4)],
[(2, 3), (3, 4)]
]
);

// Larger k — exercise the multi-pull-per-pivot path.
let triples = [10, 20, 30, 40, 50].enumerate().combinations(3).list();
assert_eq(triples.len, 10);
assert_eq(triples[0], ((0, 10), (1, 20), (2, 30)));
assert_eq(triples[-1], ((2, 30), (3, 40), (4, 50)));
assert_eq(triples[0], [(0, 10), (1, 20), (2, 30)]);
assert_eq(triples[-1], [(2, 30), (3, 40), (4, 50)]);
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,4 @@ assert_eq(lazy.list(), []);
assert_eq(lazy.list(), []);

// Valid combinations are still produced correctly.
assert_eq([1, 2, 3, 4].combinations(2).list(), [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]);
assert_eq([1, 2, 3, 4].combinations(2).list(), [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]]);
Loading