about summary refs log tree commit diff
path: root/tvix
diff options
context:
space:
mode:
authorVincent Ambo <mail@tazj.in>2022-09-20T20·47+0300
committertazjin <tazjin@tvl.su>2022-09-20T23·37+0000
commit876c4772563e8129e069f1d107b2a21378609ace (patch)
treed134db45d8a05b2579d725683afd1526f95cbc91 /tvix
parent82079776486870e6c8e6e6e6c9b56b9e91e0a181 (diff)
feat(tvix/eval): implement builtins.map r/4941
As we already have a VM passed to the builtins, we can simply execute
the provided closure/lambda in it for each value.

The primary annoyance with this is that we have to clone the upvalues
for each element, but we can try making this cheaper in the
future (it's also a general problem in the VM itself).

Change-Id: I5bcf56d58c509c0eb081e7cf52f6093216451ce4
Reviewed-on: https://cl.tvl.fyi/c/depot/+/6714
Tested-by: BuildkiteCI
Reviewed-by: sterni <sternenseemann@systemli.org>
Diffstat (limited to 'tvix')
-rw-r--r--tvix/eval/src/builtins/mod.rs18
-rw-r--r--tvix/eval/src/errors.rs9
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map.exp1
-rw-r--r--tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map.nix16
4 files changed, 43 insertions, 1 deletions
diff --git a/tvix/eval/src/builtins/mod.rs b/tvix/eval/src/builtins/mod.rs
index dfc1986141..598c8aa08e 100644
--- a/tvix/eval/src/builtins/mod.rs
+++ b/tvix/eval/src/builtins/mod.rs
@@ -12,7 +12,8 @@ use std::{
 
 use crate::{
     errors::ErrorKind,
-    value::{Builtin, CoercionKind, NixAttrs, NixList, NixString, Value},
+    upvalues::UpvalueCarrier,
+    value::{Builtin, Closure, CoercionKind, NixAttrs, NixList, NixString, Value},
     vm::VM,
 };
 
@@ -144,6 +145,21 @@ fn pure_builtins() -> Vec<Builtin> {
         Builtin::new("length", &[true], |args, _| {
             Ok(Value::Integer(args[0].to_list()?.len() as i64))
         }),
+        Builtin::new("map", &[true, true], |args, vm| {
+            let list: NixList = args[1].to_list()?;
+            let func: Closure = args[0].to_closure()?;
+
+            list.into_iter()
+                .map(|val| {
+                    // Leave the argument on the stack before calling the
+                    // function.
+                    vm.push(val);
+                    vm.call(func.lambda(), func.upvalues().clone(), 1)
+                })
+                .collect::<Result<Vec<Value>, _>>()
+                .map(|list| Value::List(NixList::from(list)))
+                .map_err(Into::into)
+        }),
         Builtin::new("hasAttr", &[true, true], |args, _| {
             let k = args[0].to_str()?;
             let xs = args[1].to_attrs()?;
diff --git a/tvix/eval/src/errors.rs b/tvix/eval/src/errors.rs
index 10bc9276ad..17b236d038 100644
--- a/tvix/eval/src/errors.rs
+++ b/tvix/eval/src/errors.rs
@@ -100,6 +100,15 @@ impl From<ParseIntError> for ErrorKind {
     }
 }
 
+/// Implementation used if errors occur while forcing thunks (which
+/// can potentially be threaded through a few contexts, i.e. nested
+/// thunks).
+impl From<Error> for ErrorKind {
+    fn from(e: Error) -> Self {
+        Self::ThunkForce(Box::new(e))
+    }
+}
+
 #[derive(Clone, Debug)]
 pub struct Error {
     pub kind: ErrorKind,
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map.exp b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map.exp
new file mode 100644
index 0000000000..e1ff708002
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map.exp
@@ -0,0 +1 @@
+[ [ 1 2 3 4 5 ] [ 2 4 6 8 10 ] [ 2 4 6 8 10 ] [ 1 2 3 4 5 ] ]
diff --git a/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map.nix b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map.nix
new file mode 100644
index 0000000000..6ff42d0891
--- /dev/null
+++ b/tvix/eval/src/tests/tvix_tests/eval-okay-builtins-map.nix
@@ -0,0 +1,16 @@
+[
+  # identity function
+  (builtins.map (x: x) [ 1 2 3 4 5 ])
+
+  # double stuff
+  (builtins.map (x: x * 2) [ 1 2 3 4 5 ])
+
+  # same but with a closure this time
+  (
+    let n = 2;
+    in builtins.map (x: x * n) [ 1 2 3 4 5 ]
+  )
+
+  # from global scope
+  (map (x: x) [ 1 2 3 4 5 ])
+]