//! This module implements the builtins exposed in the Nix language.
//!
//! See //tvix/eval/docs/builtins.md for a some context on the
//! available builtins in Nix.
use std::{
collections::{BTreeMap, HashMap},
rc::Rc,
};
use crate::{
errors::ErrorKind,
value::{Builtin, CoercionKind, NixAttrs, NixList, NixString, Value},
};
use crate::arithmetic_op;
/// Helper macro to ensure that a value has been forced. The structure
/// of this is a little cumbersome as there are different reference
/// types depending on whether the value is inside a thunk or not.
macro_rules! force {
( $vm:ident, $src:expr, $value:ident, $body:block ) => {
if let Value::Thunk(thunk) = $src {
thunk.force($vm)?;
let guard = thunk.value();
let $value: &Value = &guard;
$body
} else {
let $value: &Value = $src;
$body
}
};
( $vm:ident, $value:ident, $body:block ) => {
force!($vm, &$value, $value, $body)
};
}
/// Return all pure builtins, that is all builtins that do not rely on
/// I/O outside of the VM and which can be used in any contexts (e.g.
/// WASM).
fn pure_builtins() -> Vec<Builtin> {
vec![
Builtin::new("add", 2, |mut args, _| {
let b = args.pop().unwrap();
let a = args.pop().unwrap();
arithmetic_op!(a, b, +)
}),
Builtin::new("abort", 1, |mut args, _| {
return Err(ErrorKind::Abort(
args.pop().unwrap().to_str()?.as_str().to_owned(),
));
}),
Builtin::new("catAttrs", 2, |mut args, _| {
let list = args.pop().unwrap().to_list()?;
let key = args.pop().unwrap().to_str()?;
let mut output = vec![];
for set in list.into_iter() {
if let Some(value) = set.to_attrs()?.select(key.as_str()) {
output.push(value.clone());
}
}
Ok(Value::List(NixList::construct(output.len(), output)))
}),
Builtin::new("div", 2, |mut args, _| {
let b = args.pop().unwrap();
let a = args.pop().unwrap();
arithmetic_op!(a, b, /)
}),
Builtin::new("length", 1, |args, vm| {
if let Value::Thunk(t) = &args[0] {
t.force(vm)?;
}
Ok(Value::Integer(args[0].to_list()?.len() as i64))
}),
Builtin::new("isAttrs", 1, |args, _| {
Ok(Value::Bool(matches!(args[0], Value::Attrs(_))))
}),
Builtin::new("isBool", 1, |args, _| {
Ok(Value::Bool(matches!(args[0], Value::Bool(_))))
}),
Builtin::new("isFloat", 1, |args, _| {
Ok(Value::Bool(matches!(args[0], Value::Float(_))))
}),
Builtin::new("isFunction", 1, |args, _| {
Ok(Value::Bool(matches!(
args[0],
Value::Closure(_) | Value::Builtin(_)
)))
}),
Builtin::new("isInt", 1, |args, _| {
Ok(Value::Bool(matches!(args[0], Value::Integer(_))))
}),
Builtin::new("isList", 1, |args, _| {
Ok(Value::Bool(matches!(args[0], Value::List(_))))
}),
Builtin::new("isNull", 1, |args, _| {
Ok(Value::Bool(matches!(args[0], Value::Null)))
}),
Builtin::new("isPath", 1, |args, _| {
Ok(Value::Bool(matches!(args[0], Value::Path(_))))
}),
Builtin::new("isString", 1, |args, _| {
Ok(Value::Bool(matches!(args[0], Value::String(_))))
}),
Builtin::new("mul", 2, |mut args, _| {
let b = args.pop().unwrap();
let a = args.pop().unwrap();
arithmetic_op!(a, b, *)
}),
Builtin::new("sub", 2, |mut args, _| {
let b = args.pop().unwrap();
let a = args.pop().unwrap();
arithmetic_op!(a, b, -)
}),
Builtin::new("throw", 1, |mut args, _| {
return Err(ErrorKind::Throw(
args.pop().unwrap().to_str()?.as_str().to_owned(),
));
}),
Builtin::new("toString", 1, |args, vm| {
args[0]
.coerce_to_string(CoercionKind::Strong, vm)
.map(|s| Value::String(s))
}),
Builtin::new("typeOf", 1, |args, vm| {
force!(vm, &args[0], value, {
Ok(Value::String(value.type_of().into()))
})
}),
]
}
fn builtins_set() -> NixAttrs {
let mut map: BTreeMap<NixString, Value> = BTreeMap::new();
for builtin in pure_builtins() {
map.insert(builtin.name().into(), Value::Builtin(builtin));
}
NixAttrs::from_map(map)
}
/// Set of Nix builtins that are globally available.
pub fn global_builtins() -> HashMap<&'static str, Value> {
let builtins = builtins_set();
let mut globals: HashMap<&'static str, Value> = HashMap::new();
// known global builtins from the builtins set.
for global in &[
"abort",
"baseNameOf",
"derivation",
"derivationStrict",
"dirOf",
"fetchGit",
"fetchMercurial",
"fetchTarball",
"fromTOML",
"import",
"isNull",
"map",
"placeholder",
"removeAttrs",
"scopedImport",
"throw",
"toString",
] {
if let Some(builtin) = builtins.select(global) {
globals.insert(global, builtin.clone());
}
}
globals.insert("builtins", Value::Attrs(Rc::new(builtins)));
globals
}