about summary refs log tree commit diff
diff options
context:
space:
mode:
authorVincent Ambo <mail@tazj.in>2022-08-24T08·00+0300
committertazjin <tazjin@tvl.su>2022-09-02T12·59+0000
commite0f1356ae31783664f597b33f8b69b060a9e3033 (patch)
tree54dc3a82429b2844b2c443ab9eb0911fc78f1615
parent64746388e2c81c3dac7f520c40a4c4aacb3dc376 (diff)
feat(tvix/eval): add initial representation of builtins r/4585
Builtins are represented as a Rust function pointer that accepts a
vector of arguments, which represents variable arity builtins.

Change-Id: Ibab7e662a646caf1172695d876d2f55e187c03dd
Reviewed-on: https://cl.tvl.fyi/c/depot/+/6251
Tested-by: BuildkiteCI
Reviewed-by: grfn <grfn@gws.fyi>
-rw-r--r--tvix/eval/src/value/builtin.rs66
-rw-r--r--tvix/eval/src/value/mod.rs6
-rw-r--r--tvix/eval/src/vm.rs5
3 files changed, 76 insertions, 1 deletions
diff --git a/tvix/eval/src/value/builtin.rs b/tvix/eval/src/value/builtin.rs
new file mode 100644
index 000000000000..8d7e82136125
--- /dev/null
+++ b/tvix/eval/src/value/builtin.rs
@@ -0,0 +1,66 @@
+//! This module implements the runtime representation of a Nix
+//! builtin.
+//!
+//! Builtins are directly backed by Rust code operating on Nix values.
+
+use crate::errors::EvalResult;
+
+use super::Value;
+
+use std::fmt::{Debug, Display};
+
+pub type BuiltinFn = fn(arg: Vec<Value>) -> EvalResult<Value>;
+
+/// Represents a single built-in function which directly executes Rust
+/// code that operates on a Nix value.
+///
+/// Builtins are the only functions in Nix that have varying arities
+/// (for example, `hasAttr` has an arity of 2, but `isAttrs` an arity
+/// of 1). To facilitate this generically, builtins expect to be
+/// called with a vector of Nix values corresponding to their
+/// arguments in order.
+///
+/// Partially applied builtins act similar to closures in that they
+/// "capture" the partially applied arguments, and are treated
+/// specially when printing their representation etc.
+#[derive(Clone)]
+pub struct Builtin {
+    name: &'static str,
+    arity: usize,
+    func: BuiltinFn,
+
+    // Partially applied function arguments.
+    partials: Vec<Value>,
+}
+
+impl Builtin {
+    /// Apply an additional argument to the builtin, which will either
+    /// lead to execution of the function or to returning a partial
+    /// builtin.
+    pub fn apply(mut self, arg: Value) -> EvalResult<Value> {
+        self.partials.push(arg);
+
+        if self.partials.len() == self.arity {
+            return (self.func)(self.partials);
+        }
+
+        // Function is not yet ready to be called.
+        return Ok(Value::Builtin(self));
+    }
+}
+
+impl Debug for Builtin {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "builtin[{}]", self.name)
+    }
+}
+
+impl Display for Builtin {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        if !self.partials.is_empty() {
+            f.write_str("<<primop-app>>")
+        } else {
+            f.write_str("<<primop>>")
+        }
+    }
+}
diff --git a/tvix/eval/src/value/mod.rs b/tvix/eval/src/value/mod.rs
index 2177ca26d904..bf07c5d326d0 100644
--- a/tvix/eval/src/value/mod.rs
+++ b/tvix/eval/src/value/mod.rs
@@ -4,12 +4,14 @@ use std::rc::Rc;
 use std::{fmt::Display, path::PathBuf};
 
 mod attrs;
+mod builtin;
 mod lambda;
 mod list;
 mod string;
 
 use crate::errors::{ErrorKind, EvalResult};
 pub use attrs::NixAttrs;
+pub use builtin::Builtin;
 pub use lambda::Lambda;
 pub use list::NixList;
 pub use string::NixString;
@@ -26,6 +28,7 @@ pub enum Value {
     Attrs(Rc<NixAttrs>),
     List(NixList),
     Lambda(Lambda),
+    Builtin(Builtin),
 
     // Internal values that, while they technically exist at runtime,
     // are never returned to or created directly by users.
@@ -49,7 +52,7 @@ impl Value {
             Value::Path(_) => "path",
             Value::Attrs(_) => "set",
             Value::List(_) => "list",
-            Value::Lambda(_) => "lambda",
+            Value::Lambda(_) | Value::Builtin(_) => "lambda",
 
             // Internal types
             Value::AttrPath(_) | Value::Blackhole | Value::NotFound => "internal",
@@ -128,6 +131,7 @@ impl Display for Value {
             Value::Attrs(attrs) => attrs.fmt(f),
             Value::List(list) => list.fmt(f),
             Value::Lambda(_) => f.write_str("lambda"), // TODO: print position
+            Value::Builtin(builtin) => builtin.fmt(f),
 
             // Nix prints floats with a maximum precision of 5 digits
             // only.
diff --git a/tvix/eval/src/vm.rs b/tvix/eval/src/vm.rs
index ff81349a1531..b6d5a9838c3a 100644
--- a/tvix/eval/src/vm.rs
+++ b/tvix/eval/src/vm.rs
@@ -358,6 +358,11 @@ impl VM {
                     let callable = self.pop();
                     match callable {
                         Value::Lambda(lambda) => self.call(lambda, 1),
+                        Value::Builtin(builtin) => {
+                            let arg = self.pop();
+                            let result = builtin.apply(arg)?;
+                            self.push(result);
+                        }
                         _ => return Err(ErrorKind::NotCallable.into()),
                     };
                 }