1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
//! 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, [`Builtin::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
}
}