Bitwise `and` on integers. Change-Id: I9f2a9182a057af26906683acd97a40dfabbdded8 Reviewed-on: https://cl.tvl.fyi/c/depot/+/6548 Reviewed-by: wpcarro <wpcarro@gmail.com> Autosubmit: wpcarro <wpcarro@gmail.com> Reviewed-by: tazjin <tazjin@tvl.su> Tested-by: BuildkiteCI
		
			
				
	
	
		
			244 lines
		
	
	
	
		
			7.9 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			244 lines
		
	
	
	
		
			7.9 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| //! 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},
 | |
|     path::PathBuf,
 | |
|     rc::Rc,
 | |
| };
 | |
| 
 | |
| use crate::{
 | |
|     errors::ErrorKind,
 | |
|     value::{Builtin, CoercionKind, NixAttrs, NixList, NixString, Value},
 | |
|     vm::VM,
 | |
| };
 | |
| 
 | |
| 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)
 | |
|     };
 | |
| }
 | |
| 
 | |
| /// Coerce a Nix Value to a plain path, e.g. in order to access the file it
 | |
| /// points to in an I/O builtin. This coercion can _never_ be performed in
 | |
| /// a Nix program directly (i.e. the trick `path: /. + path` to convert from
 | |
| /// a string to a path wouldn't hit this code), so the target file
 | |
| /// doesn't need to be realised or imported into the Nix store.
 | |
| pub fn coerce_value_to_path(v: &Value, vm: &mut VM) -> Result<PathBuf, ErrorKind> {
 | |
|     force!(vm, v, value, {
 | |
|         match value {
 | |
|             Value::Thunk(t) => coerce_value_to_path(&t.value(), vm),
 | |
|             Value::Path(p) => Ok(p.clone()),
 | |
|             _ => value
 | |
|                 .coerce_to_string(CoercionKind::Weak, vm)
 | |
|                 .map(|s| PathBuf::from(s.as_str()))
 | |
|                 .and_then(|path| {
 | |
|                     if path.is_absolute() {
 | |
|                         Ok(path)
 | |
|                     } else {
 | |
|                         Err(ErrorKind::NotAnAbsolutePath(path))
 | |
|                     }
 | |
|                 }),
 | |
|         }
 | |
|     })
 | |
| }
 | |
| 
 | |
| /// 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("attrNames", 1, |args, vm| {
 | |
|             force!(vm, &args[0], value, {
 | |
|                 let xs = value.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)))
 | |
|             })
 | |
|         }),
 | |
|         Builtin::new("attrValues", 1, |args, vm| {
 | |
|             force!(vm, &args[0], value, {
 | |
|                 let xs = value.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::new("bitAnd", 2, |args, vm| {
 | |
|             force!(vm, &args[0], x, {
 | |
|                 force!(vm, &args[1], y, {
 | |
|                     Ok(Value::Integer(x.as_int()? & y.as_int()?))
 | |
|                 })
 | |
|             })
 | |
|         }),
 | |
|         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("head", 1, |args, vm| {
 | |
|             force!(vm, &args[0], xs, {
 | |
|                 match xs.to_list()?.get(0) {
 | |
|                     Some(x) => Ok(x.clone()),
 | |
|                     None => Err(ErrorKind::IndexOutOfBounds { index: 0 }),
 | |
|                 }
 | |
|             })
 | |
|         }),
 | |
|         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
 | |
| }
 |