From 2d26925dd9b949c3a98990cb615d8dc822cd2f0f Mon Sep 17 00:00:00 2001 From: Griffin Smith Date: Sun, 6 Nov 2022 09:12:12 -0500 Subject: refactor(tvix/eval): Define *all* pure builtins at the top-level Break out all pure builtin functions to top-level functions defined within the `pure_builtins` module in `builtins/mod.rs`. Change-Id: I9a10660446d557b1a86da4c45a463e9a1a9b4f2d Reviewed-on: https://cl.tvl.fyi/c/depot/+/7201 Reviewed-by: tazjin Reviewed-by: Adam Joseph Tested-by: BuildkiteCI --- tvix/eval/src/builtins/mod.rs | 1520 +++++++++++++++++++++-------------------- 1 file changed, 792 insertions(+), 728 deletions(-) (limited to 'tvix/eval/src') diff --git a/tvix/eval/src/builtins/mod.rs b/tvix/eval/src/builtins/mod.rs index 0ae43e3d38c1..caa575fcf3bc 100644 --- a/tvix/eval/src/builtins/mod.rs +++ b/tvix/eval/src/builtins/mod.rs @@ -59,781 +59,845 @@ mod pure_builtins { fn builtin_abort(_vm: &mut VM, message: Value) -> Result { Err(ErrorKind::Abort(message.to_str()?.to_string())) } -} -/// 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 { - let mut bs = vec![ - Builtin::new( - "add", - &[false, false], - |args: Vec, vm: &mut VM| arithmetic_op!(&*args[0].force(vm)?, &*args[1].force(vm)?, +), - ), - Builtin::new("all", &[true, true], |args: Vec, vm: &mut VM| { - for value in args[1].to_list()?.into_iter() { - let pred_result = vm.call_with(&args[0], [value])?; + #[builtin("add")] + fn builtin_add(vm: &mut VM, #[lazy] x: Value, #[lazy] y: Value) -> Result { + arithmetic_op!(&*x.force(vm)?, &*y.force(vm)?, +) + } - if !pred_result.force(vm)?.as_bool()? { - return Ok(Value::Bool(false)); - } + #[builtin("all")] + fn builtin_all(vm: &mut VM, pred: Value, list: Value) -> Result { + for value in list.to_list()?.into_iter() { + let pred_result = vm.call_with(&pred, [value])?; + + if !pred_result.force(vm)?.as_bool()? { + return Ok(Value::Bool(false)); } + } - Ok(Value::Bool(true)) - }), - Builtin::new("any", &[true, true], |args: Vec, vm: &mut VM| { - for value in args[1].to_list()?.into_iter() { - let pred_result = vm.call_with(&args[0], [value])?; + Ok(Value::Bool(true)) + } - if pred_result.force(vm)?.as_bool()? { - return Ok(Value::Bool(true)); - } + #[builtin("any")] + fn builtin_any(vm: &mut VM, pred: Value, list: Value) -> Result { + for value in list.to_list()?.into_iter() { + let pred_result = vm.call_with(&pred, [value])?; + + if pred_result.force(vm)?.as_bool()? { + return Ok(Value::Bool(true)); } + } - Ok(Value::Bool(false)) - }), - Builtin::new("attrNames", &[true], |args: Vec, _: &mut VM| { - let xs = args[0].to_attrs()?; - let mut output = Vec::with_capacity(xs.len()); + Ok(Value::Bool(false)) + } - for (key, _val) in xs.iter() { - output.push(Value::String(key.clone())); - } + #[builtin("attrNames")] + fn builtin_attr_names(_: &mut VM, set: Value) -> Result { + let xs = set.to_attrs()?; + let mut output = Vec::with_capacity(xs.len()); - Ok(Value::List(NixList::construct(output.len(), output))) - }), - Builtin::new("attrValues", &[true], |args: Vec, _: &mut VM| { - let xs = args[0].to_attrs()?; - let mut output = Vec::with_capacity(xs.len()); + for (key, _val) in xs.iter() { + output.push(Value::String(key.clone())); + } + + Ok(Value::List(NixList::construct(output.len(), output))) + } - for (_key, val) in xs.iter() { - output.push(val.clone()); + #[builtin("attrValues")] + fn builtin_attr_values(_: &mut VM, set: Value) -> Result { + 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")] + fn builtin_base_name_of(vm: &mut VM, s: Value) -> Result { + let s = s.coerce_to_string(CoercionKind::Weak, vm)?; + let result: String = s.rsplit_once('/').map(|(_, x)| x).unwrap_or(&s).into(); + Ok(result.into()) + } + + #[builtin("bitAnd")] + fn builtin_bit_and(_: &mut VM, x: Value, y: Value) -> Result { + Ok(Value::Integer(x.as_int()? & y.as_int()?)) + } + + #[builtin("bitOr")] + fn builtin_bit_or(_: &mut VM, x: Value, y: Value) -> Result { + Ok(Value::Integer(x.as_int()? | y.as_int()?)) + } + + #[builtin("bitXor")] + fn builtin_bit_xor(_: &mut VM, x: Value, y: Value) -> Result { + Ok(Value::Integer(x.as_int()? ^ y.as_int()?)) + } + + #[builtin("catAttrs")] + fn builtin_cat_attrs(vm: &mut VM, key: Value, list: Value) -> Result { + let key = key.to_str()?; + let list = list.to_list()?; + let mut output = vec![]; + + for item in list.into_iter() { + let set = item.force(vm)?.to_attrs()?; + if let Some(value) = set.select(key.as_str()) { + output.push(value.clone()); } + } - Ok(Value::List(NixList::construct(output.len(), output))) - }), - Builtin::new("baseNameOf", &[true], |args: Vec, vm: &mut VM| { - let s = args[0].coerce_to_string(CoercionKind::Weak, vm)?; - let result: String = s.rsplit_once('/').map(|(_, x)| x).unwrap_or(&s).into(); - Ok(result.into()) - }), - Builtin::new("bitAnd", &[true, true], |args: Vec, _: &mut VM| { - Ok(Value::Integer(args[0].as_int()? & args[1].as_int()?)) - }), - Builtin::new("bitOr", &[true, true], |args: Vec, _: &mut VM| { - Ok(Value::Integer(args[0].as_int()? | args[1].as_int()?)) - }), - Builtin::new("bitXor", &[true, true], |args: Vec, _: &mut VM| { - Ok(Value::Integer(args[0].as_int()? ^ args[1].as_int()?)) - }), - Builtin::new( - "catAttrs", - &[true, true], - |args: Vec, vm: &mut VM| { - let key = args[0].to_str()?; - let list = args[1].to_list()?; - let mut output = vec![]; - - for item in list.into_iter() { - let set = item.force(vm)?.to_attrs()?; - if let Some(value) = set.select(key.as_str()) { - output.push(value.clone()); - } - } + Ok(Value::List(NixList::construct(output.len(), output))) + } - Ok(Value::List(NixList::construct(output.len(), output))) - }, - ), - Builtin::new("ceil", &[true], |args: Vec, _: &mut VM| { - Ok(Value::Integer(args[0].as_float()?.ceil() as i64)) - }), - Builtin::new( - "compareVersions", - &[true, true], - |args: Vec, _: &mut VM| { - let s1 = args[0].to_str()?; - let s1 = VersionPartsIter::new_for_cmp(s1.as_str()); - let s2 = args[1].to_str()?; - let s2 = VersionPartsIter::new_for_cmp(s2.as_str()); - - 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::new("concatLists", &[true], |args: Vec, vm: &mut VM| { - let list = args[0].to_list()?; - let lists = list - .into_iter() - .map(|elem| { - let value = elem.force(vm)?; - value.to_list() - }) - .collect::, ErrorKind>>()?; + #[builtin("ceil")] + fn builtin_ceil(_: &mut VM, double: Value) -> Result { + Ok(Value::Integer(double.as_float()?.ceil() as i64)) + } - Ok(Value::List(NixList::from( - lists.into_iter().flatten().collect::>(), - ))) - }), - Builtin::new( - "concatMap", - &[true, true], - |args: Vec, vm: &mut VM| { - let list = args[1].to_list()?; - let mut res = Vec::new(); - for val in list { - res.extend(vm.call_with(&args[0], [val])?.force(vm)?.to_list()?); - } - Ok(Value::List(res.into())) - }, - ), - Builtin::new( - "concatStringsSep", - &[true, true], - |args: Vec, vm: &mut VM| { - let separator = args[0].to_str()?; - let list = args[1].to_list()?; - let mut res = String::new(); - for (i, val) in list.into_iter().enumerate() { - if i != 0 { - res.push_str(&separator); - } - res.push_str(&val.force(vm)?.coerce_to_string(CoercionKind::Weak, vm)?); - } - Ok(res.into()) - }, - ), - Builtin::new( - "deepSeq", - &[true, true], - |mut args: Vec, vm: &mut VM| { - let arg2 = args.pop().unwrap(); - let arg1 = args.pop().unwrap(); - arg1.deep_force(vm, &mut Default::default())?; - Ok(arg2) - }, - ), - Builtin::new( - "div", - &[false, false], - |args: Vec, vm: &mut VM| arithmetic_op!(&*args[0].force(vm)?, &*args[1].force(vm)?, /), - ), - Builtin::new("dirOf", &[true], |args: Vec, vm: &mut VM| { - let s = args[0].coerce_to_string(CoercionKind::Weak, vm)?; - let result = s - .rsplit_once('/') - .map(|(x, _)| match x { - "" => "/", - _ => x, - }) - .unwrap_or("."); - if args[0].is_path() { - Ok(Value::Path(result.into())) - } else { - Ok(result.into()) + #[builtin("compareVersions")] + fn builtin_compare_versions(_: &mut VM, x: Value, y: Value) -> Result { + let s1 = x.to_str()?; + let s1 = VersionPartsIter::new_for_cmp(s1.as_str()); + let s2 = y.to_str()?; + let s2 = VersionPartsIter::new_for_cmp(s2.as_str()); + + 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")] + fn builtin_concat_lists(vm: &mut VM, lists: Value) -> Result { + let list = lists.to_list()?; + let lists = list + .into_iter() + .map(|elem| { + let value = elem.force(vm)?; + value.to_list() + }) + .collect::, ErrorKind>>()?; + + Ok(Value::List(NixList::from( + lists.into_iter().flatten().collect::>(), + ))) + } + + #[builtin("concatMap")] + fn builtin_concat_map(vm: &mut VM, f: Value, list: Value) -> Result { + let list = list.to_list()?; + let mut res = Vec::new(); + for val in list { + res.extend(vm.call_with(&f, [val])?.force(vm)?.to_list()?); + } + Ok(Value::List(res.into())) + } + + #[builtin("concatStringsSep")] + fn builtin_concat_strings_sep( + vm: &mut VM, + separator: Value, + list: Value, + ) -> Result { + let separator = separator.to_str()?; + let list = list.to_list()?; + let mut res = String::new(); + for (i, val) in list.into_iter().enumerate() { + if i != 0 { + res.push_str(&separator); } - }), - Builtin::new("elem", &[true, true], |args: Vec, vm: &mut VM| { - for val in args[1].to_list()? { - if val.nix_eq(&args[0], vm)? { - return Ok(true.into()); - } + res.push_str(&val.force(vm)?.coerce_to_string(CoercionKind::Weak, vm)?); + } + Ok(res.into()) + } + + #[builtin("deepSeq")] + fn builtin_deep_seq(vm: &mut VM, x: Value, y: Value) -> Result { + x.deep_force(vm, &mut Default::default())?; + Ok(y) + } + + #[builtin("div")] + fn builtin_div(vm: &mut VM, #[lazy] x: Value, #[lazy] y: Value) -> Result { + arithmetic_op!(&*x.force(vm)?, &*y.force(vm)?, /) + } + + #[builtin("dirOf")] + fn builtin_dir_of(vm: &mut VM, s: Value) -> Result { + let str = s.coerce_to_string(CoercionKind::Weak, vm)?; + let result = str + .rsplit_once('/') + .map(|(x, _)| match x { + "" => "/", + _ => x, + }) + .unwrap_or("."); + if s.is_path() { + Ok(Value::Path(result.into())) + } else { + Ok(result.into()) + } + } + + #[builtin("elem")] + fn builtin_elem(vm: &mut VM, x: Value, xs: Value) -> Result { + for val in xs.to_list()? { + if val.nix_eq(&x, vm)? { + return Ok(true.into()); } - Ok(false.into()) - }), - Builtin::new("elemAt", &[true, true], |args: Vec, _: &mut VM| { - let xs = args[0].to_list()?; - let i = args[1].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 }), - } + } + Ok(false.into()) + } + + #[builtin("elemAt")] + fn builtin_elem_at(_: &mut VM, xs: Value, i: Value) -> Result { + 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::new("filter", &[true, true], |args: Vec, vm: &mut VM| { - let list: NixList = args[1].to_list()?; - - list.into_iter() - .filter_map(|elem| { - let result = match vm.call_with(&args[0], [elem.clone()]) { - Err(err) => return Some(Err(err)), - Ok(result) => result, - }; - - // Must be assigned to a local to avoid a borrowcheck - // failure related to the ForceResult destructor. - let result = match result.force(vm) { + } + } + + #[builtin("filter")] + fn builtin_filter(vm: &mut VM, pred: Value, list: Value) -> Result { + let list: NixList = list.to_list()?; + + list.into_iter() + .filter_map(|elem| { + let result = match vm.call_with(&pred, [elem.clone()]) { + Err(err) => return Some(Err(err)), + Ok(result) => result, + }; + + // Must be assigned to a local to avoid a borrowcheck + // failure related to the ForceResult destructor. + let result = match result.force(vm) { + Err(err) => Some(Err(vm.error(err))), + Ok(value) => match value.as_bool() { + Ok(true) => Some(Ok(elem)), + Ok(false) => None, Err(err) => Some(Err(vm.error(err))), - Ok(value) => match value.as_bool() { - Ok(true) => Some(Ok(elem)), - Ok(false) => None, - Err(err) => Some(Err(vm.error(err))), - }, - }; - - result + }, + }; + + result + }) + .collect::, _>>() + .map(|list| Value::List(NixList::from(list))) + .map_err(Into::into) + } + + #[builtin("floor")] + fn builtin_floor(_: &mut VM, double: Value) -> Result { + Ok(Value::Integer(double.as_float()?.floor() as i64)) + } + + #[builtin("foldl'")] + fn builtin_foldl( + vm: &mut VM, + op: Value, + #[lazy] mut nul: Value, + list: Value, + ) -> Result { + let list = list.to_list()?; + for val in list { + nul = vm.call_with(&op, [nul, val])?; + nul.force(vm)?; + } + + Ok(nul) + } + + #[builtin("functionArgs")] + fn builtin_function_args(_: &mut VM, f: Value) -> Result { + let lambda = f.to_closure()?.lambda(); + let formals = if let Some(formals) = &lambda.formals { + formals + } else { + return Ok(Value::attrs(NixAttrs::empty())); + }; + Ok(Value::attrs(NixAttrs::from_map( + formals + .arguments + .iter() + .map(|(k, v)| (k.clone(), (*v).into())) + .collect(), + ))) + } + + #[builtin("fromJSON")] + fn builtin_from_json(_: &mut VM, json: Value) -> Result { + let json_str = json.to_str()?; + let json: serde_json::Value = serde_json::from_str(&json_str)?; + json.try_into() + } + + #[builtin("genList")] + fn builtin_gen_list(vm: &mut VM, generator: Value, length: Value) -> Result { + let len = length.as_int()?; + (0..len) + .map(|i| vm.call_with(&generator, [i.into()])) + .collect::, _>>() + .map(|list| Value::List(NixList::from(list))) + .map_err(Into::into) + } + + #[builtin("getAttr")] + fn builtin_get_attr(_: &mut VM, key: Value, set: Value) -> Result { + let k = key.to_str()?; + let xs = set.to_attrs()?; + + match xs.select(k.as_str()) { + Some(x) => Ok(x.clone()), + None => Err(ErrorKind::AttributeNotFound { + name: k.to_string(), + }), + } + } + + #[builtin("groupBy")] + fn builtin_group_by(vm: &mut VM, f: Value, list: Value) -> Result { + let mut res: BTreeMap = BTreeMap::new(); + for val in list.to_list()? { + let key = vm.call_with(&f, [val.clone()])?.force(vm)?.to_str()?; + res.entry(key) + .or_insert_with(|| Value::List(NixList::new())) + .as_list_mut()? + .push(val); + } + Ok(Value::attrs(NixAttrs::from_map(res))) + } + + #[builtin("hasAttr")] + fn builtin_has_attr(_: &mut VM, key: Value, set: Value) -> Result { + let k = key.to_str()?; + let xs = set.to_attrs()?; + + Ok(Value::Bool(xs.contains(k.as_str()))) + } + + #[builtin("head")] + fn builtin_head(_: &mut VM, list: Value) -> Result { + match list.to_list()?.get(0) { + Some(x) => Ok(x.clone()), + None => Err(ErrorKind::IndexOutOfBounds { index: 0 }), + } + } + + #[builtin("intersectAttrs")] + fn builtin_intersect_attrs(_: &mut VM, x: Value, y: Value) -> Result { + let mut res = BTreeMap::new(); + let attrs1 = x.to_attrs()?; + let attrs2 = y.to_attrs()?; + for (k, v) in attrs2.iter() { + if attrs1.contains(k) { + res.insert(k.clone(), v.clone()); + } + } + Ok(Value::attrs(NixAttrs::from_map(res))) + } + + // For `is*` predicates we force manually, as Value::force also unwraps any Thunks + + #[builtin("isAttrs")] + fn builtin_is_attrs(vm: &mut VM, #[lazy] x: Value) -> Result { + let value = x.force(vm)?; + Ok(Value::Bool(matches!(*value, Value::Attrs(_)))) + } + + #[builtin("isBool")] + fn builtin_is_bool(vm: &mut VM, #[lazy] x: Value) -> Result { + let value = x.force(vm)?; + Ok(Value::Bool(matches!(*value, Value::Bool(_)))) + } + + #[builtin("isFloat")] + fn builtin_is_float(vm: &mut VM, #[lazy] x: Value) -> Result { + let value = x.force(vm)?; + Ok(Value::Bool(matches!(*value, Value::Float(_)))) + } + + #[builtin("isFunction")] + fn builtin_is_function(vm: &mut VM, #[lazy] x: Value) -> Result { + let value = x.force(vm)?; + Ok(Value::Bool(matches!( + *value, + Value::Closure(_) | Value::Builtin(_) + ))) + } + + #[builtin("isInt")] + fn builtin_is_int(vm: &mut VM, #[lazy] x: Value) -> Result { + let value = x.force(vm)?; + Ok(Value::Bool(matches!(*value, Value::Integer(_)))) + } + + #[builtin("isList")] + fn builtin_is_list(vm: &mut VM, #[lazy] x: Value) -> Result { + let value = x.force(vm)?; + Ok(Value::Bool(matches!(*value, Value::List(_)))) + } + + #[builtin("isNull")] + fn builtin_is_null(vm: &mut VM, #[lazy] x: Value) -> Result { + let value = x.force(vm)?; + Ok(Value::Bool(matches!(*value, Value::Null))) + } + + #[builtin("isPath")] + fn builtin_is_path(vm: &mut VM, #[lazy] x: Value) -> Result { + let value = x.force(vm)?; + Ok(Value::Bool(matches!(*value, Value::Path(_)))) + } + + #[builtin("isString")] + fn builtin_is_string(vm: &mut VM, #[lazy] x: Value) -> Result { + let value = x.force(vm)?; + Ok(Value::Bool(matches!(*value, Value::String(_)))) + } + + #[builtin("length")] + fn builtin_length(_: &mut VM, list: Value) -> Result { + Ok(Value::Integer(list.to_list()?.len() as i64)) + } + + #[builtin("lessThan")] + fn builtin_less_than( + vm: &mut VM, + #[lazy] x: Value, + #[lazy] y: Value, + ) -> Result { + Ok(Value::Bool(matches!( + x.force(vm)?.nix_cmp(&*y.force(vm)?, vm)?, + Some(Ordering::Less) + ))) + } + + #[builtin("listToAttrs")] + fn builtin_list_to_attrs(vm: &mut VM, list: Value) -> Result { + let list = list.to_list()?; + let mut map = BTreeMap::new(); + for val in list { + let attrs = val.force(vm)?.to_attrs()?; + let get = |key| { + attrs + .select(key) + .ok_or(ErrorKind::AttributeNotFound { name: key.into() }) + }; + let name = get("name")?.to_str()?; + let value = get("value")?.clone(); + // Map entries earlier in the list take precedence over entries later in the list + map.entry(name).or_insert(value); + } + Ok(Value::attrs(NixAttrs::from_map(map))) + } + + #[builtin("map")] + fn builtin_map(vm: &mut VM, f: Value, list: Value) -> Result { + let list: NixList = list.to_list()?; + + list.into_iter() + .map(|val| vm.call_with(&f, [val])) + .collect::, _>>() + .map(|list| Value::List(NixList::from(list))) + .map_err(Into::into) + } + + #[builtin("mapAttrs")] + fn builtin_map_attrs(vm: &mut VM, f: Value, attrs: Value) -> Result { + let attrs = attrs.to_attrs()?; + let mut res = BTreeMap::new(); + for (key, value) in attrs.as_ref() { + let value = vm.call_with(&f, [key.clone().into(), value.clone()])?; + res.insert(key.clone(), value); + } + Ok(Value::attrs(NixAttrs::from_map(res))) + } + + #[builtin("match")] + fn builtin_match(_: &mut VM, regex: Value, str: Value) -> Result { + let s = str.to_str()?; + let re = regex.to_str()?; + let re: Regex = Regex::new(&format!("^{}$", re.as_str())).unwrap(); + match re.captures(&s) { + Some(caps) => Ok(caps + .iter() + .skip(1) + .map(|grp| grp.map(|g| Value::from(g.as_str())).unwrap_or(Value::Null)) + .collect::>() + .into()), + None => Ok(Value::Null), + } + } + + #[builtin("mul")] + fn builtin_mul(vm: &mut VM, #[lazy] x: Value, #[lazy] y: Value) -> Result { + arithmetic_op!(&*x.force(vm)?, &*y.force(vm)?, *) + } + + #[builtin("parseDrvName")] + fn builtin_parse_drv_name(_vm: &mut VM, s: Value) -> Result { + // This replicates cppnix's (mis?)handling of codepoints + // above U+007f following 0x2d ('-') + let s = s.to_str()?; + let slice: &[u8] = s.as_str().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, }) - .collect::, _>>() - .map(|list| Value::List(NixList::from(list))) - .map_err(Into::into) - }), - Builtin::new("floor", &[true], |args: Vec, _: &mut VM| { - Ok(Value::Integer(args[0].as_float()?.floor() as i64)) - }), - Builtin::new( - "foldl'", - &[true, false, true], - |mut args: Vec, vm: &mut VM| { - let list = args.pop().unwrap().to_list()?; - let mut res = args.pop().unwrap(); - let op = args.pop().unwrap(); - for val in list { - res = vm.call_with(&op, [res, val])?; - res.force(vm)?; - } + .unwrap_or(slice.len()), + ); + let version = dash_and_version + .split_first() + .map(|x| core::str::from_utf8(x.1)) + .unwrap_or(Ok(""))? + .into(); + Ok(Value::attrs(NixAttrs::from_map(BTreeMap::from([ + (NixString::NAME, core::str::from_utf8(name)?.into()), + ("version".into(), version), + ])))) + } + #[builtin("partition")] + fn builtin_partition(vm: &mut VM, pred: Value, list: Value) -> Result { + let mut right: Vec = vec![]; + let mut wrong: Vec = vec![]; - Ok(res) - }, - ), - Builtin::new("functionArgs", &[true], |args: Vec, _: &mut VM| { - let lambda = args[0].to_closure()?.lambda(); - let formals = if let Some(formals) = &lambda.formals { - formals + let list: NixList = list.to_list()?; + for elem in list.into_iter() { + let result = vm.call_with(&pred, [elem.clone()])?; + + if result.force(vm)?.as_bool()? { + right.push(elem); } else { - return Ok(Value::attrs(NixAttrs::empty())); + wrong.push(elem); }; - Ok(Value::attrs(NixAttrs::from_map( - formals - .arguments - .iter() - .map(|(k, v)| (k.clone(), (*v).into())) - .collect(), - ))) - }), - Builtin::new("fromJSON", &[true], |args: Vec, _: &mut VM| { - let json_str = args[0].to_str()?; - let json: serde_json::Value = serde_json::from_str(&json_str)?; - json.try_into() - }), - Builtin::new("genList", &[true, true], |args: Vec, vm: &mut VM| { - let len = args[1].as_int()?; - (0..len) - .map(|i| vm.call_with(&args[0], [i.into()])) - .collect::, _>>() - .map(|list| Value::List(NixList::from(list))) - .map_err(Into::into) - }), - Builtin::new("getAttr", &[true, true], |args: Vec, _: &mut VM| { - let k = args[0].to_str()?; - let xs = args[1].to_attrs()?; + } - match xs.select(k.as_str()) { - Some(x) => Ok(x.clone()), - None => Err(ErrorKind::AttributeNotFound { - name: k.to_string(), - }), - } - }), - Builtin::new("groupBy", &[true, true], |args: Vec, vm: &mut VM| { - let mut res: BTreeMap = BTreeMap::new(); - for val in args[1].to_list()? { - let key = vm.call_with(&args[0], [val.clone()])?.force(vm)?.to_str()?; - res.entry(key) - .or_insert_with(|| Value::List(NixList::new())) - .as_list_mut()? - .push(val); - } - Ok(Value::attrs(NixAttrs::from_map(res))) - }), - Builtin::new("hasAttr", &[true, true], |args: Vec, _: &mut VM| { - let k = args[0].to_str()?; - let xs = args[1].to_attrs()?; + let mut res: BTreeMap = BTreeMap::new(); + res.insert("right".into(), Value::List(right.into())); + res.insert("wrong".into(), Value::List(wrong.into())); - Ok(Value::Bool(xs.contains(k.as_str()))) - }), - Builtin::new("head", &[true], |args: Vec, _: &mut VM| { - match args[0].to_list()?.get(0) { - Some(x) => Ok(x.clone()), - None => Err(ErrorKind::IndexOutOfBounds { index: 0 }), - } - }), - Builtin::new( - "intersectAttrs", - &[true, true], - |args: Vec, _: &mut VM| { - let mut res = BTreeMap::new(); - let attrs1 = args[0].to_attrs()?; - let attrs2 = args[1].to_attrs()?; - for (k, v) in attrs2.iter() { - if attrs1.contains(k) { - res.insert(k.clone(), v.clone()); - } - } - Ok(Value::attrs(NixAttrs::from_map(res))) - }, - ), - // For `is*` predicates we force manually, as Value::force also unwraps any Thunks - Builtin::new("isAttrs", &[false], |args: Vec, vm: &mut VM| { - let value = args[0].force(vm)?; - Ok(Value::Bool(matches!(*value, Value::Attrs(_)))) - }), - Builtin::new("isBool", &[false], |args: Vec, vm: &mut VM| { - let value = args[0].force(vm)?; - Ok(Value::Bool(matches!(*value, Value::Bool(_)))) - }), - Builtin::new("isFloat", &[false], |args: Vec, vm: &mut VM| { - let value = args[0].force(vm)?; - Ok(Value::Bool(matches!(*value, Value::Float(_)))) - }), - Builtin::new("isFunction", &[false], |args: Vec, vm: &mut VM| { - let value = args[0].force(vm)?; - Ok(Value::Bool(matches!( - *value, - Value::Closure(_) | Value::Builtin(_) - ))) - }), - Builtin::new("isInt", &[false], |args: Vec, vm: &mut VM| { - let value = args[0].force(vm)?; - Ok(Value::Bool(matches!(*value, Value::Integer(_)))) - }), - Builtin::new("isList", &[false], |args: Vec, vm: &mut VM| { - let value = args[0].force(vm)?; - Ok(Value::Bool(matches!(*value, Value::List(_)))) - }), - Builtin::new("isNull", &[false], |args: Vec, vm: &mut VM| { - let value = args[0].force(vm)?; - Ok(Value::Bool(matches!(*value, Value::Null))) - }), - Builtin::new("isPath", &[false], |args: Vec, vm: &mut VM| { - let value = args[0].force(vm)?; - Ok(Value::Bool(matches!(*value, Value::Path(_)))) - }), - Builtin::new("isString", &[false], |args: Vec, vm: &mut VM| { - let value = args[0].force(vm)?; - Ok(Value::Bool(matches!(*value, Value::String(_)))) - }), - Builtin::new("length", &[true], |args: Vec, _: &mut VM| { - Ok(Value::Integer(args[0].to_list()?.len() as i64)) - }), - Builtin::new( - "lessThan", - &[false, false], - |args: Vec, vm: &mut VM| { - Ok(Value::Bool(matches!( - args[0].force(vm)?.nix_cmp(&*args[1].force(vm)?, vm)?, - Some(Ordering::Less) - ))) - }, - ), - Builtin::new("listToAttrs", &[true], |args: Vec, vm: &mut VM| { - let list = args[0].to_list()?; - let mut map = BTreeMap::new(); - for val in list { - let attrs = val.force(vm)?.to_attrs()?; - let get = |key| { - attrs - .select(key) - .ok_or(ErrorKind::AttributeNotFound { name: key.into() }) - }; - let name = get("name")?.to_str()?; - let value = get("value")?.clone(); - // Map entries earlier in the list take precedence over entries later in the list - map.entry(name).or_insert(value); - } - Ok(Value::attrs(NixAttrs::from_map(map))) - }), - Builtin::new("map", &[true, true], |args: Vec, vm: &mut VM| { - let list: NixList = args[1].to_list()?; - - list.into_iter() - .map(|val| vm.call_with(&args[0], [val])) - .collect::, _>>() - .map(|list| Value::List(NixList::from(list))) - .map_err(Into::into) - }), - Builtin::new( - "mapAttrs", - &[true, true], - |args: Vec, vm: &mut VM| { - let attrs = args[1].to_attrs()?; - let mut res = BTreeMap::new(); - for (key, value) in attrs.as_ref() { - let value = vm.call_with(&args[0], [key.clone().into(), value.clone()])?; - res.insert(key.clone(), value); - } - Ok(Value::attrs(NixAttrs::from_map(res))) - }, - ), - Builtin::new( - "match", - &[true, true], - |mut args: Vec, _: &mut VM| { - let s = args.pop().unwrap().to_str()?; - let re = args.pop().unwrap().to_str()?; - let re: Regex = Regex::new(&format!("^{}$", re.as_str())).unwrap(); - match re.captures(&s) { - Some(caps) => Ok(caps - .iter() - .skip(1) - .map(|grp| grp.map(|g| Value::from(g.as_str())).unwrap_or(Value::Null)) - .collect::>() - .into()), - None => Ok(Value::Null), - } - }, - ), - Builtin::new( - "mul", - &[false, false], - |args: Vec, vm: &mut VM| arithmetic_op!(&*args[0].force(vm)?, &*args[1].force(vm)?, *), - ), - Builtin::new("parseDrvName", &[true], |args: Vec, _vm: &mut VM| { - // This replicates cppnix's (mis?)handling of codepoints - // above U+007f following 0x2d ('-') - let s = args[0].to_str()?; - let slice: &[u8] = s.as_str().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(""))? - .into(); - Ok(Value::attrs(NixAttrs::from_map(BTreeMap::from([ - (NixString::NAME, core::str::from_utf8(name)?.into()), - ("version".into(), version), - ])))) - }), - Builtin::new( - "partition", - &[true, true], - |args: Vec, vm: &mut VM| { - let mut right: Vec = vec![]; - let mut wrong: Vec = vec![]; - - let list: NixList = args[1].to_list()?; - for elem in list.into_iter() { - let result = vm.call_with(&args[0], [elem.clone()])?; - - if result.force(vm)?.as_bool()? { - right.push(elem); - } else { - wrong.push(elem); - }; - } + Ok(Value::attrs(NixAttrs::from_map(res))) + } - let mut res: BTreeMap = BTreeMap::new(); - res.insert("right".into(), Value::List(right.into())); - res.insert("wrong".into(), Value::List(wrong.into())); + #[builtin("removeAttrs")] + fn builtin_remove_attrs(_: &mut VM, attrs: Value, keys: Value) -> Result { + let attrs = attrs.to_attrs()?; + let keys = keys + .to_list()? + .into_iter() + .map(|v| v.to_str()) + .collect::, _>>()?; + let mut res = BTreeMap::new(); + for (k, v) in attrs.iter() { + if !keys.contains(k) { + res.insert(k.clone(), v.clone()); + } + } + Ok(Value::attrs(NixAttrs::from_map(res))) + } - Ok(Value::attrs(NixAttrs::from_map(res))) - }, - ), - Builtin::new( - "removeAttrs", - &[true, true], - |args: Vec, _: &mut VM| { - let attrs = args[0].to_attrs()?; - let keys = args[1] - .to_list()? - .into_iter() - .map(|v| v.to_str()) - .collect::, _>>()?; - let mut res = BTreeMap::new(); - for (k, v) in attrs.iter() { - if !keys.contains(k) { - res.insert(k.clone(), v.clone()); - } + #[builtin("replaceStrings")] + fn builtin_replace_strings( + vm: &mut VM, + from: Value, + to: Value, + s: Value, + ) -> Result { + let from = from.to_list()?; + from.force_elements(vm)?; + let to = to.to_list()?; + to.force_elements(vm)?; + let string = s.to_str()?; + + let mut res = String::new(); + + let mut i: usize = 0; + let mut empty_string_replace = false; + + // This can't be implemented using Rust's string.replace() as + // well as a map because we need to handle errors with results + // as well as "reset" the iterator to zero for the replacement + // everytime there's a successful match. + // Also, Rust's string.replace allocates a new string + // on every call which is not preferable. + 'outer: while i < string.len() { + // Try a match in all the from strings + for elem in std::iter::zip(from.iter(), to.iter()) { + let from = elem.0.to_str()?; + let to = elem.1.to_str()?; + + if i + from.len() >= string.len() { + continue; } - Ok(Value::attrs(NixAttrs::from_map(res))) - }, - ), - Builtin::new( - "replaceStrings", - &[true, true, true], - |args: Vec, vm: &mut VM| { - let from = args[0].to_list()?; - from.force_elements(vm)?; - let to = args[1].to_list()?; - to.force_elements(vm)?; - let string = args[2].to_str()?; - - let mut res = String::new(); - - let mut i: usize = 0; - let mut empty_string_replace = false; - - // This can't be implemented using Rust's string.replace() as - // well as a map because we need to handle errors with results - // as well as "reset" the iterator to zero for the replacement - // everytime there's a successful match. - // Also, Rust's string.replace allocates a new string - // on every call which is not preferable. - 'outer: while i < string.len() { - // Try a match in all the from strings - for elem in std::iter::zip(from.iter(), to.iter()) { - let from = elem.0.to_str()?; - let to = elem.1.to_str()?; - - if i + from.len() >= string.len() { - continue; - } - - // We already applied a from->to with an empty from - // transformation. - // Let's skip it so that we don't loop infinitely - if empty_string_replace && from.as_str().len() == 0 { - continue; - } - - // if we match the `from` string, let's replace - if &string[i..i + from.len()] == from.as_str() { - res += &to; - i += from.len(); - - // remember if we applied the empty from->to - empty_string_replace = from.as_str().len() == 0; - - continue 'outer; - } - } - - // If we don't match any `from`, we simply add a character - res += &string[i..i + 1]; - i += 1; - - // Since we didn't apply anything transformation, - // we reset the empty string replacement - empty_string_replace = false; + + // We already applied a from->to with an empty from + // transformation. + // Let's skip it so that we don't loop infinitely + if empty_string_replace && from.as_str().len() == 0 { + continue; } - // Special case when the string is empty or at the string's end - // and one of the from is also empty - for elem in std::iter::zip(from.iter(), to.iter()) { - let from = elem.0.to_str()?; - let to = elem.1.to_str()?; + // if we match the `from` string, let's replace + if &string[i..i + from.len()] == from.as_str() { + res += &to; + i += from.len(); - if from.as_str().len() == 0 { - res += &to; - break; - } - } - Ok(Value::String(res.into())) - }, - ), - Builtin::new("seq", &[true, true], |mut args: Vec, _: &mut VM| { - // The builtin calling infra has already forced both args for us, so - // we just return the second and ignore the first - Ok(args.pop().unwrap()) - }), - Builtin::new( - "split", - &[true, true], - |mut args: Vec, _: &mut VM| { - let s = args.pop().unwrap().to_str()?; - let text = s.as_str(); - let re = args.pop().unwrap().to_str()?; - let re: Regex = Regex::new(re.as_str()).unwrap(); - let mut capture_locations = re.capture_locations(); - let num_captures = capture_locations.len(); - let mut ret = NixList::new(); - let mut pos = 0; - - while let Some(thematch) = re.captures_read_at(&mut capture_locations, text, pos) { - // push the unmatched characters preceding the match - ret.push(Value::from(&text[pos..thematch.start()])); - - // Push a list with one element for each capture - // group in the regex, containing the characters - // matched by that capture group, or null if no match. - // We skip capture 0; it represents the whole match. - let v: Vec = (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))); - pos = thematch.end(); + // remember if we applied the empty from->to + empty_string_replace = from.as_str().len() == 0; + + continue 'outer; } + } - // push the unmatched characters following the last match - ret.push(Value::from(&text[pos..])); + // If we don't match any `from`, we simply add a character + res += &string[i..i + 1]; + i += 1; - Ok(Value::List(ret)) - }, - ), - Builtin::new("sort", &[true, true], |args: Vec, vm: &mut VM| { - let mut list = args[1].to_list()?; - let comparator = &args[0]; - - // Used to let errors "escape" from the sorting closure. If anything - // ends up setting an error, it is returned from this function. - let mut error: Option = None; - - list.sort_by(|lhs, rhs| { - let result = vm - .call_with(comparator, [lhs.clone(), rhs.clone()]) - .map_err(|err| ErrorKind::ThunkForce(Box::new(err))) - .and_then(|v| v.force(vm)?.as_bool()); - - match (&error, result) { - // The contained closure only returns a "less - // than?"-boolean, no way to yield "equal". - (None, Ok(true)) => Ordering::Less, - (None, Ok(false)) => Ordering::Greater, - - // Closest thing to short-circuiting out if an error was - // thrown. - (Some(_), _) => Ordering::Equal, - - // Propagate the error if one was encountered. - (_, Err(e)) => { - error = Some(e); - Ordering::Equal - } - } - }); + // Since we didn't apply anything transformation, + // we reset the empty string replacement + empty_string_replace = false; + } + + // Special case when the string is empty or at the string's end + // and one of the from is also empty + for elem in std::iter::zip(from.iter(), to.iter()) { + let from = elem.0.to_str()?; + let to = elem.1.to_str()?; - match error { - None => Ok(Value::List(list)), - Some(e) => Err(e), + if from.as_str().len() == 0 { + res += &to; + break; } - }), - Builtin::new("splitVersion", &[true], |args: Vec, _: &mut VM| { - let s = args[0].to_str()?; - let s = VersionPartsIter::new(s.as_str()); - - let parts = s - .map(|s| { - Value::String(match s { - VersionPart::Number(n) => n.into(), - VersionPart::Word(w) => w.into(), - }) + } + Ok(Value::String(res.into())) + } + + #[builtin("seq")] + fn builtin_seq(_: &mut VM, _x: Value, y: Value) -> Result { + // The builtin calling infra has already forced both args for us, so + // we just return the second and ignore the first + Ok(y) + } + + #[builtin("split")] + fn builtin_split(_: &mut VM, regex: Value, str: Value) -> Result { + let s = str.to_str()?; + let text = s.as_str(); + let re = regex.to_str()?; + let re: Regex = Regex::new(re.as_str()).unwrap(); + let mut capture_locations = re.capture_locations(); + let num_captures = capture_locations.len(); + let mut ret = NixList::new(); + let mut pos = 0; + + while let Some(thematch) = re.captures_read_at(&mut capture_locations, text, pos) { + // push the unmatched characters preceding the match + ret.push(Value::from(&text[pos..thematch.start()])); + + // Push a list with one element for each capture + // group in the regex, containing the characters + // matched by that capture group, or null if no match. + // We skip capture 0; it represents the whole match. + let v: Vec = (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::>(); - Ok(Value::List(NixList::construct(parts.len(), parts))) - }), - Builtin::new("stringLength", &[false], |args: Vec, vm: &mut VM| { - // also forces the value - let s = args[0].coerce_to_string(CoercionKind::Weak, vm)?; - Ok(Value::Integer(s.as_str().len() as i64)) - }), - Builtin::new( - "sub", - &[false, false], - |args: Vec, vm: &mut VM| arithmetic_op!(&*args[0].force(vm)?, &*args[1].force(vm)?, -), - ), - Builtin::new( - "substring", - &[true, true, true], - |args: Vec, _: &mut VM| { - let beg = args[0].as_int()?; - let len = args[1].as_int()?; - let x = args[2].to_str()?; - - if beg < 0 { - return Err(ErrorKind::IndexOutOfBounds { index: beg }); - } - let beg = beg as usize; + .collect(); + ret.push(Value::List(NixList::from(v))); + pos = thematch.end(); + } - // Nix doesn't assert that the length argument is - // non-negative when the starting index is GTE the - // string's length. - if beg >= x.as_str().len() { - return Ok(Value::String("".into())); - } + // push the unmatched characters following the last match + ret.push(Value::from(&text[pos..])); - if len < 0 { - return Err(ErrorKind::NegativeLength { length: len }); + Ok(Value::List(ret)) + } + + #[builtin("sort")] + fn builtin_sort(vm: &mut VM, comparator: Value, list: Value) -> Result { + let mut list = list.to_list()?; + + // Used to let errors "escape" from the sorting closure. If anything + // ends up setting an error, it is returned from this function. + let mut error: Option = None; + + list.sort_by(|lhs, rhs| { + let result = vm + .call_with(&comparator, [lhs.clone(), rhs.clone()]) + .map_err(|err| ErrorKind::ThunkForce(Box::new(err))) + .and_then(|v| v.force(vm)?.as_bool()); + + match (&error, result) { + // The contained closure only returns a "less + // than?"-boolean, no way to yield "equal". + (None, Ok(true)) => Ordering::Less, + (None, Ok(false)) => Ordering::Greater, + + // Closest thing to short-circuiting out if an error was + // thrown. + (Some(_), _) => Ordering::Equal, + + // Propagate the error if one was encountered. + (_, Err(e)) => { + error = Some(e); + Ordering::Equal } + } + }); - let len = len as usize; - let end = cmp::min(beg + len, x.as_str().len()); + match error { + None => Ok(Value::List(list)), + Some(e) => Err(e), + } + } - Ok(Value::String( - x.as_str()[(beg as usize)..(end as usize)].into(), - )) - }, - ), - Builtin::new("tail", &[true], |args: Vec, _: &mut VM| { - let xs = args[0].to_list()?; + #[builtin("splitVersion")] + fn builtin_split_version(_: &mut VM, s: Value) -> Result { + let s = s.to_str()?; + let s = VersionPartsIter::new(s.as_str()); - if xs.len() == 0 { - Err(ErrorKind::TailEmptyList) - } else { - let output = xs.into_iter().skip(1).collect::>(); - Ok(Value::List(NixList::construct(output.len(), output))) - } - }), - Builtin::new("throw", &[true], |args: Vec, _: &mut VM| { - Err(ErrorKind::Throw(args[0].to_str()?.to_string())) - }), + let parts = s + .map(|s| { + Value::String(match s { + VersionPart::Number(n) => n.into(), + VersionPart::Word(w) => w.into(), + }) + }) + .collect::>(); + Ok(Value::List(NixList::construct(parts.len(), parts))) + } + + #[builtin("stringLength")] + fn builtin_string_length(vm: &mut VM, #[lazy] s: Value) -> Result { + // also forces the value + let s = s.coerce_to_string(CoercionKind::Weak, vm)?; + Ok(Value::Integer(s.as_str().len() as i64)) + } + + #[builtin("sub")] + fn builtin_sub(vm: &mut VM, #[lazy] x: Value, #[lazy] y: Value) -> Result { + arithmetic_op!(&*x.force(vm)?, &*y.force(vm)?, -) + } + + #[builtin("substring")] + fn builtin_substring( + _: &mut VM, + start: Value, + len: Value, + s: Value, + ) -> Result { + let beg = start.as_int()?; + let len = len.as_int()?; + let x = s.to_str()?; + + if beg < 0 { + return Err(ErrorKind::IndexOutOfBounds { index: beg }); + } + let beg = beg as usize; + + // Nix doesn't assert that the length argument is + // non-negative when the starting index is GTE the + // string's length. + if beg >= x.as_str().len() { + return Ok(Value::String("".into())); + } + + if len < 0 { + return Err(ErrorKind::NegativeLength { length: len }); + } + + let len = len as usize; + let end = cmp::min(beg + len, x.as_str().len()); + + Ok(Value::String( + x.as_str()[(beg as usize)..(end as usize)].into(), + )) + } + + #[builtin("tail")] + fn builtin_tail(_: &mut VM, list: Value) -> Result { + let xs = list.to_list()?; + + if xs.len() == 0 { + Err(ErrorKind::TailEmptyList) + } else { + let output = xs.into_iter().skip(1).collect::>(); + Ok(Value::List(NixList::construct(output.len(), output))) + } + } + + #[builtin("throw")] + fn builtin_throw(_: &mut VM, message: Value) -> Result { + Err(ErrorKind::Throw(message.to_str()?.to_string())) + } + + #[builtin("toString")] + fn builtin_to_string(vm: &mut VM, #[lazy] x: Value) -> Result { // coerce_to_string forces for us - Builtin::new("toString", &[false], |args: Vec, vm: &mut VM| { - args[0] - .coerce_to_string(CoercionKind::Strong, vm) - .map(Value::String) - }), - Builtin::new( - "trace", - &[true, true], - |mut args: Vec, _: &mut VM| { - let value = args.pop().unwrap(); - let trace_value = args.pop().unwrap(); - // TODO(grfn): `trace` should be pluggable and capturable, probably via a method on - // the VM - println!("trace: {} :: {}", trace_value, trace_value.type_of()); - Ok(value) - }, - ), - Builtin::new("tryEval", &[false], |args: Vec, vm: &mut VM| { - let mut res = BTreeMap::new(); - match args[0].force(vm) { - Ok(value) => { - res.insert("value".into(), (*value).clone()); - res.insert("success".into(), true.into()); - } - Err(e) if e.is_catchable() => { - res.insert("value".into(), false.into()); - res.insert("success".into(), false.into()); - } - Err(e) => return Err(e), + x.coerce_to_string(CoercionKind::Strong, vm) + .map(Value::String) + } + + #[builtin("trace")] + fn builtin_trace(_: &mut VM, message: Value, value: Value) -> Result { + // TODO(grfn): `trace` should be pluggable and capturable, probably via a method on + // the VM + println!("trace: {} :: {}", message, message.type_of()); + Ok(value) + } + + #[builtin("toPath")] + fn builtin_to_path(vm: &mut VM, #[lazy] s: Value) -> Result { + let path: Value = crate::value::canon_path(coerce_value_to_path(&s, vm)?).into(); + Ok(path.coerce_to_string(CoercionKind::Weak, vm)?.into()) + } + + #[builtin("tryEval")] + fn builtin_try_eval(vm: &mut VM, #[lazy] e: Value) -> Result { + let mut res = BTreeMap::new(); + match e.force(vm) { + Ok(value) => { + res.insert("value".into(), (*value).clone()); + res.insert("success".into(), true.into()); } - Ok(Value::attrs(NixAttrs::from_map(res))) - }), - Builtin::new("toPath", &[false], |args: Vec, vm: &mut VM| { - let path: Value = crate::value::canon_path(coerce_value_to_path(&args[0], vm)?).into(); - Ok(path.coerce_to_string(CoercionKind::Weak, vm)?.into()) - }), - Builtin::new("typeOf", &[false], |args: Vec, vm: &mut VM| { - // We force manually here because it also unwraps the Thunk - // representation, if any. - // TODO(sterni): it'd be nice if we didn't have to worry about this - let value = args[0].force(vm)?; - Ok(Value::String(value.type_of().into())) - }), - ]; + Err(e) if e.is_catchable() => { + res.insert("value".into(), false.into()); + res.insert("success".into(), false.into()); + } + Err(e) => return Err(e), + } + Ok(Value::attrs(NixAttrs::from_map(res))) + } - bs.extend(pure_builtins::builtins()); - bs + #[builtin("typeOf")] + fn builtin_type_of(vm: &mut VM, #[lazy] x: Value) -> Result { + // We force manually here because it also unwraps the Thunk + // representation, if any. + // TODO(sterni): it'd be nice if we didn't have to worry about this + let value = x.force(vm)?; + Ok(Value::String(value.type_of().into())) + } } +pub use pure_builtins::builtins as pure_builtins; + /// Placeholder builtins that technically have a function which we do /// not yet implement, but which is also not easily observable from /// within a pure evaluation context. -- cgit 1.4.1