diff --git a/Cargo.lock b/Cargo.lock index 3e5587d..a55f155 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "aho-corasick" version = "0.7.15" @@ -76,6 +78,15 @@ dependencies = [ "constant_time_eq", ] +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + [[package]] name = "bstr" version = "0.2.15" @@ -133,6 +144,15 @@ version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "328b822bdcba4d4e402be8d9adb6eebf269f969f8eadef977a553ff3c4fbcb58" +[[package]] +name = "cpufeatures" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "281f563b2c3a0e535ab12d81d3c5859045795256ad269afa7c19542585b68f93" +dependencies = [ + "libc", +] + [[package]] name = "crossbeam-channel" version = "0.3.9" @@ -184,6 +204,15 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + [[package]] name = "dirs" version = "2.0.2" @@ -234,6 +263,37 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "gc" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3edaac0f5832202ebc99520cb77c932248010c4645d20be1dc62d6579f5b3752" +dependencies = [ + "gc_derive", +] + +[[package]] +name = "gc_derive" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60df8444f094ff7885631d80e78eb7d88c3c2361a98daaabb06256e4500db941" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.1.16" @@ -421,12 +481,24 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "percent-encoding" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "posix-regex" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0df8665cbe91ba9b18af9b0e7a36f02d00ce1123cf8d4b8a3a1104e932cdd341" + [[package]] name = "proc-macro2" version = "1.0.24" @@ -503,17 +575,20 @@ version = "0.1.0" dependencies = [ "dirs", "env_logger", + "gc", "lazy_static", "libc", "log", "lsp-server", "lsp-types", "nixpkgs-fmt", + "posix-regex", "regex", "rnix", "rowan", "serde", "serde_json", + "sha2", ] [[package]] @@ -605,6 +680,19 @@ dependencies = [ "syn", ] +[[package]] +name = "sha2" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b362ae5752fd2137731f9fa25fd4d9058af34666ca1966fb969119cc35719f12" +dependencies = [ + "block-buffer", + "cfg-if 1.0.0", + "cpufeatures", + "digest", + "opaque-debug", +] + [[package]] name = "smol_str" version = "0.1.17" @@ -628,6 +716,18 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "synstructure" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + [[package]] name = "termcolor" version = "1.1.2" @@ -679,6 +779,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +[[package]] +name = "typenum" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" + [[package]] name = "unicode-bidi" version = "0.3.4" @@ -728,6 +834,12 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +[[package]] +name = "version_check" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" + [[package]] name = "walkdir" version = "2.3.1" diff --git a/Cargo.toml b/Cargo.toml index ebd71f3..895b2a8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,14 +13,17 @@ version = "0.1.0" [dependencies] dirs = "2.0.2" env_logger = "0.7.1" +gc = { version = "0.4", features = ["derive"] } lazy_static = "1.4" libc = "0.2.66" log = "0.4.8" lsp-server = "0.3.1" lsp-types = { version = "0.68.1", features = ["proposed"] } nixpkgs-fmt = "1.2.0" +posix-regex = "0.1.1" regex = "1" rnix = "0.9.0" rowan = "0.12.6" serde = "1.0.104" serde_json = "1.0.44" +sha2 = "0.9.3" diff --git a/src/builtins.rs b/src/builtins.rs new file mode 100644 index 0000000..a7f69e1 --- /dev/null +++ b/src/builtins.rs @@ -0,0 +1,686 @@ +use crate::{ + eval::{eval_lambda, expand_path, Tree, TreeSource}, + scope::Scope, + value::*, + EvalError, +}; +use gc::{Finalize, Gc, GcCell, Trace}; +use rnix::types::{TokenWrapper, TypedNode, Wrapper}; +use std::{borrow::Borrow, collections::HashMap, path::PathBuf, str::FromStr}; + +#[macro_export] +macro_rules! maybe_push { + ($map:ident ; $description:expr ; $kind:ident) => { + let item = NixBuiltin::$kind; + let tree = Gc::new(Tree { + value: GcCell::new(None), + hash: GcCell::new(None), + source: TreeSource::Literal { + value: NixValue::Lambda(NixLambda::Builtin(item)), + }, + range: None, + scope: Gc::new(Scope::None), + }); + $map.insert($description.to_string(), tree); + }; + ($map:ident ; $description:expr ; $kind:ident$(($($member:ty),*))?) => {}; +} + +#[macro_export] +macro_rules! builtins { + ($($description:expr ; $kind:ident$(($($varname:ident: $member:ty),*))? => $body:expr)*) => { + #[derive(Clone, Trace, Finalize)] + pub enum NixBuiltin { + $( + $kind$(($($member),*))? + ),* + } + + pub fn make_builtins_map() -> NixValue { + let mut map = HashMap::>::new(); + + $( + maybe_push! { map ; $description ; $kind$(($($member),*))? } + )* + + map.insert("nixVersion".into(), Gc::new(Tree::from_concrete( + NixValue::Str("2.2".into())))); + map.insert("currentSystem".into(), Gc::new(Tree::from_concrete( + NixValue::Str("x86_64-linux".into())))); + + NixValue::Map(map) + } + + impl NixBuiltin { + pub fn call(&self, param: Gc) -> Result, EvalError> { + match self { + $( + Self::$kind$(($($varname),*))? => $body(param $($(,$varname)*)?), + )* + } + } + } + + impl std::fmt::Debug for NixBuiltin { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + $( + Self::$kind$(($($varname),*))? => write!(f, $description), + )* + } + } + } + } +} + +type GcLazy = Gc; + +builtins! { + "abort" ; Abort => |_param| { + Err(EvalError::Unimplemented("abort".to_string())) + } + "attrNames" ; AttrNames => |param: Gc| { + match param.eval()?.borrow() { + NixValue::Map(map) => { + let list = map.keys().map(|x| Gc::new(Tree::from_concrete( + NixValue::Str(x.clone())) + )).collect(); + Ok(Gc::new(NixValue::List(list))) + }, + x => Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + } + } + "genericClosure" ; GenericClosure => |param: Gc| { + let tmp = param.eval()?; + let param = match tmp.borrow() { + NixValue::Map(x) => x, + x => return Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + }; + let tmp = param.get("startSet").ok_or(EvalError::Parsing)?.eval()?; + let mut items = match tmp.borrow() { + NixValue::List(x) => x.clone(), + x => return Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + }; + let tmp = param.get("operator").ok_or(EvalError::Parsing)?.eval()?; + let operator = match tmp.borrow() { + NixValue::Lambda(x) => x, + x => return Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + }; + for i in 0.. { + if i >= items.len() { + break + } + let new = match eval_lambda(operator, items[i].clone())?.borrow() { + NixValue::List(x) => x.clone(), + x => return Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + }; + items.extend(new); + } + Ok(Gc::new(NixValue::List(items))) + } + "map" ; Map => |param: Gc| { + Ok(Gc::new(NixValue::Lambda(NixLambda::Builtin(NixBuiltin::Map1(param))))) + } + "map " ; Map1(_0: Gc) => |param: Gc, lambda: &Gc| { + let tmp = param.eval()?; + let list = tmp.as_list()?; + let mut out = vec![]; + for item in list { + let tree = Gc::new(Tree { + value: GcCell::new(None), + hash: GcCell::new(None), + source: TreeSource::Apply { + lambda: Ok(lambda.clone()), + arg: Ok(item.clone()), + }, + range: None, + scope: Gc::new(Scope::None) + }); + out.push(tree); + } + Ok(Gc::new(NixValue::List(out))) + } + "filter" ; Filter => |param: Gc| { + Ok(Gc::new(NixValue::Lambda(NixLambda::Builtin(NixBuiltin::Filter1(param))))) + } + "filter " ; Filter1(_0: Gc) => |param: Gc, lambda: &Gc| { + let tmp = param.eval()?; + let list = tmp.as_list()?; + let mut out = vec![]; + let lambda = lambda.eval()?.as_lambda()?; + for item in list { + let condition = match eval_lambda(&lambda, item.clone())?.borrow() { + NixValue::Bool(x) => *x, + x => return Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + }; + if condition { + out.push(item.clone()); + } + } + Ok(Gc::new(NixValue::List(out))) + } + "functionArgs" ; FunctionArgs => |param: Gc| match param.eval()?.borrow() { + NixValue::Lambda(NixLambda::Node { lambda, .. }) => { + let arg = lambda.arg().ok_or(EvalError::Parsing)?; + use rnix::SyntaxKind::*; + match arg.kind() { + NODE_PATTERN => { + let mut map = HashMap::new(); + let pattern = rnix::types::Pattern::cast(arg).ok_or(EvalError::Parsing)?; + for entry in pattern.entries() { + let tmp = entry.name().ok_or(EvalError::Parsing)?; + let name = tmp.as_str(); + let value = entry.default().is_some(); + map.insert(name.to_string(), Gc::new(Tree::from_concrete( + NixValue::Bool(value) + ))); + } + Ok(Gc::new(NixValue::Map(map))) + } + NODE_IDENT => { + Ok(Gc::new(NixValue::Map(HashMap::new()))) + } + x => Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + } + }, + x => Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + } + "length" ; Length => |param: Gc| match param.eval()?.borrow() { + NixValue::List(x) => Ok(Gc::new(NixValue::Integer(x.len() as i64))), + x => Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + } + "head" ; Head => |param: Gc| param.eval()?.as_list()?[0].eval() + "tail" ; Tail => |param: Gc| Ok(Gc::new( + NixValue::List(param.eval()?.as_list()?[1..].to_vec()))) + "stringLength" ; StringLength => |param: Gc| Ok(Gc::new( + NixValue::Integer(param.eval()?.as_str()?.len() as i64))) + "getEnv" ; GetEnv => |param: Gc| Ok(Gc::new(NixValue::Str( + std::env::var(param.eval()?.as_str()?).unwrap_or_else(|_| "".into())))) + "pathExists" ; PathExists => |param: Gc| match param.eval()?.borrow() { + NixValue::Path(x, y) => Ok(Gc::new(NixValue::Bool(expand_path(x.clone(), y.clone()).ok_or(EvalError::Parsing)?.exists()))), + NixValue::Str(x) => Ok(Gc::new(NixValue::Bool(PathBuf::from_str(x).map_err(|_| EvalError::Parsing)?.exists()))), + x => Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + } + "unsafeDiscardStringContext" ; UnsafeDiscardStringContext => |param: Gc| { + param.eval() + } + // FIXME: missing functionality + "addErrorContext" ; AddErrorContext => |param: Gc| { + Ok(Gc::new(NixValue::Lambda(NixLambda::Builtin(NixBuiltin::AddErrorContext1(param))))) + } + "addErrorContext " ; AddErrorContext1(_0: GcLazy) => |param: Gc, _x| { + param.eval() + } + // FIXME: missing functionality + "trace" ; Trace => |param: Gc| { + Ok(Gc::new(NixValue::Lambda(NixLambda::Builtin(NixBuiltin::Trace1(param))))) + } + "trace " ; Trace1(_0: GcLazy) => |param: Gc, _x| { + param.eval() + } + // FIXME: missing functionality + "tryEval" ; TryEval => |param: Gc| { + let mut map = HashMap::new(); + let tmp = param.eval(); + map.insert("success".to_string(), Gc::new(Tree::from_concrete( + NixValue::Bool(tmp.is_ok()) + ))); + match tmp { + Ok(x) => map.insert("value".to_string(), Gc::new( + Tree::from_concrete({ + let a: &NixValue = x.borrow(); + a.clone() + }))), + Err(_) => map.insert("value".to_string(), Gc::new( + Tree::from_concrete(NixValue::Bool(false)))), + }; + Ok(Gc::new(NixValue::Map(map))) + } + "elemAt" ; ElemAt => |param: Gc| { + Ok(Gc::new(NixValue::Lambda(NixLambda::Builtin(NixBuiltin::ElemAt1(param))))) + } + "elemAt " ; ElemAt1(_0: GcLazy) => |param: Gc, list: &GcLazy| { + let index = param.eval()?.as_int()?; + match list.eval()?.borrow() { + NixValue::List(x) => x[index as usize].eval(), + x => Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + } + } + "hasAttr" ; HasAttr => |param: Gc| { + Ok(Gc::new(NixValue::Lambda(NixLambda::Builtin(NixBuiltin::HasAttr1(param))))) + } + "hasAttr " ; HasAttr1(_0: GcLazy) => |param: Gc, index: &GcLazy| { + let map = param.eval()?.as_map()?; + let key = index.eval()?.as_str()?; + Ok(Gc::new(NixValue::Bool(map.contains_key(&key)))) + } + "elem" ; Elem => |param: Gc| { + Ok(Gc::new(NixValue::Lambda(NixLambda::Builtin(NixBuiltin::Elem1(param))))) + } + "elem " ; Elem1(_0: GcLazy) => |param: Gc, test: &GcLazy| { + let tmp = param.eval()?; + let list = tmp.as_list()?; + let test = test.eval()?; + for item in list { + if item.eval()?.equals(&test)? { + return Ok(Gc::new(NixValue::Bool(true))) + } + } + Ok(Gc::new(NixValue::Bool(false))) + } + "seq" ; Seq => |param: Gc| { + Ok(Gc::new(NixValue::Lambda(NixLambda::Builtin(NixBuiltin::Seq1(param))))) + } + "seq " ; Seq1(_0: GcLazy) => |param: Gc, first: &GcLazy| { + first.eval()?; + param.eval() + } + "isString" ; IsString => |param: Gc| match param.eval()?.borrow() { + NixValue::Str(_) => Ok(Gc::new(NixValue::Bool(true))), + _ => Ok(Gc::new(NixValue::Bool(false))), + } + "isAttrs" ; IsAttrs => |param: Gc| match param.eval()?.borrow() { + NixValue::Map(_) => Ok(Gc::new(NixValue::Bool(true))), + _ => Ok(Gc::new(NixValue::Bool(false))), + } + "isBool" ; IsBool => |param: Gc| match param.eval()?.borrow() { + NixValue::Bool(_) => Ok(Gc::new(NixValue::Bool(true))), + _ => Ok(Gc::new(NixValue::Bool(false))), + } + "isFunction" ; IsFunction => |param: Gc| match param.eval()?.borrow() { + NixValue::Lambda(_) => Ok(Gc::new(NixValue::Bool(true))), + _ => Ok(Gc::new(NixValue::Bool(false))), + } + "isList" ; IsList => |param: Gc| match param.eval()?.borrow() { + NixValue::List(_) => Ok(Gc::new(NixValue::Bool(true))), + _ => Ok(Gc::new(NixValue::Bool(false))), + } + "genList" ; GenList => |param: Gc| { + Ok(Gc::new(NixValue::Lambda(NixLambda::Builtin(NixBuiltin::GenList1(param))))) + } + "genList " ; GenList1(_0: Gc) => |param: Gc, lambda: &Gc| { + let num = param.eval()?.as_int()?; + let mut out = vec![]; + for i in 0..num { + let tree = Gc::new(Tree { + value: GcCell::new(None), + hash: GcCell::new(None), + source: TreeSource::Apply { + lambda: Ok(lambda.clone()), + arg: Ok(Gc::new(Tree::from_concrete(NixValue::Integer(i)))), + }, + range: None, + scope: Gc::new(Scope::None) + }); + out.push(tree); + } + Ok(Gc::new(NixValue::List(out))) + } + "listToAttrs" ; ListToAttrs => |param: Gc| { + match param.eval()?.borrow() { + NixValue::List(list) => { + let mut out_map = HashMap::new(); + for item in list { + match item.eval()?.borrow() { + NixValue::Map(ref map) => { + let tmp = map.get("name").ok_or(EvalError::Parsing)?; + let key = match tmp.eval()?.borrow() { + NixValue::Str(ref x) => x.clone(), + x => return Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + }; + let val = map.get("value").ok_or(EvalError::Parsing)?; + out_map.insert(key.to_string(), val.clone()); + } + x => return Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + } + } + Ok(Gc::new(NixValue::Map(out_map))) + } + x => Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + } + } + "split" ; Split => |param: Gc| { + Ok(Gc::new(NixValue::Lambda(NixLambda::Builtin(NixBuiltin::Split1(param.eval()?.as_str()?))))) + } + "split " ; Split1(_0: String) => |param: Gc, regex_str: &String| { + let regex_str = regex_str + .replace("\\|", "%%PIPE%%") + .replace("\\(", "%%L_PAREN%%") + .replace("\\)", "%%R_PAREN%%") + .replace("|", "\\|") + .replace("(", "\\(") + .replace(")", "\\)") + .replace("%%PIPE%%", "|") + .replace("%%L_PAREN%%", "(") + .replace("%%R_PAREN%%", ")"); + let regex = posix_regex::compile::PosixRegexBuilder::new(regex_str.as_bytes()) + .with_default_classes() + .compile() + .expect("error compiling regex"); + let param = match param.eval()?.borrow() { + NixValue::Str(x) => x.clone(), + x => return Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + }; + let matches = regex.matches(param.as_bytes(), None); + + let mut out = vec![]; + + let mut cursor = 0; + for found in matches { + let (from, to) = found[0].ok_or(EvalError::Parsing)?; + out.push(Gc::new(Tree::from_concrete(NixValue::Str( + param[cursor..from].to_string(), + )))); + let mut here = vec![]; + for group in found.iter().skip(1) { + here.push(match group { + Some((x, y)) => { + Gc::new(Tree::from_concrete(NixValue::Str(param[*x..*y].to_string()))) + } + None => Gc::new(Tree::from_concrete(NixValue::Null)), + }); + } + out.push(Gc::new(Tree::from_concrete(NixValue::List(here)))); + cursor = to; + } + out.push(Gc::new(Tree::from_concrete(NixValue::Str( + param[cursor..].to_string(), + )))); + Ok(Gc::new(NixValue::List(out))) + } + "concatStringsSep" ; ConcatStringsSep => |param: Gc| { + Ok(Gc::new(NixValue::Lambda(NixLambda::Builtin(NixBuiltin::ConcatStringsSep1(param.eval()?.as_str()?))))) + } + "concatStringsSep " ; ConcatStringsSep1(_0: String) => |param: Gc, sep: &String| { + let list = param.eval()?.as_list()?; + let mut out = vec![]; + for item in list { + out.push(match item.eval()?.borrow() { + NixValue::Str(ref x) => x.clone(), + x => return Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + }); + } + Ok(Gc::new(NixValue::Str(out.join(sep)))) + } + "unsafeGetAttrPos" ; UnsafeGetAttrPos => |param: Gc| { + Ok(Gc::new(NixValue::Lambda(NixLambda::Builtin(NixBuiltin::UnsafeGetAttrPos1(match param.eval()?.borrow() { + NixValue::Str(x) => x.clone(), + x => return Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + }))))) + } + // FIXME: missing functionality + "unsafeGetAttrPos " ; UnsafeGetAttrPos1(_0: String) => |_param: Gc, _attr: &String| { + let mut map = HashMap::new(); + map.insert("column".to_string(), Gc::new(Tree::from_concrete( + NixValue::Integer(1) + ))); + map.insert("line".to_string(), Gc::new(Tree::from_concrete( + NixValue::Integer(1) + ))); + map.insert("file".to_string(), Gc::new(Tree::from_concrete( + NixValue::Str("unknown".to_string()) + ))); + Ok(Gc::new(NixValue::Map(map))) + } + "toString" ; ToString => |param: Gc| match param.eval()?.borrow() { + NixValue::Str(x) => Ok(Gc::new(NixValue::Str(x.clone()))), + NixValue::Integer(x) => Ok(Gc::new(NixValue::Str(format!("{}", x)))), + NixValue::Float(x) => Ok(Gc::new(NixValue::Str(format!("{}", x)))), + // FIXME: incorrect! + NixValue::Path(_, _) => Ok(Gc::new(NixValue::Str( + "/nix/store/00000000000000000000000000000000-fake-path".to_string()))), + x => Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + } + "removeAttrs" ; RemoveAttrs => |param: Gc| { + Ok(Gc::new(NixValue::Lambda(NixLambda::Builtin(NixBuiltin::RemoveAttrs1(param))))) + } + "removeAttrs " ; RemoveAttrs1(_0: Gc) => |param: Gc, map: &Gc| { + let mut new_map = match map.eval()?.borrow() { + NixValue::Map(x) => x.clone(), + x => return Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + }; + let tmp = param.eval()?; + let items = match tmp.borrow() { + NixValue::List(x) => x, + x => return Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + }; + for item in items { + let key = match item.eval()?.borrow() { + NixValue::Str(ref x) => x.clone(), + x => return Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + }; + new_map.remove(&key); + } + Ok(Gc::new(NixValue::Map(new_map))) + } + "intersectAttrs" ; IntersectAttrs => |param: Gc| { + Ok(Gc::new(NixValue::Lambda(NixLambda::Builtin(NixBuiltin::IntersectAttrs1(param))))) + } + "intersectAttrs " ; IntersectAttrs1(_0: Gc) => |param: Gc, a: &Gc| { + let a = match a.eval()?.borrow() { + NixValue::Map(x) => x.clone(), + x => return Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + }; + let b = match param.eval()?.borrow() { + NixValue::Map(x) => x.clone(), + x => return Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + }; + let mut map = HashMap::new(); + for (bkey, bval) in b.iter() { + if a.contains_key(bkey) { + map.insert(bkey.clone(), bval.clone()); + } + } + Ok(Gc::new(NixValue::Map(map))) + } + "concatLists" ; ConcatLists => |param: Gc| { + let list = match param.eval()?.borrow() { + NixValue::List(ref x) => x.clone(), + x => return Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + }; + let mut out = vec![]; + for item in list { + let sublist = match item.eval()?.borrow() { + NixValue::List(ref x) => x.clone(), + x => return Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + }; + out.extend(sublist); + } + Ok(Gc::new(NixValue::List(out))) + } + "substring" ; Substring => |param: Gc| { + Ok(Gc::new(NixValue::Lambda(NixLambda::Builtin(NixBuiltin::Substring1(match param.eval()?.borrow() { + NixValue::Integer(x) => *x as usize, + x => return Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + }))))) + } + "substring " ; Substring1(_0: usize) => |param: Gc, from: &usize| { + Ok(Gc::new(NixValue::Lambda(NixLambda::Builtin(NixBuiltin::Substring2(*from, match param.eval()?.borrow() { + NixValue::Integer(x) => *x as usize, + x => return Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + }))))) + } + "substring " ; Substring2(_0: usize, _1: usize) => |param: Gc, from: &usize, len: &usize| { + let param = param.eval()?.as_str()?; + Ok(Gc::new(NixValue::Str(param[*from..(*from + *len).min(param.len())].to_string()))) + } + "compareVersions" ; CompareVersions => |param| { + Ok(Gc::new(NixValue::Lambda(NixLambda::Builtin(NixBuiltin::CompareVersions1(param))))) + } + "compareVersions " ; CompareVersions1(_0: Gc) => |param: Gc, left: &Gc| { + let split = |version: String| -> Vec { + let mut out = vec![]; + #[derive(PartialEq, Eq)] + enum Mode { + Alpha, + Num, + None, + } + let mut mode = Mode::None; + let mut curr = String::new(); + for ch in version.chars() { + match ch { + '0'..='9' => { + if mode == Mode::Alpha { + out.push(curr); + curr = String::new(); + } + curr.push(ch); + mode = Mode::Num; + } + 'a'..='z' => { + if mode == Mode::Num { + out.push(curr); + curr = String::new(); + } + curr.push(ch); + mode = Mode::Alpha; + } + _ => { + if mode != Mode::None { + out.push(curr); + curr = String::new(); + mode = Mode::None; + } + } + } + } + if !curr.is_empty() { + out.push(curr); + } + out + }; + let left = left.eval()?.as_str()?; + let right = param.eval()?.as_str()?; + let a = split(left); + let b = split(right); + // if a > b { 1 } else { -1 } + for i in 0..a.len().max(b.len()) { + match (a.get(i), b.get(i)) { + (Some(x), Some(y)) => { + if x == "pre" { + return Ok(Gc::new(NixValue::Integer(-1))); + } else if y == "pre" { + return Ok(Gc::new(NixValue::Integer(1))); + } else { + use std::cmp::Ordering; + match (x.parse::().ok(), y.parse::().ok()) { + (Some(a), Some(b)) => { + match a.cmp(&b) { + Ordering::Greater => return Ok(Gc::new(NixValue::Integer(1))), + Ordering::Less => return Ok(Gc::new(NixValue::Integer(-1))), + Ordering::Equal => continue, + } + } + (Some(_), None) => return Ok(Gc::new(NixValue::Integer(-1))), + (None, Some(_)) => return Ok(Gc::new(NixValue::Integer(1))), + (None, None) => { + match x.cmp(&y) { + Ordering::Greater => return Ok(Gc::new(NixValue::Integer(1))), + Ordering::Less => return Ok(Gc::new(NixValue::Integer(-1))), + Ordering::Equal => continue, + } + } + } + } + } + (Some(_), None) => return Ok(Gc::new(NixValue::Integer(1))), + (None, Some(_)) => return Ok(Gc::new(NixValue::Integer(-1))), + (None, None) => panic!(), + } + } + Ok(Gc::new(NixValue::Integer(0))) + } + "import" ; Import => |param: Gc| { + match param.eval()?.borrow() { + NixValue::Path(ref base, ref path) => { + let path = expand_path(base.clone(), path.clone()).ok_or(EvalError::Parsing)?; + let path = if path.is_dir() { + path.join("default.nix") + } else { + path + }; + let source = std::fs::read_to_string(&path).map_err(|_| EvalError::Parsing)?; + let foreign_root = rnix::parse(&source).root().inner().ok_or(EvalError::Parsing)?; + Ok(Tree::parse_legacy(&foreign_root, Gc::new(Scope::Root(path, None)))?.eval()?) + } + x => Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + } + } + "readFile" ; ReadFile => |param: Gc| { + match param.eval()?.borrow() { + NixValue::Path(x, y) => { + let path = expand_path(x.clone(), y.clone()).ok_or(EvalError::Parsing)?; + let source = std::fs::read_to_string(path).map_err(|_| EvalError::Parsing)?; + Ok(Gc::new(NixValue::Str(source))) + } + x => Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + } + } + "fromJSON" ; FromJSON => |param: Gc| match param.eval()?.borrow() { + NixValue::Str(ref source) => { + let parsed: serde_json::Value = serde_json::from_str(source).map_err(|_| EvalError::Parsing)?; + Ok(Gc::new(json_to_nix(&parsed))) + } + x => Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + } + "mapAttrs" ; MapAttrs => |param: Gc| Ok(Gc::new(NixValue::Lambda( + NixLambda::Builtin(NixBuiltin::MapAttrs1(param))))) + "mapAttrs " ; MapAttrs1(_0: Gc) => |param: Gc, lambda: &Gc| { + let attrs = param.eval()?.as_map()?; + let mut new_map = HashMap::new(); + for (key, value) in (&attrs).iter() { + let inner = Gc::new(Tree { + value: GcCell::new(None), + hash: GcCell::new(None), + source: TreeSource::Apply { + lambda: Ok(lambda.clone()), + arg: Ok(Gc::new(Tree::from_concrete(NixValue::Str(key.clone())))), + }, + range: None, + scope: Gc::new(Scope::None) + }); + let tree = Gc::new(Tree { + value: GcCell::new(None), + hash: GcCell::new(None), + source: TreeSource::Apply { + lambda: Ok(inner), + arg: Ok(value.clone()), + }, + range: None, + scope: Gc::new(Scope::None) + }); + new_map.insert(key.clone(), tree); + } + Ok(Gc::new(NixValue::Map(new_map))) + } +} + +fn json_to_nix(value: &serde_json::Value) -> NixValue { + match value { + serde_json::Value::Null => NixValue::Null, + serde_json::Value::Bool(x) => NixValue::Bool(*x), + serde_json::Value::Number(num) => { + if num.is_i64() { + NixValue::Integer(num.as_i64().unwrap()) + } else { + NixValue::Float(num.as_f64().unwrap()) + } + } + serde_json::Value::String(s) => NixValue::Str(s.clone()), + serde_json::Value::Array(arr) => NixValue::List( + arr.iter() + .map(json_to_nix) + .map(Tree::from_concrete) + .map(Gc::new) + .collect(), + ), + serde_json::Value::Object(map) => { + let mut out_obj = HashMap::new(); + for (key, val) in map.iter() { + out_obj.insert(key.clone(), Gc::new(Tree::from_concrete(json_to_nix(val)))); + } + NixValue::Map(out_obj) + } + } +} diff --git a/src/derivation.nix b/src/derivation.nix new file mode 100644 index 0000000..88c0df9 --- /dev/null +++ b/src/derivation.nix @@ -0,0 +1,15 @@ +{ outputs ? [ "out" ], ... } @ drvAttrs: +let + outputAttrs = builtins.listToAttrs (map + (name: { inherit name; value = toValue name; }) + outputs); + strict = derivationStrict drvAttrs; + toValue = outputName: drvAttrs // outputAttrs // { + inherit drvAttrs outputName; + inherit (strict) drvPath; + outPath = strict.${outputName}; + type = "derivation"; + all = map toValue outputs; + }; +in +toValue (builtins.head outputs) diff --git a/src/eval.rs b/src/eval.rs new file mode 100644 index 0000000..b36feca --- /dev/null +++ b/src/eval.rs @@ -0,0 +1,844 @@ +use crate::parse::BinOpKind; +use crate::scope::*; +use crate::value::*; +use crate::EvalError; +use gc::{Finalize, Gc, GcCell, Trace}; +use rnix::types::Dynamic; +use rnix::{ + types::{Pattern, Str, TokenWrapper, TypedNode, Wrapper}, + StrPart, SyntaxNode, TextRange, +}; +use std::str::FromStr; +use std::{borrow::Borrow, collections::HashMap, path::PathBuf}; + +pub fn expand_path(base: NixPathAnchor, path: String) -> Option { + match &base { + NixPathAnchor::Absolute => PathBuf::from_str(&path).ok(), + NixPathAnchor::Relative(rel) => Some(rel.join(path)), + } +} + +pub fn eval_lambda(lambda: &NixLambda, param: Gc) -> Result, EvalError> { + match &lambda { + NixLambda::Builtin(builtin) => builtin.call(param), + NixLambda::Node { lambda, scope } => { + let arg = lambda.arg().ok_or(EvalError::Parsing)?; + use rnix::SyntaxKind::*; + match arg.kind() { + NODE_PATTERN => { + let new_scope_gc = { + let new = Scope::Normal { + parent: scope.clone(), + contents: GcCell::new(HashMap::new()), + }; + Gc::new(new) + }; + + let mut map = HashMap::new(); + + let pattern = Pattern::cast(arg).ok_or(EvalError::Parsing)?; + if let Some(at) = pattern.at() { + map.insert(at.as_str().to_string(), param.clone()); + } + + if pattern.entries().next().is_some() { + let tmp = param.eval()?; + let param = match tmp.borrow() { + NixValue::Map(x) => x, + x => { + return Err(EvalError::Unexpected(format!( + "cannot destructure {:?}", + x + ))) + } + }; + for entry in pattern.entries() { + let key = entry.name().ok_or(EvalError::Parsing)?; + let name = key.as_str(); + if let Some(x) = param.get(name) { + map.insert(name.to_string(), x.clone()); + } else if let Some(default) = entry.default() { + map.insert( + name.to_string(), + Tree::parse(default, new_scope_gc.clone())?, + ); + } else { + return Err(EvalError::Unexpected(format!( + "Lambda param missing attr: {}\n\n{}", + name, + lambda.arg().ok_or(EvalError::Parsing)?.text() + ))); + } + } + } + + if let Scope::Normal { contents, .. } = new_scope_gc.borrow() { + *contents.borrow_mut() = map; + } + + Tree::parse_legacy(&lambda.body().ok_or(EvalError::Parsing)?, new_scope_gc) + .and_then(|x| x.eval()) + } + NODE_IDENT => { + let mut map = HashMap::new(); + let key = to_string(&arg, scope.clone())?; + map.insert(key, param); + Tree::parse_legacy( + &lambda.body().ok_or(EvalError::Parsing)?, + Gc::new(Scope::Normal { + parent: scope.clone(), + contents: GcCell::new(map), + }), + ) + .and_then(|x| x.eval()) + } + x => unimplemented!("UNEXPECTED {:?}", x), + } + } + } +} + +pub fn to_string(root: &SyntaxNode, scope: Gc) -> Result { + use rnix::SyntaxKind::*; + match root.kind() { + NODE_STRING => Ok(Str::cast(root.clone()) + .ok_or(EvalError::Parsing)? + .parts() + .into_iter() + .map(|part| match part { + StrPart::Literal(x) => x, + StrPart::Ast(node) => { + match Tree::parse_legacy(&node, scope.clone()) + .unwrap() + .eval() + .unwrap() + .borrow() + { + NixValue::Str(ref x) => x.clone(), + x => panic!("Cannot cast {:?} to string", x), + } + } + }) + .collect::>() + .join("")), + NODE_IDENT => Ok(root.text().to_string()), + NODE_DYNAMIC => { + let inner = Dynamic::cast(root.clone()) + .ok_or(EvalError::Parsing)? + .inner() + .ok_or(EvalError::Parsing)?; + match Tree::parse_legacy(&inner, scope)?.eval()?.borrow() { + NixValue::Str(ref x) => Ok(x.clone()), + _ => Err(EvalError::Parsing), + } + } + x => unimplemented!("Unexpected attr key kind: {:?}\n\n{}", x, root.text()), + } +} + +type TreeResult = Result, EvalError>; + +#[derive(Debug, Clone, Trace, Finalize)] +#[allow(dead_code)] +pub enum TreeSource { + Legacy { + #[unsafe_ignore_trace] + syntax: SyntaxNode, + }, + Literal { + value: NixValue, + }, + Assert { + condition: TreeResult, + body: TreeResult, + }, + IfElse { + condition: TreeResult, + true_body: TreeResult, + false_body: TreeResult, + }, + Paren { + inner: TreeResult, + }, + Dynamic { + inner: TreeResult, + }, + LetIn { + inherits: Vec, // inherit (foobar) ->abc<- ->xyz<-; + definitions: Vec, // ->abc = "hello"<-; + body: TreeResult, + }, + LetAttr { + path: Vec, + value: TreeResult, + }, + Ident { + name: String, + }, + Select { + from: TreeResult, + index: TreeResult, + }, + BinOp { + op: BinOpKind, + left: TreeResult, + right: TreeResult, + }, + BoolAnd { + left: TreeResult, + right: TreeResult, + }, + BoolOr { + left: TreeResult, + right: TreeResult, + }, + InSet { + set: TreeResult, + index: TreeResult, + }, + Implication { + left: TreeResult, + right: TreeResult, + }, + With { + value: TreeResult, + body: TreeResult, + }, + List { + items: Vec, + }, + Apply { + lambda: TreeResult, + arg: TreeResult, + }, + Map { + inherits: Vec, + definitions: Vec, + }, + MapAttr { + path: Vec, + value: TreeResult, + }, + StringInterpol { + parts: Vec, + }, + UnaryInvert { + value: TreeResult, + }, + UnaryNegate { + value: TreeResult, + }, + Lambda { + literal: Gc, + params: Vec, + body: TreeResult, + }, + InferredIdent { + name: String, + }, +} + +#[derive(Clone, Trace, Finalize)] +pub struct Tree { + #[unsafe_ignore_trace] + pub range: Option, + pub hash: GcCell>, + pub value: GcCell>>, + pub source: TreeSource, + pub scope: Gc, +} +impl std::fmt::Debug for Tree { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Tree") + .field("value", &self.value) + .field("source", &self.source) + .field("range", &self.range) + .finish() + } +} + +pub fn to_hex_string(bytes: Vec) -> String { + let strs: Vec = bytes.iter().map(|b| format!("{:02x}", b)).collect(); + strs.join("") +} + +impl Tree { + pub fn hash(&self) -> Result { + use std::ops::Deref; + let value_borrow = self.hash.borrow(); + if let Some(ref value) = value_borrow.deref() { + Ok(value.clone()) + } else { + drop(value_borrow); + let value = self.hash_uncached()?; + *self.hash.borrow_mut() = Some(value.clone()); + Ok(value) + } + } + + pub fn completions(&self) -> Option<(String, Vec, TextRange)> { + match &self.source { + TreeSource::Ident { name } => Some((name.clone(), self.scope.list(), self.range?)), + TreeSource::Select { from, index } => Some(( + index.as_ref().ok()?.get_ident().ok()?, + if let Ok(x) = from { + x.get_keys().unwrap_or_default() + } else { + vec![] + }, + index.as_ref().ok()?.range?, + )), + _ => Some(("".to_string(), self.scope.list(), self.range?)), + } + } + + pub fn get_keys(&self) -> Result, EvalError> { + Ok(self.eval()?.as_map()?.keys().cloned().collect()) + } + + fn hash_uncached(&self) -> Result { + use sha2::{Digest, Sha256}; + let mut hasher = Sha256::new(); + hasher.update(b"("); + match &self.source { + TreeSource::Legacy { syntax: _ } => { + return Err(EvalError::Unimplemented("legacy".to_string())) + } + TreeSource::Literal { value } => match value { + NixValue::Bool(x) => hasher.update(vec![*x as u8]), + NixValue::Float(x) => hasher.update(format!("{}", x)), + NixValue::Integer(x) => hasher.update(format!("{}", x)), + NixValue::Lambda(lambda) => match lambda { + NixLambda::Node { + lambda: _, + scope: _, + } => return Err(EvalError::Unimplemented("node".to_string())), + NixLambda::Builtin(x) => hasher.update(format!("{:?}", x)), + }, + NixValue::List(_) => return Err(EvalError::Unimplemented("list".to_string())), + NixValue::Map(_) => return Err(EvalError::Unimplemented("map".to_string())), + NixValue::Null => hasher.update("null"), + NixValue::Path(x, y) => { + hasher.update(format!("{:?}", expand_path(x.clone(), y.clone()))) + } + NixValue::Str(x) => hasher.update(x), + }, + TreeSource::LetAttr { path: _, value } => { + hasher.update("letattr"); + hasher.update(value.as_ref()?.hash()?); + } + TreeSource::Ident { name } => { + hasher.update("ident"); + hasher.update(match self.scope.get(name) { + Some(x) => x.hash()?, + None => return Err(EvalError::Unimplemented("legacy".to_string())), + }); + } + TreeSource::Apply { lambda, arg } => { + hasher.update("apply"); + hasher.update(lambda.as_ref()?.hash()?); + hasher.update("arg"); + hasher.update(arg.as_ref()?.hash()?); + } + TreeSource::Map { + inherits, + definitions: _, + } => { + for inherit in inherits { + hasher.update(inherit.as_ref()?.hash()?); + } + } + _ => return Err(EvalError::Unimplemented("other".to_string())), + } + hasher.update(b")"); + let result = hasher.finalize(); + Ok(to_hex_string(result.to_vec())) + } + + pub fn from_concrete(value: NixValue) -> Self { + Self { + value: GcCell::new(None), + hash: GcCell::new(None), + source: TreeSource::Literal { value }, + range: None, + scope: Gc::new(Scope::None), + } + } + + pub fn eval(&self) -> Result, EvalError> { + use std::ops::Deref; + let value_borrow = self.value.borrow(); + if let Some(ref value) = value_borrow.deref() { + Ok(value.clone()) + } else { + drop(value_borrow); + + if let Ok(hash) = self.hash() { + if let Some(value) = self.scope.pull_hash(hash) { + *self.value.borrow_mut() = Some(value.clone()); + return Ok(value); + } + } + + let value = match self.eval_uncached() { + Ok(x) => x, + Err(e) => return Err(EvalError::StackTrace(format!("{}\n{}", self.position(), e))), + }; + *self.value.borrow_mut() = Some(value.clone()); + if let Ok(hash) = self.hash() { + self.scope.submit_hash(hash, value.clone()); + } + Ok(value) + } + } + + fn position(&self) -> String { + let path = match self.scope.root_path() { + Some(x) => match x.canonicalize() { + Ok(y) => format!("{:?}", y), + Err(_) => "[unknown path]".to_string(), + }, + None => "[unknown path]".to_string(), + }; + path + } + + fn eval_uncached(&self) -> Result, EvalError> { + match &self.source { + TreeSource::Legacy { syntax } => crate::parse::eval_node(syntax, self.scope.clone()), + // TODO: actually evaluate the condition + TreeSource::Assert { condition: _, body } => body.as_ref()?.eval(), + TreeSource::IfElse { + condition, + true_body, + false_body, + } => { + if condition.as_ref()?.eval()?.as_bool()? { + true_body.as_ref()?.eval() + } else { + false_body.as_ref()?.eval() + } + } + TreeSource::Paren { inner } => inner.as_ref()?.eval(), + TreeSource::Dynamic { inner } => inner.as_ref()?.eval(), + TreeSource::LetIn { + inherits: _, + definitions: _, + body, + } => body.as_ref()?.eval(), + TreeSource::LetAttr { path: _, value } => value.as_ref()?.eval(), + TreeSource::Literal { value } => Ok(Gc::new(value.clone())), + TreeSource::Ident { name } => self + .scope + .get(name) + .ok_or(EvalError::Unexpected(format!( + "missing {} from scope", + name + )))? + .eval(), + TreeSource::Select { from, index } => { + let key = index.as_ref()?.get_ident()?; + let tmp = from.as_ref()?.eval()?; + let map = tmp.as_map()?; + let val = match map.get(&key) { + Some(x) => x, + None => return Err(EvalError::Unexpected(format!("missing attr {}", key))), + }; + val.eval() + } + TreeSource::BoolAnd { left, right } => { + if left.as_ref()?.eval()?.as_bool()? { + right.as_ref()?.eval() + } else { + Ok(Gc::new(NixValue::Bool(false))) + } + } + TreeSource::BoolOr { left, right } => { + if !left.as_ref()?.eval()?.as_bool()? { + right.as_ref()?.eval() + } else { + Ok(Gc::new(NixValue::Bool(true))) + } + } + TreeSource::BinOp { op, left, right } => { + use BinOpKind::*; + use NixValue::*; + let tmp1 = left.as_ref()?.eval()?; + let tmp2 = right.as_ref()?.eval()?; + let left = tmp1.borrow(); + let right = tmp2.borrow(); + match (left, right) { + (Map(x), Map(y)) => Ok(Gc::new(match op { + Update => { + let mut out = x.clone(); + for (key, val) in y.iter() { + out.insert(key.clone(), val.clone()); + } + Map(out) + } + Equal => Bool(Gc::ptr_eq(&tmp1, &tmp2) || left.equals(right)?), + NotEqual => Bool(!Gc::ptr_eq(&tmp1, &tmp2) && !left.equals(right)?), + _ => { + return Err(EvalError::Unexpected(format!( + "cannot handle map {:?} map", + op + ))) + } + })), + (Path(x, y), Str(z)) => Ok(Gc::new(match op { + Add => Path(x.clone(), y.clone() + z), + _ => { + return Err(EvalError::Unexpected(format!( + "cannot handle {:?} {:?} {:?} {:?}", + x, y, op, z + ))) + } + })), + (Bool(x), Bool(y)) => Ok(Gc::new(match op { + Concat => { + return Err(EvalError::Unexpected("cannot do int ++ int".to_string())) + } + Update => { + return Err(EvalError::Unexpected("cannot do int // int".to_string())) + } + Add | Sub | Mul | Div | Less | LessOrEq | More | MoreOrEq => { + return Err(EvalError::Unexpected( + "cannot do boolean arithmetic".to_string(), + )) + } + Equal => Bool(x == y), + NotEqual => Bool(x != y), + })), + (Integer(x), Integer(y)) => Ok(Gc::new(match op { + Concat => { + return Err(EvalError::Unexpected("cannot do int ++ int".to_string())) + } + Update => { + return Err(EvalError::Unexpected("cannot do int // int".to_string())) + } + Add => Integer(x + y), + Sub => Integer(x - y), + Mul => Integer(x * y), + Div => Integer(x / y), + Equal => Bool(x == y), + Less => Bool(x < y), + LessOrEq => Bool(x < y), + More => Bool(x > y), + MoreOrEq => Bool(x >= y), + NotEqual => Bool(x != y), + })), + (Float(x), Float(y)) => Ok(Gc::new(match op { + Concat => { + return Err(EvalError::Unexpected( + "cannot do float ++ float".to_string(), + )) + } + Update => { + return Err(EvalError::Unexpected( + "cannot do float // float".to_string(), + )) + } + Add => Float(x + y), + Sub => Float(x - y), + Mul => Float(x * y), + Div => Float(x / y), + Equal => Bool((x - y) < 0.001), + Less => Bool(x < y), + LessOrEq => Bool(x < y), + More => Bool(x > y), + MoreOrEq => Bool(x >= y), + NotEqual => Bool((x - y) >= 0.001), + })), + (Str(x), Str(y)) => Ok(Gc::new(match op { + Add => Str(x.to_string() + y), + Equal => Bool(x == y), + NotEqual => Bool(x != y), + _ => { + return Err(EvalError::Unexpected(format!( + "cannot handle {:?} {:?} {:?}", + x, op, y + ))) + } + })), + (List(x), List(y)) => Ok(Gc::new(match op { + Equal => Bool(left.equals(right)?), + NotEqual => Bool(!left.equals(right)?), + Concat => List({ + let mut new = vec![]; + new.extend(x.clone()); + new.extend(y.clone()); + new + }), + _ => { + return Err(EvalError::Unexpected(format!( + "cannot handle list {:?} list", + op + ))) + } + })), + (Null, Null) => match op { + Equal => Ok(Gc::new(Bool(true))), + NotEqual => Ok(Gc::new(Bool(false))), + _ => Err(EvalError::Unexpected(format!( + "cannot handle null {:?} null", + op + ))), + }, + (x, y) => match op { + Equal => Ok(Gc::new(Bool(false))), + NotEqual => Ok(Gc::new(Bool(true))), + _ => Err(EvalError::Unexpected(format!( + "cannot handle {:?} {:?} {:?}", + x, op, y + ))), + }, + } + } + TreeSource::InSet { set, index } => Ok(Gc::new(NixValue::Bool({ + let map = set.as_ref()?.eval()?; + let path = index.as_ref()?.get_idents()?; + let out = map.contains(&path)?; + println!("{:?} {}", path, out); + out + }))), + TreeSource::Implication { left, right } => { + if left.as_ref()?.eval()?.as_bool()? { + Ok(Gc::new(NixValue::Bool(right.as_ref()?.eval()?.as_bool()?))) + } else { + Ok(Gc::new(NixValue::Bool(true))) + } + } + TreeSource::With { value: _, body } => body.as_ref()?.eval(), + TreeSource::List { items } => Ok(Gc::new(NixValue::List( + items + .iter() + .map(|x| x.as_ref()) + .filter_map(Result::ok) + .cloned() + .collect::>(), + ))), + TreeSource::Apply { lambda, arg } => { + let lambda = lambda.as_ref()?; + let lambda = match lambda.eval()?.borrow() { + NixValue::Lambda(x) => x.clone(), + NixValue::Map(x) => { + let tmp = x.get("__functor").ok_or(EvalError::Parsing)?.eval()?; + let functor = tmp.as_lambda()?; + eval_lambda(&functor, lambda.clone())?.as_lambda()? + } + unknown => { + return Err(EvalError::Unexpected(format!( + "cannot apply lambda {:?}", + unknown + ))) + } + }; + eval_lambda(&lambda, arg.as_ref()?.clone()) + } + TreeSource::Map { + inherits: _, + definitions: _, + } => unreachable!(), + TreeSource::MapAttr { path: _, value } => value.as_ref()?.eval(), + TreeSource::StringInterpol { parts } => { + let mut out = String::new(); + for part in parts { + out += &part.as_ref()?.eval()?.as_str()?; + } + Ok(Gc::new(NixValue::Str(out))) + } + TreeSource::UnaryInvert { value } => { + Ok(Gc::new(NixValue::Bool(!value.as_ref()?.eval()?.as_bool()?))) + } + TreeSource::UnaryNegate { value } => { + Ok(Gc::new(match value.as_ref()?.eval()?.borrow() { + NixValue::Integer(x) => NixValue::Integer(-x), + NixValue::Float(x) => NixValue::Float(-x), + _ => { + return Err(EvalError::TypeError( + "cannot negate a non-number".to_string(), + )) + } + })) + } + TreeSource::Lambda { + literal, + params: _, + body: _, + } => Ok(literal.clone()), + TreeSource::InferredIdent { name: _ } => Err(EvalError::Unknown), + } + } + + pub fn get_definition(&self) -> Option> { + use TreeSource::*; + match &self.source { + Ident { name } => self.scope.get(&name), + InferredIdent { name } => self.scope.get(&name), + Select { from, index } => { + let idx = index.as_ref().ok()?.get_ident().ok()?; + Some( + from.as_ref() + .ok()? + .eval() + .ok()? + .as_map() + .ok()? + .get(&idx)? + .clone(), + ) + } + _ => None, + } + } + + pub fn children(&self) -> Vec<&Gc> { + match &self.source { + TreeSource::Legacy { syntax: _ } => vec![], + TreeSource::Assert { condition, body } => vec![condition, body], + TreeSource::IfElse { + condition, + true_body, + false_body, + } => vec![condition, true_body, false_body], + TreeSource::Paren { inner } => vec![inner], + TreeSource::Dynamic { inner } => vec![inner], + TreeSource::LetIn { + inherits, + definitions, + body, + } => { + let mut out = vec![body]; + out.extend(inherits); + out.extend(definitions); + out + } + TreeSource::LetAttr { path: _, value } => vec![value], + TreeSource::Literal { value: _ } => vec![], + TreeSource::Ident { name: _ } => vec![], + TreeSource::Select { from, index } => match index { + Ok(x) => { + let mut out = if let Ok(tmp) = from { + vec![tmp] + } else { + vec![] + }; + out.extend(x.children()); + return out; + } + Err(_) => vec![from], + }, + TreeSource::BinOp { op: _, left, right } => vec![left, right], + TreeSource::BoolAnd { left, right } => vec![left, right], + TreeSource::BoolOr { left, right } => vec![left, right], + TreeSource::InSet { set, index: _ } => vec![set], + TreeSource::Implication { left, right } => vec![left, right], + TreeSource::With { value, body } => vec![value, body], + TreeSource::List { items } => { + return items + .iter() + .map(|x| x.as_ref()) + .filter_map(Result::ok) + .collect() + } + TreeSource::Apply { lambda, arg } => vec![lambda, arg], + TreeSource::Map { + inherits, + definitions, + } => { + let mut out = vec![]; + out.extend(inherits); + out.extend(definitions); + out + } + TreeSource::MapAttr { path: _, value } => vec![value], + TreeSource::StringInterpol { parts } => { + let mut out = vec![]; + for part in parts { + out.push(part); + } + out + } + TreeSource::UnaryInvert { value } => vec![value], + TreeSource::UnaryNegate { value } => vec![value], + TreeSource::Lambda { + literal: _, + params, + body, + } => { + let mut out = vec![body]; + out.extend(params); + out + } + TreeSource::InferredIdent { name: _ } => vec![], + } + .into_iter() + .map(|x| x.as_ref()) + .filter_map(Result::ok) + .collect() + } + + pub fn get_ident(&self) -> Result { + use TreeSource::*; + match &self.source { + Ident { ref name } => Ok(name.clone()), + Dynamic { ref inner } => inner.as_ref()?.eval()?.as_str(), + Literal { ref value } => value.as_str(), + other => Err(EvalError::Unexpected(format!( + "cannot get ident from {:?}", + other + ))), + } + } + + pub fn get_idents(&self) -> Result, EvalError> { + use TreeSource::*; + match &self.source { + Ident { name } => Ok(vec![name.clone()]), + Literal { ref value } => Ok(vec![value.as_str()?]), + Dynamic { ref inner } => Ok(vec![inner.as_ref()?.eval()?.as_str()?]), + Select { from, index } => Ok({ + let mut out = from.as_ref()?.get_idents()?; + out.push(index.as_ref()?.get_ident()?); + out + }), + _ => Err(EvalError::Unexpected("cannot get ident".to_string())), + } + } + + pub fn parse_legacy(node: &SyntaxNode, scope: Gc) -> Result { + match Self::parse(node.clone(), scope) { + Ok(x) => Ok({ + let tmp: &Tree = x.borrow(); + tmp.clone() + }), + Err(e) => Err(e), + } + } +} + +pub fn merge_values(a: Gc, b: Gc) -> Result, EvalError> { + let a = match a.eval()?.borrow() { + NixValue::Map(x) => x.clone(), + x => return Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + }; + let b = match b.eval()?.borrow() { + NixValue::Map(x) => x.clone(), + x => return Err(EvalError::Unimplemented(format!("cannot cast {:?}", x))), + }; + let mut out = HashMap::new(); + for (key, val) in a.iter() { + let tmp = match b.get(key) { + Some(x) => merge_values(x.clone(), val.clone())?, + None => val.clone(), + }; + out.insert(key.clone(), tmp); + } + for (key, val) in b.iter() { + if !a.contains_key(key) { + out.insert(key.clone(), val.clone()); + } + } + + Ok(Gc::new(Tree::from_concrete(NixValue::Map(out)))) +} diff --git a/src/lookup.rs b/src/lookup.rs index 3474389..65d7007 100644 --- a/src/lookup.rs +++ b/src/lookup.rs @@ -1,7 +1,4 @@ -use crate::{ - utils::{self, Datatype, Var}, - App, -}; +use crate::{App, eval::Tree, utils::{self, Datatype, Var}}; use lsp_types::Url; use rnix::{types::*, value::Value as ParsedValue, SyntaxNode}; use std::{ @@ -14,6 +11,8 @@ use lazy_static::lazy_static; use std::{process, str}; use regex; +use gc::Gc; +use crate::scope::Scope; lazy_static! { static ref BUILTINS: Vec = vec![ @@ -147,14 +146,16 @@ impl App { let path = utils::uri_path(&file)?; node = match self.files.entry((**file).clone()) { Entry::Occupied(entry) => { - let (ast, _code) = entry.get(); + let (ast, _code, _) = entry.get(); ast.root().inner()?.clone() } Entry::Vacant(placeholder) => { let content = fs::read_to_string(&path).ok()?; let ast = rnix::parse(&content); let node = ast.root().inner()?.clone(); - placeholder.insert((ast, content)); + let gc_root = Gc::new(Scope::Root(path, Some(self.store.clone()))); + let evaluated = Tree::parse_legacy(&node, gc_root); + placeholder.insert((ast, content, evaluated)); node } }; diff --git a/src/main.rs b/src/main.rs index 8354dd5..1cceb46 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,10 +21,18 @@ clippy::integer_arithmetic, )] +mod builtins; +mod eval; mod lookup; +mod parse; +mod scope; +mod tests; mod utils; +mod value; use dirs::home_dir; +use eval::Tree; +use gc::{Finalize, Gc, GcCell, Trace}; use log::{error, trace, warn}; use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; use lsp_types::{ @@ -38,6 +46,7 @@ use rnix::{ value::{Anchor as RAnchor, Value as RValue}, SyntaxNode, TextRange, TextSize, }; +use scope::Scope; use std::{ borrow::Cow, collections::HashMap, @@ -45,9 +54,41 @@ use std::{ path::{Path, PathBuf}, process, rc::Rc, + str::FromStr, }; +use value::NixValue; type Error = Box; +#[derive(Debug, Clone, Trace, Finalize)] +pub enum EvalError { + Unimplemented(String), + Unexpected(String), + StackTrace(String), + TypeError(String), + Parsing, + Unknown, +} + +impl std::error::Error for EvalError {} + +impl std::fmt::Display for EvalError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + EvalError::Unimplemented(msg) => write!(f, "unimplemented: {}", msg), + EvalError::Unexpected(msg) => write!(f, "unexpected: {}", msg), + EvalError::StackTrace(msg) => write!(f, "{}", msg), + EvalError::TypeError(msg) => write!(f, "type error: {}", msg), + EvalError::Parsing => write!(f, "parsing error"), + EvalError::Unknown => write!(f, "unknown value"), + } + } +} + +impl From<&EvalError> for EvalError { + fn from(x: &EvalError) -> Self { + x.clone() + } +} fn main() { if let Err(err) = real_main() { @@ -82,6 +123,7 @@ fn real_main() -> Result<(), Error> { resolve_provider: Some(false), work_done_progress_options: WorkDoneProgressOptions::default(), }), + hover_provider: Some(true), rename_provider: Some(RenameProviderCapability::Simple(true)), selection_range_provider: Some(SelectionRangeProviderCapability::Simple(true)), ..ServerCapabilities::default() @@ -92,6 +134,7 @@ fn real_main() -> Result<(), Error> { App { files: HashMap::new(), + store: Gc::new(GcCell::new(HashMap::new())), conn: connection, } .main(); @@ -102,7 +145,8 @@ fn real_main() -> Result<(), Error> { } struct App { - files: HashMap, + files: HashMap)>, + store: Gc>>>, conn: Connection, } impl App { @@ -193,7 +237,7 @@ impl App { let document_links = self.document_links(¶ms).unwrap_or_default(); self.reply(Response::new_ok(id, document_links)); } else if let Some((id, params)) = cast::(&mut req) { - let changes = if let Some((ast, code)) = self.files.get(¶ms.text_document.uri) { + let changes = if let Some((ast, code, _)) = self.files.get(¶ms.text_document.uri) { let fmt = nixpkgs_fmt::reformat_node(&ast.node()); vec![TextEdit { range: utils::range(&code, TextRange::up_to(ast.node().text().len())), @@ -203,9 +247,24 @@ impl App { Vec::new() }; self.reply(Response::new_ok(id, changes)); + } else if let Some((id, params)) = cast::(&mut req) { + if let Some((range, markdown)) = self.handle_hover(params) { + self.reply(Response::new_ok( + id, + Hover { + contents: HoverContents::Markup(MarkupContent { + kind: MarkupKind::Markdown, + value: markdown, + }), + range, + }, + )); + } else { + self.reply(Response::new_ok(id, ())); + } } else if let Some((id, params)) = cast::(&mut req) { let mut selections = Vec::new(); - if let Some((ast, code)) = self.files.get(¶ms.text_document.uri) { + if let Some((ast, code, _)) = self.files.get(¶ms.text_document.uri) { for pos in params.positions { selections.push(utils::selection_ranges(&ast.node(), code, pos)); } @@ -228,7 +287,13 @@ impl App { let text = params.text_document.text; let parsed = rnix::parse(&text); self.send_diagnostics(params.text_document.uri.clone(), &text, &parsed)?; - self.files.insert(params.text_document.uri, (parsed, text)); + if let Ok(path) = PathBuf::from_str(params.text_document.uri.path()) { + let gc_root = Gc::new(Scope::Root(path, Some(self.store.clone()))); + let parsed_root = parsed.root().inner().ok_or(EvalError::Parsing); + let evaluated = parsed_root.and_then(|x| Tree::parse_legacy(&x, gc_root)); + self.files + .insert(params.text_document.uri, (parsed, text, evaluated)); + } } DidChangeTextDocument::METHOD => { let params: DidChangeTextDocumentParams = serde_json::from_value(req.params)?; @@ -237,8 +302,8 @@ impl App { let mut content = Cow::from(&change.text); if let Some(range) = &change.range { if self.files.contains_key(&uri) { - let original = self.files.get(&uri) - .unwrap().1.lines().collect::>(); + let original = + self.files.get(&uri).unwrap().1.lines().collect::>(); let start_line = range.start.line; let start_char = range.start.character; let end_line = range.end.line; @@ -284,8 +349,13 @@ impl App { } let parsed = rnix::parse(&content); self.send_diagnostics(uri.clone(), &content, &parsed)?; - self.files - .insert(uri, (parsed, content.to_owned().to_string())); + if let Ok(path) = PathBuf::from_str(uri.path()) { + let gc_root = Gc::new(Scope::Root(path, Some(self.store.clone()))); + let parsed_root = parsed.root().inner().ok_or(EvalError::Parsing); + let evaluated = parsed_root.and_then(|x| Tree::parse_legacy(&x, gc_root)); + self.files + .insert(uri, (parsed, content.to_owned().to_string(), evaluated)); + } } } _ => (), @@ -293,14 +363,14 @@ impl App { Ok(()) } fn lookup_definition(&mut self, params: TextDocumentPositionParams) -> Option { - let (current_ast, current_content) = self.files.get(¶ms.text_document.uri)?; + let (current_ast, current_content, _) = self.files.get(¶ms.text_document.uri)?; let offset = utils::lookup_pos(current_content, params.position)?; let node = current_ast.node(); let (name, scope, _) = self.scope_for_ident(params.text_document.uri, &node, offset)?; let var_e = scope.get(name.as_str())?; if let Some(var) = &var_e.var { - let (_definition_ast, definition_content) = self.files.get(&var.file)?; + let (_definition_ast, definition_content, _) = self.files.get(&var.file)?; Some(Location { uri: (*var.file).clone(), range: utils::range(definition_content, var.key.text_range()), @@ -309,9 +379,26 @@ impl App { None } } + fn handle_hover(&self, params: TextDocumentPositionParams) -> Option<(Option, String)> { + let (_, content, tree) = self.files.get(¶ms.text_document.uri)?; + let offset = utils::lookup_pos(content, params.position)?; + let tree = match tree { + Ok(x) => { + let tmp = Gc::new(x.clone()); + climb_tree(&tmp, offset).clone() + } + Err(e) => return Some((None, format!("{:?}", e))), + }; + let range = utils::range(content, tree.range?); + let val = match tree.eval() { + Ok(x) => x.format_markdown(), + Err(e) => format!("{}", e), + }; + Some((Some(range), val)) + } #[allow(clippy::shadow_unrelated)] // false positive fn completions(&mut self, params: &TextDocumentPositionParams) -> Option> { - let (ast, content) = self.files.get(¶ms.text_document.uri)?; + let (ast, content, _) = self.files.get(¶ms.text_document.uri)?; let offset = utils::lookup_pos(content, params.position)?; let node = ast.node(); @@ -319,7 +406,7 @@ impl App { self.scope_for_ident(params.text_document.uri.clone(), &node, offset)?; // Re-open, because scope_for_ident may mutably borrow - let (_, content) = self.files.get(¶ms.text_document.uri)?; + let (_, content, _) = self.files.get(¶ms.text_document.uri)?; let mut completions = Vec::new(); for (var, data) in scope { @@ -327,7 +414,9 @@ impl App { let det = data.render_detail(); completions.push(CompletionItem { label: var.clone(), - documentation: data.documentation.map(|x| lsp_types::Documentation::String(x)), + documentation: data + .documentation + .map(|x| lsp_types::Documentation::String(x)), deprecated: Some(data.deprecated), text_edit: Some(TextEdit { range: utils::range(content, node.node().text_range()), @@ -371,7 +460,7 @@ impl App { } let uri = params.text_document_position.text_document.uri; - let (ast, code) = self.files.get(&uri)?; + let (ast, code, _) = self.files.get(&uri)?; let offset = utils::lookup_pos(code, params.text_document_position.position)?; let info = utils::ident_at(&ast.node(), offset)?; if !info.path.is_empty() { @@ -395,7 +484,7 @@ impl App { Some(changes) } fn document_links(&mut self, params: &DocumentLinkParams) -> Option> { - let (current_ast, current_content) = self.files.get(¶ms.text_document.uri)?; + let (current_ast, current_content, _) = self.files.get(¶ms.text_document.uri)?; let parent_dir = Path::new(params.text_document.uri.path()).parent(); let home_dir = home_dir(); let home_dir = home_dir.as_ref(); @@ -457,3 +546,18 @@ impl App { Ok(()) } } + +fn climb_tree(here: &Gc, offset: usize) -> &Gc { + for child in here.children().clone() { + let range = match child.range { + Some(x) => x, + None => continue, + }; + let start: usize = range.start().into(); + let end: usize = range.end().into(); + if start <= offset && offset <= end { + return climb_tree(child, offset); + } + } + here +} diff --git a/src/parse.rs b/src/parse.rs new file mode 100644 index 0000000..d4186c0 --- /dev/null +++ b/src/parse.rs @@ -0,0 +1,634 @@ +use crate::value::*; +use crate::EvalError; +use crate::{ + eval::{merge_values, to_string, Tree, TreeSource}, + scope::Scope, +}; +use gc::{Finalize, Gc, GcCell, Trace}; +use rnix::{ + types::{ + Apply, AttrSet, BinOp, EntryHolder, Ident, IfElse, Lambda, LetIn, List, OrDefault, Paren, + Select, Str, TokenWrapper, TypedNode, UnaryOp, Value, With, Wrapper, + }, + StrPart, SyntaxNode, +}; +use rnix::{ + types::{Assert, Dynamic}, + value::Anchor, +}; +use std::{borrow::Borrow, collections::HashMap}; + +#[derive(Debug, Clone, Trace, Finalize)] +pub enum BinOpKind { + Concat, + Update, + Add, + Sub, + Mul, + Div, + Equal, + Less, + LessOrEq, + More, + MoreOrEq, + NotEqual, +} + +impl Tree { + pub fn parse(node: SyntaxNode, scope: Gc) -> Result, EvalError> { + use rnix::SyntaxKind::*; + let range = Some(node.text_range()); + let recurse = |node| Tree::parse(node, scope.clone()); + let source = match node.kind() { + NODE_ERROR => return Err(EvalError::Parsing), + NODE_ASSERT => { + let node = Assert::cast(node).ok_or(EvalError::Parsing)?; + let node_condition = node.condition().ok_or(EvalError::Parsing)?; + let node_body = node.body().ok_or(EvalError::Parsing)?; + TreeSource::Assert { + condition: recurse(node_condition), + body: recurse(node_body), + } + } + NODE_IF_ELSE => { + let node = IfElse::cast(node).ok_or(EvalError::Parsing)?; + let node_condition = node.condition().ok_or(EvalError::Parsing)?; + let node_true_body = node.body().ok_or(EvalError::Parsing)?; + let node_false_body = node.else_body().ok_or(EvalError::Parsing)?; + TreeSource::IfElse { + condition: recurse(node_condition), + true_body: recurse(node_true_body), + false_body: recurse(node_false_body), + } + } + NODE_PAREN => { + let paren = Paren::cast(node).ok_or(EvalError::Parsing)?; + let inner = paren.inner().ok_or(EvalError::Parsing)?; + TreeSource::Paren { + inner: recurse(inner), + } + } + NODE_DYNAMIC => { + let paren = Dynamic::cast(node).ok_or(EvalError::Parsing)?; + let inner = paren.inner().ok_or(EvalError::Parsing)?; + TreeSource::Dynamic { + inner: recurse(inner), + } + } + NODE_LET_IN => { + let node = LetIn::cast(node).ok_or(EvalError::Parsing)?; + + let new_scope = Gc::new(Scope::Normal { + parent: scope.clone(), + contents: GcCell::new(HashMap::new()), + }); + + let mut map = HashMap::new(); + let mut definitions = vec![]; + let mut inherits = vec![]; + + for entry in node.entries() { + let inner = + Self::parse(entry.value().ok_or(EvalError::Parsing)?, new_scope.clone())?; + + let path: Vec = + entry.key().ok_or(EvalError::Parsing)?.path().collect(); + + let value: Gc = if path.len() == 1 { + inner + } else { + let mut out = inner; + for part in path.clone().into_iter().skip(1).rev() { + let mut map = HashMap::new(); + map.insert(to_string(&part, scope.clone())?, out); + out = Gc::new(Tree { + value: GcCell::new(None), + hash: GcCell::new(None), + source: TreeSource::Literal { + value: NixValue::Map(map), + }, + range, + scope: new_scope.clone(), + }); + } + out + }; + + let attr = Gc::new(Tree { + value: GcCell::new(None), + hash: GcCell::new(None), + source: TreeSource::LetAttr { + path: path + .clone() + .into_iter() + .map(|x| Self::parse(x, scope.clone())) + .collect(), + value: Ok(value), + }, + range: Some(entry.node().text_range()), + scope: new_scope.clone(), + }); + definitions.push(Ok(attr.clone())); + + let root_key = to_string(&path[0], scope.clone())?; + let insertion = match map.get(&root_key) as Option<&Gc> { + Some(existing) => merge_values(existing.clone(), attr)?, + None => attr, + }; + map.insert(root_key, insertion); + } + + for inherit in node.inherits() { + if let Some(from_node) = inherit.from() { + let from = Self::parse( + from_node.inner().ok_or(EvalError::Parsing)?, + new_scope.clone(), + )?; + inherits.push(Ok(from.clone())); // allow handling inside the parentheses + for ident in inherit.idents() { + let name = ident.as_str(); + let index = Gc::new(Tree { + value: GcCell::new(None), + hash: GcCell::new(None), + source: TreeSource::Ident { + name: name.to_string(), + }, + range: Some(from_node.node().text_range()), + scope: new_scope.clone(), + }); + let attr = Gc::new(Tree { + value: GcCell::new(None), + hash: GcCell::new(None), + source: TreeSource::Select { + from: Ok(from.clone()), + index: Ok(index), + }, + range: Some(ident.node().text_range()), + scope: new_scope.clone(), + }); + inherits.push(Ok(attr.clone())); + map.insert(name.to_string(), attr); + } + } else { + for ident in inherit.idents() { + let name = ident.as_str(); + let attr = Gc::new(Tree { + value: GcCell::new(None), + hash: GcCell::new(None), + source: TreeSource::Ident { + name: name.to_string(), + }, + range: Some(ident.node().text_range()), + scope: new_scope.clone(), + }); + inherits.push(Ok(attr.clone())); + map.insert(name.to_string(), attr); + } + } + } + + if let Scope::Normal { contents, .. } = new_scope.borrow() { + *contents.borrow_mut() = map; + } + + return Ok(Gc::new(Tree { + value: GcCell::new(None), + hash: GcCell::new(None), + source: TreeSource::LetIn { + inherits, + definitions, + body: Tree::parse( + node.body().ok_or(EvalError::Parsing)?, + new_scope.clone(), + ), + }, + range, + scope: new_scope, + })); + } + NODE_BIN_OP => { + let node_binop = BinOp::cast(node.clone()).ok_or(EvalError::Parsing)?; + let left = Self::parse(node_binop.lhs().ok_or(EvalError::Parsing)?, scope.clone()); + let right = Self::parse(node_binop.rhs().ok_or(EvalError::Parsing)?, scope.clone()); + use rnix::types::BinOpKind::*; + match node_binop.operator() { + IsSet => TreeSource::Legacy { syntax: node }, + And => TreeSource::BoolAnd { left, right }, + Or => TreeSource::BoolOr { left, right }, + Implication => TreeSource::Implication { left, right }, + _ => { + let op = match node_binop.operator() { + And | Or | IsSet | Implication => unreachable!(), + Concat => BinOpKind::Concat, + Update => BinOpKind::Update, + Add => BinOpKind::Add, + Sub => BinOpKind::Sub, + Mul => BinOpKind::Mul, + Div => BinOpKind::Div, + Equal => BinOpKind::Equal, + NotEqual => BinOpKind::NotEqual, + Less => BinOpKind::Less, + LessOrEq => BinOpKind::LessOrEq, + More => BinOpKind::More, + MoreOrEq => BinOpKind::MoreOrEq, + }; + TreeSource::BinOp { op, left, right } + } + } + } + NODE_IDENT => { + let name = Ident::cast(node) + .ok_or(EvalError::Parsing)? + .as_str() + .to_string(); + TreeSource::Ident { name } + } + NODE_WITH => { + let node = With::cast(node).ok_or(EvalError::Parsing)?; + let namespace = + Self::parse(node.namespace().ok_or(EvalError::Parsing)?, scope.clone()); + let new_scope = Gc::new(Scope::With { + parent: scope.clone(), + contents: namespace.clone()?, + }); + TreeSource::With { + value: namespace, + body: Self::parse(node.body().ok_or(EvalError::Parsing)?, new_scope), + } + } + NODE_LIST => { + let node = List::cast(node).ok_or(EvalError::Parsing)?; + let mut out = vec![]; + for item in node.items() { + out.push(Self::parse(item, scope.clone())); + } + TreeSource::List { items: out } + } + NODE_APPLY => { + let node_apply = Apply::cast(node).ok_or(EvalError::Parsing)?; + let node_lambda = node_apply.lambda().ok_or(EvalError::Parsing)?; + let node_arg = node_apply.value().ok_or(EvalError::Parsing)?; + TreeSource::Apply { + lambda: recurse(node_lambda), + arg: recurse(node_arg), + } + } + NODE_ATTR_SET => { + let node = AttrSet::cast(node).ok_or(EvalError::Parsing)?; + let is_recursive = node.recursive(); + + // create a new scope if we're a recursive attr set + let new_scope = if is_recursive { + let new = Scope::Normal { + parent: scope.clone(), + contents: GcCell::new(HashMap::new()), + }; + Gc::new(new) + } else { + scope.clone() + }; + + let mut map = HashMap::new(); + let mut definitions = vec![]; + let mut inherits = vec![]; + + for entry in node.entries() { + let inner = + Self::parse(entry.value().ok_or(EvalError::Parsing)?, new_scope.clone())?; + + let mut path = vec![]; + for tmp in entry.key().ok_or(EvalError::Parsing)?.path() { + path.push(Self::parse(tmp, scope.clone())); + } + + let value = if path.len() == 1 { + inner + } else { + let mut out = inner; + for part in path.clone().into_iter().skip(1).rev() { + let mut map = HashMap::new(); + map.insert(part?.get_ident()?, out); + out = Gc::new(Tree { + value: GcCell::new(None), + hash: GcCell::new(None), + source: TreeSource::Literal { + value: NixValue::Map(map), + }, + range: Some(entry.node().text_range()), + scope: new_scope.clone(), + }); + } + out + }; + + let attr = Gc::new(Tree { + value: GcCell::new(None), + hash: GcCell::new(None), + source: TreeSource::MapAttr { + path: path.clone(), + value: Ok(value), + }, + range: Some(entry.node().text_range()), + scope: new_scope.clone(), + }); + definitions.push(Ok(attr.clone())); + + let root_key = match path[0].as_ref()?.get_ident() { + Ok(x) => x, + Err(_) => continue, + }; + let insertion = match map.get(&root_key) as Option<&Gc> { + Some(existing) => merge_values(existing.clone(), attr)?, + None => attr, + }; + map.insert(root_key, insertion); + } + + for inherit in node.inherits() { + if let Some(from) = inherit.from() { + let from = Self::parse( + from.inner().ok_or(EvalError::Parsing)?, + new_scope.clone(), + )?; + inherits.push(Ok(from.clone())); // allow handling inside the parenthesis + for ident in inherit.idents() { + let name = ident.as_str(); + let index = Gc::new(Tree { + value: GcCell::new(None), + hash: GcCell::new(None), + source: TreeSource::Ident { + name: name.to_string(), + }, + range: None, + scope: scope.clone(), + }); + let attr = Gc::new(Tree { + value: GcCell::new(None), + hash: GcCell::new(None), + source: TreeSource::Select { + from: Ok(from.clone()), + index: Ok(index), + }, + range: Some(ident.node().text_range()), + scope: scope.clone(), + }); + inherits.push(Ok(attr.clone())); + map.insert(name.to_string(), attr); + } + } else { + for ident in inherit.idents() { + let name = ident.as_str(); + let attr = Gc::new(Tree { + value: GcCell::new(None), + hash: GcCell::new(None), + source: TreeSource::Ident { + name: name.to_string(), + }, + range: Some(ident.node().text_range()), + scope: scope.clone(), + }); + inherits.push(Ok(attr.clone())); + map.insert(name.to_string(), attr); + } + } + } + + if is_recursive { + // update the scope to include our hashmap + if let Scope::Normal { contents, .. } = new_scope.borrow() { + *contents.borrow_mut() = map.clone(); + } + } + + return Ok(Gc::new(Tree { + value: GcCell::new(Some(Gc::new(NixValue::Map(map)))), + hash: GcCell::new(None), + source: TreeSource::Map { + inherits, + definitions, + }, + range, + scope: new_scope, + })); + } + NODE_STRING_INTERPOL => { + return Self::parse(node.first_child().ok_or(EvalError::Parsing)?, scope) + } + NODE_STRING => { + let root = Str::cast(node).ok_or(EvalError::Parsing)?; + let parts = root.parts(); + if parts.len() == 1 { + if let StrPart::Literal(ref x) = parts[0] { + return Ok(Gc::new(Tree { + value: GcCell::new(None), + hash: GcCell::new(None), + source: TreeSource::Literal { + value: NixValue::Str(x.clone()), + }, + range, + scope: scope.clone(), + })); + } + } + + let mut out = vec![]; + for part in root.parts() { + let x = match part { + StrPart::Literal(ref x) => Gc::new(Tree { + value: GcCell::new(None), + hash: GcCell::new(None), + source: TreeSource::Literal { + value: NixValue::Str(x.clone()), + }, + range: None, + scope: Gc::new(Scope::None), + }), + StrPart::Ast(inner_node) => recurse(inner_node)?, + }; + out.push(Ok(x)); + } + + TreeSource::StringInterpol { parts: out } + } + NODE_SELECT => { + let node = Select::cast(node).ok_or(EvalError::Parsing)?; + TreeSource::Select { + from: recurse(node.set().ok_or(EvalError::Parsing)?), + index: recurse(node.index().ok_or(EvalError::Parsing)?), + } + } + NODE_UNARY_OP => { + let node = UnaryOp::cast(node).ok_or(EvalError::Parsing)?; + use rnix::types::UnaryOpKind; + match node.operator() { + UnaryOpKind::Invert => TreeSource::UnaryInvert { + value: recurse(node.value().ok_or(EvalError::Parsing)?), + }, + UnaryOpKind::Negate => TreeSource::UnaryNegate { + value: recurse(node.value().ok_or(EvalError::Parsing)?), + }, + } + } + NODE_LITERAL => { + let root = Value::cast(node.clone()) + .ok_or(EvalError::Parsing)? + .to_value() + .map_err(|_| EvalError::Parsing)?; + use rnix::value::Value::*; + TreeSource::Literal { + value: match root { + Float(x) => NixValue::Float(x), + Integer(x) => NixValue::Integer(x), + String(x) => NixValue::Str(x), + Path(x, y) => match x { + Anchor::Relative => NixValue::Path( + NixPathAnchor::Relative( + scope + .root_path() + .ok_or(EvalError::Unexpected("missing path".to_string()))? + .parent() + .ok_or(EvalError::Parsing)? + .to_path_buf(), + ), + y, + ), + Anchor::Absolute => NixValue::Path(NixPathAnchor::Absolute, y), + _ => { + return Err(EvalError::Unimplemented(format!( + "{:?} -- {:?}\n\n{}", + x, + y, + node.text() + ))) + } + }, + }, + } + } + NODE_LAMBDA => { + let node_lambda = Lambda::cast(node).ok_or(EvalError::Parsing)?; + TreeSource::Lambda { + literal: Gc::new(NixValue::Lambda(NixLambda::Node { + lambda: node_lambda, + scope: scope.clone(), + })), + params: vec![], + body: Err(EvalError::Parsing), + } + } + _ => TreeSource::Legacy { syntax: node }, + }; + Ok(Gc::new(Self { + hash: GcCell::new(None), + value: GcCell::new(None), + source, + range, + scope, + })) + } +} + +pub fn eval_node(node: &SyntaxNode, scope: Gc) -> Result, EvalError> { + use rnix::SyntaxKind::*; + Ok(match node.kind() { + NODE_LAMBDA => { + let out = NixValue::Lambda(NixLambda::Node { + lambda: Lambda::cast(node.clone()).ok_or(EvalError::Parsing)?, + scope, + }); + Gc::new(out) + } + NODE_BIN_OP => { + let root = BinOp::cast(node.clone()).ok_or(EvalError::Parsing)?; + use rnix::types::BinOpKind; + match root.operator() { + BinOpKind::IsSet => { + let tmp = + Tree::parse_legacy(&root.lhs().ok_or(EvalError::Parsing)?, scope.clone())?; + match tmp.eval()?.borrow() { + NixValue::Map(map) => { + let rhs = root.rhs().ok_or(EvalError::Parsing)?; + match rhs.kind() { + NODE_IDENT | NODE_DYNAMIC => { + let key = to_string(&rhs, scope)?; + Gc::new(NixValue::Bool(map.contains_key(&key))) + } + NODE_SELECT => { + let mut path = vec![]; // NB: inner ... outer + let mut base = rhs; + while base.kind() == NODE_SELECT { + let tmp = + Select::cast(base.clone()).ok_or(EvalError::Parsing)?; + let part = to_string( + &tmp.index().ok_or(EvalError::Parsing)?, + scope.clone(), + )?; + path.push(part); + base = tmp.set().ok_or(EvalError::Parsing)?; + } + let part = to_string(&base, scope)?; + path.push(part); + + let mut cursor = tmp.eval()?; + let mut valid = true; + 'foobar: for part in path.into_iter().rev() { + let map = match cursor.borrow() { + NixValue::Map(x) => x, + _ => { + valid = false; + break 'foobar; + } + }; + match map.get(&part) { + Some(x) => cursor = x.eval()?, + None => { + valid = false; + break 'foobar; + } + } + } + + Gc::new(NixValue::Bool(valid)) + } + x => { + return Err(EvalError::Unimplemented(format!( + "cannot handle {:?}", + x + ))) + } + } + } + _ => Gc::new(NixValue::Bool(false)), + } + } + _ => unreachable!(), + } + } + NODE_OR_DEFAULT => { + let nodex = OrDefault::cast(node.clone()).ok_or(EvalError::Parsing)?; + let select = nodex.index().ok_or(EvalError::Parsing)?; + let tmp = Tree::parse_legacy(&select.set().ok_or(EvalError::Parsing)?, scope.clone())?; + match tmp.eval()?.borrow() { + NixValue::Map(x) => { + let key = to_string(&select.index().ok_or(EvalError::Parsing)?, scope.clone())?; + match x.get(&key) { + Some(x) => x.eval()?, + None => { + Tree::parse_legacy(&nodex.default().ok_or(EvalError::Parsing)?, scope)? + .eval()? + } + } + } + _ => Tree::parse_legacy(&nodex.default().ok_or(EvalError::Parsing)?, scope)? + .eval()?, + } + } + x => { + return Err(EvalError::Unimplemented(format!( + "Unexpected syntax node kind: {:?}\n\n{}", + x, + node.text() + ))) + } + }) +} diff --git a/src/scope.rs b/src/scope.rs new file mode 100644 index 0000000..d8d28e7 --- /dev/null +++ b/src/scope.rs @@ -0,0 +1,128 @@ +use crate::builtins::NixBuiltin; +use crate::eval::Tree; +use crate::value::{NixLambda, NixValue}; +use gc::{Finalize, Gc, GcCell, Trace}; +use std::{borrow::Borrow, collections::HashMap, path::PathBuf}; + +#[derive(Trace, Finalize)] +pub enum Scope { + None, + Root(PathBuf, Option>>>>), + Normal { + parent: Gc, + contents: GcCell>>, + }, + With { + parent: Gc, + contents: Gc, + }, +} + +impl Scope { + pub fn root_path(&self) -> Option { + match &self { + Scope::None => None, + Scope::Root(path, _) => Some(path.clone()), + Scope::Normal { parent, .. } => parent.root_path(), + Scope::With { parent, .. } => parent.root_path(), + } + } + + pub fn submit_hash(&self, hash: String, value: Gc) { + match &self { + Scope::Root(_, None) | Scope::None => (), + Scope::Root(_, Some(store)) => { + let tmp: &GcCell<_> = store.borrow(); + tmp.borrow_mut().insert(hash, value); + } + Scope::Normal { parent, .. } => parent.submit_hash(hash, value), + Scope::With { parent, .. } => parent.submit_hash(hash, value), + } + } + + pub fn pull_hash(&self, hash: String) -> Option> { + match &self { + Scope::Root(_, None) | Scope::None => None, + Scope::Root(_, Some(store)) => { + let tmp: &GcCell<_> = store.borrow(); + tmp.borrow().get(&hash).cloned() + } + Scope::Normal { parent, .. } => parent.pull_hash(hash), + Scope::With { parent, .. } => parent.pull_hash(hash), + } + } + + pub fn list(&self) -> Vec { + match self { + Scope::None => vec![], + Scope::Root(_, _) => vec![], + Scope::Normal { parent, contents } => { + let mut out: Vec = contents.borrow().keys().cloned().collect(); + out.extend(parent.list()); + out + } + Scope::With { parent, contents } => { + let eval = match contents.eval() { + Ok(x) => x, + Err(_) => return vec![], + }; + let map = match eval.as_map() { + Ok(x) => x, + Err(_) => return vec![], + }; + let mut out: Vec = map.keys().cloned().collect(); + out.extend(parent.list()); + out + } + } + } + + pub fn get(&self, name: &str) -> Option> { + self.get_normal(name).or_else(|| self.get_with(name)) + } + + pub fn get_normal(&self, name: &str) -> Option> { + use rnix::types::Wrapper; + match self { + Scope::None | Scope::Root(_, _) => Some(Gc::new(Tree::from_concrete(match name { + "true" => NixValue::Bool(true), + "false" => NixValue::Bool(false), + "null" => NixValue::Null, + "derivation" => { + let source = include_str!("./derivation.nix"); + let root = rnix::parse(&source).root().inner()?; + let tmp = Tree::parse(root, Gc::new(Scope::None)).ok()?.eval().ok()?; + let val: &NixValue = tmp.borrow(); + val.clone() + } + "abort" => NixValue::Lambda(NixLambda::Builtin(NixBuiltin::Abort)), + "import" => NixValue::Lambda(NixLambda::Builtin(NixBuiltin::Import)), + "map" => NixValue::Lambda(NixLambda::Builtin(NixBuiltin::Map)), + "toString" => NixValue::Lambda(NixLambda::Builtin(NixBuiltin::ToString)), + "removeAttrs" => NixValue::Lambda(NixLambda::Builtin(NixBuiltin::RemoveAttrs)), + "builtins" => crate::builtins::make_builtins_map(), + _ => return None, + }))), + Scope::Normal { parent, contents } => match contents.borrow().get(name) { + Some(x) => Some(x.clone()), + None => parent.get_normal(name), + }, + Scope::With { parent, .. } => parent.get_normal(name), + } + } + + fn get_with(&self, name: &str) -> Option> { + match self { + Scope::None => None, + Scope::Root(_, _) => None, + Scope::Normal { parent, .. } => parent.get_with(name), + Scope::With { parent, contents } => match contents.eval().ok()?.borrow() { + NixValue::Map(map) => match map.get(name) { + Some(x) => Some(x.clone()), + None => parent.get_with(name), + }, + _ => None, + }, + } + } +} diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..ef9fd2c --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,69 @@ +#![allow(clippy::unwrap_used)] + +use eval::Tree; +use gc::{Gc, GcCell}; +use rnix::types::Wrapper; +use scope::Scope; +use std::borrow::Borrow; +use std::collections::HashMap; +use value::NixValue; + +#[allow(dead_code)] +fn eval(code: &str) -> NixValue { + let ast = rnix::parse(&code); + let root = ast.root().inner().unwrap(); + let path = std::env::current_dir().unwrap(); + let out = Tree::parse_legacy( + &root, + Gc::new(Scope::Root( + path, + Some(Gc::new(GcCell::new(HashMap::new()))), + )), + ) + .unwrap(); + let tmp = out.eval(); + let val: &NixValue = tmp.as_ref().unwrap().borrow(); + val.clone() +} + +use super::*; + +#[test] +fn basic_math() { + let code = "let abc = 10; in 1 + abc"; + assert_eq!(eval(code).as_int().unwrap(), 11); +} + +#[test] +fn ptr_equality() { + let code = "let a = { f = (x: x); }; b = a; in a == b"; + assert_eq!(eval(code).as_bool().unwrap(), true); +} + +#[test] +fn shadowing() { + let code = "let import = 1; in import"; + assert_eq!(eval(code).as_int().unwrap(), 1); + let code = "let fun = { import }: import; in fun { import = 3; }"; + assert_eq!(eval(code).as_int().unwrap(), 3); + let code = "builtins.elemAt ({ inherit (builtins) map; }.map (x: x*2) [1]) 0"; + assert_eq!(eval(code).as_int().unwrap(), 2); +} + +#[test] +fn fixed_point_combinator() { + let code = "((f: let x = f x; in x) (self: { x = 1; y = self.x; })).y"; + assert_eq!(eval(code).as_int().unwrap(), 1); +} + +#[test] +fn infinite_recursion() { + let code = "let x = x; in 1"; + assert_eq!(eval(code).as_int().unwrap(), 1); +} + +#[test] +fn rec_inherit() { + let code = "let x = 1; y = rec { inherit x; }; in y.x"; + assert_eq!(eval(code).as_int().unwrap(), 1); +} diff --git a/src/value.rs b/src/value.rs new file mode 100644 index 0000000..e12939f --- /dev/null +++ b/src/value.rs @@ -0,0 +1,311 @@ +use crate::eval::Tree; +use crate::scope::*; +use crate::{builtins::NixBuiltin, EvalError}; +use gc::{Finalize, Gc, Trace}; +use rnix::types::{Lambda, PatBind, PatEntry, TokenWrapper, TypedNode}; +use std::{borrow::Borrow, collections::HashMap, fmt::Debug, path::PathBuf}; + +#[derive(Clone, Trace, Finalize)] +pub enum NixValue { + Bool(bool), + Float(f64), + Integer(i64), + Lambda(NixLambda), + List(Vec>), + Map(HashMap>), + Null, + Path(NixPathAnchor, String), + Str(String), +} + +#[derive(Debug, Clone, Trace, Finalize)] +pub enum NixPathAnchor { + Absolute, + Relative(PathBuf), +} + +#[derive(Clone, Trace, Finalize)] +pub enum NixLambda { + Node { + #[unsafe_ignore_trace] + lambda: Lambda, + scope: Gc, + }, + Builtin(NixBuiltin), +} + +impl NixValue { + fn type_name(&self) -> String { + match self { + NixValue::Bool(_) => "bool", + NixValue::Float(_) => "float", + NixValue::Integer(_) => "integer", + NixValue::Lambda(_) => "lambda", + NixValue::List(_) => "list", + NixValue::Map(_) => "map", + NixValue::Null => "null", + NixValue::Path(_, _) => "path", + NixValue::Str(_) => "string", + } + .to_string() + } + + pub fn as_str(&self) -> Result { + match self { + NixValue::Str(x) => Ok(x.clone()), + _ => Err(EvalError::TypeError(format!( + "expected string, got {}", + self.type_name() + ))), + } + } + + pub fn as_lambda(&self) -> Result { + match self { + NixValue::Lambda(x) => Ok(x.clone()), + _ => Err(EvalError::TypeError(format!( + "expected lambda, got {}", + self.type_name() + ))), + } + } + + pub fn as_bool(&self) -> Result { + match self { + NixValue::Bool(x) => Ok(*x), + _ => Err(EvalError::TypeError(format!( + "expected bool, got {}", + self.type_name() + ))), + } + } + + pub fn as_int(&self) -> Result { + match self { + NixValue::Integer(x) => Ok(*x), + _ => Err(EvalError::TypeError(format!( + "expected int, got {}", + self.type_name() + ))), + } + } + + pub fn as_list(&self) -> Result>, EvalError> { + match self { + NixValue::List(x) => Ok(x.clone()), + _ => Err(EvalError::TypeError(format!( + "expected list, got {}", + self.type_name() + ))), + } + } + + pub fn as_map(&self) -> Result>, EvalError> { + match self { + NixValue::Map(x) => Ok(x.clone()), + _ => Err(EvalError::TypeError(format!( + "expected map, got {}", + self.type_name() + ))), + } + } + + pub fn contains(&self, path: &[String]) -> Result { + let mut cursor = Gc::new(self.clone()); + for part in path { + let map = match cursor.as_map() { + Ok(x) => x, + Err(_) => return Ok(false), + }; + match map.get(part) { + Some(x) => cursor = x.eval()?, + None => return Ok(false), + } + } + Ok(true) + } + + pub fn equals(&self, other: &Self) -> Result { + match (self, other) { + (NixValue::Integer(x), NixValue::Integer(y)) => Ok(x == y), + (NixValue::Float(x), NixValue::Float(y)) => Ok((x - y).abs() < 0.001), + (NixValue::Str(x), NixValue::Str(y)) => Ok(x == y), + (NixValue::Null, NixValue::Null) => Ok(true), + (NixValue::Bool(x), NixValue::Bool(y)) => Ok(x == y), + (NixValue::Map(x), NixValue::Map(y)) => { + let x: &HashMap<_, _> = x.borrow(); + let y: &HashMap<_, _> = y.borrow(); + for (key, valx) in x.iter() { + match y.get(key) { + Some(valy) => { + let a: Gc = valx.eval()?; + let b: Gc = valy.eval()?; + if !a.equals(&b)? { + println!("MISMATCH: {}", key); + return Ok(false); + } + } + None => return Ok(false), + } + } + for key in y.keys() { + if !x.contains_key(key) { + println!("MISSING KEY: {}", key); + return Ok(false); + } + } + Ok(true) + } + (NixValue::List(x), NixValue::List(y)) => { + let mut x1 = vec![]; + for item in x { + x1.push(item.eval()?); + } + let mut y1 = vec![]; + for item in y { + y1.push(item.eval()?); + } + if x1.len() != y1.len() { + Ok(false) + } else { + for i in 0..x1.len() { + if !x1[i].equals(&y1[i])? { + return Ok(false); + } + } + Ok(true) + } + } + _ => Ok(false), + } + } + + pub fn format_markdown(&self) -> String { + use NixValue::*; + match self { + Bool(x) => format!("```nix\n{}\n```", x), + Float(x) => format!("```nix\n{}\n```", x), + Integer(x) => format!("```nix\n{}\n```", x), + Null => "```nix\nnull\n```".to_string(), + // TODO: show definition! + Lambda(_) => "```nix\n\n```".to_string(), + List(_) => "```nix\n[ ... ]\n```".to_string(), + // TODO: show derivations and functors! + Map(map) => { + if map.is_empty() { + "```nix\n{ }\n```".to_string() + } else if let Some(out) = try_format_derivation(map) { + out + } else { + let mut attrs = map + .keys() + .map(|x| format!(" {} = ...;\n", x)) + .collect::>(); + + let limit = 100; + if attrs.len() > limit { + attrs.truncate(limit); + attrs.push(" /* ... */\n".to_string()); + } + + format!("```nix\n{{\n{}}}\n```", attrs.join("")) + } + } + Path(_, s) => s.clone(), + Str(s) => format!("```nix\n{:?}\n```", s), + } + } +} + +fn try_format_derivation(input: &HashMap>) -> Option { + if input.get("type")?.eval().ok()?.as_str().ok()? != "derivation" { + return None; + } + + let name = input.get("name")?.eval().ok()?.as_str().ok()?; + let meta = input + .get("meta") + .and_then(|x| x.eval().ok()) + .and_then(|x| x.as_map().ok()); + let description = meta.and_then(|x| x.get("description")?.eval().ok()?.as_str().ok()); + + let body_text = description + .map(|x| "\n".to_string() + &x) + .unwrap_or_else(|| "".to_string()); + + Some(format!( + "```nix\nderivation {{ name = {:?}; }}\n```{}", + name, body_text + )) +} + +impl Debug for NixValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + NixValue::Lambda(lambda) => match &lambda { + &NixLambda::Node { lambda, scope: _ } => { + use rnix::SyntaxKind::*; + let arg = lambda.arg().unwrap(); + match arg.kind() { + NODE_PATTERN => { + let mut current_comment = "".to_string(); + for child in arg.children_with_tokens().skip(1) { + match child.kind() { + TOKEN_WHITESPACE | TOKEN_COMMA => continue, + TOKEN_COMMENT | TOKEN_CURLY_B_CLOSE => { + let s = child.to_string(); + let text = s.trim().trim_start_matches('#').trim_start(); + current_comment += text; + current_comment += " "; + } + NODE_PAT_ENTRY => { + let node = child.as_node().unwrap().clone(); + let entry = PatEntry::cast(node).unwrap(); + let name = entry.name().unwrap(); + writeln!( + f, + "{} \x1b[2m{}\x1b[m", + name.as_str(), + current_comment + )?; + current_comment = "".to_string(); + } + TOKEN_ELLIPSIS => { + writeln!(f, "...")?; + } + NODE_PAT_BIND => { + let node = child.as_node().unwrap().clone(); + let bind = PatBind::cast(node).unwrap(); + let name = bind.name().unwrap(); + writeln!(f, "@ {}", name.as_str())?; + } + x => write!(f, "[unrecognized: {:?}]", x)?, + } + } + + Ok(()) + } + NODE_IDENT => { + let ident = rnix::types::Ident::cast(arg).unwrap(); + write!(f, "{}: {}", ident.as_str(), lambda.body().unwrap().text()) + } + _ => write!(f, "??"), + } + } + NixLambda::Builtin(builtin) => write!(f, "{:?}", builtin), + }, + NixValue::List(_) => write!(f, "[ ... ]"), + NixValue::Map(map) => write!( + f, + "{{ {} }}", + map.keys().cloned().collect::>().join(" ; ") + ), + NixValue::Bool(x) => write!(f, "{}", x), + NixValue::Float(x) => write!(f, "{}", x), + NixValue::Integer(x) => write!(f, "{}", x), + NixValue::Null => write!(f, "null"), + NixValue::Path(_, _) => write!(f, ""), + NixValue::Str(x) => write!(f, "{:?}", x), + } + } +}