//! This module implements the runtime representation of a Nix //! builtin. //! //! Builtins are directly backed by Rust code operating on Nix values. use crate::vm::generators::Generator; use super::Value; use std::{ fmt::{Debug, Display}, rc::Rc, }; /// Trait for closure types of builtins. /// /// Builtins are expected to yield a generator which can be run by the VM to /// produce the final value. /// /// Implementors should use the builtins-macros to create these functions /// instead of handling the argument-passing logic manually. pub trait BuiltinGen: Fn(Vec<Value>) -> Generator {} impl<F: Fn(Vec<Value>) -> Generator> BuiltinGen for F {} #[derive(Clone)] pub struct BuiltinRepr { name: &'static str, /// Optional documentation for the builtin. documentation: Option<&'static str>, arg_count: usize, func: Rc<dyn BuiltinGen>, /// Partially applied function arguments. partials: Vec<Value>, } pub enum BuiltinResult { /// Builtin was not ready to be called (arguments missing) and remains /// partially applied. Partial(Builtin), /// Builtin was called and constructed a generator that the VM must run. Called(&'static str, Generator), } /// 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(Box<BuiltinRepr>); impl From<BuiltinRepr> for Builtin { fn from(value: BuiltinRepr) -> Self { Builtin(Box::new(value)) } } impl Builtin { pub fn new<F: BuiltinGen + 'static>( name: &'static str, documentation: Option<&'static str>, arg_count: usize, func: F, ) -> Self { BuiltinRepr { name, documentation, arg_count, func: Rc::new(func), partials: vec![], } .into() } pub fn name(&self) -> &'static str { self.0.name } pub fn documentation(&self) -> Option<&'static str> { self.0.documentation } /// Apply an additional argument to the builtin. After this, [`call`] *must* /// be called, otherwise it may leave the builtin in an incorrect state. pub fn apply_arg(&mut self, arg: Value) { self.0.partials.push(arg); debug_assert!( self.0.partials.len() <= self.0.arg_count, "Tvix bug: pushed too many arguments to builtin" ); } /// Attempt to call a builtin, which will produce a generator if it is fully /// applied or return the builtin if it is partially applied. pub fn call(self) -> BuiltinResult { if self.0.partials.len() == self.0.arg_count { BuiltinResult::Called(self.0.name, (self.0.func)(self.0.partials)) } else { BuiltinResult::Partial(self) } } } impl Debug for Builtin { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "builtin[{}]", self.0.name) } } impl Display for Builtin { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if !self.0.partials.is_empty() { f.write_str("<<primop-app>>") } else { f.write_str("<<primop>>") } } } /// Builtins are uniquely identified by their name impl PartialEq for Builtin { fn eq(&self, other: &Self) -> bool { self.0.name == other.0.name } }