about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--tvix/eval/src/builtins/mod.rs47
1 files changed, 47 insertions, 0 deletions
diff --git a/tvix/eval/src/builtins/mod.rs b/tvix/eval/src/builtins/mod.rs
index 41fa4d4d9992..3ceafa498fcd 100644
--- a/tvix/eval/src/builtins/mod.rs
+++ b/tvix/eval/src/builtins/mod.rs
@@ -54,6 +54,8 @@ pub fn coerce_value_to_path(v: &Value, vm: &mut VM) -> Result<PathBuf, ErrorKind
 
 #[builtins]
 mod pure_builtins {
+    use std::collections::VecDeque;
+
     use super::*;
 
     #[builtin("abort")]
@@ -343,6 +345,51 @@ mod pure_builtins {
         json.try_into()
     }
 
+    #[builtin("genericClosure")]
+    fn builtin_generic_closure(vm: &mut VM, input: Value) -> Result<Value, ErrorKind> {
+        let attrs = input.to_attrs()?;
+
+        // The work set is maintained as a VecDeque because new items
+        // are popped from the front.
+        let mut work_set: VecDeque<Value> = attrs
+            .select_required("startSet")?
+            .force(vm)?
+            .to_list()?
+            .into_iter()
+            .collect();
+
+        let operator = attrs.select_required("operator")?;
+
+        let mut res = NixList::new();
+        let mut done_keys: Vec<Value> = vec![];
+
+        let mut insert_key = |k: Value, vm: &mut VM| -> Result<bool, ErrorKind> {
+            for existing in &done_keys {
+                if existing.nix_eq(&k, vm)? {
+                    return Ok(false);
+                }
+            }
+            done_keys.push(k);
+            Ok(true)
+        };
+
+        while let Some(val) = work_set.pop_front() {
+            let attrs = val.force(vm)?.to_attrs()?;
+            let key = attrs.select_required("key")?;
+
+            if !insert_key(key.clone(), vm)? {
+                continue;
+            }
+
+            res.push(val.clone());
+
+            let op_result = vm.call_with(operator, Some(val))?.force(vm)?.to_list()?;
+            work_set.extend(op_result.into_iter());
+        }
+
+        Ok(Value::List(res))
+    }
+
     #[builtin("genList")]
     fn builtin_gen_list(vm: &mut VM, generator: Value, length: Value) -> Result<Value, ErrorKind> {
         let len = length.as_int()?;