about summary refs log tree commit diff
path: root/tvix/eval/src/value/builtin.rs
blob: c7fc33903d96d66c7546bc7fda6dd495d8c472a3 (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::{errors::ErrorKind, vm::VM};

use super::Value;

use std::{
    fmt::{Debug, Display},
    rc::Rc,
};

/// Trait for closure types of builtins implemented directly by
/// backing Rust code.
///
/// Builtins declare their arity and are passed a vector with the
/// right number of arguments. Additionally, as they might have to
/// force the evaluation of thunks, they are passed a reference to the
/// current VM which they can use for forcing a value.
///
/// Errors returned from a builtin will be annotated with the location
/// of the call to the builtin.
pub trait BuiltinFn: Fn(Vec<Value>, &mut VM) -> Result<Value, ErrorKind> {}
impl<F: Fn(Vec<Value>, &mut VM) -> Result<Value, ErrorKind>> BuiltinFn for F {}

/// Description of a single argument passed to a builtin
pub struct BuiltinArgument {
    /// Whether the argument should be forced before the underlying builtin function is called
    pub strict: bool,
    /// The name of the argument, to be used in docstrings and error messages
    pub name: &'static str,
}

#[derive(Clone)]
pub struct BuiltinRepr {
    name: &'static str,
    /// Array of arguments to the builtin.
    arguments: &'static [BuiltinArgument],
    /// Optional documentation for the builtin.
    documentation: Option<&'static str>,
    func: Rc<dyn BuiltinFn>,

    /// Partially applied function arguments.
    partials: Vec<Value>,
}

/// 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: BuiltinFn + 'static>(
        name: &'static str,
        arguments: &'static [BuiltinArgument],
        documentation: Option<&'static str>,
        func: F,
    ) -> Self {
        BuiltinRepr {
            name,
            arguments,
            documentation,
            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, which will either
    /// lead to execution of the function or to returning a partial
    /// builtin.
    pub fn apply(mut self, vm: &mut VM, arg: Value) -> Result<Value, ErrorKind> {
        self.0.partials.push(arg);

        if self.0.partials.len() == self.0.arguments.len() {
            for (idx, BuiltinArgument { strict, .. }) in self.0.arguments.iter().enumerate() {
                if *strict {
                    self.0.partials[idx].force(vm)?;
                }
            }
            return (self.0.func)(self.0.partials, vm);
        }

        // Function is not yet ready to be called.
        Ok(Value::Builtin(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
    }
}