snix_eval/value/
builtin.rs

1//! This module implements the runtime representation of a Nix
2//! builtin.
3//!
4//! Builtins are directly backed by Rust code operating on Nix values.
5
6use crate::vm::generators::Generator;
7
8use super::Value;
9
10use std::{
11    fmt::{Debug, Display},
12    rc::Rc,
13};
14
15/// Trait for closure types of builtins.
16///
17/// Builtins are expected to yield a generator which can be run by the VM to
18/// produce the final value.
19///
20/// Implementors should use the builtins-macros to create these functions
21/// instead of handling the argument-passing logic manually.
22pub trait BuiltinGen: Fn(Vec<Value>) -> Generator {}
23impl<F: Fn(Vec<Value>) -> Generator> BuiltinGen for F {}
24
25#[derive(Clone)]
26pub struct BuiltinRepr {
27    name: &'static str,
28    /// Optional documentation for the builtin.
29    documentation: Option<&'static str>,
30    arg_count: usize,
31
32    func: Rc<dyn BuiltinGen>,
33
34    /// Partially applied function arguments.
35    partials: Vec<Value>,
36}
37
38pub enum BuiltinResult {
39    /// Builtin was not ready to be called (arguments missing) and remains
40    /// partially applied.
41    Partial(Builtin),
42
43    /// Builtin was called and constructed a generator that the VM must run.
44    Called(&'static str, Generator),
45}
46
47/// Represents a single built-in function which directly executes Rust
48/// code that operates on a Nix value.
49///
50/// Builtins are the only functions in Nix that have varying arities
51/// (for example, `hasAttr` has an arity of 2, but `isAttrs` an arity
52/// of 1). To facilitate this generically, builtins expect to be
53/// called with a vector of Nix values corresponding to their
54/// arguments in order.
55///
56/// Partially applied builtins act similar to closures in that they
57/// "capture" the partially applied arguments, and are treated
58/// specially when printing their representation etc.
59#[derive(Clone)]
60pub struct Builtin(Box<BuiltinRepr>);
61
62impl From<BuiltinRepr> for Builtin {
63    fn from(value: BuiltinRepr) -> Self {
64        Builtin(Box::new(value))
65    }
66}
67
68impl Builtin {
69    pub fn new<F: BuiltinGen + 'static>(
70        name: &'static str,
71        documentation: Option<&'static str>,
72        arg_count: usize,
73        func: F,
74    ) -> Self {
75        BuiltinRepr {
76            name,
77            documentation,
78            arg_count,
79            func: Rc::new(func),
80            partials: vec![],
81        }
82        .into()
83    }
84
85    pub fn name(&self) -> &'static str {
86        self.0.name
87    }
88
89    pub fn documentation(&self) -> Option<&'static str> {
90        self.0.documentation
91    }
92
93    /// Apply an additional argument to the builtin.
94    /// After this, [`Builtin::call`] *must* be called, otherwise it may leave
95    /// the builtin in an incorrect state.
96    pub fn apply_arg(&mut self, arg: Value) {
97        self.0.partials.push(arg);
98
99        debug_assert!(
100            self.0.partials.len() <= self.0.arg_count,
101            "Snix bug: pushed too many arguments to builtin"
102        );
103    }
104
105    /// Attempt to call a builtin, which will produce a generator if it is fully
106    /// applied or return the builtin if it is partially applied.
107    pub fn call(self) -> BuiltinResult {
108        if self.0.partials.len() == self.0.arg_count {
109            BuiltinResult::Called(self.0.name, (self.0.func)(self.0.partials))
110        } else {
111            BuiltinResult::Partial(self)
112        }
113    }
114}
115
116impl Debug for Builtin {
117    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118        write!(f, "builtin[{}]", self.0.name)
119    }
120}
121
122impl Display for Builtin {
123    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124        if !self.0.partials.is_empty() {
125            f.write_str("<PRIMOP-APP>")
126        } else {
127            f.write_str("<PRIMOP>")
128        }
129    }
130}
131
132/// Builtins are uniquely identified by their name
133impl PartialEq for Builtin {
134    fn eq(&self, other: &Self) -> bool {
135        self.0.name == other.0.name
136    }
137}