feat(tvix/eval): add initial representation of builtins
Builtins are represented as a Rust function pointer that accepts a vector of arguments, which represents variable arity builtins. Change-Id: Ibab7e662a646caf1172695d876d2f55e187c03dd Reviewed-on: https://cl.tvl.fyi/c/depot/+/6251 Tested-by: BuildkiteCI Reviewed-by: grfn <grfn@gws.fyi>
This commit is contained in:
parent
64746388e2
commit
e0f1356ae3
3 changed files with 76 additions and 1 deletions
66
tvix/eval/src/value/builtin.rs
Normal file
66
tvix/eval/src/value/builtin.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
//! This module implements the runtime representation of a Nix
|
||||||
|
//! builtin.
|
||||||
|
//!
|
||||||
|
//! Builtins are directly backed by Rust code operating on Nix values.
|
||||||
|
|
||||||
|
use crate::errors::EvalResult;
|
||||||
|
|
||||||
|
use super::Value;
|
||||||
|
|
||||||
|
use std::fmt::{Debug, Display};
|
||||||
|
|
||||||
|
pub type BuiltinFn = fn(arg: Vec<Value>) -> EvalResult<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 {
|
||||||
|
name: &'static str,
|
||||||
|
arity: usize,
|
||||||
|
func: BuiltinFn,
|
||||||
|
|
||||||
|
// Partially applied function arguments.
|
||||||
|
partials: Vec<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Builtin {
|
||||||
|
/// 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, arg: Value) -> EvalResult<Value> {
|
||||||
|
self.partials.push(arg);
|
||||||
|
|
||||||
|
if self.partials.len() == self.arity {
|
||||||
|
return (self.func)(self.partials);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function is not yet ready to be called.
|
||||||
|
return Ok(Value::Builtin(self));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Builtin {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "builtin[{}]", self.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Builtin {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
if !self.partials.is_empty() {
|
||||||
|
f.write_str("<<primop-app>>")
|
||||||
|
} else {
|
||||||
|
f.write_str("<<primop>>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,12 +4,14 @@ use std::rc::Rc;
|
||||||
use std::{fmt::Display, path::PathBuf};
|
use std::{fmt::Display, path::PathBuf};
|
||||||
|
|
||||||
mod attrs;
|
mod attrs;
|
||||||
|
mod builtin;
|
||||||
mod lambda;
|
mod lambda;
|
||||||
mod list;
|
mod list;
|
||||||
mod string;
|
mod string;
|
||||||
|
|
||||||
use crate::errors::{ErrorKind, EvalResult};
|
use crate::errors::{ErrorKind, EvalResult};
|
||||||
pub use attrs::NixAttrs;
|
pub use attrs::NixAttrs;
|
||||||
|
pub use builtin::Builtin;
|
||||||
pub use lambda::Lambda;
|
pub use lambda::Lambda;
|
||||||
pub use list::NixList;
|
pub use list::NixList;
|
||||||
pub use string::NixString;
|
pub use string::NixString;
|
||||||
|
@ -26,6 +28,7 @@ pub enum Value {
|
||||||
Attrs(Rc<NixAttrs>),
|
Attrs(Rc<NixAttrs>),
|
||||||
List(NixList),
|
List(NixList),
|
||||||
Lambda(Lambda),
|
Lambda(Lambda),
|
||||||
|
Builtin(Builtin),
|
||||||
|
|
||||||
// Internal values that, while they technically exist at runtime,
|
// Internal values that, while they technically exist at runtime,
|
||||||
// are never returned to or created directly by users.
|
// are never returned to or created directly by users.
|
||||||
|
@ -49,7 +52,7 @@ impl Value {
|
||||||
Value::Path(_) => "path",
|
Value::Path(_) => "path",
|
||||||
Value::Attrs(_) => "set",
|
Value::Attrs(_) => "set",
|
||||||
Value::List(_) => "list",
|
Value::List(_) => "list",
|
||||||
Value::Lambda(_) => "lambda",
|
Value::Lambda(_) | Value::Builtin(_) => "lambda",
|
||||||
|
|
||||||
// Internal types
|
// Internal types
|
||||||
Value::AttrPath(_) | Value::Blackhole | Value::NotFound => "internal",
|
Value::AttrPath(_) | Value::Blackhole | Value::NotFound => "internal",
|
||||||
|
@ -128,6 +131,7 @@ impl Display for Value {
|
||||||
Value::Attrs(attrs) => attrs.fmt(f),
|
Value::Attrs(attrs) => attrs.fmt(f),
|
||||||
Value::List(list) => list.fmt(f),
|
Value::List(list) => list.fmt(f),
|
||||||
Value::Lambda(_) => f.write_str("lambda"), // TODO: print position
|
Value::Lambda(_) => f.write_str("lambda"), // TODO: print position
|
||||||
|
Value::Builtin(builtin) => builtin.fmt(f),
|
||||||
|
|
||||||
// Nix prints floats with a maximum precision of 5 digits
|
// Nix prints floats with a maximum precision of 5 digits
|
||||||
// only.
|
// only.
|
||||||
|
|
|
@ -358,6 +358,11 @@ impl VM {
|
||||||
let callable = self.pop();
|
let callable = self.pop();
|
||||||
match callable {
|
match callable {
|
||||||
Value::Lambda(lambda) => self.call(lambda, 1),
|
Value::Lambda(lambda) => self.call(lambda, 1),
|
||||||
|
Value::Builtin(builtin) => {
|
||||||
|
let arg = self.pop();
|
||||||
|
let result = builtin.apply(arg)?;
|
||||||
|
self.push(result);
|
||||||
|
}
|
||||||
_ => return Err(ErrorKind::NotCallable.into()),
|
_ => return Err(ErrorKind::NotCallable.into()),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue