use bstr::{ByteSlice, ByteVec};
use builtin_macros::builtins;
use genawaiter::rc::Gen;
use regex::Regex;
use std::cmp::{self, Ordering};
use std::collections::BTreeMap;
use std::collections::VecDeque;
use std::path::PathBuf;
use crate::arithmetic_op;
use crate::value::PointerEquality;
use crate::vm::generators::{self, GenCo};
use crate::warnings::WarningKind;
use crate::{
self as tvix_eval,
builtins::hash::hash_nix_string,
errors::{CatchableErrorKind, ErrorKind},
value::{CoercionKind, NixAttrs, NixList, NixString, Thunk, Value},
};
use self::versions::{VersionPart, VersionPartsIter};
mod hash;
mod to_xml;
mod versions;
#[cfg(test)]
pub use to_xml::value_to_xml;
#[cfg(feature = "impure")]
mod impure;
#[cfg(feature = "impure")]
pub use impure::impure_builtins;
pub const CURRENT_PLATFORM: &str = env!("TVIX_CURRENT_SYSTEM");
pub async fn coerce_value_to_path(
co: &GenCo,
v: Value,
) -> Result<Result<PathBuf, CatchableErrorKind>, ErrorKind> {
let value = generators::request_force(co, v).await;
if let Value::Path(p) = value {
return Ok(Ok(*p));
}
match generators::request_string_coerce(
co,
value,
CoercionKind {
strong: false,
import_paths: false,
},
)
.await
{
Ok(vs) => {
let path = vs.to_path()?.to_owned();
if path.is_absolute() {
Ok(Ok(path))
} else {
Err(ErrorKind::NotAnAbsolutePath(path))
}
}
Err(cek) => Ok(Err(cek)),
}
}
#[builtins]
mod pure_builtins {
use std::ffi::OsString;
use bstr::{BString, ByteSlice, B};
use itertools::Itertools;
use os_str_bytes::OsStringBytes;
use rustc_hash::FxHashSet;
use crate::{value::PointerEquality, AddContext, NixContext, NixContextElement};
use super::*;
macro_rules! try_value {
($value:expr) => {{
let val = $value;
if val.is_catchable() {
return Ok(val);
}
val
}};
}
#[builtin("abort")]
async fn builtin_abort(co: GenCo, message: Value) -> Result<Value, ErrorKind> {
Err(ErrorKind::Abort(message.to_contextful_str()?.to_string()))
}
#[builtin("add")]
async fn builtin_add(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
arithmetic_op!(&x, &y, +)
}
#[builtin("all")]
async fn builtin_all(co: GenCo, pred: Value, list: Value) -> Result<Value, ErrorKind> {
for value in list.to_list()?.into_iter() {
let pred_result = generators::request_call_with(&co, pred.clone(), [value]).await;
let pred_result = try_value!(generators::request_force(&co, pred_result).await);
if !pred_result.as_bool()? {
return Ok(Value::Bool(false));
}
}
Ok(Value::Bool(true))
}
#[builtin("any")]
async fn builtin_any(co: GenCo, pred: Value, list: Value) -> Result<Value, ErrorKind> {
for value in list.to_list()?.into_iter() {
let pred_result = generators::request_call_with(&co, pred.clone(), [value]).await;
let pred_result = try_value!(generators::request_force(&co, pred_result).await);
if pred_result.as_bool()? {
return Ok(Value::Bool(true));
}
}
Ok(Value::Bool(false))
}
#[builtin("attrNames")]
async fn builtin_attr_names(co: GenCo, set: Value) -> Result<Value, ErrorKind> {
let xs = set.to_attrs()?;
let mut output = Vec::with_capacity(xs.len());
for (key, _val) in xs.iter() {
output.push(Value::from(key.clone()));
}
Ok(Value::List(NixList::construct(output.len(), output)))
}
#[builtin("attrValues")]
async fn builtin_attr_values(co: GenCo, set: Value) -> Result<Value, ErrorKind> {
let xs = set.to_attrs()?;
let mut output = Vec::with_capacity(xs.len());
for (_key, val) in xs.iter() {
output.push(val.clone());
}
Ok(Value::List(NixList::construct(output.len(), output)))
}
#[builtin("baseNameOf")]
async fn builtin_base_name_of(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
let span = generators::request_span(&co).await;
let s = s
.coerce_to_string(
co,
CoercionKind {
strong: false,
import_paths: false,
},
span,
)
.await?
.to_contextful_str()?;
let mut bs = (**s).to_owned();
if let Some(last_slash) = bs.rfind_char('/') {
bs = bs[(last_slash + 1)..].into();
}
Ok(NixString::new_inherit_context_from(&s, bs).into())
}
#[builtin("bitAnd")]
async fn builtin_bit_and(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
Ok(Value::Integer(x.as_int()? & y.as_int()?))
}
#[builtin("bitOr")]
async fn builtin_bit_or(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
Ok(Value::Integer(x.as_int()? | y.as_int()?))
}
#[builtin("bitXor")]
async fn builtin_bit_xor(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
Ok(Value::Integer(x.as_int()? ^ y.as_int()?))
}
#[builtin("catAttrs")]
async fn builtin_cat_attrs(co: GenCo, key: Value, list: Value) -> Result<Value, ErrorKind> {
let key = key.to_str()?;
let list = list.to_list()?;
let mut output = vec![];
for item in list.into_iter() {
let set = generators::request_force(&co, item).await.to_attrs()?;
if let Some(value) = set.select(&key) {
output.push(value.clone());
}
}
Ok(Value::List(NixList::construct(output.len(), output)))
}
#[builtin("ceil")]
async fn builtin_ceil(co: GenCo, double: Value) -> Result<Value, ErrorKind> {
Ok(Value::Integer(double.as_float()?.ceil() as i64))
}
#[builtin("compareVersions")]
async fn builtin_compare_versions(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
let s1 = x.to_str()?;
let s1 = VersionPartsIter::new_for_cmp((&s1).into());
let s2 = y.to_str()?;
let s2 = VersionPartsIter::new_for_cmp((&s2).into());
match s1.cmp(s2) {
std::cmp::Ordering::Less => Ok(Value::Integer(-1)),
std::cmp::Ordering::Equal => Ok(Value::Integer(0)),
std::cmp::Ordering::Greater => Ok(Value::Integer(1)),
}
}
#[builtin("concatLists")]
async fn builtin_concat_lists(co: GenCo, lists: Value) -> Result<Value, ErrorKind> {
let mut out = Vec::new();
for value in lists.to_list()? {
let list = try_value!(generators::request_force(&co, value).await).to_list()?;
out.extend(list.into_iter());
}
Ok(Value::List(out.into()))
}
#[builtin("concatMap")]
async fn builtin_concat_map(co: GenCo, f: Value, list: Value) -> Result<Value, ErrorKind> {
let list = list.to_list()?;
let mut res = Vec::new();
for val in list {
let out = generators::request_call_with(&co, f.clone(), [val]).await;
let out = try_value!(generators::request_force(&co, out).await);
res.extend(out.to_list()?);
}
Ok(Value::List(res.into()))
}
#[builtin("concatStringsSep")]
async fn builtin_concat_strings_sep(
co: GenCo,
separator: Value,
list: Value,
) -> Result<Value, ErrorKind> {
let mut separator = separator.to_contextful_str()?;
let mut context = NixContext::new();
if let Some(sep_context) = separator.take_context() {
context.extend(sep_context.into_iter())
}
let list = list.to_list()?;
let mut res = BString::default();
for (i, val) in list.into_iter().enumerate() {
if i != 0 {
res.push_str(&separator);
}
match generators::request_string_coerce(
&co,
val,
CoercionKind {
strong: false,
import_paths: true,
},
)
.await
{
Ok(mut s) => {
res.push_str(&s);
if let Some(other_context) = s.take_context() {
context.extend(other_context.into_iter());
}
}
Err(c) => return Ok(Value::Catchable(Box::new(c))),
}
}
Ok(NixString::new_context_from(context, res).into())
}
#[builtin("deepSeq")]
async fn builtin_deep_seq(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
generators::request_deep_force(&co, x).await;
Ok(y)
}
#[builtin("div")]
async fn builtin_div(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
arithmetic_op!(&x, &y, /)
}
#[builtin("dirOf")]
async fn builtin_dir_of(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
let is_path = s.is_path();
let span = generators::request_span(&co).await;
let str = s
.coerce_to_string(
co,
CoercionKind {
strong: false,
import_paths: false,
},
span,
)
.await?
.to_contextful_str()?;
let result = str
.rfind_char('/')
.map(|last_slash| {
let x = &str[..last_slash];
if x.is_empty() {
B("/")
} else {
x
}
})
.unwrap_or(b".");
if is_path {
Ok(Value::Path(Box::new(PathBuf::from(
OsString::assert_from_raw_vec(result.to_owned()),
))))
} else {
Ok(Value::from(NixString::new_inherit_context_from(
&str, result,
)))
}
}
#[builtin("elem")]
async fn builtin_elem(co: GenCo, x: Value, xs: Value) -> Result<Value, ErrorKind> {
for val in xs.to_list()? {
match generators::check_equality(&co, x.clone(), val, PointerEquality::AllowAll).await?
{
Ok(true) => return Ok(true.into()),
Ok(false) => continue,
Err(cek) => return Ok(Value::from(cek)),
}
}
Ok(false.into())
}
#[builtin("elemAt")]
async fn builtin_elem_at(co: GenCo, xs: Value, i: Value) -> Result<Value, ErrorKind> {
let xs = xs.to_list()?;
let i = i.as_int()?;
if i < 0 {
Err(ErrorKind::IndexOutOfBounds { index: i })
} else {
match xs.get(i as usize) {
Some(x) => Ok(x.clone()),
None => Err(ErrorKind::IndexOutOfBounds { index: i }),
}
}
}
#[builtin("filter")]
async fn builtin_filter(co: GenCo, pred: Value, list: Value) -> Result<Value, ErrorKind> {
let list: NixList = list.to_list()?;
let mut out = Vec::new();
for value in list {
let result = generators::request_call_with(&co, pred.clone(), [value.clone()]).await;
let verdict = try_value!(generators::request_force(&co, result).await);
if verdict.as_bool()? {
out.push(value);
}
}
Ok(Value::List(out.into()))
}
#[builtin("floor")]
async fn builtin_floor(co: GenCo, double: Value) -> Result<Value, ErrorKind> {
Ok(Value::Integer(double.as_float()?.floor() as i64))
}
#[builtin("foldl'")]
async fn builtin_foldl(
co: GenCo,
op: Value,
#[lazy] nul: Value,
list: Value,
) -> Result<Value, ErrorKind> {
let mut nul = nul;
let list = list.to_list()?;
for val in list {
nul = generators::request_call_with(&co, op.clone(), [nul, val]).await;
nul = generators::request_force(&co, nul).await;
if let c @ Value::Catchable(_) = nul {
return Ok(c);
}
}
Ok(nul)
}
#[builtin("functionArgs")]
async fn builtin_function_args(co: GenCo, f: Value) -> Result<Value, ErrorKind> {
let lambda = &f.as_closure()?.lambda();
let formals = if let Some(formals) = &lambda.formals {
formals
} else {
return Ok(Value::attrs(NixAttrs::empty()));
};
Ok(Value::attrs(NixAttrs::from_iter(
formals.arguments.iter().map(|(k, v)| (k.clone(), (*v))),
)))
}
#[builtin("fromJSON")]
async fn builtin_from_json(co: GenCo, json: Value) -> Result<Value, ErrorKind> {
let json_str = json.to_str()?;
serde_json::from_slice(&json_str).map_err(|err| err.into())
}
#[builtin("toJSON")]
async fn builtin_to_json(co: GenCo, val: Value) -> Result<Value, ErrorKind> {
match val.into_contextful_json(&co).await? {
Err(cek) => Ok(Value::from(cek)),
Ok((json_value, ctx)) => {
let json_str = serde_json::to_string(&json_value)?;
Ok(NixString::new_context_from(ctx, json_str).into())
}
}
}
#[builtin("fromTOML")]
async fn builtin_from_toml(co: GenCo, toml: Value) -> Result<Value, ErrorKind> {
let toml_str = toml.to_str()?;
toml::from_str(toml_str.to_str()?).map_err(|err| err.into())
}
#[builtin("genericClosure")]
async fn builtin_generic_closure(co: GenCo, input: Value) -> Result<Value, ErrorKind> {
let attrs = input.to_attrs()?;
let mut work_set: VecDeque<Value> =
generators::request_force(&co, attrs.select_required("startSet")?.clone())
.await
.to_list()?
.into_iter()
.collect();
let operator = attrs.select_required("operator")?;
let mut res = Vec::new();
let mut done_keys: Vec<Value> = vec![];
while let Some(val) = work_set.pop_front() {
let val = generators::request_force(&co, val).await;
let attrs = val.to_attrs()?;
let key = attrs.select_required("key")?;
let value_missing = bgc_insert_key(&co, key.clone(), &mut done_keys).await?;
if let Err(cek) = value_missing {
return Ok(Value::Catchable(Box::new(cek)));
}
if let Ok(false) = value_missing {
continue;
}
res.push(val.clone());
let op_result = generators::request_force(
&co,
generators::request_call_with(&co, operator.clone(), [val]).await,
)
.await;
work_set.extend(op_result.to_list()?.into_iter());
}
Ok(Value::List(NixList::from(res)))
}
#[builtin("genList")]
async fn builtin_gen_list(
co: GenCo,
#[lazy] generator: Value,
length: Value,
) -> Result<Value, ErrorKind> {
let len = length.as_int()?;
let mut out = Vec::with_capacity(
len.try_into()
.map_err(|_| ErrorKind::Abort(format!("can not create list of size {}", len)))?,
);
let span = generators::request_span(&co).await;
for i in 0..len {
let val = Value::Thunk(Thunk::new_suspended_call(generator.clone(), i.into(), span));
out.push(val);
}
Ok(Value::List(out.into()))
}
#[builtin("getAttr")]
async fn builtin_get_attr(co: GenCo, key: Value, set: Value) -> Result<Value, ErrorKind> {
let k = key.to_str()?;
let xs = set.to_attrs()?;
match xs.select(&k) {
Some(x) => Ok(x.clone()),
None => Err(ErrorKind::AttributeNotFound {
name: k.to_string(),
}),
}
}
#[builtin("groupBy")]
async fn builtin_group_by(co: GenCo, f: Value, list: Value) -> Result<Value, ErrorKind> {
let mut res: BTreeMap<NixString, Vec<Value>> = BTreeMap::new();
for val in list.to_list()? {
let key = try_value!(
generators::request_force(
&co,
generators::request_call_with(&co, f.clone(), [val.clone()]).await,
)
.await
)
.to_str()?;
res.entry(key).or_default().push(val);
}
Ok(Value::attrs(NixAttrs::from_iter(
res.into_iter()
.map(|(k, v)| (k, Value::List(NixList::from(v)))),
)))
}
#[builtin("hasAttr")]
async fn builtin_has_attr(co: GenCo, key: Value, set: Value) -> Result<Value, ErrorKind> {
let k = key.to_str()?;
let xs = set.to_attrs()?;
Ok(Value::Bool(xs.contains(&k)))
}
#[builtin("hasContext")]
#[allow(non_snake_case)]
async fn builtin_hasContext(co: GenCo, e: Value) -> Result<Value, ErrorKind> {
if e.is_catchable() {
return Ok(e);
}
let v = e.to_contextful_str()?;
Ok(Value::Bool(v.has_context()))
}
#[builtin("getContext")]
#[allow(non_snake_case)]
async fn builtin_getContext(co: GenCo, e: Value) -> Result<Value, ErrorKind> {
if e.is_catchable() {
return Ok(e);
}
let span = generators::request_span(&co).await;
let v = e
.coerce_to_string(
co,
CoercionKind {
strong: true,
import_paths: true,
},
span,
)
.await?;
let s = v.to_contextful_str()?;
let groups = s
.iter_context()
.flat_map(|context| context.iter())
.into_grouping_map_by(|ctx_element| match ctx_element {
NixContextElement::Plain(spath) => spath,
NixContextElement::Single { derivation, .. } => derivation,
NixContextElement::Derivation(drv_path) => drv_path,
})
.collect::<Vec<_>>();
let elements = groups
.into_iter()
.map(|(key, group)| {
let mut outputs: Vec<NixString> = Vec::new();
let mut is_path = false;
let mut all_outputs = false;
for ctx_element in group {
match ctx_element {
NixContextElement::Plain(spath) => {
debug_assert!(spath == key, "Unexpected group containing mixed keys, expected: {:?}, encountered {:?}", key, spath);
is_path = true;
}
NixContextElement::Single { name, derivation } => {
debug_assert!(derivation == key, "Unexpected group containing mixed keys, expected: {:?}, encountered {:?}", key, derivation);
outputs.push(name.clone().into());
}
NixContextElement::Derivation(drv_path) => {
debug_assert!(drv_path == key, "Unexpected group containing mixed keys, expected: {:?}, encountered {:?}", key, drv_path);
all_outputs = true;
}
}
}
let mut vec_attrs: Vec<(&str, Value)> = Vec::new();
if is_path {
vec_attrs.push(("path", true.into()));
}
if all_outputs {
vec_attrs.push(("allOutputs", true.into()));
}
if !outputs.is_empty() {
outputs.sort();
vec_attrs.push(("outputs", Value::List(outputs
.into_iter()
.map(|s| s.into())
.collect::<Vec<Value>>()
.into()
)));
}
(key.clone(), Value::attrs(NixAttrs::from_iter(vec_attrs.into_iter())))
});
Ok(Value::attrs(NixAttrs::from_iter(elements)))
}
#[builtin("appendContext")]
#[allow(non_snake_case)]
async fn builtin_appendContext(
co: GenCo,
origin: Value,
added_context: Value,
) -> Result<Value, ErrorKind> {
let mut ctx_elements: FxHashSet<NixContextElement> = FxHashSet::default();
let span = generators::request_span(&co).await;
let origin = origin
.coerce_to_string(
co,
CoercionKind {
strong: true,
import_paths: true,
},
span,
)
.await?;
let mut origin = origin.to_contextful_str()?;
let added_context = added_context.to_attrs()?;
for (context_key, context_element) in added_context.into_iter() {
let context_element = context_element.to_attrs()?;
if let Some(path) = context_element.select("path") {
if path.as_bool()? {
ctx_elements.insert(NixContextElement::Plain(context_key.to_string()));
}
}
if let Some(all_outputs) = context_element.select("allOutputs") {
if all_outputs.as_bool()? {
ctx_elements.insert(NixContextElement::Derivation(context_key.to_string()));
}
}
if let Some(some_outputs) = context_element.select("outputs") {
let some_outputs = some_outputs.to_list()?;
for output in some_outputs.into_iter() {
let output = output.to_str()?;
ctx_elements.insert(NixContextElement::Single {
derivation: context_key.to_string(),
name: output.to_string(),
});
}
}
}
if let Some(origin_ctx) = origin.context_mut() {
origin_ctx.extend(ctx_elements)
}
Ok(origin.into())
}
#[builtin("hashString")]
async fn builtin_hash_string(co: GenCo, algo: Value, s: Value) -> Result<Value, ErrorKind> {
hash_nix_string(algo.to_str()?, std::io::Cursor::new(s.to_str()?)).map(Value::from)
}
#[builtin("head")]
async fn builtin_head(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
if list.is_catchable() {
return Ok(list);
}
match list.to_list()?.get(0) {
Some(x) => Ok(x.clone()),
None => Err(ErrorKind::IndexOutOfBounds { index: 0 }),
}
}
#[builtin("intersectAttrs")]
async fn builtin_intersect_attrs(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
if x.is_catchable() {
return Ok(x);
}
if y.is_catchable() {
return Ok(y);
}
let left_set = x.to_attrs()?;
if left_set.is_empty() {
return Ok(Value::attrs(NixAttrs::empty()));
}
let mut left_keys = left_set.keys();
let right_set = y.to_attrs()?;
if right_set.is_empty() {
return Ok(Value::attrs(NixAttrs::empty()));
}
let mut right_keys = right_set.keys();
let mut out: BTreeMap<NixString, Value> = BTreeMap::new();
let mut left = left_keys.next().unwrap();
let mut right = right_keys.next().unwrap();
loop {
if left == right {
unsafe {
out.insert(
right.clone(),
right_set.select(right).unwrap_unchecked().clone(),
);
}
left = match left_keys.next() {
Some(x) => x,
None => break,
};
right = match right_keys.next() {
Some(x) => x,
None => break,
};
continue;
}
if left < right {
left = match left_keys.next() {
Some(x) => x,
None => break,
};
continue;
}
if right < left {
right = match right_keys.next() {
Some(x) => x,
None => break,
};
continue;
}
}
Ok(Value::attrs(out.into()))
}
#[builtin("isAttrs")]
async fn builtin_is_attrs(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
if value.is_catchable() {
return Ok(value);
}
Ok(Value::Bool(matches!(value, Value::Attrs(_))))
}
#[builtin("isBool")]
async fn builtin_is_bool(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
if value.is_catchable() {
return Ok(value);
}
Ok(Value::Bool(matches!(value, Value::Bool(_))))
}
#[builtin("isFloat")]
async fn builtin_is_float(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
if value.is_catchable() {
return Ok(value);
}
Ok(Value::Bool(matches!(value, Value::Float(_))))
}
#[builtin("isFunction")]
async fn builtin_is_function(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
if value.is_catchable() {
return Ok(value);
}
Ok(Value::Bool(matches!(
value,
Value::Closure(_) | Value::Builtin(_)
)))
}
#[builtin("isInt")]
async fn builtin_is_int(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
if value.is_catchable() {
return Ok(value);
}
Ok(Value::Bool(matches!(value, Value::Integer(_))))
}
#[builtin("isList")]
async fn builtin_is_list(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
if value.is_catchable() {
return Ok(value);
}
Ok(Value::Bool(matches!(value, Value::List(_))))
}
#[builtin("isNull")]
async fn builtin_is_null(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
if value.is_catchable() {
return Ok(value);
}
Ok(Value::Bool(matches!(value, Value::Null)))
}
#[builtin("isPath")]
async fn builtin_is_path(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
if value.is_catchable() {
return Ok(value);
}
Ok(Value::Bool(matches!(value, Value::Path(_))))
}
#[builtin("isString")]
async fn builtin_is_string(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
if value.is_catchable() {
return Ok(value);
}
Ok(Value::Bool(matches!(value, Value::String(_))))
}
#[builtin("length")]
async fn builtin_length(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
if list.is_catchable() {
return Ok(list);
}
Ok(Value::Integer(list.to_list()?.len() as i64))
}
#[builtin("lessThan")]
async fn builtin_less_than(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
let span = generators::request_span(&co).await;
match x.nix_cmp_ordering(y, co, span).await? {
Err(cek) => Ok(Value::from(cek)),
Ok(Ordering::Less) => Ok(Value::Bool(true)),
Ok(_) => Ok(Value::Bool(false)),
}
}
#[builtin("listToAttrs")]
async fn builtin_list_to_attrs(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
let list = list.to_list()?;
let mut map = BTreeMap::new();
for val in list {
let attrs = try_value!(generators::request_force(&co, val).await).to_attrs()?;
let name = try_value!(
generators::request_force(&co, attrs.select_required("name")?.clone()).await
)
.to_str()?;
let value = attrs.select_required("value")?.clone();
map.entry(name).or_insert(value);
}
Ok(Value::attrs(NixAttrs::from_iter(map.into_iter())))
}
#[builtin("map")]
async fn builtin_map(co: GenCo, #[lazy] f: Value, list_val: Value) -> Result<Value, ErrorKind> {
let list = list_val.to_list()?;
let mut out = Vec::with_capacity(list.len());
let span = generators::request_span(&co).await;
for val in list {
let result = Value::Thunk(Thunk::new_suspended_call(f.clone(), val, span));
out.push(result)
}
Ok(Value::List(out.into()))
}
#[builtin("mapAttrs")]
async fn builtin_map_attrs(
co: GenCo,
#[lazy] f: Value,
attrs: Value,
) -> Result<Value, ErrorKind> {
let attrs = attrs.to_attrs()?;
let mut out: BTreeMap<NixString, Value> = BTreeMap::new();
let span = generators::request_span(&co).await;
for (key, value) in attrs.into_iter() {
let result = Value::Thunk(Thunk::new_suspended_call(
f.clone(),
key.clone().into(),
span,
));
let result = Value::Thunk(Thunk::new_suspended_call(result, value, span));
out.insert(key, result);
}
Ok(Value::attrs(out.into()))
}
#[builtin("match")]
async fn builtin_match(co: GenCo, regex: Value, str: Value) -> Result<Value, ErrorKind> {
let s = str;
if s.is_catchable() {
return Ok(s);
}
let s = s.to_contextful_str()?;
let re = regex;
if re.is_catchable() {
return Ok(re);
}
let re = re.to_str()?;
let re: Regex = Regex::new(&format!("^{}$", re.to_str()?)).unwrap();
match re.captures(s.to_str()?) {
Some(caps) => Ok(Value::List(
caps.iter()
.skip(1)
.map(|grp| {
grp.map(|g| Value::from(g.as_str())).unwrap_or(Value::Null)
})
.collect::<Vec<Value>>()
.into(),
)),
None => Ok(Value::Null),
}
}
#[builtin("mul")]
async fn builtin_mul(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
arithmetic_op!(&x, &y, *)
}
#[builtin("parseDrvName")]
async fn builtin_parse_drv_name(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
if s.is_catchable() {
return Ok(s);
}
let s = s.to_str()?;
let slice: &[u8] = s.as_ref();
let (name, dash_and_version) = slice.split_at(
slice
.windows(2)
.enumerate()
.find_map(|x| match x {
(idx, [b'-', c1]) if !c1.is_ascii_alphabetic() => Some(idx),
_ => None,
})
.unwrap_or(slice.len()),
);
let version = dash_and_version
.split_first()
.map(|x| core::str::from_utf8(x.1))
.unwrap_or(Ok(""))?;
Ok(Value::attrs(NixAttrs::from_iter(
[("name", core::str::from_utf8(name)?), ("version", version)].into_iter(),
)))
}
#[builtin("partition")]
async fn builtin_partition(co: GenCo, pred: Value, list: Value) -> Result<Value, ErrorKind> {
let mut right: Vec<Value> = Default::default();
let mut wrong: Vec<Value> = Default::default();
let list: NixList = list.to_list()?;
for elem in list {
let result = generators::request_call_with(&co, pred.clone(), [elem.clone()]).await;
if try_value!(generators::request_force(&co, result).await).as_bool()? {
right.push(elem);
} else {
wrong.push(elem);
};
}
let res = [
("right", Value::List(NixList::from(right))),
("wrong", Value::List(NixList::from(wrong))),
];
Ok(Value::attrs(NixAttrs::from_iter(res.into_iter())))
}
#[builtin("removeAttrs")]
async fn builtin_remove_attrs(
co: GenCo,
attrs: Value,
keys: Value,
) -> Result<Value, ErrorKind> {
let attrs = attrs.to_attrs()?;
let keys = keys
.to_list()?
.into_iter()
.map(|v| v.to_str())
.collect::<Result<FxHashSet<_>, _>>()?;
let res = attrs.iter().filter_map(|(k, v)| {
if !keys.contains(k) {
Some((k.clone(), v.clone()))
} else {
None
}
});
Ok(Value::attrs(NixAttrs::from_iter(res)))
}
#[builtin("replaceStrings")]
async fn builtin_replace_strings(
co: GenCo,
from: Value,
to: Value,
s: Value,
) -> Result<Value, ErrorKind> {
let from = from.to_list()?;
for val in &from {
try_value!(generators::request_force(&co, val.clone()).await);
}
let to = to.to_list()?;
for val in &to {
try_value!(generators::request_force(&co, val.clone()).await);
}
let mut string = s.to_contextful_str()?;
let mut res = BString::default();
let mut i: usize = 0;
let mut empty_string_replace = false;
let mut context = NixContext::new();
if let Some(string_context) = string.take_context() {
context.extend(string_context.into_iter());
}
'outer: while i < string.len() {
for elem in std::iter::zip(from.iter(), to.iter()) {
let from = elem.0.to_contextful_str()?;
let mut to = elem.1.to_contextful_str()?;
if i + from.len() > string.len() {
continue;
}
if empty_string_replace && from.is_empty() {
continue;
}
if string[i..i + from.len()] == *from {
res.push_str(&to);
i += from.len();
if let Some(to_ctx) = to.take_context() {
context.extend(to_ctx.into_iter());
}
empty_string_replace = from.is_empty();
continue 'outer;
}
}
res.push_str(&string[i..i + 1]);
i += 1;
empty_string_replace = false;
}
for elem in std::iter::zip(from.iter(), to.iter()) {
let from = elem.0.to_contextful_str()?;
let mut to = elem.1.to_contextful_str()?;
if from.is_empty() {
res.push_str(&to);
if let Some(to_ctx) = to.take_context() {
context.extend(to_ctx.into_iter());
}
break;
}
}
Ok(Value::from(NixString::new_context_from(context, res)))
}
#[builtin("seq")]
async fn builtin_seq(co: GenCo, _x: Value, y: Value) -> Result<Value, ErrorKind> {
Ok(y)
}
#[builtin("split")]
async fn builtin_split(co: GenCo, regex: Value, str: Value) -> Result<Value, ErrorKind> {
if str.is_catchable() {
return Ok(str);
}
if regex.is_catchable() {
return Ok(regex);
}
let s = str.to_contextful_str()?;
let text = s.to_str()?;
let re = regex.to_str()?;
let re = Regex::new(re.to_str()?).unwrap();
let mut capture_locations = re.capture_locations();
let num_captures = capture_locations.len();
let mut ret = Vec::new();
let mut pos = 0;
while let Some(thematch) = re.captures_read_at(&mut capture_locations, text, pos) {
ret.push(Value::from(NixString::new_inherit_context_from(
&s,
&text[pos..thematch.start()],
)));
let v: Vec<Value> = (1..num_captures)
.map(|i| capture_locations.get(i))
.map(|o| {
o.map(|(start, end)| {
Value::from(&text[start..end])
})
.unwrap_or(Value::Null)
})
.collect();
ret.push(Value::List(NixList::from(v)));
if pos == text.len() {
break;
}
pos = thematch.end();
}
ret.push(Value::from(&text[pos..]));
Ok(Value::List(NixList::from(ret)))
}
#[builtin("sort")]
async fn builtin_sort(co: GenCo, comparator: Value, list: Value) -> Result<Value, ErrorKind> {
let list = list.to_list()?;
let mut len = list.len();
let mut data = list.into_inner();
loop {
let mut new_len = 0;
for i in 1..len {
if try_value!(
generators::request_force(
&co,
generators::request_call_with(
&co,
comparator.clone(),
[data[i].clone(), data[i - 1].clone()],
)
.await,
)
.await
)
.as_bool()
.context("evaluating comparator in `builtins.sort`")?
{
data.swap(i, i - 1);
new_len = i;
}
}
if new_len == 0 {
break;
}
len = new_len;
}
Ok(Value::List(data.into()))
}
#[builtin("splitVersion")]
async fn builtin_split_version(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
if s.is_catchable() {
return Ok(s);
}
let s = s.to_str()?;
let s = VersionPartsIter::new((&s).into());
let parts = s
.map(|s| {
Value::from(match s {
VersionPart::Number(n) => n,
VersionPart::Word(w) => w,
})
})
.collect::<Vec<Value>>();
Ok(Value::List(NixList::construct(parts.len(), parts)))
}
#[builtin("stringLength")]
async fn builtin_string_length(co: GenCo, #[lazy] s: Value) -> Result<Value, ErrorKind> {
let span = generators::request_span(&co).await;
let s = s
.coerce_to_string(
co,
CoercionKind {
strong: false,
import_paths: true,
},
span,
)
.await?;
if s.is_catchable() {
return Ok(s);
}
Ok(Value::Integer(s.to_contextful_str()?.len() as i64))
}
#[builtin("sub")]
async fn builtin_sub(co: GenCo, x: Value, y: Value) -> Result<Value, ErrorKind> {
arithmetic_op!(&x, &y, -)
}
#[builtin("substring")]
async fn builtin_substring(
co: GenCo,
start: Value,
len: Value,
s: Value,
) -> Result<Value, ErrorKind> {
let beg = start.as_int()?;
let len = len.as_int()?;
let span = generators::request_span(&co).await;
let x = s
.coerce_to_string(
co,
CoercionKind {
strong: false,
import_paths: true,
},
span,
)
.await?;
if x.is_catchable() {
return Ok(x);
}
let x = x.to_contextful_str()?;
if beg < 0 {
return Err(ErrorKind::IndexOutOfBounds { index: beg });
}
let beg = beg as usize;
if beg >= x.len() {
return Ok(Value::from(NixString::new_inherit_context_from(
&x,
BString::default(),
)));
}
let end = if len < 0 {
x.len()
} else {
cmp::min(beg + (len as usize), x.len())
};
Ok(Value::from(NixString::new_inherit_context_from(
&x,
&x[beg..end],
)))
}
#[builtin("tail")]
async fn builtin_tail(co: GenCo, list: Value) -> Result<Value, ErrorKind> {
if list.is_catchable() {
return Ok(list);
}
let xs = list.to_list()?;
if xs.is_empty() {
Err(ErrorKind::TailEmptyList)
} else {
let output = xs.into_iter().skip(1).collect::<Vec<_>>();
Ok(Value::List(NixList::construct(output.len(), output)))
}
}
#[builtin("throw")]
async fn builtin_throw(co: GenCo, message: Value) -> Result<Value, ErrorKind> {
if message.is_catchable() {
return Ok(message);
}
Ok(Value::from(CatchableErrorKind::Throw(
message.to_contextful_str()?.to_string().into(),
)))
}
#[builtin("toString")]
async fn builtin_to_string(co: GenCo, #[lazy] x: Value) -> Result<Value, ErrorKind> {
let span = generators::request_span(&co).await;
x.coerce_to_string(
co,
CoercionKind {
strong: true,
import_paths: false,
},
span,
)
.await
}
#[builtin("toXML")]
async fn builtin_to_xml(co: GenCo, value: Value) -> Result<Value, ErrorKind> {
let value = generators::request_deep_force(&co, value).await;
if value.is_catchable() {
return Ok(value);
}
let mut buf: Vec<u8> = vec![];
let context = to_xml::value_to_xml(&mut buf, &value)?;
Ok((
buf,
if !context.is_empty() {
Some(Box::new(context))
} else {
None
},
)
.into())
}
#[builtin("trace")]
async fn builtin_trace(co: GenCo, message: Value, value: Value) -> Result<Value, ErrorKind> {
eprintln!("trace: {} :: {}", message, message.type_of());
Ok(value)
}
#[builtin("toPath")]
async fn builtin_to_path(co: GenCo, s: Value) -> Result<Value, ErrorKind> {
if s.is_catchable() {
return Ok(s);
}
match coerce_value_to_path(&co, s).await? {
Err(cek) => Ok(Value::from(cek)),
Ok(path) => {
let path: Value = crate::value::canon_path(path).into();
let span = generators::request_span(&co).await;
Ok(path
.coerce_to_string(
co,
CoercionKind {
strong: false,
import_paths: false,
},
span,
)
.await?)
}
}
}
#[builtin("tryEval")]
async fn builtin_try_eval(co: GenCo, #[lazy] e: Value) -> Result<Value, ErrorKind> {
let res = match generators::request_try_force(&co, e).await {
Value::Catchable(_) => [("value", false.into()), ("success", false.into())],
value => [("value", value), ("success", true.into())],
};
Ok(Value::attrs(NixAttrs::from_iter(res.into_iter())))
}
#[builtin("typeOf")]
async fn builtin_type_of(co: GenCo, x: Value) -> Result<Value, ErrorKind> {
if x.is_catchable() {
return Ok(x);
}
Ok(Value::from(x.type_of()))
}
}
async fn bgc_insert_key(
co: &GenCo,
key: Value,
done: &mut Vec<Value>,
) -> Result<Result<bool, CatchableErrorKind>, ErrorKind> {
for existing in done.iter() {
match generators::check_equality(
co,
existing.clone(),
key.clone(),
PointerEquality::ForbidAll,
)
.await?
{
Ok(true) => return Ok(Ok(false)),
Ok(false) => (),
Err(cek) => return Ok(Err(cek)),
}
}
done.push(key);
Ok(Ok(true))
}
pub fn pure_builtins() -> Vec<(&'static str, Value)> {
let mut result = pure_builtins::builtins();
result.push(("nixVersion", Value::from("2.3-compat-tvix-0.1")));
result.push(("langVersion", Value::Integer(6)));
result.push(("null", Value::Null));
result.push(("true", Value::Bool(true)));
result.push(("false", Value::Bool(false)));
result.push((
"currentSystem",
crate::systems::llvm_triple_to_nix_double(CURRENT_PLATFORM).into(),
));
result.push((
"__curPos",
Value::Thunk(Thunk::new_suspended_native(Box::new(move || {
Ok(Value::attrs(NixAttrs::from_iter([
("line", 42.into()),
("column", 42.into()),
("file", Value::String("/deep/thought".into())),
])))
}))),
));
result
}
#[builtins]
mod placeholder_builtins {
use crate::NixContext;
use super::*;
#[builtin("unsafeDiscardStringContext")]
async fn builtin_unsafe_discard_string_context(
co: GenCo,
s: Value,
) -> Result<Value, ErrorKind> {
let span = generators::request_span(&co).await;
let mut v = s
.coerce_to_string(
co,
CoercionKind {
strong: false,
import_paths: true,
},
span,
)
.await?
.to_contextful_str()?;
v.clear_context();
Ok(Value::from(v))
}
#[builtin("unsafeDiscardOutputDependency")]
async fn builtin_unsafe_discard_output_dependency(
co: GenCo,
s: Value,
) -> Result<Value, ErrorKind> {
let span = generators::request_span(&co).await;
let mut v = s
.coerce_to_string(
co,
CoercionKind {
strong: false,
import_paths: true,
},
span,
)
.await?
.to_contextful_str()?;
if let Some(c) = v.take_context() {
let mut context = NixContext::new();
context.extend(c.into_iter().map(|elem| match elem {
crate::NixContextElement::Derivation(drv_path) => {
crate::NixContextElement::Plain(drv_path.to_string())
}
elem => elem.clone(),
}));
return Ok(Value::String(NixString::new_context_from(context, v)));
}
Ok(Value::from(v))
}
#[builtin("addErrorContext")]
async fn builtin_add_error_context(
co: GenCo,
#[lazy] _context: Value,
#[lazy] val: Value,
) -> Result<Value, ErrorKind> {
generators::emit_warning_kind(&co, WarningKind::NotImplemented("builtins.addErrorContext"))
.await;
Ok(val)
}
#[builtin("unsafeGetAttrPos")]
async fn builtin_unsafe_get_attr_pos(
co: GenCo,
_name: Value,
_attrset: Value,
) -> Result<Value, ErrorKind> {
generators::emit_warning_kind(
&co,
WarningKind::NotImplemented("builtins.unsafeGetAttrsPos"),
)
.await;
let res = [
("line", 42.into()),
("column", 42.into()),
("file", Value::String("/deep/thought".into())),
];
Ok(Value::attrs(NixAttrs::from_iter(res.into_iter())))
}
}
pub fn placeholders() -> Vec<(&'static str, Value)> {
placeholder_builtins::builtins()
}