about summary refs log tree commit diff
path: root/tvix/eval/src/value/builtin.rs
blob: 6d08ebf9506dda73cfa57b00c22442d949095430 (plain) (blame)
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
//! 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
    }
}