From e0f1356ae31783664f597b33f8b69b060a9e3033 Mon Sep 17 00:00:00 2001 From: Vincent Ambo Date: Wed, 24 Aug 2022 11:00:30 +0300 Subject: [PATCH] feat(tvix/eval): add initial representation of builtins 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 --- tvix/eval/src/value/builtin.rs | 66 ++++++++++++++++++++++++++++++++++ tvix/eval/src/value/mod.rs | 6 +++- tvix/eval/src/vm.rs | 5 +++ 3 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 tvix/eval/src/value/builtin.rs diff --git a/tvix/eval/src/value/builtin.rs b/tvix/eval/src/value/builtin.rs new file mode 100644 index 000000000..8d7e82136 --- /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) -> EvalResult; + +/// 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, +} + +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 { + 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("<>") + } else { + f.write_str("<>") + } + } +} diff --git a/tvix/eval/src/value/mod.rs b/tvix/eval/src/value/mod.rs index 2177ca26d..bf07c5d32 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), 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 ff81349a1..b6d5a9838 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()), }; }