From 8e38c4eac2c676de4fd8efccc132f268f7122c03 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Mon, 13 Jun 2022 16:10:03 +0200 Subject: [PATCH 01/73] first draft of nix parser to nickel AST - parse a let binding of type `let = ; in ` - can parse when multiple bindings in the let - for now, concider every let as recursive let. --- Cargo.lock | 69 ++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + src/bin/nickel.rs | 18 ++++++++++++- src/lib.rs | 1 + src/nix.rs | 49 +++++++++++++++++++++++++++++++++ 5 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 src/nix.rs diff --git a/Cargo.lock b/Cargo.lock index 69319d6201..6bd737c3b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -231,6 +231,15 @@ dependencies = [ "rustc_version", ] +[[package]] +name = "cbitset" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29b6ad25ae296159fb0da12b970b2fe179b234584d7cd294c891e2bbb284466b" +dependencies = [ + "num-traits", +] + [[package]] name = "cc" version = "1.0.73" @@ -346,6 +355,12 @@ dependencies = [ "crossterm", ] +[[package]] +name = "countme" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "328b822bdcba4d4e402be8d9adb6eebf269f969f8eadef977a553ff3c4fbcb58" + [[package]] name = "cpp_demangle" version = "0.3.5" @@ -843,6 +858,12 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +[[package]] +name = "hashbrown" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" + [[package]] name = "hashbrown" version = "0.11.2" @@ -891,7 +912,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.11.2", ] [[package]] @@ -1239,6 +1260,7 @@ dependencies = [ "pretty", "pretty_assertions", "regex", + "rnix", "rustyline", "rustyline-derive", "serde", @@ -1892,12 +1914,42 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "rnix" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065c5eac8e8f7e7b0e7227bdc46e2d8dd7d69fff7a9a2734e21f686bbcb9b2c9" +dependencies = [ + "cbitset", + "rowan", + "smol_str", +] + +[[package]] +name = "rowan" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1b36e449f3702f3b0c821411db1cbdf30fb451726a9456dce5dabcd44420043" +dependencies = [ + "countme", + "hashbrown 0.9.1", + "memoffset", + "rustc-hash", + "text-size", +] + [[package]] name = "rustc-demangle" version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.4.0" @@ -2163,6 +2215,15 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +[[package]] +name = "smol_str" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7475118a28b7e3a2e157ce0131ba8c5526ea96e90ee601d9f6bb2e286a35ab44" +dependencies = [ + "serde", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -2357,6 +2418,12 @@ dependencies = [ "syn 0.15.44", ] +[[package]] +name = "text-size" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "288cb548dbe72b652243ea797201f3d481a0609a967980fcc5b2315ea811560a" + [[package]] name = "textwrap" version = "0.11.0" diff --git a/Cargo.toml b/Cargo.toml index d68dc0d934..cf6dd1d021 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,6 +63,7 @@ pretty = "0.11.3" comrak = { version = "0.12.1", optional = true, features = [] } once_cell = "1.14.0" typed-arena = "2.0.1" +rnix = "0.10.1" [dev-dependencies] pretty_assertions = "1.2.1" diff --git a/src/bin/nickel.rs b/src/bin/nickel.rs index 07151d3784..2d23d428f1 100644 --- a/src/bin/nickel.rs +++ b/src/bin/nickel.rs @@ -11,6 +11,7 @@ use std::path::{Path, PathBuf}; use std::{ fs::{self, File}, process, + io::Read, }; // use std::ffi::OsStr; use directories::BaseDirs; @@ -40,6 +41,10 @@ struct Opt { /// Available subcommands. #[derive(StructOpt, Debug)] enum Command { + /// translate Nix input to Nickel code. + /// Only a POC, main target is to be able to run Nix code on nickel. + /// May never be a complet source to source transformation. + Nixin, /// Converts the parsed representation (AST) back to Nickel source code and prints it. Used for /// debugging purpose PprintAst { @@ -108,6 +113,17 @@ fn main() { #[cfg(not(feature = "repl"))] eprintln!("error: this executable was not compiled with REPL support"); + } else if let Some(Command::Nixin) = opts.command { + let mut buf = String::new(); + opts.file + .map(std::fs::File::open) + .map(|f| f.unwrap().read_to_string(&mut buf)) + .unwrap_or(std::io::stdin().read_to_string(&mut buf)) + .unwrap_or_else(|err| { + eprintln!("Error when reading input: {}", err); + process::exit(1) + }); + println!("{:#?}", nickel_lang::nix::parse(&buf)); } else { let mut program = opts .file @@ -158,7 +174,7 @@ fn main() { }) } Some(Command::Typecheck) => program.typecheck(), - Some(Command::Repl { .. }) => unreachable!(), + Some(Command::Repl { .. }) | Some(Command::Nixin) => unreachable!(), #[cfg(feature = "doc")] Some(Command::Doc { ref output }) => output .as_ref() diff --git a/src/lib.rs b/src/lib.rs index eda28e2c6f..f8ef5c9c68 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,7 @@ pub mod error; pub mod eval; pub mod identifier; pub mod label; +pub mod nix; pub mod parser; pub mod position; pub mod pretty; diff --git a/src/nix.rs b/src/nix.rs new file mode 100644 index 0000000000..bab1be776c --- /dev/null +++ b/src/nix.rs @@ -0,0 +1,49 @@ +use crate::term::make; +use crate::term::{RichTerm, Term}; +use rnix::{self, SyntaxNode}; + +fn translate(node: &rnix::SyntaxNode) -> RichTerm { + use rnix::SyntaxKind::*; + println!("{:?}", node); + match node.kind() { + NODE_ERROR => Term::ParseError.into(), + NODE_ROOT | NODE_PAREN => node.children().map(|n| translate(&n)).next().unwrap(), + + NODE_IDENT => Term::Var(node.text().to_string().into()).into(), + NODE_LITERAL => node + .children_with_tokens() + .map(|n| { + println!(" {:?}", n); + match n.kind() { + TOKEN_INTEGER | TOKEN_FLOAT => { + Term::Num(n.as_token().unwrap().text().to_string().parse().unwrap()).into() + } + _ => panic!("{} token is not a literal", n), + } + }) + .next() + .unwrap(), + NODE_LET_IN => node + .children() + .collect::>() + .iter() + .rev() + .fold(Term::Null.into(), |rt, n| { + if n.kind() == NODE_KEY_VALUE { + let key_value: Vec = n.children().take(2).collect(); + let key = key_value[0].first_child().unwrap().text().to_string(); + let value = translate(key_value.get(1).unwrap()); + make::let_rec_in(key, value, rt) + } else { + translate(n) + } + }), + _ => panic!("{}", node), + } +} + +pub fn parse(source: &str) -> Result { + use rnix::types::TypedNode; + let nixast = rnix::parse(source).as_result()?; + Ok(translate(nixast.root().node())) +} From 5bb04741ee37cf73f611fafdf3dc2fe5f85641c1 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Mon, 13 Jun 2022 17:32:10 +0200 Subject: [PATCH 02/73] print output to pretty nickel --- src/bin/nickel.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/bin/nickel.rs b/src/bin/nickel.rs index 2d23d428f1..c80734e15c 100644 --- a/src/bin/nickel.rs +++ b/src/bin/nickel.rs @@ -114,7 +114,11 @@ fn main() { #[cfg(not(feature = "repl"))] eprintln!("error: this executable was not compiled with REPL support"); } else if let Some(Command::Nixin) = opts.command { + use nickel_lang::pretty::*; + use pretty::BoxAllocator; + let mut buf = String::new(); + let mut out: Vec = Vec::new(); opts.file .map(std::fs::File::open) .map(|f| f.unwrap().read_to_string(&mut buf)) @@ -123,7 +127,11 @@ fn main() { eprintln!("Error when reading input: {}", err); process::exit(1) }); - println!("{:#?}", nickel_lang::nix::parse(&buf)); + let allocator = BoxAllocator; + let rt = nickel_lang::nix::parse(&buf).unwrap(); + let doc: DocBuilder<_, ()> = rt.pretty(&allocator); + doc.render(80, &mut out).unwrap(); + println!("{}", String::from_utf8_lossy(&out).as_ref()); } else { let mut program = opts .file From 64bc9e8456c84dd7633fc5e23f06e74a3891c7ba Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Mon, 13 Jun 2022 17:44:17 +0200 Subject: [PATCH 03/73] parse lists to arrays --- src/nix.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/nix.rs b/src/nix.rs index bab1be776c..2b36000c5a 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -9,7 +9,6 @@ fn translate(node: &rnix::SyntaxNode) -> RichTerm { NODE_ERROR => Term::ParseError.into(), NODE_ROOT | NODE_PAREN => node.children().map(|n| translate(&n)).next().unwrap(), - NODE_IDENT => Term::Var(node.text().to_string().into()).into(), NODE_LITERAL => node .children_with_tokens() .map(|n| { @@ -23,6 +22,13 @@ fn translate(node: &rnix::SyntaxNode) -> RichTerm { }) .next() .unwrap(), + NODE_LIST => Term::Array( + node.children().map(|n| translate(&n)).collect(), + Default::default(), + ) + .into(), + + NODE_IDENT => Term::Var(node.text().to_string().into()).into(), NODE_LET_IN => node .children() .collect::>() From d6ede7768dd90d4f10e2935e5ad5612b54a25372 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Tue, 14 Jun 2022 11:17:56 +0200 Subject: [PATCH 04/73] add some BinOp translation. --- src/nix.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/nix.rs b/src/nix.rs index 2b36000c5a..8c6e0bb486 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -1,7 +1,48 @@ use crate::term::make; +use crate::term::{BinaryOp, UnaryOp}; use crate::term::{RichTerm, Term}; +use rnix::types::{BinOp, TypedNode}; use rnix::{self, SyntaxNode}; +impl From for RichTerm { + fn from(op: BinOp) -> Self { + use rnix::types::BinOpKind::*; + let lhs = op.lhs().unwrap(); + let rhs = op.rhs().unwrap(); + match op.operator().unwrap() { + // TODO: how to manage diff between strconcat and arrayconcat? + Concat => Term::Op2(BinaryOp::ArrayConcat(), translate(&lhs), translate(&rhs)).into(), + IsSet => unimplemented!(), + Update => unimplemented!(), + + Add => Term::Op2(BinaryOp::Plus(), translate(&lhs), translate(&rhs)).into(), + Sub => Term::Op2(BinaryOp::Sub(), translate(&lhs), translate(&rhs)).into(), + Mul => Term::Op2(BinaryOp::Mult(), translate(&lhs), translate(&rhs)).into(), + Div => Term::Op2(BinaryOp::Div(), translate(&lhs), translate(&rhs)).into(), + + Equal => Term::Op2(BinaryOp::Eq(), translate(&lhs), translate(&rhs)).into(), + Less => Term::Op2(BinaryOp::LessThan(), translate(&lhs), translate(&rhs)).into(), + More => Term::Op2(BinaryOp::GreaterThan(), translate(&lhs), translate(&rhs)).into(), + LessOrEq => Term::Op2(BinaryOp::LessOrEq(), translate(&lhs), translate(&rhs)).into(), + MoreOrEq => Term::Op2(BinaryOp::GreaterOrEq(), translate(&lhs), translate(&rhs)).into(), + NotEqual => unimplemented!(), + + Implication => unimplemented!(), + + And => Term::App( + Term::Op1(UnaryOp::BoolAnd(), translate(&lhs)).into(), + translate(&rhs), + ) + .into(), + Or => Term::App( + Term::Op1(UnaryOp::BoolOr(), translate(&lhs)).into(), + translate(&rhs), + ) + .into(), + } + } +} + fn translate(node: &rnix::SyntaxNode) -> RichTerm { use rnix::SyntaxKind::*; println!("{:?}", node); @@ -44,6 +85,8 @@ fn translate(node: &rnix::SyntaxNode) -> RichTerm { translate(n) } }), + + NODE_BIN_OP => BinOp::cast(node.clone()).unwrap().into(), _ => panic!("{}", node), } } From 028fc24d4977ef9a35b70d91bab23af581d4a70d Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Wed, 15 Jun 2022 09:31:05 +0200 Subject: [PATCH 05/73] add string translate --- src/nix.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/nix.rs b/src/nix.rs index 8c6e0bb486..c20d07c8e8 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -63,6 +63,25 @@ fn translate(node: &rnix::SyntaxNode) -> RichTerm { }) .next() .unwrap(), + NODE_STRING => { + let parts = rnix::types::Str::cast(node.clone()).unwrap().parts(); + //TODO: Do we actualy need the `Term::Str` variant in nickel AST? + Term::StrChunks( + parts + .iter() + .enumerate() + .map(|(i, c)| match c { + rnix::value::StrPart::Literal(s) => { + crate::term::StrChunk::Literal(s.clone()) + } + rnix::value::StrPart::Ast(a) => { + crate::term::StrChunk::Expr(translate(a), i) + } + }) + .collect(), + ) + .into() + } NODE_LIST => Term::Array( node.children().map(|n| translate(&n)).collect(), Default::default(), From 04b32ec69b88596db33775bfff8c625cf441dfe3 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Wed, 15 Jun 2022 17:16:40 +0200 Subject: [PATCH 06/73] use more typed `rnix` API --- src/nix.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/nix.rs b/src/nix.rs index c20d07c8e8..ad746c5e38 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -1,7 +1,7 @@ use crate::term::make; use crate::term::{BinaryOp, UnaryOp}; use crate::term::{RichTerm, Term}; -use rnix::types::{BinOp, TypedNode}; +use rnix::types::{BinOp, TokenWrapper, TypedNode}; use rnix::{self, SyntaxNode}; impl From for RichTerm { @@ -83,12 +83,22 @@ fn translate(node: &rnix::SyntaxNode) -> RichTerm { .into() } NODE_LIST => Term::Array( - node.children().map(|n| translate(&n)).collect(), + rnix::types::List::cast(node.clone()) + .unwrap() + .items() + .map(|n| translate(&n)) + .collect(), Default::default(), ) .into(), - NODE_IDENT => Term::Var(node.text().to_string().into()).into(), + NODE_IDENT => Term::Var( + rnix::types::Ident::cast(node.clone()) + .unwrap() + .as_str() + .into(), + ) + .into(), NODE_LET_IN => node .children() .collect::>() From d2a5a7c75814150b45e48865fd4ee97bed05ce24 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Wed, 15 Jun 2022 17:20:57 +0200 Subject: [PATCH 07/73] add string interpolation translation --- src/nix.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nix.rs b/src/nix.rs index ad746c5e38..41eb43ee08 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -75,7 +75,7 @@ fn translate(node: &rnix::SyntaxNode) -> RichTerm { crate::term::StrChunk::Literal(s.clone()) } rnix::value::StrPart::Ast(a) => { - crate::term::StrChunk::Expr(translate(a), i) + crate::term::StrChunk::Expr(translate(&a.first_child().unwrap()), i) } }) .collect(), From 986114db1d047839ce6d20b0a511d01e05b630e7 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Wed, 15 Jun 2022 18:53:41 +0200 Subject: [PATCH 08/73] add simple function definition and application translation (no patterns) --- src/nix.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/nix.rs b/src/nix.rs index 41eb43ee08..49ad7ab046 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -114,7 +114,24 @@ fn translate(node: &rnix::SyntaxNode) -> RichTerm { translate(n) } }), + NODE_LAMBDA => { + let fun = rnix::types::Lambda::cast(node.clone()).unwrap(); + let arg = fun.arg().unwrap(); + match arg.kind() { + NODE_IDENT => Term::Fun(arg.to_string().into(), translate(&fun.body().unwrap())), + _ => unimplemented!(), + } + .into() + } + NODE_APPLY => { + let fun = rnix::types::Apply::cast(node.clone()).unwrap(); + Term::App( + translate(&fun.lambda().unwrap()), + translate(&fun.value().unwrap()), + ) + .into() + } NODE_BIN_OP => BinOp::cast(node.clone()).unwrap().into(), _ => panic!("{}", node), } From bfd6e130139ded515e7632bfeafbce2dfcfccab8 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Wed, 15 Jun 2022 19:01:00 +0200 Subject: [PATCH 09/73] add `!=` operator --- src/nix.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/nix.rs b/src/nix.rs index 49ad7ab046..5c4acea7b9 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -25,7 +25,11 @@ impl From for RichTerm { More => Term::Op2(BinaryOp::GreaterThan(), translate(&lhs), translate(&rhs)).into(), LessOrEq => Term::Op2(BinaryOp::LessOrEq(), translate(&lhs), translate(&rhs)).into(), MoreOrEq => Term::Op2(BinaryOp::GreaterOrEq(), translate(&lhs), translate(&rhs)).into(), - NotEqual => unimplemented!(), + NotEqual => Term::Op1( + UnaryOp::BoolNot(), + Term::Op2(BinaryOp::Eq(), translate(&lhs), translate(&rhs)).into(), + ) + .into(), Implication => unimplemented!(), From b49f9c545deb6c1437ace004427091502b25ce83 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Wed, 15 Jun 2022 19:21:22 +0200 Subject: [PATCH 10/73] add `if then else` translation --- src/nix.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/nix.rs b/src/nix.rs index 5c4acea7b9..464c8a2943 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -50,6 +50,7 @@ impl From for RichTerm { fn translate(node: &rnix::SyntaxNode) -> RichTerm { use rnix::SyntaxKind::*; println!("{:?}", node); + // TODO: is there a nix boolean type? match node.kind() { NODE_ERROR => Term::ParseError.into(), NODE_ROOT | NODE_PAREN => node.children().map(|n| translate(&n)).next().unwrap(), @@ -136,6 +137,18 @@ fn translate(node: &rnix::SyntaxNode) -> RichTerm { ) .into() } + NODE_IF_ELSE => { + let ifelse = rnix::types::IfElse::cast(node.clone()).unwrap(); + Term::App( + Term::App( + Term::Op1(UnaryOp::Ite(), translate(&ifelse.condition().unwrap())).into(), + translate(&ifelse.body().unwrap()), + ) + .into(), + translate(&ifelse.else_body().unwrap()), + ) + .into() + } NODE_BIN_OP => BinOp::cast(node.clone()).unwrap().into(), _ => panic!("{}", node), } From 7ea431332ece67df4c517dbe70edc732af32ad01 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Wed, 15 Jun 2022 19:43:00 +0200 Subject: [PATCH 11/73] translate both nix unary ops --- src/nix.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/nix.rs b/src/nix.rs index 464c8a2943..8c3e74f9bd 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -1,9 +1,20 @@ use crate::term::make; use crate::term::{BinaryOp, UnaryOp}; use crate::term::{RichTerm, Term}; -use rnix::types::{BinOp, TokenWrapper, TypedNode}; +use rnix::types::{BinOp, TokenWrapper, TypedNode, UnaryOp as UniOp}; use rnix::{self, SyntaxNode}; +impl From for RichTerm { + fn from(op: UniOp) -> Self { + use rnix::types::UnaryOpKind::*; + let value = op.value().unwrap(); + match op.operator() { + Negate => Term::Op2(BinaryOp::Sub(), Term::Num(0.).into(), translate(&value)).into(), + Invert => Term::Op1(UnaryOp::BoolNot(), translate(&value)).into(), + } + } +} + impl From for RichTerm { fn from(op: BinOp) -> Self { use rnix::types::BinOpKind::*; @@ -150,6 +161,7 @@ fn translate(node: &rnix::SyntaxNode) -> RichTerm { .into() } NODE_BIN_OP => BinOp::cast(node.clone()).unwrap().into(), + NODE_UNARY_OP => UniOp::cast(node.clone()).unwrap().into(), _ => panic!("{}", node), } } From 948d2053d8327af06bb086a8fb4e61a247e9751d Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Mon, 20 Jun 2022 14:56:48 +0200 Subject: [PATCH 12/73] Fix let bindings. Now they are mutualy recursives. Have to translate nix let blocks to a nickel destructuring. Because destructuring need a span, this commit make `FileId` required to parse nix files. Also it was a good oportunity to add terms positions to parsed terms. --- src/bin/nickel.rs | 5 +- src/cache.rs | 6 ++ src/nix.rs | 209 ++++++++++++++++++++++++++++++++++------------ 3 files changed, 165 insertions(+), 55 deletions(-) diff --git a/src/bin/nickel.rs b/src/bin/nickel.rs index c80734e15c..7cfd59e85b 100644 --- a/src/bin/nickel.rs +++ b/src/bin/nickel.rs @@ -114,10 +114,12 @@ fn main() { #[cfg(not(feature = "repl"))] eprintln!("error: this executable was not compiled with REPL support"); } else if let Some(Command::Nixin) = opts.command { + use nickel_lang::cache::Cache; use nickel_lang::pretty::*; use pretty::BoxAllocator; let mut buf = String::new(); + let mut cache = Cache::new(); let mut out: Vec = Vec::new(); opts.file .map(std::fs::File::open) @@ -128,7 +130,8 @@ fn main() { process::exit(1) }); let allocator = BoxAllocator; - let rt = nickel_lang::nix::parse(&buf).unwrap(); + let file_id = cache.add_source(".nix", buf.as_bytes()).unwrap(); + let rt = nickel_lang::nix::parse(&cache, file_id).unwrap(); let doc: DocBuilder<_, ()> = rt.pretty(&allocator); doc.render(80, &mut out).unwrap(); println!("{}", String::from_utf8_lossy(&out).as_ref()); diff --git a/src/cache.rs b/src/cache.rs index 4fbb713f8f..d2eeb44329 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -28,6 +28,7 @@ use void::Void; #[derive(Clone, Copy, Eq, Debug, PartialEq)] pub enum InputFormat { Nickel, + Nix, Json, Yaml, Toml, @@ -37,6 +38,7 @@ impl InputFormat { fn from_path_buf(path_buf: &Path) -> Option { match path_buf.extension().and_then(OsStr::to_str) { Some("ncl") => Some(InputFormat::Nickel), + Some("nix") => Some(InputFormat::Nix), Some("json") => Some(InputFormat::Json), Some("yaml") | Some("yml") => Some(InputFormat::Yaml), Some("toml") => Some(InputFormat::Toml), @@ -457,6 +459,10 @@ impl Cache { Ok((t, parse_errs)) } + InputFormat::Nix => Ok(( + crate::nix::parse(&self, file_id).unwrap(), + ParseErrors::default(), + )), InputFormat::Json => serde_json::from_str(self.files.source(file_id)) .map(|t| (t, ParseErrors::default())) .map_err(|err| ParseError::from_serde_json(err, file_id, &self.files)), diff --git a/src/nix.rs b/src/nix.rs index 8c3e74f9bd..fd8e751705 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -1,70 +1,141 @@ +use crate::cache::Cache; +use crate::parser::utils::mk_span; use crate::term::make; use crate::term::{BinaryOp, UnaryOp}; use crate::term::{RichTerm, Term}; -use rnix::types::{BinOp, TokenWrapper, TypedNode, UnaryOp as UniOp}; +use codespan::FileId; +use rnix::types::{BinOp, EntryHolder, TokenWrapper, TypedNode, UnaryOp as UniOp}; use rnix::{self, SyntaxNode}; +use std::collections::HashMap; -impl From for RichTerm { - fn from(op: UniOp) -> Self { +impl From<(UniOp, FileId)> for RichTerm { + fn from(op: (UniOp, FileId)) -> Self { use rnix::types::UnaryOpKind::*; + let (op, file_id) = op; let value = op.value().unwrap(); match op.operator() { - Negate => Term::Op2(BinaryOp::Sub(), Term::Num(0.).into(), translate(&value)).into(), - Invert => Term::Op1(UnaryOp::BoolNot(), translate(&value)).into(), + Negate => Term::Op2( + BinaryOp::Sub(), + Term::Num(0.).into(), + translate(&value, file_id), + ) + .into(), + Invert => Term::Op1(UnaryOp::BoolNot(), translate(&value, file_id)).into(), } } } -impl From for RichTerm { - fn from(op: BinOp) -> Self { +impl From<(BinOp, FileId)> for RichTerm { + fn from(op: (BinOp, FileId)) -> Self { use rnix::types::BinOpKind::*; + let (op, file_id) = op; let lhs = op.lhs().unwrap(); let rhs = op.rhs().unwrap(); match op.operator().unwrap() { // TODO: how to manage diff between strconcat and arrayconcat? - Concat => Term::Op2(BinaryOp::ArrayConcat(), translate(&lhs), translate(&rhs)).into(), + Concat => Term::Op2( + BinaryOp::ArrayConcat(), + translate(&lhs, file_id), + translate(&rhs, file_id), + ) + .into(), IsSet => unimplemented!(), Update => unimplemented!(), - Add => Term::Op2(BinaryOp::Plus(), translate(&lhs), translate(&rhs)).into(), - Sub => Term::Op2(BinaryOp::Sub(), translate(&lhs), translate(&rhs)).into(), - Mul => Term::Op2(BinaryOp::Mult(), translate(&lhs), translate(&rhs)).into(), - Div => Term::Op2(BinaryOp::Div(), translate(&lhs), translate(&rhs)).into(), + Add => Term::Op2( + BinaryOp::Plus(), + translate(&lhs, file_id), + translate(&rhs, file_id), + ) + .into(), + Sub => Term::Op2( + BinaryOp::Sub(), + translate(&lhs, file_id), + translate(&rhs, file_id), + ) + .into(), + Mul => Term::Op2( + BinaryOp::Mult(), + translate(&lhs, file_id), + translate(&rhs, file_id), + ) + .into(), + Div => Term::Op2( + BinaryOp::Div(), + translate(&lhs, file_id), + translate(&rhs, file_id), + ) + .into(), - Equal => Term::Op2(BinaryOp::Eq(), translate(&lhs), translate(&rhs)).into(), - Less => Term::Op2(BinaryOp::LessThan(), translate(&lhs), translate(&rhs)).into(), - More => Term::Op2(BinaryOp::GreaterThan(), translate(&lhs), translate(&rhs)).into(), - LessOrEq => Term::Op2(BinaryOp::LessOrEq(), translate(&lhs), translate(&rhs)).into(), - MoreOrEq => Term::Op2(BinaryOp::GreaterOrEq(), translate(&lhs), translate(&rhs)).into(), + Equal => Term::Op2( + BinaryOp::Eq(), + translate(&lhs, file_id), + translate(&rhs, file_id), + ) + .into(), + Less => Term::Op2( + BinaryOp::LessThan(), + translate(&lhs, file_id), + translate(&rhs, file_id), + ) + .into(), + More => Term::Op2( + BinaryOp::GreaterThan(), + translate(&lhs, file_id), + translate(&rhs, file_id), + ) + .into(), + LessOrEq => Term::Op2( + BinaryOp::LessOrEq(), + translate(&lhs, file_id), + translate(&rhs, file_id), + ) + .into(), + MoreOrEq => Term::Op2( + BinaryOp::GreaterOrEq(), + translate(&lhs, file_id), + translate(&rhs, file_id), + ) + .into(), NotEqual => Term::Op1( UnaryOp::BoolNot(), - Term::Op2(BinaryOp::Eq(), translate(&lhs), translate(&rhs)).into(), + Term::Op2( + BinaryOp::Eq(), + translate(&lhs, file_id), + translate(&rhs, file_id), + ) + .into(), ) .into(), Implication => unimplemented!(), And => Term::App( - Term::Op1(UnaryOp::BoolAnd(), translate(&lhs)).into(), - translate(&rhs), + Term::Op1(UnaryOp::BoolAnd(), translate(&lhs, file_id)).into(), + translate(&rhs, file_id), ) .into(), Or => Term::App( - Term::Op1(UnaryOp::BoolOr(), translate(&lhs)).into(), - translate(&rhs), + Term::Op1(UnaryOp::BoolOr(), translate(&lhs, file_id)).into(), + translate(&rhs, file_id), ) .into(), } } } -fn translate(node: &rnix::SyntaxNode) -> RichTerm { +fn translate(node: &rnix::SyntaxNode, file_id: FileId) -> RichTerm { use rnix::SyntaxKind::*; + let pos = node.text_range(); println!("{:?}", node); // TODO: is there a nix boolean type? match node.kind() { NODE_ERROR => Term::ParseError.into(), - NODE_ROOT | NODE_PAREN => node.children().map(|n| translate(&n)).next().unwrap(), + NODE_ROOT | NODE_PAREN => node + .children() + .map(|n| translate(&n, file_id)) + .next() + .unwrap(), NODE_LITERAL => node .children_with_tokens() @@ -90,9 +161,10 @@ fn translate(node: &rnix::SyntaxNode) -> RichTerm { rnix::value::StrPart::Literal(s) => { crate::term::StrChunk::Literal(s.clone()) } - rnix::value::StrPart::Ast(a) => { - crate::term::StrChunk::Expr(translate(&a.first_child().unwrap()), i) - } + rnix::value::StrPart::Ast(a) => crate::term::StrChunk::Expr( + translate(&a.first_child().unwrap(), file_id), + i, + ), }) .collect(), ) @@ -102,7 +174,7 @@ fn translate(node: &rnix::SyntaxNode) -> RichTerm { rnix::types::List::cast(node.clone()) .unwrap() .items() - .map(|n| translate(&n)) + .map(|n| translate(&n, file_id)) .collect(), Default::default(), ) @@ -115,26 +187,46 @@ fn translate(node: &rnix::SyntaxNode) -> RichTerm { .into(), ) .into(), - NODE_LET_IN => node - .children() - .collect::>() - .iter() - .rev() - .fold(Term::Null.into(), |rt, n| { - if n.kind() == NODE_KEY_VALUE { - let key_value: Vec = n.children().take(2).collect(); - let key = key_value[0].first_child().unwrap().text().to_string(); - let value = translate(key_value.get(1).unwrap()); - make::let_rec_in(key, value, rt) - } else { - translate(n) - } - }), + NODE_LET_IN => { + use crate::destruct; + use crate::identifier::Ident; + use rnix::types::LetIn; + let letin = LetIn::cast(node.clone()).unwrap(); + let mut destruct_vec = Vec::new(); + let mut fields = HashMap::new(); + for kv in letin.entries() { + // In `let` blocks, the key is suposed to be a single ident so `Path` exactly one + // element. + let id: Ident = rnix::types::Ident::cast(kv.key().unwrap().path().next().unwrap()) + .unwrap() + .as_str() + .into(); + let rt = translate(&kv.value().unwrap(), file_id); + destruct_vec.push(destruct::Match::Simple(id.clone(), Default::default())); + fields.insert(id.into(), rt); + } + Term::LetPattern( + None, + destruct::Destruct::Record { + matches: destruct_vec, + open: false, + rest: None, + span: mk_span(file_id, pos.start().into(), pos.end().into()), + }, + Term::RecRecord(fields, vec![], Default::default(), None).into(), + translate(&letin.body().unwrap(), file_id), + ) + .into() + } + NODE_LAMBDA => { let fun = rnix::types::Lambda::cast(node.clone()).unwrap(); let arg = fun.arg().unwrap(); match arg.kind() { - NODE_IDENT => Term::Fun(arg.to_string().into(), translate(&fun.body().unwrap())), + NODE_IDENT => Term::Fun( + arg.to_string().into(), + translate(&fun.body().unwrap(), file_id), + ), _ => unimplemented!(), } .into() @@ -143,8 +235,8 @@ fn translate(node: &rnix::SyntaxNode) -> RichTerm { NODE_APPLY => { let fun = rnix::types::Apply::cast(node.clone()).unwrap(); Term::App( - translate(&fun.lambda().unwrap()), - translate(&fun.value().unwrap()), + translate(&fun.lambda().unwrap(), file_id), + translate(&fun.value().unwrap(), file_id), ) .into() } @@ -152,22 +244,31 @@ fn translate(node: &rnix::SyntaxNode) -> RichTerm { let ifelse = rnix::types::IfElse::cast(node.clone()).unwrap(); Term::App( Term::App( - Term::Op1(UnaryOp::Ite(), translate(&ifelse.condition().unwrap())).into(), - translate(&ifelse.body().unwrap()), + Term::Op1( + UnaryOp::Ite(), + translate(&ifelse.condition().unwrap(), file_id), + ) + .into(), + translate(&ifelse.body().unwrap(), file_id), ) .into(), - translate(&ifelse.else_body().unwrap()), + translate(&ifelse.else_body().unwrap(), file_id), ) .into() } - NODE_BIN_OP => BinOp::cast(node.clone()).unwrap().into(), - NODE_UNARY_OP => UniOp::cast(node.clone()).unwrap().into(), + NODE_BIN_OP => (BinOp::cast(node.clone()).unwrap(), file_id).into(), + NODE_UNARY_OP => (UniOp::cast(node.clone()).unwrap(), file_id).into(), _ => panic!("{}", node), } + .with_pos(crate::position::TermPos::Original(mk_span( + file_id, + pos.start().into(), + pos.end().into(), + ))) } -pub fn parse(source: &str) -> Result { - use rnix::types::TypedNode; +pub fn parse(cache: &Cache, file_id: FileId) -> Result { + let source = cache.files().source(file_id); let nixast = rnix::parse(source).as_result()?; - Ok(translate(nixast.root().node())) + Ok(translate(nixast.root().node(), file_id)) } From f40f98768fead36be2f232c79a63a3a64e276b39 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Thu, 23 Jun 2022 20:34:46 +0200 Subject: [PATCH 13/73] first draft of records translation. --- src/nix.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/nix.rs b/src/nix.rs index fd8e751705..b6b2066920 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -179,6 +179,29 @@ fn translate(node: &rnix::SyntaxNode, file_id: FileId) -> RichTerm { Default::default(), ) .into(), + NODE_ATTR_SET => { + use crate::parser::utils::{build_record, elaborate_field_path, FieldPathElem}; + let attrset = rnix::types::AttrSet::cast(node.clone()).unwrap(); + let fields: Vec<(_, _)> = attrset + .entries() + .map(|kv| { + let val = translate(&kv.value().unwrap(), file_id); + let p: Vec<_> = kv + .key() + .unwrap() + .path() + .map(|p| match p.kind() { + NODE_IDENT => FieldPathElem::Ident( + rnix::types::Ident::cast(p).unwrap().as_str().into(), + ), + _ => FieldPathElem::Expr(translate(&p, file_id)), + }) + .collect(); + elaborate_field_path(p, val) + }) + .collect(); + build_record(fields, Default::default()).into() + } NODE_IDENT => Term::Var( rnix::types::Ident::cast(node.clone()) From 6beca6c93d845f03cdebbfa0391ff364ede0a57a Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Fri, 24 Jun 2022 11:48:10 +0200 Subject: [PATCH 14/73] translate function with patterns. does not manage defaults values for patterns fields now. tODO: update the pretty printer to print the full pattern an not only the at binding. May be in a extra PR. --- src/nix.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/nix.rs b/src/nix.rs index b6b2066920..a69502678f 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -250,6 +250,25 @@ fn translate(node: &rnix::SyntaxNode, file_id: FileId) -> RichTerm { arg.to_string().into(), translate(&fun.body().unwrap(), file_id), ), + NODE_PATTERN => { + use crate::destruct::*; + let pat = rnix::types::Pattern::cast(arg).unwrap(); + let at = pat.at().map(|id| id.as_str().into()); + // TODO: manage default values: + let matches = pat + .entries() + .map(|e| { + Match::Simple(e.name().unwrap().as_str().into(), Default::default()) + }) + .collect(); + let dest = Destruct::Record { + matches, + open: pat.ellipsis(), + rest: None, + span: mk_span(file_id, pos.start().into(), pos.end().into()), + }; + Term::FunPattern(at, dest, translate(&fun.body().unwrap(), file_id)).into() + } _ => unimplemented!(), } .into() From 7a9ccdbb8bc6cf79a9791ed2b6ebffbefd4a2924 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Mon, 27 Jun 2022 11:30:18 +0200 Subject: [PATCH 15/73] pass node by value to `translate` to avoid useless clones. --- src/nix.rs | 114 +++++++++++++++++++++++++---------------------------- 1 file changed, 54 insertions(+), 60 deletions(-) diff --git a/src/nix.rs b/src/nix.rs index a69502678f..a4cd3a99e0 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -17,10 +17,10 @@ impl From<(UniOp, FileId)> for RichTerm { Negate => Term::Op2( BinaryOp::Sub(), Term::Num(0.).into(), - translate(&value, file_id), + translate(value, file_id), ) .into(), - Invert => Term::Op1(UnaryOp::BoolNot(), translate(&value, file_id)).into(), + Invert => Term::Op1(UnaryOp::BoolNot(), translate(value, file_id)).into(), } } } @@ -35,8 +35,8 @@ impl From<(BinOp, FileId)> for RichTerm { // TODO: how to manage diff between strconcat and arrayconcat? Concat => Term::Op2( BinaryOp::ArrayConcat(), - translate(&lhs, file_id), - translate(&rhs, file_id), + translate(lhs, file_id), + translate(rhs, file_id), ) .into(), IsSet => unimplemented!(), @@ -44,65 +44,65 @@ impl From<(BinOp, FileId)> for RichTerm { Add => Term::Op2( BinaryOp::Plus(), - translate(&lhs, file_id), - translate(&rhs, file_id), + translate(lhs, file_id), + translate(rhs, file_id), ) .into(), Sub => Term::Op2( BinaryOp::Sub(), - translate(&lhs, file_id), - translate(&rhs, file_id), + translate(lhs, file_id), + translate(rhs, file_id), ) .into(), Mul => Term::Op2( BinaryOp::Mult(), - translate(&lhs, file_id), - translate(&rhs, file_id), + translate(lhs, file_id), + translate(rhs, file_id), ) .into(), Div => Term::Op2( BinaryOp::Div(), - translate(&lhs, file_id), - translate(&rhs, file_id), + translate(lhs, file_id), + translate(rhs, file_id), ) .into(), Equal => Term::Op2( BinaryOp::Eq(), - translate(&lhs, file_id), - translate(&rhs, file_id), + translate(lhs, file_id), + translate(rhs, file_id), ) .into(), Less => Term::Op2( BinaryOp::LessThan(), - translate(&lhs, file_id), - translate(&rhs, file_id), + translate(lhs, file_id), + translate(rhs, file_id), ) .into(), More => Term::Op2( BinaryOp::GreaterThan(), - translate(&lhs, file_id), - translate(&rhs, file_id), + translate(lhs, file_id), + translate(rhs, file_id), ) .into(), LessOrEq => Term::Op2( BinaryOp::LessOrEq(), - translate(&lhs, file_id), - translate(&rhs, file_id), + translate(lhs, file_id), + translate(rhs, file_id), ) .into(), MoreOrEq => Term::Op2( BinaryOp::GreaterOrEq(), - translate(&lhs, file_id), - translate(&rhs, file_id), + translate(lhs, file_id), + translate(rhs, file_id), ) .into(), NotEqual => Term::Op1( UnaryOp::BoolNot(), Term::Op2( BinaryOp::Eq(), - translate(&lhs, file_id), - translate(&rhs, file_id), + translate(lhs, file_id), + translate(rhs, file_id), ) .into(), ) @@ -111,20 +111,20 @@ impl From<(BinOp, FileId)> for RichTerm { Implication => unimplemented!(), And => Term::App( - Term::Op1(UnaryOp::BoolAnd(), translate(&lhs, file_id)).into(), - translate(&rhs, file_id), + Term::Op1(UnaryOp::BoolAnd(), translate(lhs, file_id)).into(), + translate(rhs, file_id), ) .into(), Or => Term::App( - Term::Op1(UnaryOp::BoolOr(), translate(&lhs, file_id)).into(), - translate(&rhs, file_id), + Term::Op1(UnaryOp::BoolOr(), translate(lhs, file_id)).into(), + translate(rhs, file_id), ) .into(), } } } -fn translate(node: &rnix::SyntaxNode, file_id: FileId) -> RichTerm { +fn translate(node: rnix::SyntaxNode, file_id: FileId) -> RichTerm { use rnix::SyntaxKind::*; let pos = node.text_range(); println!("{:?}", node); @@ -133,7 +133,7 @@ fn translate(node: &rnix::SyntaxNode, file_id: FileId) -> RichTerm { NODE_ERROR => Term::ParseError.into(), NODE_ROOT | NODE_PAREN => node .children() - .map(|n| translate(&n, file_id)) + .map(|n| translate(n, file_id)) .next() .unwrap(), @@ -151,7 +151,7 @@ fn translate(node: &rnix::SyntaxNode, file_id: FileId) -> RichTerm { .next() .unwrap(), NODE_STRING => { - let parts = rnix::types::Str::cast(node.clone()).unwrap().parts(); + let parts = rnix::types::Str::cast(node).unwrap().parts(); //TODO: Do we actualy need the `Term::Str` variant in nickel AST? Term::StrChunks( parts @@ -162,7 +162,7 @@ fn translate(node: &rnix::SyntaxNode, file_id: FileId) -> RichTerm { crate::term::StrChunk::Literal(s.clone()) } rnix::value::StrPart::Ast(a) => crate::term::StrChunk::Expr( - translate(&a.first_child().unwrap(), file_id), + translate(a.first_child().unwrap(), file_id), i, ), }) @@ -171,21 +171,21 @@ fn translate(node: &rnix::SyntaxNode, file_id: FileId) -> RichTerm { .into() } NODE_LIST => Term::Array( - rnix::types::List::cast(node.clone()) + rnix::types::List::cast(node) .unwrap() .items() - .map(|n| translate(&n, file_id)) + .map(|n| translate(n, file_id)) .collect(), Default::default(), ) .into(), NODE_ATTR_SET => { use crate::parser::utils::{build_record, elaborate_field_path, FieldPathElem}; - let attrset = rnix::types::AttrSet::cast(node.clone()).unwrap(); + let attrset = rnix::types::AttrSet::cast(node).unwrap(); let fields: Vec<(_, _)> = attrset .entries() .map(|kv| { - let val = translate(&kv.value().unwrap(), file_id); + let val = translate(kv.value().unwrap(), file_id); let p: Vec<_> = kv .key() .unwrap() @@ -194,7 +194,7 @@ fn translate(node: &rnix::SyntaxNode, file_id: FileId) -> RichTerm { NODE_IDENT => FieldPathElem::Ident( rnix::types::Ident::cast(p).unwrap().as_str().into(), ), - _ => FieldPathElem::Expr(translate(&p, file_id)), + _ => FieldPathElem::Expr(translate(p, file_id)), }) .collect(); elaborate_field_path(p, val) @@ -203,18 +203,12 @@ fn translate(node: &rnix::SyntaxNode, file_id: FileId) -> RichTerm { build_record(fields, Default::default()).into() } - NODE_IDENT => Term::Var( - rnix::types::Ident::cast(node.clone()) - .unwrap() - .as_str() - .into(), - ) - .into(), + NODE_IDENT => Term::Var(rnix::types::Ident::cast(node).unwrap().as_str().into()).into(), NODE_LET_IN => { use crate::destruct; use crate::identifier::Ident; use rnix::types::LetIn; - let letin = LetIn::cast(node.clone()).unwrap(); + let letin = LetIn::cast(node).unwrap(); let mut destruct_vec = Vec::new(); let mut fields = HashMap::new(); for kv in letin.entries() { @@ -224,7 +218,7 @@ fn translate(node: &rnix::SyntaxNode, file_id: FileId) -> RichTerm { .unwrap() .as_str() .into(); - let rt = translate(&kv.value().unwrap(), file_id); + let rt = translate(kv.value().unwrap(), file_id); destruct_vec.push(destruct::Match::Simple(id.clone(), Default::default())); fields.insert(id.into(), rt); } @@ -237,18 +231,18 @@ fn translate(node: &rnix::SyntaxNode, file_id: FileId) -> RichTerm { span: mk_span(file_id, pos.start().into(), pos.end().into()), }, Term::RecRecord(fields, vec![], Default::default(), None).into(), - translate(&letin.body().unwrap(), file_id), + translate(letin.body().unwrap(), file_id), ) .into() } NODE_LAMBDA => { - let fun = rnix::types::Lambda::cast(node.clone()).unwrap(); + let fun = rnix::types::Lambda::cast(node).unwrap(); let arg = fun.arg().unwrap(); match arg.kind() { NODE_IDENT => Term::Fun( arg.to_string().into(), - translate(&fun.body().unwrap(), file_id), + translate(fun.body().unwrap(), file_id), ), NODE_PATTERN => { use crate::destruct::*; @@ -267,7 +261,7 @@ fn translate(node: &rnix::SyntaxNode, file_id: FileId) -> RichTerm { rest: None, span: mk_span(file_id, pos.start().into(), pos.end().into()), }; - Term::FunPattern(at, dest, translate(&fun.body().unwrap(), file_id)).into() + Term::FunPattern(at, dest, translate(fun.body().unwrap(), file_id)).into() } _ => unimplemented!(), } @@ -275,31 +269,31 @@ fn translate(node: &rnix::SyntaxNode, file_id: FileId) -> RichTerm { } NODE_APPLY => { - let fun = rnix::types::Apply::cast(node.clone()).unwrap(); + let fun = rnix::types::Apply::cast(node).unwrap(); Term::App( - translate(&fun.lambda().unwrap(), file_id), - translate(&fun.value().unwrap(), file_id), + translate(fun.lambda().unwrap(), file_id), + translate(fun.value().unwrap(), file_id), ) .into() } NODE_IF_ELSE => { - let ifelse = rnix::types::IfElse::cast(node.clone()).unwrap(); + let ifelse = rnix::types::IfElse::cast(node).unwrap(); Term::App( Term::App( Term::Op1( UnaryOp::Ite(), - translate(&ifelse.condition().unwrap(), file_id), + translate(ifelse.condition().unwrap(), file_id), ) .into(), - translate(&ifelse.body().unwrap(), file_id), + translate(ifelse.body().unwrap(), file_id), ) .into(), - translate(&ifelse.else_body().unwrap(), file_id), + translate(ifelse.else_body().unwrap(), file_id), ) .into() } - NODE_BIN_OP => (BinOp::cast(node.clone()).unwrap(), file_id).into(), - NODE_UNARY_OP => (UniOp::cast(node.clone()).unwrap(), file_id).into(), + NODE_BIN_OP => (BinOp::cast(node).unwrap(), file_id).into(), + NODE_UNARY_OP => (UniOp::cast(node).unwrap(), file_id).into(), _ => panic!("{}", node), } .with_pos(crate::position::TermPos::Original(mk_span( @@ -312,5 +306,5 @@ fn translate(node: &rnix::SyntaxNode, file_id: FileId) -> RichTerm { pub fn parse(cache: &Cache, file_id: FileId) -> Result { let source = cache.files().source(file_id); let nixast = rnix::parse(source).as_result()?; - Ok(translate(nixast.root().node(), file_id)) + Ok(translate(nixast.node(), file_id)) } From 7408cc34abaaa1d48478c6b6542a9c9c704b9a7b Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Mon, 27 Jun 2022 11:56:15 +0200 Subject: [PATCH 16/73] add implication operator translation --- src/nix.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/nix.rs b/src/nix.rs index a4cd3a99e0..efd50ae25b 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -108,7 +108,15 @@ impl From<(BinOp, FileId)> for RichTerm { ) .into(), - Implication => unimplemented!(), + Implication => Term::App( + Term::App( + Term::Op1(UnaryOp::Ite(), translate(lhs, file_id)).into(), + translate(rhs, file_id), + ) + .into(), + Term::Bool(true).into(), + ) + .into(), And => Term::App( Term::Op1(UnaryOp::BoolAnd(), translate(lhs, file_id)).into(), From e54603ac6f4a9120ef5f70d9802b5c67eafcd157 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Tue, 28 Jun 2022 12:27:11 +0200 Subject: [PATCH 17/73] Use better typed `rnix` API. --- src/nix.rs | 154 ++++++++++++++++++++++++----------------------------- 1 file changed, 71 insertions(+), 83 deletions(-) diff --git a/src/nix.rs b/src/nix.rs index efd50ae25b..57e54f8739 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -1,11 +1,10 @@ use crate::cache::Cache; use crate::parser::utils::mk_span; -use crate::term::make; use crate::term::{BinaryOp, UnaryOp}; use crate::term::{RichTerm, Term}; use codespan::FileId; +use rnix; use rnix::types::{BinOp, EntryHolder, TokenWrapper, TypedNode, UnaryOp as UniOp}; -use rnix::{self, SyntaxNode}; use std::collections::HashMap; impl From<(UniOp, FileId)> for RichTerm { @@ -133,42 +132,37 @@ impl From<(BinOp, FileId)> for RichTerm { } fn translate(node: rnix::SyntaxNode, file_id: FileId) -> RichTerm { + use rnix::types::{self, ParsedType, Wrapper}; + use rnix::value; use rnix::SyntaxKind::*; + use std::convert::TryInto; let pos = node.text_range(); + let span = mk_span(file_id, pos.start().into(), pos.end().into()); println!("{:?}", node); - // TODO: is there a nix boolean type? - match node.kind() { - NODE_ERROR => Term::ParseError.into(), - NODE_ROOT | NODE_PAREN => node - .children() - .map(|n| translate(n, file_id)) - .next() - .unwrap(), + let node: ParsedType = node.try_into().unwrap(); + match node { + ParsedType::Error(_) => Term::ParseError.into(), + ParsedType::Root(n) => translate(n.inner().unwrap(), file_id), + ParsedType::Paren(n) => translate(n.inner().unwrap(), file_id), - NODE_LITERAL => node - .children_with_tokens() - .map(|n| { - println!(" {:?}", n); - match n.kind() { - TOKEN_INTEGER | TOKEN_FLOAT => { - Term::Num(n.as_token().unwrap().text().to_string().parse().unwrap()).into() - } - _ => panic!("{} token is not a literal", n), - } - }) - .next() - .unwrap(), - NODE_STRING => { - let parts = rnix::types::Str::cast(node).unwrap().parts(); + ParsedType::Assert(n) => unimplemented!(), + + ParsedType::Value(n) => match n.to_value().unwrap() { + value::Value::Float(v) => Term::Num(v).into(), + value::Value::Integer(v) => Term::Num(v as f64).into(), + value::Value::String(v) => Term::Str(v).into(), + // TODO: How to manage Paths in nickel? + value::Value::Path(a, v) => Term::Str(v).into(), + }, + ParsedType::Str(n) => { + let parts = n.parts(); //TODO: Do we actualy need the `Term::Str` variant in nickel AST? Term::StrChunks( parts - .iter() + .into_iter() .enumerate() .map(|(i, c)| match c { - rnix::value::StrPart::Literal(s) => { - crate::term::StrChunk::Literal(s.clone()) - } + rnix::value::StrPart::Literal(s) => crate::term::StrChunk::Literal(s), rnix::value::StrPart::Ast(a) => crate::term::StrChunk::Expr( translate(a.first_child().unwrap(), file_id), i, @@ -178,19 +172,14 @@ fn translate(node: rnix::SyntaxNode, file_id: FileId) -> RichTerm { ) .into() } - NODE_LIST => Term::Array( - rnix::types::List::cast(node) - .unwrap() - .items() - .map(|n| translate(n, file_id)) - .collect(), + ParsedType::List(n) => Term::Array( + n.items().map(|elm| translate(elm, file_id)).collect(), Default::default(), ) .into(), - NODE_ATTR_SET => { + ParsedType::AttrSet(n) => { use crate::parser::utils::{build_record, elaborate_field_path, FieldPathElem}; - let attrset = rnix::types::AttrSet::cast(node).unwrap(); - let fields: Vec<(_, _)> = attrset + let fields: Vec<(_, _)> = n .entries() .map(|kv| { let val = translate(kv.value().unwrap(), file_id); @@ -211,18 +200,17 @@ fn translate(node: rnix::SyntaxNode, file_id: FileId) -> RichTerm { build_record(fields, Default::default()).into() } - NODE_IDENT => Term::Var(rnix::types::Ident::cast(node).unwrap().as_str().into()).into(), - NODE_LET_IN => { + ParsedType::Ident(n) => Term::Var(n.as_str().into()).into(), + ParsedType::LegacyLet(_) => unimplemented!(), // Probably useless to suport it in a short term. + ParsedType::LetIn(n) => { use crate::destruct; use crate::identifier::Ident; - use rnix::types::LetIn; - let letin = LetIn::cast(node).unwrap(); let mut destruct_vec = Vec::new(); let mut fields = HashMap::new(); - for kv in letin.entries() { + for kv in n.entries() { // In `let` blocks, the key is suposed to be a single ident so `Path` exactly one // element. - let id: Ident = rnix::types::Ident::cast(kv.key().unwrap().path().next().unwrap()) + let id: Ident = types::Ident::cast(kv.key().unwrap().path().next().unwrap()) .unwrap() .as_str() .into(); @@ -236,21 +224,21 @@ fn translate(node: rnix::SyntaxNode, file_id: FileId) -> RichTerm { matches: destruct_vec, open: false, rest: None, - span: mk_span(file_id, pos.start().into(), pos.end().into()), + span, }, Term::RecRecord(fields, vec![], Default::default(), None).into(), - translate(letin.body().unwrap(), file_id), + translate(n.body().unwrap(), file_id), ) .into() } + ParsedType::With(n) => unimplemented!(), - NODE_LAMBDA => { - let fun = rnix::types::Lambda::cast(node).unwrap(); - let arg = fun.arg().unwrap(); + ParsedType::Lambda(n) => { + let arg = n.arg().unwrap(); match arg.kind() { NODE_IDENT => Term::Fun( arg.to_string().into(), - translate(fun.body().unwrap(), file_id), + translate(n.body().unwrap(), file_id), ), NODE_PATTERN => { use crate::destruct::*; @@ -267,48 +255,48 @@ fn translate(node: rnix::SyntaxNode, file_id: FileId) -> RichTerm { matches, open: pat.ellipsis(), rest: None, - span: mk_span(file_id, pos.start().into(), pos.end().into()), + span, }; - Term::FunPattern(at, dest, translate(fun.body().unwrap(), file_id)).into() + Term::FunPattern(at, dest, translate(n.body().unwrap(), file_id)).into() } - _ => unimplemented!(), + _ => unreachable!(), } .into() } - NODE_APPLY => { - let fun = rnix::types::Apply::cast(node).unwrap(); - Term::App( - translate(fun.lambda().unwrap(), file_id), - translate(fun.value().unwrap(), file_id), - ) - .into() - } - NODE_IF_ELSE => { - let ifelse = rnix::types::IfElse::cast(node).unwrap(); + ParsedType::Apply(n) => Term::App( + translate(n.lambda().unwrap(), file_id), + translate(n.value().unwrap(), file_id), + ) + .into(), + ParsedType::IfElse(n) => Term::App( Term::App( - Term::App( - Term::Op1( - UnaryOp::Ite(), - translate(ifelse.condition().unwrap(), file_id), - ) - .into(), - translate(ifelse.body().unwrap(), file_id), - ) - .into(), - translate(ifelse.else_body().unwrap(), file_id), + Term::Op1(UnaryOp::Ite(), translate(n.condition().unwrap(), file_id)).into(), + translate(n.body().unwrap(), file_id), ) - .into() - } - NODE_BIN_OP => (BinOp::cast(node).unwrap(), file_id).into(), - NODE_UNARY_OP => (UniOp::cast(node).unwrap(), file_id).into(), - _ => panic!("{}", node), + .into(), + translate(n.else_body().unwrap(), file_id), + ) + .into(), + ParsedType::BinOp(n) => (n, file_id).into(), + ParsedType::UnaryOp(n) => (n, file_id).into(), + ParsedType::OrDefault(n) => unimplemented!(), + + // TODO: what are these? + ParsedType::Dynamic(_) + | ParsedType::Select(_) + | ParsedType::PatBind(_) + | ParsedType::PathWithInterpol(_) => unimplemented!(), + + // Is not a `RichTerm` so is transformed as part of actual ones. + ParsedType::Pattern(_) + | ParsedType::PatEntry(_) + | ParsedType::Inherit(..) + | ParsedType::InheritFrom(..) + | ParsedType::Key(..) + | ParsedType::KeyValue(..) => unreachable!(), } - .with_pos(crate::position::TermPos::Original(mk_span( - file_id, - pos.start().into(), - pos.end().into(), - ))) + .with_pos(crate::position::TermPos::Original(span)) } pub fn parse(cache: &Cache, file_id: FileId) -> Result { From 7466c0aee39e6fc3a1c27fe694d73cd5f3f162b6 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Wed, 29 Jun 2022 12:06:25 +0200 Subject: [PATCH 18/73] Parse `Dynamic`s in `translate` function. Make parsing of `record.${a field expr}` parsable. It may not be parsed at term level but should not be an issue because it effectively return a term and if not used at right place, the parser will return an error anyway. --- src/nix.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/nix.rs b/src/nix.rs index 57e54f8739..612d52ca36 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -144,6 +144,7 @@ fn translate(node: rnix::SyntaxNode, file_id: FileId) -> RichTerm { ParsedType::Error(_) => Term::ParseError.into(), ParsedType::Root(n) => translate(n.inner().unwrap(), file_id), ParsedType::Paren(n) => translate(n.inner().unwrap(), file_id), + ParsedType::Dynamic(n) => translate(n.inner().unwrap(), file_id), ParsedType::Assert(n) => unimplemented!(), @@ -283,10 +284,9 @@ fn translate(node: rnix::SyntaxNode, file_id: FileId) -> RichTerm { ParsedType::OrDefault(n) => unimplemented!(), // TODO: what are these? - ParsedType::Dynamic(_) - | ParsedType::Select(_) - | ParsedType::PatBind(_) - | ParsedType::PathWithInterpol(_) => unimplemented!(), + ParsedType::Select(_) | ParsedType::PatBind(_) | ParsedType::PathWithInterpol(_) => { + unimplemented!() + } // Is not a `RichTerm` so is transformed as part of actual ones. ParsedType::Pattern(_) From a32df792dfb554631c83e8519a82a61a1d3a4f23 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Wed, 29 Jun 2022 16:31:00 +0200 Subject: [PATCH 19/73] add record access translation --- src/nix.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/nix.rs b/src/nix.rs index 612d52ca36..aca2a4d9dc 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -135,7 +135,7 @@ fn translate(node: rnix::SyntaxNode, file_id: FileId) -> RichTerm { use rnix::types::{self, ParsedType, Wrapper}; use rnix::value; use rnix::SyntaxKind::*; - use std::convert::TryInto; + use std::convert::{TryFrom, TryInto}; let pos = node.text_range(); let span = mk_span(file_id, pos.start().into(), pos.end().into()); println!("{:?}", node); @@ -281,10 +281,23 @@ fn translate(node: rnix::SyntaxNode, file_id: FileId) -> RichTerm { .into(), ParsedType::BinOp(n) => (n, file_id).into(), ParsedType::UnaryOp(n) => (n, file_id).into(), + ParsedType::Select(n) => match ParsedType::try_from(n.index().unwrap()).unwrap() { + ParsedType::Ident(id) => Term::Op1( + UnaryOp::StaticAccess(id.as_str().into()), + translate(n.set().unwrap(), file_id), + ) + .into(), + _ => Term::Op2( + BinaryOp::DynAccess(), + translate(n.index().unwrap(), file_id), + translate(n.set().unwrap(), file_id), + ) + .into(), + }, ParsedType::OrDefault(n) => unimplemented!(), // TODO: what are these? - ParsedType::Select(_) | ParsedType::PatBind(_) | ParsedType::PathWithInterpol(_) => { + ParsedType::PatBind(_) | ParsedType::PathWithInterpol(_) => { unimplemented!() } From 3b4ff2f2f8c818d5563892c72eb1756eba265aef Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Wed, 29 Jun 2022 17:58:10 +0200 Subject: [PATCH 20/73] add suport of default value for patterns. --- src/nix.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/nix.rs b/src/nix.rs index aca2a4d9dc..9d90f7e655 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -1,7 +1,7 @@ use crate::cache::Cache; use crate::parser::utils::mk_span; use crate::term::{BinaryOp, UnaryOp}; -use crate::term::{RichTerm, Term}; +use crate::term::{MergePriority, MetaValue, RichTerm, Term}; use codespan::FileId; use rnix; use rnix::types::{BinOp, EntryHolder, TokenWrapper, TypedNode, UnaryOp as UniOp}; @@ -249,7 +249,16 @@ fn translate(node: rnix::SyntaxNode, file_id: FileId) -> RichTerm { let matches = pat .entries() .map(|e| { - Match::Simple(e.name().unwrap().as_str().into(), Default::default()) + let mv = if let Some(def) = e.default() { + MetaValue { + value: Some(translate(def, file_id)), + priority: MergePriority::Default, + ..Default::default() + } + } else { + Default::default() + }; + Match::Simple(e.name().unwrap().as_str().into(), mv) }) .collect(); let dest = Destruct::Record { From f7beb9775773a744d43889a2553aa2f5d3852d48 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Thu, 30 Jun 2022 11:18:18 +0200 Subject: [PATCH 21/73] translate `true`, `false` and `null` into the proper value/type --- src/nix.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/nix.rs b/src/nix.rs index 9d90f7e655..bf28e9a13f 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -201,7 +201,17 @@ fn translate(node: rnix::SyntaxNode, file_id: FileId) -> RichTerm { build_record(fields, Default::default()).into() } - ParsedType::Ident(n) => Term::Var(n.as_str().into()).into(), + // In nix it's allowed to define vars named `true`, `false` or `null`. + // Concidering it a bad feature and something probably never used, this parser don't manage + // it. Right now it doesn't throw error when defining these vars but only ignore the + // definition. + ParsedType::Ident(n) => match n.as_str() { + "true" => Term::Bool(true), + "false" => Term::Bool(false), + "null" => Term::Null, + id => Term::Var(id.into()), + } + .into(), ParsedType::LegacyLet(_) => unimplemented!(), // Probably useless to suport it in a short term. ParsedType::LetIn(n) => { use crate::destruct; From f3788b0e07f644212fce33cde951d83ee90c30e1 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Thu, 30 Jun 2022 19:41:09 +0200 Subject: [PATCH 22/73] Update src/nix.rs Co-authored-by: Yann Hamdaoui --- src/nix.rs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/nix.rs b/src/nix.rs index bf28e9a13f..addd288c6b 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -107,15 +107,10 @@ impl From<(BinOp, FileId)> for RichTerm { ) .into(), - Implication => Term::App( - Term::App( - Term::Op1(UnaryOp::Ite(), translate(lhs, file_id)).into(), - translate(rhs, file_id), - ) - .into(), - Term::Bool(true).into(), - ) - .into(), + Implication => mk_app!( + make::op1(UnaryOp::Ite(), translate(lhs, file_id)), + translate(lhs, file_id), + make::bool(true)), And => Term::App( Term::Op1(UnaryOp::BoolAnd(), translate(lhs, file_id)).into(), From 2a86acaaaba8e0e2105ec2a8e5ee7b9718313dbd Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Wed, 16 Nov 2022 10:35:00 +0100 Subject: [PATCH 23/73] Add a trait `ToNickel` - impl this trait for `rnix` operators and `SyntaxNode` - others code cleaning --- src/bin/nickel.rs | 4 +- src/convertion.rs | 13 ++ src/error.rs | 4 + src/lib.rs | 1 + src/nix.rs | 499 ++++++++++++++++++++-------------------------- src/term/mod.rs | 1 - 6 files changed, 235 insertions(+), 287 deletions(-) create mode 100644 src/convertion.rs diff --git a/src/bin/nickel.rs b/src/bin/nickel.rs index 7cfd59e85b..efd8c1ebb1 100644 --- a/src/bin/nickel.rs +++ b/src/bin/nickel.rs @@ -10,8 +10,8 @@ use nickel_lang::{serialize, serialize::ExportFormat}; use std::path::{Path, PathBuf}; use std::{ fs::{self, File}, - process, io::Read, + process, }; // use std::ffi::OsStr; use directories::BaseDirs; @@ -123,7 +123,7 @@ fn main() { let mut out: Vec = Vec::new(); opts.file .map(std::fs::File::open) - .map(|f| f.unwrap().read_to_string(&mut buf)) + .map(|f| f.and_then(|mut f| f.read_to_string(&mut buf))) .unwrap_or(std::io::stdin().read_to_string(&mut buf)) .unwrap_or_else(|err| { eprintln!("Error when reading input: {}", err); diff --git a/src/convertion.rs b/src/convertion.rs new file mode 100644 index 0000000000..68ef7312de --- /dev/null +++ b/src/convertion.rs @@ -0,0 +1,13 @@ +//! This module contains a trait to implement on every thing you want to be convertible into nickel +//! AST. +//! It should be used mostly for AST to AST convertion (e.g.: nix to nickel). Effectively the +//! `translate` function require to pass a `codespan::FileId` so it's not wors to use it for +//! convertions which not imply a file/stream input. For these convertions, prefer `Into`/`From` +//! standart traits. + +use crate::term::RichTerm; +use codespan::FileId; + +pub trait ToNickel { + fn translate(self, file_id: FileId) -> RichTerm; +} diff --git a/src/error.rs b/src/error.rs index 3dc4b8f1f6..e779cf0187 100644 --- a/src/error.rs +++ b/src/error.rs @@ -314,6 +314,10 @@ impl IntoDiagnostics for ParseErrors { /// An error occurring during parsing. #[derive(Debug, PartialEq, Eq, Clone)] pub enum ParseError { + /// Temporary Nix error variant. + /// TODO: because parsing errors are almost the same between Nix and Nickel, Have a seamless + /// convertion from Nix errors to Nickel ones could be a good improvement. + NixParseError(FileId), /// Unexpected end of file. UnexpectedEOF(FileId, /* tokens expected by the parser */ Vec), /// Unexpected token. diff --git a/src/lib.rs b/src/lib.rs index f8ef5c9c68..a553d25785 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ pub mod cache; +pub mod convertion; pub mod deserialize; pub mod destructuring; pub mod environment; diff --git a/src/nix.rs b/src/nix.rs index addd288c6b..abdd3da242 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -1,333 +1,264 @@ use crate::cache::Cache; +use crate::convertion::ToNickel; +use crate::mk_app; use crate::parser::utils::mk_span; -use crate::term::{BinaryOp, UnaryOp}; +use crate::term::make::{self, if_then_else}; +use crate::term::{BinaryOp, RecordData, UnaryOp}; use crate::term::{MergePriority, MetaValue, RichTerm, Term}; use codespan::FileId; use rnix; use rnix::types::{BinOp, EntryHolder, TokenWrapper, TypedNode, UnaryOp as UniOp}; use std::collections::HashMap; -impl From<(UniOp, FileId)> for RichTerm { - fn from(op: (UniOp, FileId)) -> Self { +impl ToNickel for UniOp { + fn translate(self, file_id: FileId) -> RichTerm { use rnix::types::UnaryOpKind::*; - let (op, file_id) = op; - let value = op.value().unwrap(); - match op.operator() { - Negate => Term::Op2( - BinaryOp::Sub(), - Term::Num(0.).into(), - translate(value, file_id), - ) - .into(), - Invert => Term::Op1(UnaryOp::BoolNot(), translate(value, file_id)).into(), + let value = self.value().unwrap().translate(file_id); + match self.operator() { + Negate => make::op2(BinaryOp::Sub(), Term::Num(0.), value), + Invert => make::op1(UnaryOp::BoolNot(), value), } } } -impl From<(BinOp, FileId)> for RichTerm { - fn from(op: (BinOp, FileId)) -> Self { +impl ToNickel for BinOp { + fn translate(self, file_id: FileId) -> RichTerm { use rnix::types::BinOpKind::*; - let (op, file_id) = op; - let lhs = op.lhs().unwrap(); - let rhs = op.rhs().unwrap(); - match op.operator().unwrap() { - // TODO: how to manage diff between strconcat and arrayconcat? - Concat => Term::Op2( - BinaryOp::ArrayConcat(), - translate(lhs, file_id), - translate(rhs, file_id), - ) - .into(), + let lhs = self.lhs().unwrap().translate(file_id); + let rhs = self.rhs().unwrap().translate(file_id); + match self.operator().unwrap() { + // TODO: Should be fixed using a nickel function `compat.concat` of type `a -> a -> a` + // using `str_conct` or `array_concat` in respect to `typeof a`. + Concat => Term::Op2(BinaryOp::ArrayConcat(), lhs, rhs).into(), IsSet => unimplemented!(), Update => unimplemented!(), - Add => Term::Op2( - BinaryOp::Plus(), - translate(lhs, file_id), - translate(rhs, file_id), - ) - .into(), - Sub => Term::Op2( - BinaryOp::Sub(), - translate(lhs, file_id), - translate(rhs, file_id), - ) - .into(), - Mul => Term::Op2( - BinaryOp::Mult(), - translate(lhs, file_id), - translate(rhs, file_id), - ) - .into(), - Div => Term::Op2( - BinaryOp::Div(), - translate(lhs, file_id), - translate(rhs, file_id), - ) - .into(), + Add => make::op2(BinaryOp::Plus(), lhs, rhs), + Sub => make::op2(BinaryOp::Sub(), lhs, rhs), + Mul => make::op2(BinaryOp::Mult(), lhs, rhs), + Div => make::op2(BinaryOp::Div(), lhs, rhs), - Equal => Term::Op2( - BinaryOp::Eq(), - translate(lhs, file_id), - translate(rhs, file_id), - ) - .into(), - Less => Term::Op2( - BinaryOp::LessThan(), - translate(lhs, file_id), - translate(rhs, file_id), - ) - .into(), - More => Term::Op2( - BinaryOp::GreaterThan(), - translate(lhs, file_id), - translate(rhs, file_id), - ) - .into(), - LessOrEq => Term::Op2( - BinaryOp::LessOrEq(), - translate(lhs, file_id), - translate(rhs, file_id), - ) - .into(), - MoreOrEq => Term::Op2( - BinaryOp::GreaterOrEq(), - translate(lhs, file_id), - translate(rhs, file_id), - ) - .into(), - NotEqual => Term::Op1( - UnaryOp::BoolNot(), - Term::Op2( - BinaryOp::Eq(), - translate(lhs, file_id), - translate(rhs, file_id), - ) - .into(), - ) - .into(), + Equal => make::op2(BinaryOp::Eq(), lhs, rhs), + Less => make::op2(BinaryOp::LessThan(), lhs, rhs), + More => make::op2(BinaryOp::GreaterThan(), lhs, rhs), + LessOrEq => make::op2(BinaryOp::LessOrEq(), lhs, rhs), + MoreOrEq => make::op2(BinaryOp::GreaterOrEq(), lhs, rhs), + NotEqual => make::op1(UnaryOp::BoolNot(), make::op2(BinaryOp::Eq(), lhs, rhs)), - Implication => mk_app!( - make::op1(UnaryOp::Ite(), translate(lhs, file_id)), - translate(lhs, file_id), - make::bool(true)), + Implication => if_then_else(lhs, rhs, Term::Bool(true)), - And => Term::App( - Term::Op1(UnaryOp::BoolAnd(), translate(lhs, file_id)).into(), - translate(rhs, file_id), - ) - .into(), - Or => Term::App( - Term::Op1(UnaryOp::BoolOr(), translate(lhs, file_id)).into(), - translate(rhs, file_id), - ) - .into(), + And => mk_app!(Term::Op1(UnaryOp::BoolAnd(), lhs), rhs), + Or => mk_app!(Term::Op1(UnaryOp::BoolOr(), lhs), rhs), } } } -fn translate(node: rnix::SyntaxNode, file_id: FileId) -> RichTerm { - use rnix::types::{self, ParsedType, Wrapper}; - use rnix::value; - use rnix::SyntaxKind::*; - use std::convert::{TryFrom, TryInto}; - let pos = node.text_range(); - let span = mk_span(file_id, pos.start().into(), pos.end().into()); - println!("{:?}", node); - let node: ParsedType = node.try_into().unwrap(); - match node { - ParsedType::Error(_) => Term::ParseError.into(), - ParsedType::Root(n) => translate(n.inner().unwrap(), file_id), - ParsedType::Paren(n) => translate(n.inner().unwrap(), file_id), - ParsedType::Dynamic(n) => translate(n.inner().unwrap(), file_id), +impl ToNickel for rnix::SyntaxNode { + fn translate(self, file_id: FileId) -> RichTerm { + use rnix::types::{self, ParsedType, Wrapper}; + use rnix::value; + use rnix::SyntaxKind::*; + use std::convert::{TryFrom, TryInto}; + let pos = self.text_range(); + let span = mk_span(file_id, pos.start().into(), pos.end().into()); + println!("{:?}: {}", self, self); + let node: ParsedType = self.try_into().unwrap(); + match node { + ParsedType::Error(_) => { + Term::ParseError(crate::error::ParseError::NixParseError(file_id)).into() + } + ParsedType::Root(n) => n.inner().unwrap().translate(file_id), + ParsedType::Paren(n) => n.inner().unwrap().translate(file_id), + ParsedType::Dynamic(n) => n.inner().unwrap().translate(file_id), - ParsedType::Assert(n) => unimplemented!(), + ParsedType::Assert(n) => unimplemented!(), - ParsedType::Value(n) => match n.to_value().unwrap() { - value::Value::Float(v) => Term::Num(v).into(), - value::Value::Integer(v) => Term::Num(v as f64).into(), - value::Value::String(v) => Term::Str(v).into(), - // TODO: How to manage Paths in nickel? - value::Value::Path(a, v) => Term::Str(v).into(), - }, - ParsedType::Str(n) => { - let parts = n.parts(); - //TODO: Do we actualy need the `Term::Str` variant in nickel AST? - Term::StrChunks( - parts - .into_iter() - .enumerate() - .map(|(i, c)| match c { - rnix::value::StrPart::Literal(s) => crate::term::StrChunk::Literal(s), - rnix::value::StrPart::Ast(a) => crate::term::StrChunk::Expr( - translate(a.first_child().unwrap(), file_id), - i, - ), - }) - .collect(), - ) - .into() - } - ParsedType::List(n) => Term::Array( - n.items().map(|elm| translate(elm, file_id)).collect(), - Default::default(), - ) - .into(), - ParsedType::AttrSet(n) => { - use crate::parser::utils::{build_record, elaborate_field_path, FieldPathElem}; - let fields: Vec<(_, _)> = n - .entries() - .map(|kv| { - let val = translate(kv.value().unwrap(), file_id); - let p: Vec<_> = kv - .key() - .unwrap() - .path() - .map(|p| match p.kind() { - NODE_IDENT => FieldPathElem::Ident( - rnix::types::Ident::cast(p).unwrap().as_str().into(), + ParsedType::Value(n) => match n.to_value().unwrap() { + value::Value::Float(v) => Term::Num(v), + value::Value::Integer(v) => Term::Num(v as f64), + value::Value::String(v) => Term::Str(v), + // TODO: How to manage Paths in nickel? + value::Value::Path(a, v) => Term::Str(v), + } + .into(), + ParsedType::Str(n) => { + let parts = n.parts(); + //TODO: Do we actualy need the `Term::Str` variant in nickel AST? + Term::StrChunks( + parts + .into_iter() + .enumerate() + .map(|(i, c)| match c { + rnix::value::StrPart::Literal(s) => crate::term::StrChunk::Literal(s), + rnix::value::StrPart::Ast(a) => crate::term::StrChunk::Expr( + a.first_child().unwrap().translate(file_id), + i, ), - _ => FieldPathElem::Expr(translate(p, file_id)), }) - .collect(); - elaborate_field_path(p, val) - }) - .collect(); - build_record(fields, Default::default()).into() - } - - // In nix it's allowed to define vars named `true`, `false` or `null`. - // Concidering it a bad feature and something probably never used, this parser don't manage - // it. Right now it doesn't throw error when defining these vars but only ignore the - // definition. - ParsedType::Ident(n) => match n.as_str() { - "true" => Term::Bool(true), - "false" => Term::Bool(false), - "null" => Term::Null, - id => Term::Var(id.into()), - } - .into(), - ParsedType::LegacyLet(_) => unimplemented!(), // Probably useless to suport it in a short term. - ParsedType::LetIn(n) => { - use crate::destruct; - use crate::identifier::Ident; - let mut destruct_vec = Vec::new(); - let mut fields = HashMap::new(); - for kv in n.entries() { - // In `let` blocks, the key is suposed to be a single ident so `Path` exactly one - // element. - let id: Ident = types::Ident::cast(kv.key().unwrap().path().next().unwrap()) - .unwrap() - .as_str() - .into(); - let rt = translate(kv.value().unwrap(), file_id); - destruct_vec.push(destruct::Match::Simple(id.clone(), Default::default())); - fields.insert(id.into(), rt); + .collect(), + ) + .into() } - Term::LetPattern( - None, - destruct::Destruct::Record { - matches: destruct_vec, - open: false, - rest: None, - span, - }, - Term::RecRecord(fields, vec![], Default::default(), None).into(), - translate(n.body().unwrap(), file_id), + ParsedType::List(n) => Term::Array( + n.items().map(|elm| elm.translate(file_id)).collect(), + Default::default(), ) - .into() - } - ParsedType::With(n) => unimplemented!(), + .into(), + ParsedType::AttrSet(n) => { + use crate::parser::utils::{build_record, elaborate_field_path, FieldPathElem}; + let fields: Vec<(_, _)> = n + .entries() + .map(|kv| { + let val = kv.value().unwrap().translate(file_id); + let p: Vec<_> = kv + .key() + .unwrap() + .path() + .map(|p| match p.kind() { + NODE_IDENT => FieldPathElem::Ident( + rnix::types::Ident::cast(p).unwrap().as_str().into(), + ), + _ => FieldPathElem::Expr(p.translate(file_id)), + }) + .collect(); + elaborate_field_path(p, val) + }) + .collect(); + build_record(fields, Default::default()).into() + } - ParsedType::Lambda(n) => { - let arg = n.arg().unwrap(); - match arg.kind() { - NODE_IDENT => Term::Fun( - arg.to_string().into(), - translate(n.body().unwrap(), file_id), - ), - NODE_PATTERN => { - use crate::destruct::*; - let pat = rnix::types::Pattern::cast(arg).unwrap(); - let at = pat.at().map(|id| id.as_str().into()); - // TODO: manage default values: - let matches = pat - .entries() - .map(|e| { - let mv = if let Some(def) = e.default() { - MetaValue { - value: Some(translate(def, file_id)), - priority: MergePriority::Default, - ..Default::default() - } - } else { - Default::default() - }; - Match::Simple(e.name().unwrap().as_str().into(), mv) - }) - .collect(); - let dest = Destruct::Record { - matches, - open: pat.ellipsis(), + // In nix it's allowed to define vars named `true`, `false` or `null`. + // Concidering it a bad feature and something probably never used, this parser don't manage + // it. Right now it doesn't throw error when defining these vars but only ignore the + // definition. + ParsedType::Ident(n) => match n.as_str() { + "true" => Term::Bool(true), + "false" => Term::Bool(false), + "null" => Term::Null, + id => Term::Var(id.into()), + } + .into(), + ParsedType::LegacyLet(_) => unimplemented!(), // Probably useless to suport it in a short term. + ParsedType::LetIn(n) => { + use crate::destruct; + use crate::identifier::Ident; + let mut destruct_vec = Vec::new(); + let mut fields = HashMap::new(); + for kv in n.entries() { + // In `let` blocks, the key is suposed to be a single ident so `Path` exactly one + // element. + let id: Ident = types::Ident::cast(kv.key().unwrap().path().next().unwrap()) + .unwrap() + .as_str() + .into(); + let rt = kv.value().unwrap().translate(file_id); + destruct_vec.push(destruct::Match::Simple(id.clone(), Default::default())); + fields.insert(id.into(), rt); + } + Term::LetPattern( + None, + destruct::Destruct::Record { + matches: destruct_vec, + open: false, rest: None, span, - }; - Term::FunPattern(at, dest, translate(n.body().unwrap(), file_id)).into() + }, + Term::RecRecord(RecordData::with_fields(fields), vec![], None).into(), + n.body().unwrap().translate(file_id), + ) + .into() + } + ParsedType::With(n) => unimplemented!(), + + ParsedType::Lambda(n) => { + let arg = n.arg().unwrap(); + match arg.kind() { + NODE_IDENT => { + Term::Fun(arg.to_string().into(), n.body().unwrap().translate(file_id)) + } + NODE_PATTERN => { + use crate::destruct::*; + let pat = rnix::types::Pattern::cast(arg).unwrap(); + let at = pat.at().map(|id| id.as_str().into()); + // TODO: manage default values: + let matches = pat + .entries() + .map(|e| { + let mv = if let Some(def) = e.default() { + MetaValue { + value: Some(def.translate(file_id)), + priority: MergePriority::Bottom, + ..Default::default() + } + } else { + Default::default() + }; + Match::Simple(e.name().unwrap().as_str().into(), mv) + }) + .collect(); + let dest = Destruct::Record { + matches, + open: pat.ellipsis(), + rest: None, + span, + }; + Term::FunPattern(at, dest, n.body().unwrap().translate(file_id)).into() + } + _ => unreachable!(), } - _ => unreachable!(), + .into() } - .into() - } - ParsedType::Apply(n) => Term::App( - translate(n.lambda().unwrap(), file_id), - translate(n.value().unwrap(), file_id), - ) - .into(), - ParsedType::IfElse(n) => Term::App( - Term::App( - Term::Op1(UnaryOp::Ite(), translate(n.condition().unwrap(), file_id)).into(), - translate(n.body().unwrap(), file_id), - ) - .into(), - translate(n.else_body().unwrap(), file_id), - ) - .into(), - ParsedType::BinOp(n) => (n, file_id).into(), - ParsedType::UnaryOp(n) => (n, file_id).into(), - ParsedType::Select(n) => match ParsedType::try_from(n.index().unwrap()).unwrap() { - ParsedType::Ident(id) => Term::Op1( - UnaryOp::StaticAccess(id.as_str().into()), - translate(n.set().unwrap(), file_id), + ParsedType::Apply(n) => Term::App( + n.lambda().unwrap().translate(file_id), + n.value().unwrap().translate(file_id), ) .into(), - _ => Term::Op2( - BinaryOp::DynAccess(), - translate(n.index().unwrap(), file_id), - translate(n.set().unwrap(), file_id), + ParsedType::IfElse(n) => Term::App( + Term::App( + Term::Op1(UnaryOp::Ite(), n.condition().unwrap().translate(file_id)).into(), + n.body().unwrap().translate(file_id), + ) + .into(), + n.else_body().unwrap().translate(file_id), ) .into(), - }, - ParsedType::OrDefault(n) => unimplemented!(), + ParsedType::BinOp(n) => n.translate(file_id), + ParsedType::UnaryOp(n) => n.translate(file_id), + ParsedType::Select(n) => match ParsedType::try_from(n.index().unwrap()).unwrap() { + ParsedType::Ident(id) => Term::Op1( + UnaryOp::StaticAccess(id.as_str().into()), + n.set().unwrap().translate(file_id), + ) + .into(), + _ => Term::Op2( + BinaryOp::DynAccess(), + n.index().unwrap().translate(file_id), + n.set().unwrap().translate(file_id), + ) + .into(), + }, + ParsedType::OrDefault(n) => unimplemented!(), - // TODO: what are these? - ParsedType::PatBind(_) | ParsedType::PathWithInterpol(_) => { - unimplemented!() - } + // TODO: what are these? + ParsedType::PatBind(_) | ParsedType::PathWithInterpol(_) => { + unimplemented!() + } - // Is not a `RichTerm` so is transformed as part of actual ones. - ParsedType::Pattern(_) - | ParsedType::PatEntry(_) - | ParsedType::Inherit(..) - | ParsedType::InheritFrom(..) - | ParsedType::Key(..) - | ParsedType::KeyValue(..) => unreachable!(), + // Is not a `RichTerm` so is transformed as part of actual ones. + ParsedType::Pattern(_) + | ParsedType::PatEntry(_) + | ParsedType::Inherit(..) + | ParsedType::InheritFrom(..) + | ParsedType::Key(..) + | ParsedType::KeyValue(..) => unreachable!(), + } + .with_pos(crate::position::TermPos::Original(span)) } - .with_pos(crate::position::TermPos::Original(span)) } pub fn parse(cache: &Cache, file_id: FileId) -> Result { let source = cache.files().source(file_id); let nixast = rnix::parse(source).as_result()?; - Ok(translate(nixast.node(), file_id)) + Ok(nixast.node().translate(file_id)) } diff --git a/src/term/mod.rs b/src/term/mod.rs index b804bd8445..6795e60328 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -1888,7 +1888,6 @@ pub mod make { Term::LetPattern(id.map(|i| i.into()), pat.into(), t1.into(), t2.into()).into() } - #[cfg(test)] pub fn if_then_else(cond: T1, t1: T2, t2: T3) -> RichTerm where T1: Into, From a5d94733d896541c2b946e505a1d67b1ace25a47 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Mon, 4 Jul 2022 11:47:31 +0200 Subject: [PATCH 24/73] code cleaning --- src/nix.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/nix.rs b/src/nix.rs index abdd3da242..b5397a4bf7 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -29,7 +29,7 @@ impl ToNickel for BinOp { match self.operator().unwrap() { // TODO: Should be fixed using a nickel function `compat.concat` of type `a -> a -> a` // using `str_conct` or `array_concat` in respect to `typeof a`. - Concat => Term::Op2(BinaryOp::ArrayConcat(), lhs, rhs).into(), + Concat => make::op2(BinaryOp::ArrayConcat(), lhs, rhs), IsSet => unimplemented!(), Update => unimplemented!(), @@ -155,7 +155,7 @@ impl ToNickel for rnix::SyntaxNode { destruct_vec.push(destruct::Match::Simple(id.clone(), Default::default())); fields.insert(id.into(), rt); } - Term::LetPattern( + make::let_pat::( None, destruct::Destruct::Record { matches: destruct_vec, @@ -166,7 +166,6 @@ impl ToNickel for rnix::SyntaxNode { Term::RecRecord(RecordData::with_fields(fields), vec![], None).into(), n.body().unwrap().translate(file_id), ) - .into() } ParsedType::With(n) => unimplemented!(), @@ -202,12 +201,12 @@ impl ToNickel for rnix::SyntaxNode { rest: None, span, }; - Term::FunPattern(at, dest, n.body().unwrap().translate(file_id)).into() + Term::FunPattern(at, dest, n.body().unwrap().translate(file_id)) } _ => unreachable!(), } - .into() } + .into(), ParsedType::Apply(n) => Term::App( n.lambda().unwrap().translate(file_id), From e2c1b6a6a401c4007ba45d53e46ae01acb891035 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Mon, 4 Jul 2022 12:06:29 +0200 Subject: [PATCH 25/73] panic when redefining builtin value --- src/nix.rs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/nix.rs b/src/nix.rs index b5397a4bf7..32eb675d83 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -128,9 +128,8 @@ impl ToNickel for rnix::SyntaxNode { } // In nix it's allowed to define vars named `true`, `false` or `null`. - // Concidering it a bad feature and something probably never used, this parser don't manage - // it. Right now it doesn't throw error when defining these vars but only ignore the - // definition. + // But we prefer to not suport it. If we try to redefine one of these builtins, nickel + // will panic (see below in the `LetIn` arm. ParsedType::Ident(n) => match n.as_str() { "true" => Term::Bool(true), "false" => Term::Bool(false), @@ -147,10 +146,16 @@ impl ToNickel for rnix::SyntaxNode { for kv in n.entries() { // In `let` blocks, the key is suposed to be a single ident so `Path` exactly one // element. - let id: Ident = types::Ident::cast(kv.key().unwrap().path().next().unwrap()) - .unwrap() - .as_str() - .into(); + let id = types::Ident::cast(kv.key().unwrap().path().next().unwrap()).unwrap(); + // Check we don't try to redefine builtin values. Even if it's possible in Nix, + // we don't suport it. + let id: Ident = match id.as_str() { + "true" | "false" | "nul" => panic!( + "`let {}` is forbiden. Can not redifine `true`, `false` or `nul`", + id.as_str() + ), + s => s.into(), + }; let rt = kv.value().unwrap().translate(file_id); destruct_vec.push(destruct::Match::Simple(id.clone(), Default::default())); fields.insert(id.into(), rt); From 6fac91cc2fac81dbcd6e71d14ba04843cccad929 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Tue, 5 Jul 2022 10:31:10 +0200 Subject: [PATCH 26/73] Comments updates and panic when using unsuported legacy let. --- src/nix.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/nix.rs b/src/nix.rs index 32eb675d83..fe24622c69 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -137,7 +137,10 @@ impl ToNickel for rnix::SyntaxNode { id => Term::Var(id.into()), } .into(), - ParsedType::LegacyLet(_) => unimplemented!(), // Probably useless to suport it in a short term. + ParsedType::LegacyLet(_) => panic!("Legacy let form is not supported"), // Probably useless to suport it in a short term. + // `let ... in` blocks are recursive in Nix and not in Nickel. To emulate this, we use + // a `let = in`. The record provide recursivity then the values + // are destructured by the pattern. ParsedType::LetIn(n) => { use crate::destruct; use crate::identifier::Ident; @@ -249,7 +252,8 @@ impl ToNickel for rnix::SyntaxNode { unimplemented!() } - // Is not a `RichTerm` so is transformed as part of actual ones. + // Are not `RichTerm`s and are transformed as part of ones higher in the syntax tree. + // Actualy, these variants are not "Terms" in the sens they can not be parsed independently of a Term. They can only be used in a term. Our translate function return a term so these can not be parsed at this level. they are parsed as parts of actual terms. ParsedType::Pattern(_) | ParsedType::PatEntry(_) | ParsedType::Inherit(..) From 39fd5f74af0d3532f4e6721fffe55c4ef7007d73 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Tue, 5 Jul 2022 10:55:06 +0200 Subject: [PATCH 27/73] Translate `Path` value to its string repr --- src/nix.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/nix.rs b/src/nix.rs index fe24622c69..89800bb935 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -78,7 +78,16 @@ impl ToNickel for rnix::SyntaxNode { value::Value::Integer(v) => Term::Num(v as f64), value::Value::String(v) => Term::Str(v), // TODO: How to manage Paths in nickel? - value::Value::Path(a, v) => Term::Str(v), + value::Value::Path(a, v) => { + use value::Anchor::*; + let strpath = match a { + Absolute => format!("/{}", v), + Relative => format!("./{}", v), + Home => format!("~/{}", v), + Store => format!("<{}>", v), + }; + Term::Str(strpath) + } } .into(), ParsedType::Str(n) => { From 6717c7654856cabfd6c1b939d6220e0e7f854525 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Tue, 5 Jul 2022 11:03:18 +0200 Subject: [PATCH 28/73] clean code for `IfElse` match arm --- src/nix.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/nix.rs b/src/nix.rs index 89800bb935..bb57606809 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -230,15 +230,11 @@ impl ToNickel for rnix::SyntaxNode { n.value().unwrap().translate(file_id), ) .into(), - ParsedType::IfElse(n) => Term::App( - Term::App( - Term::Op1(UnaryOp::Ite(), n.condition().unwrap().translate(file_id)).into(), - n.body().unwrap().translate(file_id), - ) - .into(), + ParsedType::IfElse(n) => if_then_else( + n.condition().unwrap().translate(file_id), + n.body().unwrap().translate(file_id), n.else_body().unwrap().translate(file_id), - ) - .into(), + ), ParsedType::BinOp(n) => n.translate(file_id), ParsedType::UnaryOp(n) => n.translate(file_id), ParsedType::Select(n) => match ParsedType::try_from(n.index().unwrap()).unwrap() { From a950b7ae64a9ebfcb2d8e8c9da476f36774e3983 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Tue, 5 Jul 2022 15:49:51 +0200 Subject: [PATCH 29/73] Apply suggestions from code review Comments updates and typos fix. Co-authored-by: Yann Hamdaoui --- src/nix.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/nix.rs b/src/nix.rs index bb57606809..f1bcf6d9f4 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -28,7 +28,7 @@ impl ToNickel for BinOp { let rhs = self.rhs().unwrap().translate(file_id); match self.operator().unwrap() { // TODO: Should be fixed using a nickel function `compat.concat` of type `a -> a -> a` - // using `str_conct` or `array_concat` in respect to `typeof a`. + // using `str_concat` or `array_concat` in respect to `typeof a`. Concat => make::op2(BinaryOp::ArrayConcat(), lhs, rhs), IsSet => unimplemented!(), Update => unimplemented!(), @@ -92,7 +92,6 @@ impl ToNickel for rnix::SyntaxNode { .into(), ParsedType::Str(n) => { let parts = n.parts(); - //TODO: Do we actualy need the `Term::Str` variant in nickel AST? Term::StrChunks( parts .into_iter() @@ -137,8 +136,8 @@ impl ToNickel for rnix::SyntaxNode { } // In nix it's allowed to define vars named `true`, `false` or `null`. - // But we prefer to not suport it. If we try to redefine one of these builtins, nickel - // will panic (see below in the `LetIn` arm. + // But we prefer to not support it. If we try to redefine one of these builtins, nickel + // will panic (see below in the `LetIn` arm). ParsedType::Ident(n) => match n.as_str() { "true" => Term::Bool(true), "false" => Term::Bool(false), @@ -162,7 +161,7 @@ impl ToNickel for rnix::SyntaxNode { // Check we don't try to redefine builtin values. Even if it's possible in Nix, // we don't suport it. let id: Ident = match id.as_str() { - "true" | "false" | "nul" => panic!( + "true" | "false" | "null" => panic!( "`let {}` is forbiden. Can not redifine `true`, `false` or `nul`", id.as_str() ), @@ -257,8 +256,8 @@ impl ToNickel for rnix::SyntaxNode { unimplemented!() } - // Are not `RichTerm`s and are transformed as part of ones higher in the syntax tree. - // Actualy, these variants are not "Terms" in the sens they can not be parsed independently of a Term. They can only be used in a term. Our translate function return a term so these can not be parsed at this level. they are parsed as parts of actual terms. + // The following nodes aren't `RichTerm`s but sub-parts of other nodes higher in the syntax tree. + // They can't be parsed independently of a term and should never occur at this level of the translation. ParsedType::Pattern(_) | ParsedType::PatEntry(_) | ParsedType::Inherit(..) From bec4ff46de3f5264616cdb320627024f920ea160 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Tue, 5 Jul 2022 16:16:22 +0200 Subject: [PATCH 30/73] update `ToNickel` trait to take a set of defined vars (`env`) --- src/convertion.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/convertion.rs b/src/convertion.rs index 68ef7312de..a2d8c742c3 100644 --- a/src/convertion.rs +++ b/src/convertion.rs @@ -7,7 +7,20 @@ use crate::term::RichTerm; use codespan::FileId; +use std::collections::HashSet; + +pub struct State { + file_id: FileId, + env: HashSet, +} pub trait ToNickel { - fn translate(self, file_id: FileId) -> RichTerm; + fn to_nickel(&self, file_id: FileId) -> RichTerm { + let state = State { + file_id, + env: HashSet::new(), + }; + self.translate(state) + } + fn translate(self, state: State) -> RichTerm; } From 465111ab0fe240ff88b6862c77c47f67bb4e5b31 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Tue, 19 Jul 2022 11:03:55 +0200 Subject: [PATCH 31/73] pass `State` around convertion. --- src/convertion.rs | 18 +++++++++----- src/nix.rs | 62 ++++++++++++++++++++++++----------------------- 2 files changed, 44 insertions(+), 36 deletions(-) diff --git a/src/convertion.rs b/src/convertion.rs index a2d8c742c3..4addc044e5 100644 --- a/src/convertion.rs +++ b/src/convertion.rs @@ -9,18 +9,24 @@ use crate::term::RichTerm; use codespan::FileId; use std::collections::HashSet; +/// State of the convertion. It contain scope definition of the currently converted node (e.g.: +/// `with` environments, declared variables, current file id...). This state is used to generate +/// nickel AST from complex statefull language forms. pub struct State { - file_id: FileId, - env: HashSet, + /// The current transformation file ID. + pub file_id: FileId, + /// Variables accessibles in the scope. + pub env: HashSet, } -pub trait ToNickel { - fn to_nickel(&self, file_id: FileId) -> RichTerm { +pub trait ToNickel: Sized { + /// Used when converting a full file. Actually call `translate` with a initial `State`. + fn to_nickel(self, file_id: FileId) -> RichTerm { let state = State { file_id, env: HashSet::new(), }; - self.translate(state) + self.translate(&state) } - fn translate(self, state: State) -> RichTerm; + fn translate(self, state: &State) -> RichTerm; } diff --git a/src/nix.rs b/src/nix.rs index f1bcf6d9f4..771cee3dc1 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -1,5 +1,6 @@ use crate::cache::Cache; -use crate::convertion::ToNickel; +use crate::convertion::State; +pub use crate::convertion::ToNickel; use crate::mk_app; use crate::parser::utils::mk_span; use crate::term::make::{self, if_then_else}; @@ -11,9 +12,9 @@ use rnix::types::{BinOp, EntryHolder, TokenWrapper, TypedNode, UnaryOp as UniOp} use std::collections::HashMap; impl ToNickel for UniOp { - fn translate(self, file_id: FileId) -> RichTerm { + fn translate(self, state: &State) -> RichTerm { use rnix::types::UnaryOpKind::*; - let value = self.value().unwrap().translate(file_id); + let value = self.value().unwrap().translate(state); match self.operator() { Negate => make::op2(BinaryOp::Sub(), Term::Num(0.), value), Invert => make::op1(UnaryOp::BoolNot(), value), @@ -22,10 +23,10 @@ impl ToNickel for UniOp { } impl ToNickel for BinOp { - fn translate(self, file_id: FileId) -> RichTerm { + fn translate(self, state: &State) -> RichTerm { use rnix::types::BinOpKind::*; - let lhs = self.lhs().unwrap().translate(file_id); - let rhs = self.rhs().unwrap().translate(file_id); + let lhs = self.lhs().unwrap().translate(state); + let rhs = self.rhs().unwrap().translate(state); match self.operator().unwrap() { // TODO: Should be fixed using a nickel function `compat.concat` of type `a -> a -> a` // using `str_concat` or `array_concat` in respect to `typeof a`. @@ -54,12 +55,13 @@ impl ToNickel for BinOp { } impl ToNickel for rnix::SyntaxNode { - fn translate(self, file_id: FileId) -> RichTerm { + fn translate(self, state: &State) -> RichTerm { use rnix::types::{self, ParsedType, Wrapper}; use rnix::value; use rnix::SyntaxKind::*; use std::convert::{TryFrom, TryInto}; let pos = self.text_range(); + let file_id = state.file_id.clone(); let span = mk_span(file_id, pos.start().into(), pos.end().into()); println!("{:?}: {}", self, self); let node: ParsedType = self.try_into().unwrap(); @@ -67,9 +69,9 @@ impl ToNickel for rnix::SyntaxNode { ParsedType::Error(_) => { Term::ParseError(crate::error::ParseError::NixParseError(file_id)).into() } - ParsedType::Root(n) => n.inner().unwrap().translate(file_id), - ParsedType::Paren(n) => n.inner().unwrap().translate(file_id), - ParsedType::Dynamic(n) => n.inner().unwrap().translate(file_id), + ParsedType::Root(n) => n.inner().unwrap().translate(state), + ParsedType::Paren(n) => n.inner().unwrap().translate(state), + ParsedType::Dynamic(n) => n.inner().unwrap().translate(state), ParsedType::Assert(n) => unimplemented!(), @@ -99,7 +101,7 @@ impl ToNickel for rnix::SyntaxNode { .map(|(i, c)| match c { rnix::value::StrPart::Literal(s) => crate::term::StrChunk::Literal(s), rnix::value::StrPart::Ast(a) => crate::term::StrChunk::Expr( - a.first_child().unwrap().translate(file_id), + a.first_child().unwrap().translate(state), i, ), }) @@ -108,7 +110,7 @@ impl ToNickel for rnix::SyntaxNode { .into() } ParsedType::List(n) => Term::Array( - n.items().map(|elm| elm.translate(file_id)).collect(), + n.items().map(|elm| elm.translate(state)).collect(), Default::default(), ) .into(), @@ -117,7 +119,7 @@ impl ToNickel for rnix::SyntaxNode { let fields: Vec<(_, _)> = n .entries() .map(|kv| { - let val = kv.value().unwrap().translate(file_id); + let val = kv.value().unwrap().translate(state); let p: Vec<_> = kv .key() .unwrap() @@ -126,7 +128,7 @@ impl ToNickel for rnix::SyntaxNode { NODE_IDENT => FieldPathElem::Ident( rnix::types::Ident::cast(p).unwrap().as_str().into(), ), - _ => FieldPathElem::Expr(p.translate(file_id)), + _ => FieldPathElem::Expr(p.translate(state)), }) .collect(); elaborate_field_path(p, val) @@ -167,7 +169,7 @@ impl ToNickel for rnix::SyntaxNode { ), s => s.into(), }; - let rt = kv.value().unwrap().translate(file_id); + let rt = kv.value().unwrap().translate(state); destruct_vec.push(destruct::Match::Simple(id.clone(), Default::default())); fields.insert(id.into(), rt); } @@ -180,7 +182,7 @@ impl ToNickel for rnix::SyntaxNode { span, }, Term::RecRecord(RecordData::with_fields(fields), vec![], None).into(), - n.body().unwrap().translate(file_id), + n.body().unwrap().translate(state), ) } ParsedType::With(n) => unimplemented!(), @@ -189,7 +191,7 @@ impl ToNickel for rnix::SyntaxNode { let arg = n.arg().unwrap(); match arg.kind() { NODE_IDENT => { - Term::Fun(arg.to_string().into(), n.body().unwrap().translate(file_id)) + Term::Fun(arg.to_string().into(), n.body().unwrap().translate(state)) } NODE_PATTERN => { use crate::destruct::*; @@ -201,7 +203,7 @@ impl ToNickel for rnix::SyntaxNode { .map(|e| { let mv = if let Some(def) = e.default() { MetaValue { - value: Some(def.translate(file_id)), + value: Some(def.translate(state)), priority: MergePriority::Bottom, ..Default::default() } @@ -217,7 +219,7 @@ impl ToNickel for rnix::SyntaxNode { rest: None, span, }; - Term::FunPattern(at, dest, n.body().unwrap().translate(file_id)) + Term::FunPattern(at, dest, n.body().unwrap().translate(state)) } _ => unreachable!(), } @@ -225,27 +227,27 @@ impl ToNickel for rnix::SyntaxNode { .into(), ParsedType::Apply(n) => Term::App( - n.lambda().unwrap().translate(file_id), - n.value().unwrap().translate(file_id), + n.lambda().unwrap().translate(state), + n.value().unwrap().translate(state), ) .into(), ParsedType::IfElse(n) => if_then_else( - n.condition().unwrap().translate(file_id), - n.body().unwrap().translate(file_id), - n.else_body().unwrap().translate(file_id), + n.condition().unwrap().translate(state), + n.body().unwrap().translate(state), + n.else_body().unwrap().translate(state), ), - ParsedType::BinOp(n) => n.translate(file_id), - ParsedType::UnaryOp(n) => n.translate(file_id), + ParsedType::BinOp(n) => n.translate(state), + ParsedType::UnaryOp(n) => n.translate(state), ParsedType::Select(n) => match ParsedType::try_from(n.index().unwrap()).unwrap() { ParsedType::Ident(id) => Term::Op1( UnaryOp::StaticAccess(id.as_str().into()), - n.set().unwrap().translate(file_id), + n.set().unwrap().translate(state), ) .into(), _ => Term::Op2( BinaryOp::DynAccess(), - n.index().unwrap().translate(file_id), - n.set().unwrap().translate(file_id), + n.index().unwrap().translate(state), + n.set().unwrap().translate(state), ) .into(), }, @@ -272,5 +274,5 @@ impl ToNickel for rnix::SyntaxNode { pub fn parse(cache: &Cache, file_id: FileId) -> Result { let source = cache.files().source(file_id); let nixast = rnix::parse(source).as_result()?; - Ok(nixast.node().translate(file_id)) + Ok(nixast.node().to_nickel(file_id)) } From 9461cc988a3cbb3363c45fb1a1b66c3434d61b25 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Thu, 21 Jul 2022 11:36:00 +0200 Subject: [PATCH 32/73] Apply suggestions from code review typos fixes Co-authored-by: Yann Hamdaoui --- src/convertion.rs | 9 ++++----- src/nix.rs | 4 ++-- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/convertion.rs b/src/convertion.rs index 4addc044e5..f924a3769c 100644 --- a/src/convertion.rs +++ b/src/convertion.rs @@ -9,18 +9,17 @@ use crate::term::RichTerm; use codespan::FileId; use std::collections::HashSet; -/// State of the convertion. It contain scope definition of the currently converted node (e.g.: -/// `with` environments, declared variables, current file id...). This state is used to generate -/// nickel AST from complex statefull language forms. +/// State of the conversion. It contains the definitions in scope of the currently converted node (e.g.: +/// `with` environments, declared variables, current file id...), required for elaborate compilation (`with`). pub struct State { /// The current transformation file ID. pub file_id: FileId, - /// Variables accessibles in the scope. + /// Variables in scope. pub env: HashSet, } pub trait ToNickel: Sized { - /// Used when converting a full file. Actually call `translate` with a initial `State`. + /// Used when converting a full file. Actually call `translate` with an initial `State`. fn to_nickel(self, file_id: FileId) -> RichTerm { let state = State { file_id, diff --git a/src/nix.rs b/src/nix.rs index 771cee3dc1..2027e2bbc6 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -1,6 +1,6 @@ use crate::cache::Cache; -use crate::convertion::State; -pub use crate::convertion::ToNickel; +use crate::conversion::State; +pub use crate::conversion::ToNickel; use crate::mk_app; use crate::parser::utils::mk_span; use crate::term::make::{self, if_then_else}; From e88f90f791c7e1f6aade19097d8612513e670c1d Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Thu, 21 Jul 2022 11:41:40 +0200 Subject: [PATCH 33/73] fix "convertion" -> "conversion" typo --- src/{convertion.rs => conversion.rs} | 0 src/lib.rs | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename src/{convertion.rs => conversion.rs} (100%) diff --git a/src/convertion.rs b/src/conversion.rs similarity index 100% rename from src/convertion.rs rename to src/conversion.rs diff --git a/src/lib.rs b/src/lib.rs index a553d25785..23aa04ae7d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ pub mod cache; -pub mod convertion; pub mod deserialize; pub mod destructuring; +pub mod conversion; pub mod environment; pub mod error; pub mod eval; From e064169d739cb192acc90117a66b610f41598603 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Fri, 9 Sep 2022 17:51:41 +0200 Subject: [PATCH 34/73] Nix with parsed and transpiled with let shadowing: --- src/conversion.rs | 2 ++ src/nix.rs | 52 +++++++++++++++++++++++++++++++++++++++++++---- src/stdlib.rs | 10 ++++++++- stdlib/compat.ncl | 12 +++++++++++ 4 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 stdlib/compat.ncl diff --git a/src/conversion.rs b/src/conversion.rs index f924a3769c..34099820f2 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -16,6 +16,7 @@ pub struct State { pub file_id: FileId, /// Variables in scope. pub env: HashSet, + pub with: Vec, } pub trait ToNickel: Sized { @@ -24,6 +25,7 @@ pub trait ToNickel: Sized { let state = State { file_id, env: HashSet::new(), + with: Vec::new(), }; self.translate(&state) } diff --git a/src/nix.rs b/src/nix.rs index 2027e2bbc6..0270c56f0c 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -1,6 +1,7 @@ use crate::cache::Cache; use crate::conversion::State; pub use crate::conversion::ToNickel; +use crate::identifier::Ident; use crate::mk_app; use crate::parser::utils::mk_span; use crate::term::make::{self, if_then_else}; @@ -144,7 +145,22 @@ impl ToNickel for rnix::SyntaxNode { "true" => Term::Bool(true), "false" => Term::Bool(false), "null" => Term::Null, - id => Term::Var(id.into()), + id => { + if state.env.contains(id) || state.with.is_empty() { + Term::Var(id.into()) + } else { + Term::App( + mk_app!( + make::op1( + UnaryOp::StaticAccess("with".into()), + Term::Var("compat".into()), + ), + Term::Array(state.with.clone(), Default::default()) + ), + Term::Str(id.into()).into(), + ) + } + } } .into(), ParsedType::LegacyLet(_) => panic!("Legacy let form is not supported"), // Probably useless to suport it in a short term. @@ -156,6 +172,25 @@ impl ToNickel for rnix::SyntaxNode { use crate::identifier::Ident; let mut destruct_vec = Vec::new(); let mut fields = HashMap::new(); + let env = state + .env + .union( + &n.entries() + .map(|kv| { + types::Ident::cast(kv.key().unwrap().path().next().unwrap()) + .unwrap() + .as_str() + .to_string() + }) + .collect(), + ) + .map(|e| e.clone()) + .collect(); + let state = State { + env, + file_id: state.file_id.clone(), + with: state.with.clone(), + }; for kv in n.entries() { // In `let` blocks, the key is suposed to be a single ident so `Path` exactly one // element. @@ -169,7 +204,7 @@ impl ToNickel for rnix::SyntaxNode { ), s => s.into(), }; - let rt = kv.value().unwrap().translate(state); + let rt = kv.value().unwrap().translate(&state); destruct_vec.push(destruct::Match::Simple(id.clone(), Default::default())); fields.insert(id.into(), rt); } @@ -182,10 +217,19 @@ impl ToNickel for rnix::SyntaxNode { span, }, Term::RecRecord(RecordData::with_fields(fields), vec![], None).into(), - n.body().unwrap().translate(state), + n.body().unwrap().translate(&state), ) } - ParsedType::With(n) => unimplemented!(), + ParsedType::With(n) => { + let State { file_id, env, with } = state; + let mut with = with.clone(); + with.push(n.namespace().unwrap().translate(state)); + n.body().unwrap().translate(&State { + file_id: file_id.clone(), + env: env.clone(), + with, + }) + } ParsedType::Lambda(n) => { let arg = n.arg().unwrap(); diff --git a/src/stdlib.rs b/src/stdlib.rs index 545c849538..47548ee14f 100644 --- a/src/stdlib.rs +++ b/src/stdlib.rs @@ -4,8 +4,9 @@ use crate::identifier::Ident; use crate::term::make as mk_term; use crate::term::RichTerm; +<<<<<<< HEAD /// This is an array containing all the Nickel standard library modules. -pub fn modules() -> [StdlibModule; 8] { +pub fn modules() -> [StdlibModule; 9] { [ StdlibModule::Builtin, StdlibModule::Contract, @@ -15,9 +16,11 @@ pub fn modules() -> [StdlibModule; 8] { StdlibModule::Num, StdlibModule::Function, StdlibModule::Internals, + StdlibModule::Compat, ] } + /// Represents a particular Nickel standard library module. #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] pub enum StdlibModule { @@ -29,6 +32,7 @@ pub enum StdlibModule { Num, Function, Internals, + Compat, } impl StdlibModule { @@ -42,6 +46,7 @@ impl StdlibModule { StdlibModule::Num => "", StdlibModule::Function => "", StdlibModule::Internals => "", + StdlibModule::Compat => "", } } @@ -55,6 +60,7 @@ impl StdlibModule { StdlibModule::Num => include_str!("../stdlib/num.ncl"), StdlibModule::Function => include_str!("../stdlib/function.ncl"), StdlibModule::Internals => include_str!("../stdlib/internals.ncl"), + StdlibModule::Compat => include_str!("../stdlib/compat.ncl"), } } } @@ -74,6 +80,7 @@ impl TryFrom for StdlibModule { "num" => StdlibModule::Num, "function" => StdlibModule::Function, "internals" => StdlibModule::Internals, + "compat" => StdlibModule::Compat, _ => return Err(UnknownStdlibModule), }; Ok(module) @@ -91,6 +98,7 @@ impl From for Ident { StdlibModule::Num => "num", StdlibModule::Function => "function", StdlibModule::Internals => "internals", + StdlibModule::Compat=> "Compat", }; Ident::from(name) } diff --git a/stdlib/compat.ncl b/stdlib/compat.ncl new file mode 100644 index 0000000000..a01302ae1a --- /dev/null +++ b/stdlib/compat.ncl @@ -0,0 +1,12 @@ +{ +compat | doc "Nix compatibility layer. should not be used by Nickel program." += { + with: Array {_: Dyn} -> Str -> Dyn + = fun envs field => ( + array.fold (fun prev current => + if record.has_field field current + then current + else prev + ) {} envs)."%{field}" +} +} From ad5f72f4d4298967efaa012b272c7f0c39ce339f Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Fri, 16 Sep 2022 10:33:36 +0200 Subject: [PATCH 35/73] Update src/nix.rs Co-authored-by: Yann Hamdaoui --- src/nix.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nix.rs b/src/nix.rs index 0270c56f0c..b1247fbdef 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -184,7 +184,7 @@ impl ToNickel for rnix::SyntaxNode { }) .collect(), ) - .map(|e| e.clone()) + .cloned() .collect(); let state = State { env, From 10c6402ea12e14cb578e880009cc4e922e24e703 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Thu, 29 Sep 2022 16:48:03 +0200 Subject: [PATCH 36/73] Update src/nix.rs Co-authored-by: Yann Hamdaoui --- src/nix.rs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/nix.rs b/src/nix.rs index b1247fbdef..8717b86eb1 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -221,14 +221,9 @@ impl ToNickel for rnix::SyntaxNode { ) } ParsedType::With(n) => { - let State { file_id, env, with } = state; - let mut with = with.clone(); - with.push(n.namespace().unwrap().translate(state)); - n.body().unwrap().translate(&State { - file_id: file_id.clone(), - env: env.clone(), - with, - }) + let mut state = state.clone(); + state.with.push(n.namespace().unwrap().translate(state)); + n.body().unwrap().translate(&state) } ParsedType::Lambda(n) => { From 40260eb3a0412da47de81aa319db2d939bba14cd Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Thu, 29 Sep 2022 17:17:43 +0200 Subject: [PATCH 37/73] add rust modul helper for nix compatibility functions in `stdlib::compat` this module for now only let us build a with from a list of arrays also add implementation of Clone for conversion::State --- src/bin/nickel.rs | 3 ++- src/conversion.rs | 1 + src/error.rs | 15 +++++++++++++++ src/nix.rs | 14 ++++---------- src/stdlib.rs | 21 +++++++++++++++++++++ 5 files changed, 43 insertions(+), 11 deletions(-) diff --git a/src/bin/nickel.rs b/src/bin/nickel.rs index efd8c1ebb1..fbf8dff57d 100644 --- a/src/bin/nickel.rs +++ b/src/bin/nickel.rs @@ -115,11 +115,12 @@ fn main() { eprintln!("error: this executable was not compiled with REPL support"); } else if let Some(Command::Nixin) = opts.command { use nickel_lang::cache::Cache; + use nickel_lang::cache::ErrorTolerance; use nickel_lang::pretty::*; use pretty::BoxAllocator; let mut buf = String::new(); - let mut cache = Cache::new(); + let mut cache = Cache::new(ErrorTolerance::Strict); let mut out: Vec = Vec::new(); opts.file .map(std::fs::File::open) diff --git a/src/conversion.rs b/src/conversion.rs index 34099820f2..5c04a5d961 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -11,6 +11,7 @@ use std::collections::HashSet; /// State of the conversion. It contains the definitions in scope of the currently converted node (e.g.: /// `with` environments, declared variables, current file id...), required for elaborate compilation (`with`). +#[derive(Clone)] pub struct State { /// The current transformation file ID. pub file_id: FileId, diff --git a/src/error.rs b/src/error.rs index e779cf0187..ee59680444 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1403,6 +1403,21 @@ impl IntoDiagnostics for ParseError { _stdlib_ids: Option<&Vec>, ) -> Vec> { let diagnostic = match self { + // TODO: improve error management for nix parser. + ParseError::NixParseError(file_id) => { + let end = files.source_span(*file_id).end(); + let start = files.source_span(*file_id).start(); + Diagnostic::error() + .with_message(format!( + "error parsing nix file {}", + files.name(*file_id).to_string_lossy() + )) + .with_labels(vec![primary(&RawSpan { + start, + end, + src_id: *file_id, + })]) + } ParseError::UnexpectedEOF(file_id, _expected) => { let end = files.source_span(file_id).end(); Diagnostic::error() diff --git a/src/nix.rs b/src/nix.rs index 8717b86eb1..9ebba3c350 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -5,7 +5,7 @@ use crate::identifier::Ident; use crate::mk_app; use crate::parser::utils::mk_span; use crate::term::make::{self, if_then_else}; -use crate::term::{BinaryOp, RecordData, UnaryOp}; +use crate::term::{record::RecordData, BinaryOp, UnaryOp}; use crate::term::{MergePriority, MetaValue, RichTerm, Term}; use codespan::FileId; use rnix; @@ -150,13 +150,7 @@ impl ToNickel for rnix::SyntaxNode { Term::Var(id.into()) } else { Term::App( - mk_app!( - make::op1( - UnaryOp::StaticAccess("with".into()), - Term::Var("compat".into()), - ), - Term::Array(state.with.clone(), Default::default()) - ), + crate::stdlib::compat::with(state.with.clone().into_iter().collect()), Term::Str(id.into()).into(), ) } @@ -216,13 +210,13 @@ impl ToNickel for rnix::SyntaxNode { rest: None, span, }, - Term::RecRecord(RecordData::with_fields(fields), vec![], None).into(), + Term::RecRecord(RecordData::with_fields(fields), vec![], None), n.body().unwrap().translate(&state), ) } ParsedType::With(n) => { let mut state = state.clone(); - state.with.push(n.namespace().unwrap().translate(state)); + state.with.push(n.namespace().unwrap().translate(&state)); n.body().unwrap().translate(&state) } diff --git a/src/stdlib.rs b/src/stdlib.rs index 47548ee14f..331a015bc3 100644 --- a/src/stdlib.rs +++ b/src/stdlib.rs @@ -144,3 +144,24 @@ pub mod internals { generate_accessor!(rec_default); generate_accessor!(rec_force); } + +/// Contains functions helper for Nix evaluation by Nickel. +pub mod compat { + use super::*; + use crate::identifier::*; + use crate::mk_app; + use crate::term::make::op1; + use crate::term::{array::Array, Term, UnaryOp}; + + /// Generate the `with` compatibility Nickel function which may be applied to an `Ident` + /// you have to pass a list of with records in ordered from outer-most to inner-most one. + pub fn with(array: Array) -> RichTerm { + mk_app!( + op1( + UnaryOp::StaticAccess("with".into()), + Term::Var("compat".into()), + ), + Term::Array(array, Default::default()) + ) + } +} From e3c2f03133a1bd05de58e852aa532a26c4c778cb Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Wed, 5 Oct 2022 15:37:04 +0200 Subject: [PATCH 38/73] Update stdlib/compat.ncl Co-authored-by: Yann Hamdaoui --- stdlib/compat.ncl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/stdlib/compat.ncl b/stdlib/compat.ncl index a01302ae1a..d78ccb0cf0 100644 --- a/stdlib/compat.ncl +++ b/stdlib/compat.ncl @@ -3,10 +3,10 @@ compat | doc "Nix compatibility layer. should not be used by Nickel program." = { with: Array {_: Dyn} -> Str -> Dyn = fun envs field => ( - array.fold (fun prev current => - if record.has_field field current - then current - else prev + array.fold (fun current prev => + if record.has_field field prev + then prev + else current ) {} envs)."%{field}" } } From 860bda6cf18c0fbc040f89e89477d7195792dc18 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Wed, 5 Oct 2022 16:19:54 +0200 Subject: [PATCH 39/73] last optimizations as proposed by @yannham --- src/nix.rs | 26 +++++++------------------- stdlib/compat.ncl | 17 ++++++++++------- 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/src/nix.rs b/src/nix.rs index 9ebba3c350..7ed35128bc 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -166,25 +166,13 @@ impl ToNickel for rnix::SyntaxNode { use crate::identifier::Ident; let mut destruct_vec = Vec::new(); let mut fields = HashMap::new(); - let env = state - .env - .union( - &n.entries() - .map(|kv| { - types::Ident::cast(kv.key().unwrap().path().next().unwrap()) - .unwrap() - .as_str() - .to_string() - }) - .collect(), - ) - .cloned() - .collect(); - let state = State { - env, - file_id: state.file_id.clone(), - with: state.with.clone(), - }; + let mut state = state.clone(); + state.env.extend(n.entries().map(|kv| { + types::Ident::cast(kv.key().unwrap().path().next().unwrap()) + .unwrap() + .as_str() + .to_string() + })); for kv in n.entries() { // In `let` blocks, the key is suposed to be a single ident so `Path` exactly one // element. diff --git a/stdlib/compat.ncl b/stdlib/compat.ncl index d78ccb0cf0..4d9e59815a 100644 --- a/stdlib/compat.ncl +++ b/stdlib/compat.ncl @@ -1,12 +1,15 @@ { compat | doc "Nix compatibility layer. should not be used by Nickel program." = { - with: Array {_: Dyn} -> Str -> Dyn - = fun envs field => ( - array.fold (fun current prev => - if record.has_field field prev - then prev - else current - ) {} envs)."%{field}" + with = + let AssertFound = fun l val => if val.found + then val.value + else %blame% l in + fun envs field => ( + array.fold (fun current acc => + if !acc.found && record.has_field field current + then { value = current."%{field}", found = true} + else acc + ) {value = null, found = false} envs) | AssertFound } } From 87c71e35254e0a333383b96a89e49773e1155150 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Fri, 4 Nov 2022 10:18:38 +0100 Subject: [PATCH 40/73] add suport for `?` operator of Nix only supprt when rhs is an identifier or a string. does not suport field path for now. --- src/nix.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/nix.rs b/src/nix.rs index 7ed35128bc..d5f046ecb6 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -32,7 +32,14 @@ impl ToNickel for BinOp { // TODO: Should be fixed using a nickel function `compat.concat` of type `a -> a -> a` // using `str_concat` or `array_concat` in respect to `typeof a`. Concat => make::op2(BinaryOp::ArrayConcat(), lhs, rhs), - IsSet => unimplemented!(), + IsSet => { + let rhs = if self.rhs().unwrap().kind() == rnix::SyntaxKind::NODE_IDENT { + Term::Str(self.rhs().unwrap().to_string()).into() + } else { + rhs + }; + make::op2(BinaryOp::HasField(), rhs, lhs) + } Update => unimplemented!(), Add => make::op2(BinaryOp::Plus(), lhs, rhs), From 4f9fcbda5544bcb9ace0711e9ca1295bd64262be Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Fri, 28 Oct 2022 11:51:08 +0200 Subject: [PATCH 41/73] add simple test suite for nix compatibility --- tests/integration/pass/nix.ncl | 4 ++++ tests/integration/pass/nix/basics.nix | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 tests/integration/pass/nix.ncl create mode 100644 tests/integration/pass/nix/basics.nix diff --git a/tests/integration/pass/nix.ncl b/tests/integration/pass/nix.ncl new file mode 100644 index 0000000000..929eba912d --- /dev/null +++ b/tests/integration/pass/nix.ncl @@ -0,0 +1,4 @@ + +let Assert = fun l x => x || %blame% l in +(import "nix/basics.nix" | Array Bool) +|> array.foldl (fun x y => (x | Assert) && y) true diff --git a/tests/integration/pass/nix/basics.nix b/tests/integration/pass/nix/basics.nix new file mode 100644 index 0000000000..30bc228b2c --- /dev/null +++ b/tests/integration/pass/nix/basics.nix @@ -0,0 +1,18 @@ +# Basics tests taken from basics.ncl and rewritten with Nix +[ + # basic arithmetic + (1 + 1 == 2) + (1 - 2 + 3 - 4 == -2) + (2 - 3 - 4 == -5) + (-1 - 2 == -3) + (2 * 2 + 2 * 3 - 2 * 4 == 2) + (1 / 2 + 1 / 4 - 1 / 8 == 0.625) + #((10 + 1/4) % 3 == 1.25) + #(10 + 1/4 % 3 == 10.25) + + # comparisons + (1 < 1 == false) + (1 <= 1 == true) + (1 > 1 == false) + (1 >= 1 == true) +] From e0c88d06834aa3b375edd861995e5865e9ef009a Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Thu, 3 Nov 2022 16:51:23 +0100 Subject: [PATCH 42/73] add some tests and discovered issues --- src/nix.rs | 4 ++-- tests/integration/pass/nix/basics.nix | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/nix.rs b/src/nix.rs index d5f046ecb6..e3fa2cf3bc 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -29,8 +29,6 @@ impl ToNickel for BinOp { let lhs = self.lhs().unwrap().translate(state); let rhs = self.rhs().unwrap().translate(state); match self.operator().unwrap() { - // TODO: Should be fixed using a nickel function `compat.concat` of type `a -> a -> a` - // using `str_concat` or `array_concat` in respect to `typeof a`. Concat => make::op2(BinaryOp::ArrayConcat(), lhs, rhs), IsSet => { let rhs = if self.rhs().unwrap().kind() == rnix::SyntaxKind::NODE_IDENT { @@ -42,6 +40,8 @@ impl ToNickel for BinOp { } Update => unimplemented!(), + // TODO: Should be fixed using a nickel function `compat.add` of type `a -> a -> a` + // using `str_concat` or `add` in respect to `typeof a`. Add => make::op2(BinaryOp::Plus(), lhs, rhs), Sub => make::op2(BinaryOp::Sub(), lhs, rhs), Mul => make::op2(BinaryOp::Mult(), lhs, rhs), diff --git a/tests/integration/pass/nix/basics.nix b/tests/integration/pass/nix/basics.nix index 30bc228b2c..a81c66bcc5 100644 --- a/tests/integration/pass/nix/basics.nix +++ b/tests/integration/pass/nix/basics.nix @@ -15,4 +15,24 @@ (1 <= 1 == true) (1 > 1 == false) (1 >= 1 == true) + + # booleans expr + (true && false == false) + (true || false == true) + (true -> true == true) + (true -> false == false) + (false -> true == true) + (false -> false == true) + (!false == true) + (false && true || true == true) + # (false && (true || true) == false) # TODO: what happen here? + + # lists concataination + ([ 1 2 ] ++ [ 1 2 ] == [ 1 2 1 2 ]) + + # strings concataination + # ("hello" + " " + "world" == "hello world") # TODO: to be fixed + + # if then else + ((if true then 1 else 2) == 1) ] From 6a6dd03a9f2499e8fb928824088f3945a92d7842 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Wed, 9 Nov 2022 09:49:59 +0100 Subject: [PATCH 43/73] move Nix's tests to there own tests --- tests/integration/pass/nix.ncl | 4 ---- tests/nix.rs | 20 ++++++++++++++++++++ tests/{integration/pass => }/nix/basics.nix | 0 3 files changed, 20 insertions(+), 4 deletions(-) delete mode 100644 tests/integration/pass/nix.ncl create mode 100644 tests/nix.rs rename tests/{integration/pass => }/nix/basics.nix (100%) diff --git a/tests/integration/pass/nix.ncl b/tests/integration/pass/nix.ncl deleted file mode 100644 index 929eba912d..0000000000 --- a/tests/integration/pass/nix.ncl +++ /dev/null @@ -1,4 +0,0 @@ - -let Assert = fun l x => x || %blame% l in -(import "nix/basics.nix" | Array Bool) -|> array.foldl (fun x y => (x | Assert) && y) true diff --git a/tests/nix.rs b/tests/nix.rs new file mode 100644 index 0000000000..73dafd0cad --- /dev/null +++ b/tests/nix.rs @@ -0,0 +1,20 @@ +use nickel_lang::term::Term; +use nickel_lang_utilities::eval; + +fn run(path: &str) { + let res = eval(format!( + "let t = import \"{}/tests/nix/{}\" in array.fold (fun x acc => acc && x) true t", + env!("CARGO_MANIFEST_DIR"), + path + )) + .map(|rt| { + let term = Term::from(rt); + assert_eq!(term, Term::Bool(true), "error in test {}", path,); + }) + .unwrap(); +} + +#[test] +fn basics_nix() { + run("basics.nix"); +} diff --git a/tests/integration/pass/nix/basics.nix b/tests/nix/basics.nix similarity index 100% rename from tests/integration/pass/nix/basics.nix rename to tests/nix/basics.nix From 245c90bc835b058322d7e32dfc066d4d18b5b564 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Thu, 3 Nov 2022 17:46:08 +0100 Subject: [PATCH 44/73] fix string concat for Nix --- src/nix.rs | 4 +--- src/stdlib.rs | 7 +++++++ stdlib/compat.ncl | 8 ++++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/nix.rs b/src/nix.rs index e3fa2cf3bc..a0ea2c3da1 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -40,9 +40,7 @@ impl ToNickel for BinOp { } Update => unimplemented!(), - // TODO: Should be fixed using a nickel function `compat.add` of type `a -> a -> a` - // using `str_concat` or `add` in respect to `typeof a`. - Add => make::op2(BinaryOp::Plus(), lhs, rhs), + Add => mk_app!(crate::stdlib::compat::add(), lhs, rhs), Sub => make::op2(BinaryOp::Sub(), lhs, rhs), Mul => make::op2(BinaryOp::Mult(), lhs, rhs), Div => make::op2(BinaryOp::Div(), lhs, rhs), diff --git a/src/stdlib.rs b/src/stdlib.rs index 331a015bc3..4b80d80b77 100644 --- a/src/stdlib.rs +++ b/src/stdlib.rs @@ -164,4 +164,11 @@ pub mod compat { Term::Array(array, Default::default()) ) } + + pub fn add() -> RichTerm { + op1( + UnaryOp::StaticAccess("add".into()), + Term::Var("compat".into()), + ) + } } diff --git a/stdlib/compat.ncl b/stdlib/compat.ncl index 4d9e59815a..734612dd0a 100644 --- a/stdlib/compat.ncl +++ b/stdlib/compat.ncl @@ -1,6 +1,14 @@ { compat | doc "Nix compatibility layer. should not be used by Nickel program." = { + + # the adition in Nix can be a string concataination. + # this function handle this behaviour. + add = fun a b => + if %typeof% a == `Str && %typeof% b == `Str + then a ++ b + else a + b, + with = let AssertFound = fun l val => if val.found then val.value From 0d197d81bd45596f3860a8e8b2f9697a41fe34c1 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Wed, 9 Nov 2022 09:55:44 +0100 Subject: [PATCH 45/73] Update stdlib/compat.ncl Co-authored-by: Yann Hamdaoui --- stdlib/compat.ncl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/compat.ncl b/stdlib/compat.ncl index 734612dd0a..5999ccc163 100644 --- a/stdlib/compat.ncl +++ b/stdlib/compat.ncl @@ -1,5 +1,5 @@ { -compat | doc "Nix compatibility layer. should not be used by Nickel program." +compat | doc "Nix compatibility layer. This library should not be used by Nickel program." = { # the adition in Nix can be a string concataination. From 0ab076016de3aefb02cfd543a375a3c0d5cf14e0 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Wed, 9 Nov 2022 09:56:30 +0100 Subject: [PATCH 46/73] Update stdlib/compat.ncl Co-authored-by: Yann Hamdaoui --- stdlib/compat.ncl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/compat.ncl b/stdlib/compat.ncl index 5999ccc163..4f4017d049 100644 --- a/stdlib/compat.ncl +++ b/stdlib/compat.ncl @@ -2,7 +2,7 @@ compat | doc "Nix compatibility layer. This library should not be used by Nickel program." = { - # the adition in Nix can be a string concataination. + # The addition in Nix can be a string concatenation. # this function handle this behaviour. add = fun a b => if %typeof% a == `Str && %typeof% b == `Str From 3736f10ac6b6368a464e25e3cee970e0cd88f299 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Wed, 9 Nov 2022 10:20:55 +0100 Subject: [PATCH 47/73] update comment on compat.ncl `add` function as proposed by @yannham --- stdlib/compat.ncl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stdlib/compat.ncl b/stdlib/compat.ncl index 4f4017d049..6e443b1298 100644 --- a/stdlib/compat.ncl +++ b/stdlib/compat.ncl @@ -1,9 +1,9 @@ { compat | doc "Nix compatibility layer. This library should not be used by Nickel program." = { - - # The addition in Nix can be a string concatenation. - # this function handle this behaviour. + # Addition in Nix is overloaded to work both as number addition and string concatenation. + # There is no such operator in Nickel. This function implement the equivalent of the Nix primitive operator by dynamically + # dispatching between addition and concatenation, based on the runtime type of its arguments. add = fun a b => if %typeof% a == `Str && %typeof% b == `Str then a ++ b From c1e99dc874f37e8bade268f8f43b66f338cea99f Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Thu, 10 Nov 2022 15:28:06 +0100 Subject: [PATCH 48/73] ireenable simple string concat testing --- tests/nix/basics.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/nix/basics.nix b/tests/nix/basics.nix index a81c66bcc5..b765cc7fcf 100644 --- a/tests/nix/basics.nix +++ b/tests/nix/basics.nix @@ -31,7 +31,7 @@ ([ 1 2 ] ++ [ 1 2 ] == [ 1 2 1 2 ]) # strings concataination - # ("hello" + " " + "world" == "hello world") # TODO: to be fixed + ("hello" + " " + "world" == "hello world") # if then else ((if true then 1 else 2) == 1) From 921007dababa0426119a3f5abe6b816141dbcd48 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Tue, 20 Dec 2022 15:27:31 +0100 Subject: [PATCH 49/73] fix a few warnings and remove a poluting line which came from the last rebase --- src/nix.rs | 2 -- src/stdlib.rs | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/nix.rs b/src/nix.rs index a0ea2c3da1..7b4cbb611f 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -65,7 +65,6 @@ impl ToNickel for rnix::SyntaxNode { use rnix::types::{self, ParsedType, Wrapper}; use rnix::value; use rnix::SyntaxKind::*; - use std::convert::{TryFrom, TryInto}; let pos = self.text_range(); let file_id = state.file_id.clone(); let span = mk_span(file_id, pos.start().into(), pos.end().into()); @@ -168,7 +167,6 @@ impl ToNickel for rnix::SyntaxNode { // are destructured by the pattern. ParsedType::LetIn(n) => { use crate::destruct; - use crate::identifier::Ident; let mut destruct_vec = Vec::new(); let mut fields = HashMap::new(); let mut state = state.clone(); diff --git a/src/stdlib.rs b/src/stdlib.rs index 4b80d80b77..4cbb73df72 100644 --- a/src/stdlib.rs +++ b/src/stdlib.rs @@ -4,7 +4,6 @@ use crate::identifier::Ident; use crate::term::make as mk_term; use crate::term::RichTerm; -<<<<<<< HEAD /// This is an array containing all the Nickel standard library modules. pub fn modules() -> [StdlibModule; 9] { [ @@ -20,7 +19,6 @@ pub fn modules() -> [StdlibModule; 9] { ] } - /// Represents a particular Nickel standard library module. #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] pub enum StdlibModule { @@ -98,7 +96,7 @@ impl From for Ident { StdlibModule::Num => "num", StdlibModule::Function => "function", StdlibModule::Internals => "internals", - StdlibModule::Compat=> "Compat", + StdlibModule::Compat => "Compat", }; Ident::from(name) } From b441062cbd453468d93eb6c55681fa2b03646735 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Mon, 23 Jan 2023 17:31:17 +0100 Subject: [PATCH 50/73] fix checkPhase on nix tests. Add a filter to include `tests/nix/*.nix` files --- flake.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flake.nix b/flake.nix index 64621227c4..17e1602be3 100644 --- a/flake.nix +++ b/flake.nix @@ -162,6 +162,7 @@ mkFilter = regexp: path: _type: builtins.match regexp path != null; lalrpopFilter = mkFilter ".*lalrpop$"; nclFilter = mkFilter ".*ncl$"; + nixFilter = mkFilter ".*/tests/nix/.+\\.nix$"; txtFilter = mkFilter ".*txt$"; snapFilter = mkFilter ".*snap$"; in @@ -174,6 +175,7 @@ builtins.any (filter: filter path type) [ lalrpopFilter nclFilter + nixFilter txtFilter snapFilter filterCargoSources From adec631851e28c58a23bee0406d2e38593777f70 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Mon, 23 Jan 2023 17:55:40 +0100 Subject: [PATCH 51/73] fix a test to pass also with official Nix interpreter. in Nix `0.625` is not equivalant to `5/8` even if it works in nickel, we prefer to stay correct and compare frac with frac. --- tests/nix/basics.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/nix/basics.nix b/tests/nix/basics.nix index b765cc7fcf..a0a17037f6 100644 --- a/tests/nix/basics.nix +++ b/tests/nix/basics.nix @@ -6,7 +6,7 @@ (2 - 3 - 4 == -5) (-1 - 2 == -3) (2 * 2 + 2 * 3 - 2 * 4 == 2) - (1 / 2 + 1 / 4 - 1 / 8 == 0.625) + (1 / 2 + 1 / 4 - 1 / 8 == 5 / 8) #((10 + 1/4) % 3 == 1.25) #(10 + 1/4 % 3 == 10.25) From acb6d136f4cad04292e53c18b75d7b40147cd3e4 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Sun, 22 Jan 2023 19:57:13 +0100 Subject: [PATCH 52/73] Update `rnix` to 0.11. At the same time, it clean and simplify some parts of the code --- Cargo.lock | 43 +++------- Cargo.toml | 3 +- src/nix.rs | 240 +++++++++++++++++++++++++---------------------------- 3 files changed, 126 insertions(+), 160 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6bd737c3b3..1b54f4dab1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -231,15 +231,6 @@ dependencies = [ "rustc_version", ] -[[package]] -name = "cbitset" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29b6ad25ae296159fb0da12b970b2fe179b234584d7cd294c891e2bbb284466b" -dependencies = [ - "num-traits", -] - [[package]] name = "cc" version = "1.0.73" @@ -357,9 +348,9 @@ dependencies = [ [[package]] name = "countme" -version = "2.0.4" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "328b822bdcba4d4e402be8d9adb6eebf269f969f8eadef977a553ff3c4fbcb58" +checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" [[package]] name = "cpp_demangle" @@ -860,15 +851,15 @@ checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "hashbrown" -version = "0.9.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" [[package]] name = "hashbrown" -version = "0.11.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "heck" @@ -1261,6 +1252,7 @@ dependencies = [ "pretty_assertions", "regex", "rnix", + "rowan", "rustyline", "rustyline-derive", "serde", @@ -1916,23 +1908,21 @@ dependencies = [ [[package]] name = "rnix" -version = "0.10.1" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "065c5eac8e8f7e7b0e7227bdc46e2d8dd7d69fff7a9a2734e21f686bbcb9b2c9" +checksum = "bb35cedbeb70e0ccabef2a31bcff0aebd114f19566086300b8f42c725fc2cb5f" dependencies = [ - "cbitset", "rowan", - "smol_str", ] [[package]] name = "rowan" -version = "0.12.6" +version = "0.15.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1b36e449f3702f3b0c821411db1cbdf30fb451726a9456dce5dabcd44420043" +checksum = "5811547e7ba31e903fe48c8ceab10d40d70a101f3d15523c847cce91aa71f332" dependencies = [ "countme", - "hashbrown 0.9.1", + "hashbrown 0.12.3", "memoffset", "rustc-hash", "text-size", @@ -2215,15 +2205,6 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" -[[package]] -name = "smol_str" -version = "0.1.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7475118a28b7e3a2e157ce0131ba8c5526ea96e90ee601d9f6bb2e286a35ab44" -dependencies = [ - "serde", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" diff --git a/Cargo.toml b/Cargo.toml index cf6dd1d021..22a124e931 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,8 @@ pretty = "0.11.3" comrak = { version = "0.12.1", optional = true, features = [] } once_cell = "1.14.0" typed-arena = "2.0.1" -rnix = "0.10.1" +rnix = "0.11" +rowan = "0.15.10" [dev-dependencies] pretty_assertions = "1.2.1" diff --git a/src/nix.rs b/src/nix.rs index 7b4cbb611f..13665db026 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -8,36 +8,45 @@ use crate::term::make::{self, if_then_else}; use crate::term::{record::RecordData, BinaryOp, UnaryOp}; use crate::term::{MergePriority, MetaValue, RichTerm, Term}; use codespan::FileId; -use rnix; -use rnix::types::{BinOp, EntryHolder, TokenWrapper, TypedNode, UnaryOp as UniOp}; +use rnix::ast::{BinOp as NixBinOp, Path as NixPath, Str as NixStr, UnaryOp as NixUniOp}; use std::collections::HashMap; -impl ToNickel for UniOp { +impl ToNickel for NixStr { fn translate(self, state: &State) -> RichTerm { - use rnix::types::UnaryOpKind::*; - let value = self.value().unwrap().translate(state); - match self.operator() { + use rnix::ast::InterpolPart; + Term::StrChunks( + self.parts() + .enumerate() + .map(|(i, c)| match c { + InterpolPart::Literal(s) => crate::term::StrChunk::Literal(s.to_string()), + InterpolPart::Interpolation(interp) => { + crate::term::StrChunk::Expr(interp.expr().unwrap().translate(state), i) + } + }) + .collect(), + ) + .into() + } +} + +impl ToNickel for NixUniOp { + fn translate(self, state: &State) -> RichTerm { + use rnix::ast::UnaryOpKind::*; + let value = self.expr().unwrap().translate(state); + match self.operator().unwrap() { Negate => make::op2(BinaryOp::Sub(), Term::Num(0.), value), Invert => make::op1(UnaryOp::BoolNot(), value), } } } -impl ToNickel for BinOp { +impl ToNickel for NixBinOp { fn translate(self, state: &State) -> RichTerm { - use rnix::types::BinOpKind::*; + use rnix::ast::BinOpKind::*; let lhs = self.lhs().unwrap().translate(state); let rhs = self.rhs().unwrap().translate(state); match self.operator().unwrap() { Concat => make::op2(BinaryOp::ArrayConcat(), lhs, rhs), - IsSet => { - let rhs = if self.rhs().unwrap().kind() == rnix::SyntaxKind::NODE_IDENT { - Term::Str(self.rhs().unwrap().to_string()).into() - } else { - rhs - }; - make::op2(BinaryOp::HasField(), rhs, lhs) - } Update => unimplemented!(), Add => mk_app!(crate::stdlib::compat::add(), lhs, rhs), @@ -60,80 +69,59 @@ impl ToNickel for BinOp { } } -impl ToNickel for rnix::SyntaxNode { +impl ToNickel for rnix::ast::Expr { fn translate(self, state: &State) -> RichTerm { - use rnix::types::{self, ParsedType, Wrapper}; - use rnix::value; - use rnix::SyntaxKind::*; - let pos = self.text_range(); + use rnix::ast::{self, Expr}; + use rowan::ast::AstNode; + let pos = self.syntax().text_range(); let file_id = state.file_id.clone(); let span = mk_span(file_id, pos.start().into(), pos.end().into()); println!("{:?}: {}", self, self); - let node: ParsedType = self.try_into().unwrap(); - match node { - ParsedType::Error(_) => { + match self { + Expr::Error(_) => { Term::ParseError(crate::error::ParseError::NixParseError(file_id)).into() + // TODO: + // Improve + // error + // management } - ParsedType::Root(n) => n.inner().unwrap().translate(state), - ParsedType::Paren(n) => n.inner().unwrap().translate(state), - ParsedType::Dynamic(n) => n.inner().unwrap().translate(state), + Expr::Root(n) => n.expr().unwrap().translate(state), + Expr::Paren(n) => n.expr().unwrap().translate(state), - ParsedType::Assert(n) => unimplemented!(), + Expr::Assert(n) => unimplemented!(), - ParsedType::Value(n) => match n.to_value().unwrap() { - value::Value::Float(v) => Term::Num(v), - value::Value::Integer(v) => Term::Num(v as f64), - value::Value::String(v) => Term::Str(v), + Expr::Literal(n) => match n.kind() { + rnix::ast::LiteralKind::Float(v) => Term::Num(v.value().unwrap()), + rnix::ast::LiteralKind::Integer(v) => Term::Num(v.value().unwrap() as f64), // TODO: How to manage Paths in nickel? - value::Value::Path(a, v) => { - use value::Anchor::*; - let strpath = match a { - Absolute => format!("/{}", v), - Relative => format!("./{}", v), - Home => format!("~/{}", v), - Store => format!("<{}>", v), - }; - Term::Str(strpath) - } + rnix::ast::LiteralKind::Uri(v) => Term::Str(v.to_string()), } .into(), - ParsedType::Str(n) => { - let parts = n.parts(); - Term::StrChunks( - parts - .into_iter() - .enumerate() - .map(|(i, c)| match c { - rnix::value::StrPart::Literal(s) => crate::term::StrChunk::Literal(s), - rnix::value::StrPart::Ast(a) => crate::term::StrChunk::Expr( - a.first_child().unwrap().translate(state), - i, - ), - }) - .collect(), - ) - .into() - } - ParsedType::List(n) => Term::Array( + Expr::Str(n) => n.translate(state), + Expr::List(n) => Term::Array( n.items().map(|elm| elm.translate(state)).collect(), Default::default(), ) .into(), - ParsedType::AttrSet(n) => { + Expr::AttrSet(n) => { use crate::parser::utils::{build_record, elaborate_field_path, FieldPathElem}; + use rnix::ast::HasEntry; let fields: Vec<(_, _)> = n - .entries() + .attrpath_values() .map(|kv| { let val = kv.value().unwrap().translate(state); let p: Vec<_> = kv - .key() + .attrpath() .unwrap() - .path() - .map(|p| match p.kind() { - NODE_IDENT => FieldPathElem::Ident( - rnix::types::Ident::cast(p).unwrap().as_str().into(), - ), - _ => FieldPathElem::Expr(p.translate(state)), + .attrs() + .map(|p| match p { + rnix::ast::Attr::Ident(id) => { + FieldPathElem::Ident(id.to_string().into()) + } + rnix::ast::Attr::Dynamic(d) => { + FieldPathElem::Expr(d.expr().unwrap().translate(state)) + } + rnix::ast::Attr::Str(s) => FieldPathElem::Expr(s.translate(state)), }) .collect(); elaborate_field_path(p, val) @@ -145,7 +133,7 @@ impl ToNickel for rnix::SyntaxNode { // In nix it's allowed to define vars named `true`, `false` or `null`. // But we prefer to not support it. If we try to redefine one of these builtins, nickel // will panic (see below in the `LetIn` arm). - ParsedType::Ident(n) => match n.as_str() { + Expr::Ident(id) => match id.to_string().as_str() { "true" => Term::Bool(true), "false" => Term::Bool(false), "null" => Term::Null, @@ -161,31 +149,30 @@ impl ToNickel for rnix::SyntaxNode { } } .into(), - ParsedType::LegacyLet(_) => panic!("Legacy let form is not supported"), // Probably useless to suport it in a short term. + Expr::LegacyLet(_) => panic!("Legacy let form is not supported"), // Probably useless to suport it in a short term. // `let ... in` blocks are recursive in Nix and not in Nickel. To emulate this, we use // a `let = in`. The record provide recursivity then the values // are destructured by the pattern. - ParsedType::LetIn(n) => { + Expr::LetIn(n) => { use crate::destruct; + use rnix::ast::HasEntry; let mut destruct_vec = Vec::new(); let mut fields = HashMap::new(); let mut state = state.clone(); - state.env.extend(n.entries().map(|kv| { - types::Ident::cast(kv.key().unwrap().path().next().unwrap()) - .unwrap() - .as_str() - .to_string() + state.env.extend(n.attrpath_values().map(|kv| { + kv.attrpath().unwrap().attrs().next().unwrap().to_string() // TODO: does not work if the let contains Dynamic or Str. Is + // it possible in Nix? })); - for kv in n.entries() { + for kv in n.attrpath_values() { // In `let` blocks, the key is suposed to be a single ident so `Path` exactly one // element. - let id = types::Ident::cast(kv.key().unwrap().path().next().unwrap()).unwrap(); + let id = kv.attrpath().unwrap().attrs().next().unwrap(); // Check we don't try to redefine builtin values. Even if it's possible in Nix, // we don't suport it. - let id: Ident = match id.as_str() { + let id: Ident = match id.to_string().as_str() { "true" | "false" | "null" => panic!( "`let {}` is forbiden. Can not redifine `true`, `false` or `nul`", - id.as_str() + id.to_string() ), s => s.into(), }; @@ -205,25 +192,26 @@ impl ToNickel for rnix::SyntaxNode { n.body().unwrap().translate(&state), ) } - ParsedType::With(n) => { + Expr::With(n) => { let mut state = state.clone(); state.with.push(n.namespace().unwrap().translate(&state)); n.body().unwrap().translate(&state) } - ParsedType::Lambda(n) => { - let arg = n.arg().unwrap(); - match arg.kind() { - NODE_IDENT => { - Term::Fun(arg.to_string().into(), n.body().unwrap().translate(state)) - } - NODE_PATTERN => { + Expr::Lambda(n) => { + match n.param().unwrap() { + rnix::ast::Param::IdentParam(idp) => Term::Fun( + idp.ident().unwrap().to_string().into(), + n.body().unwrap().translate(state), + ), + rnix::ast::Param::Pattern(pat) => { use crate::destruct::*; - let pat = rnix::types::Pattern::cast(arg).unwrap(); - let at = pat.at().map(|id| id.as_str().into()); + let at = pat + .pat_bind() + .map(|id| id.ident().unwrap().to_string().into()); // TODO: manage default values: let matches = pat - .entries() + .pat_entries() .map(|e| { let mv = if let Some(def) = e.default() { MetaValue { @@ -234,12 +222,12 @@ impl ToNickel for rnix::SyntaxNode { } else { Default::default() }; - Match::Simple(e.name().unwrap().as_str().into(), mv) + Match::Simple(e.ident().unwrap().to_string().into(), mv) }) .collect(); let dest = Destruct::Record { matches, - open: pat.ellipsis(), + open: pat.ellipsis_token().is_some(), rest: None, span, }; @@ -250,46 +238,41 @@ impl ToNickel for rnix::SyntaxNode { } .into(), - ParsedType::Apply(n) => Term::App( + Expr::Apply(n) => Term::App( n.lambda().unwrap().translate(state), - n.value().unwrap().translate(state), + n.argument().unwrap().translate(state), ) .into(), - ParsedType::IfElse(n) => if_then_else( + Expr::IfElse(n) => if_then_else( n.condition().unwrap().translate(state), n.body().unwrap().translate(state), n.else_body().unwrap().translate(state), ), - ParsedType::BinOp(n) => n.translate(state), - ParsedType::UnaryOp(n) => n.translate(state), - ParsedType::Select(n) => match ParsedType::try_from(n.index().unwrap()).unwrap() { - ParsedType::Ident(id) => Term::Op1( - UnaryOp::StaticAccess(id.as_str().into()), - n.set().unwrap().translate(state), - ) - .into(), - _ => Term::Op2( - BinaryOp::DynAccess(), - n.index().unwrap().translate(state), - n.set().unwrap().translate(state), - ) - .into(), - }, - ParsedType::OrDefault(n) => unimplemented!(), - - // TODO: what are these? - ParsedType::PatBind(_) | ParsedType::PathWithInterpol(_) => { - unimplemented!() + Expr::BinOp(n) => n.translate(state), + Expr::UnaryOp(n) => n.translate(state), + Expr::Select(n) => { + n.attrpath() + .unwrap() + .attrs() + .fold(n.expr().unwrap().translate(state), |acc, i| { + match i { + rnix::ast::Attr::Ident(id) => { + Term::Op1(UnaryOp::StaticAccess(id.to_string().into()), acc) + } + rnix::ast::Attr::Dynamic(d) => Term::Op2( + BinaryOp::DynAccess(), + d.expr().unwrap().translate(state), + acc, + ), + rnix::ast::Attr::Str(s) => { + Term::Op2(BinaryOp::DynAccess(), s.translate(state), acc) + } + } + .into() + }) } - - // The following nodes aren't `RichTerm`s but sub-parts of other nodes higher in the syntax tree. - // They can't be parsed independently of a term and should never occur at this level of the translation. - ParsedType::Pattern(_) - | ParsedType::PatEntry(_) - | ParsedType::Inherit(..) - | ParsedType::InheritFrom(..) - | ParsedType::Key(..) - | ParsedType::KeyValue(..) => unreachable!(), + Expr::Path(_) | Expr::HasAttr(_) => unimplemented!(), // TODO: What is the diff with + // Select / operator HasField? } .with_pos(crate::position::TermPos::Original(span)) } @@ -297,6 +280,7 @@ impl ToNickel for rnix::SyntaxNode { pub fn parse(cache: &Cache, file_id: FileId) -> Result { let source = cache.files().source(file_id); - let nixast = rnix::parse(source).as_result()?; - Ok(nixast.node().to_nickel(file_id)) + let root = rnix::Root::parse(source).ok()?; // TODO: we could return a list of errors calling + // `errors()` could improve error management. + Ok(root.expr().unwrap().to_nickel(file_id)) } From d3cf2bf991551602b90c96cd4a3ef96361bbb87c Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Mon, 23 Jan 2023 15:42:51 +0100 Subject: [PATCH 53/73] Update src/nix.rs Co-authored-by: Yann Hamdaoui --- src/nix.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nix.rs b/src/nix.rs index 13665db026..ca1e279c25 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -281,6 +281,6 @@ impl ToNickel for rnix::ast::Expr { pub fn parse(cache: &Cache, file_id: FileId) -> Result { let source = cache.files().source(file_id); let root = rnix::Root::parse(source).ok()?; // TODO: we could return a list of errors calling - // `errors()` could improve error management. + // `errors()` to improve error management. Ok(root.expr().unwrap().to_nickel(file_id)) } From 5823f86f31d59e4ff0c95f869383f7766e447ae5 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Mon, 23 Jan 2023 15:44:18 +0100 Subject: [PATCH 54/73] Update src/nix.rs Co-authored-by: Yann Hamdaoui --- src/nix.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/nix.rs b/src/nix.rs index ca1e279c25..97035d7c74 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -80,10 +80,7 @@ impl ToNickel for rnix::ast::Expr { match self { Expr::Error(_) => { Term::ParseError(crate::error::ParseError::NixParseError(file_id)).into() - // TODO: - // Improve - // error - // management + // TODO: Improve error management } Expr::Root(n) => n.expr().unwrap().translate(state), Expr::Paren(n) => n.expr().unwrap().translate(state), From 3dd06955275d211214f761784348cfebb084e166 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Mon, 23 Jan 2023 19:59:42 +0100 Subject: [PATCH 55/73] clippy pass --- src/bin/nickel.rs | 2 +- src/cache.rs | 2 +- src/nix.rs | 15 +++++++-------- src/stdlib.rs | 1 - tests/nix.rs | 5 ++--- 5 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/bin/nickel.rs b/src/bin/nickel.rs index fbf8dff57d..1aa7cf70c2 100644 --- a/src/bin/nickel.rs +++ b/src/bin/nickel.rs @@ -125,7 +125,7 @@ fn main() { opts.file .map(std::fs::File::open) .map(|f| f.and_then(|mut f| f.read_to_string(&mut buf))) - .unwrap_or(std::io::stdin().read_to_string(&mut buf)) + .unwrap_or_else(|| std::io::stdin().read_to_string(&mut buf)) .unwrap_or_else(|err| { eprintln!("Error when reading input: {}", err); process::exit(1) diff --git a/src/cache.rs b/src/cache.rs index d2eeb44329..b96f19475b 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -460,7 +460,7 @@ impl Cache { Ok((t, parse_errs)) } InputFormat::Nix => Ok(( - crate::nix::parse(&self, file_id).unwrap(), + crate::nix::parse(self, file_id).unwrap(), ParseErrors::default(), )), InputFormat::Json => serde_json::from_str(self.files.source(file_id)) diff --git a/src/nix.rs b/src/nix.rs index 97035d7c74..fca5e9c4d1 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -8,7 +8,7 @@ use crate::term::make::{self, if_then_else}; use crate::term::{record::RecordData, BinaryOp, UnaryOp}; use crate::term::{MergePriority, MetaValue, RichTerm, Term}; use codespan::FileId; -use rnix::ast::{BinOp as NixBinOp, Path as NixPath, Str as NixStr, UnaryOp as NixUniOp}; +use rnix::ast::{BinOp as NixBinOp, Str as NixStr, UnaryOp as NixUniOp}; use std::collections::HashMap; impl ToNickel for NixStr { @@ -71,10 +71,10 @@ impl ToNickel for NixBinOp { impl ToNickel for rnix::ast::Expr { fn translate(self, state: &State) -> RichTerm { - use rnix::ast::{self, Expr}; + use rnix::ast::Expr; use rowan::ast::AstNode; let pos = self.syntax().text_range(); - let file_id = state.file_id.clone(); + let file_id = state.file_id; let span = mk_span(file_id, pos.start().into(), pos.end().into()); println!("{:?}: {}", self, self); match self { @@ -85,7 +85,7 @@ impl ToNickel for rnix::ast::Expr { Expr::Root(n) => n.expr().unwrap().translate(state), Expr::Paren(n) => n.expr().unwrap().translate(state), - Expr::Assert(n) => unimplemented!(), + Expr::Assert(_) => unimplemented!(), Expr::Literal(n) => match n.kind() { rnix::ast::LiteralKind::Float(v) => Term::Num(v.value().unwrap()), @@ -169,13 +169,13 @@ impl ToNickel for rnix::ast::Expr { let id: Ident = match id.to_string().as_str() { "true" | "false" | "null" => panic!( "`let {}` is forbiden. Can not redifine `true`, `false` or `nul`", - id.to_string() + id ), s => s.into(), }; let rt = kv.value().unwrap().translate(&state); - destruct_vec.push(destruct::Match::Simple(id.clone(), Default::default())); - fields.insert(id.into(), rt); + destruct_vec.push(destruct::Match::Simple(id, Default::default())); + fields.insert(id, rt); } make::let_pat::( None, @@ -230,7 +230,6 @@ impl ToNickel for rnix::ast::Expr { }; Term::FunPattern(at, dest, n.body().unwrap().translate(state)) } - _ => unreachable!(), } } .into(), diff --git a/src/stdlib.rs b/src/stdlib.rs index 4cbb73df72..e46f32d04c 100644 --- a/src/stdlib.rs +++ b/src/stdlib.rs @@ -146,7 +146,6 @@ pub mod internals { /// Contains functions helper for Nix evaluation by Nickel. pub mod compat { use super::*; - use crate::identifier::*; use crate::mk_app; use crate::term::make::op1; use crate::term::{array::Array, Term, UnaryOp}; diff --git a/tests/nix.rs b/tests/nix.rs index 73dafd0cad..c154ff13d4 100644 --- a/tests/nix.rs +++ b/tests/nix.rs @@ -2,13 +2,12 @@ use nickel_lang::term::Term; use nickel_lang_utilities::eval; fn run(path: &str) { - let res = eval(format!( + eval(format!( "let t = import \"{}/tests/nix/{}\" in array.fold (fun x acc => acc && x) true t", env!("CARGO_MANIFEST_DIR"), path )) - .map(|rt| { - let term = Term::from(rt); + .map(|term| { assert_eq!(term, Term::Bool(true), "error in test {}", path,); }) .unwrap(); From 3a555ae8c48eca86cb3e1bbae44d7f17d5f24882 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Sat, 2 Jul 2022 18:07:33 +0200 Subject: [PATCH 56/73] Add a trait `ToNickel` - impl this trait for `rnix` operators and `SyntaxNode` - others code cleaning --- src/convertion.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/convertion.rs diff --git a/src/convertion.rs b/src/convertion.rs new file mode 100644 index 0000000000..68ef7312de --- /dev/null +++ b/src/convertion.rs @@ -0,0 +1,13 @@ +//! This module contains a trait to implement on every thing you want to be convertible into nickel +//! AST. +//! It should be used mostly for AST to AST convertion (e.g.: nix to nickel). Effectively the +//! `translate` function require to pass a `codespan::FileId` so it's not wors to use it for +//! convertions which not imply a file/stream input. For these convertions, prefer `Into`/`From` +//! standart traits. + +use crate::term::RichTerm; +use codespan::FileId; + +pub trait ToNickel { + fn translate(self, file_id: FileId) -> RichTerm; +} From a338e9d6d633895df33d27d25a45fbc61c8ee7cf Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Tue, 19 Jul 2022 11:03:55 +0200 Subject: [PATCH 57/73] pass `State` around convertion. --- src/convertion.rs | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 src/convertion.rs diff --git a/src/convertion.rs b/src/convertion.rs deleted file mode 100644 index 68ef7312de..0000000000 --- a/src/convertion.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! This module contains a trait to implement on every thing you want to be convertible into nickel -//! AST. -//! It should be used mostly for AST to AST convertion (e.g.: nix to nickel). Effectively the -//! `translate` function require to pass a `codespan::FileId` so it's not wors to use it for -//! convertions which not imply a file/stream input. For these convertions, prefer `Into`/`From` -//! standart traits. - -use crate::term::RichTerm; -use codespan::FileId; - -pub trait ToNickel { - fn translate(self, file_id: FileId) -> RichTerm; -} From af9e0ea4d128b456918319a252d6f0732aa4d954 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Tue, 8 Nov 2022 12:03:56 +0100 Subject: [PATCH 58/73] add has_field operator compatibility now able to evaluate `{...} ? ` --- src/nix.rs | 36 ++++++++++++++++++++++++++++++++---- src/stdlib.rs | 8 ++++++++ stdlib/compat.ncl | 7 +++++++ 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/src/nix.rs b/src/nix.rs index fca5e9c4d1..60dc3bca94 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -3,13 +3,37 @@ use crate::conversion::State; pub use crate::conversion::ToNickel; use crate::identifier::Ident; use crate::mk_app; -use crate::parser::utils::mk_span; +use crate::parser::utils::{mk_span, FieldPathElem}; use crate::term::make::{self, if_then_else}; use crate::term::{record::RecordData, BinaryOp, UnaryOp}; use crate::term::{MergePriority, MetaValue, RichTerm, Term}; use codespan::FileId; -use rnix::ast::{BinOp as NixBinOp, Str as NixStr, UnaryOp as NixUniOp}; +use rnix::ast::{BinOp as NixBinOp, Str as NixStr, UnaryOp as NixUniOp, Attr as NixAttr}; use std::collections::HashMap; +use std::iter::repeat_with; + +fn path_elem_from_nix(attr: NixAttr, state: &State) -> FieldPathElem { + match attr { + NixAttr::Ident(id) => FieldPathElem::Ident(id.to_string().into()), + NixAttr::Str(s) => FieldPathElem::Expr(s.translate(state)), + NixAttr::Dynamic(d) => FieldPathElem::Expr(d.expr().unwrap().translate(state)), + } +} + +fn path_elem_rt(attr: NixAttr, state: &State) -> RichTerm { + match attr { + NixAttr::Ident(id) => Term::Str(id.to_string()).into(), + NixAttr::Str(s) => s.translate(state), + NixAttr::Dynamic(d) => d.expr().unwrap().translate(state), + } +} + +fn path_rts_from_nix(n: rnix::ast::Attrpath, state: &State) -> T +where +T: FromIterator +{ + n.attrs().map(|a| path_elem_rt(a, state)).collect() +} impl ToNickel for NixStr { fn translate(self, state: &State) -> RichTerm { @@ -267,8 +291,12 @@ impl ToNickel for rnix::ast::Expr { .into() }) } - Expr::Path(_) | Expr::HasAttr(_) => unimplemented!(), // TODO: What is the diff with - // Select / operator HasField? + Expr::HasAttr(n) => { + let path = path_rts_from_nix(n.attrpath().unwrap(), state); + let path = Term::Array(path, Default::default()); + mk_app!(crate::stdlib::compat::has_field_path(), path, n.expr().unwrap().translate(state)) + } + Expr::Path(_) => unimplemented!(), // TODO: What is the diff with select? } .with_pos(crate::position::TermPos::Original(span)) } diff --git a/src/stdlib.rs b/src/stdlib.rs index e46f32d04c..d6f896c90c 100644 --- a/src/stdlib.rs +++ b/src/stdlib.rs @@ -150,6 +150,14 @@ pub mod compat { use crate::term::make::op1; use crate::term::{array::Array, Term, UnaryOp}; + /// helper function to check if a record has a nested field. + pub fn has_field_path() -> RichTerm { + op1( + UnaryOp::StaticAccess("has_field_path".into()), + Term::Var("compat".into()), + ) + } + /// Generate the `with` compatibility Nickel function which may be applied to an `Ident` /// you have to pass a list of with records in ordered from outer-most to inner-most one. pub fn with(array: Array) -> RichTerm { diff --git a/stdlib/compat.ncl b/stdlib/compat.ncl index 6e443b1298..05f08f110e 100644 --- a/stdlib/compat.ncl +++ b/stdlib/compat.ncl @@ -9,6 +9,13 @@ compat | doc "Nix compatibility layer. This library should not be used by Nickel then a ++ b else a + b, + has_field_path = fun fields record => + %length% fields == 0 || # Because it's only used by generated code, this length will never be initialy 0. So if it's 0, it mean the end of the path. + ( + %has_field% (%head% fields) record && + has_field_path (%tail% fields) record."%{%head% fields}" + ), + with = let AssertFound = fun l val => if val.found then val.value From 8c05372b32853d7b1343f34c96bb71eb4012fc99 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Fri, 20 Jan 2023 18:12:15 +0100 Subject: [PATCH 59/73] add tests on records syntax --- tests/nix.rs | 5 +++++ tests/nix/records.nix | 12 ++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 tests/nix/records.nix diff --git a/tests/nix.rs b/tests/nix.rs index c154ff13d4..ed0c25160b 100644 --- a/tests/nix.rs +++ b/tests/nix.rs @@ -17,3 +17,8 @@ fn run(path: &str) { fn basics_nix() { run("basics.nix"); } + +#[test] +fn records_nix() { + run("records.nix"); +} diff --git a/tests/nix/records.nix b/tests/nix/records.nix new file mode 100644 index 0000000000..ec597a7e06 --- /dev/null +++ b/tests/nix/records.nix @@ -0,0 +1,12 @@ +# has_field operator (`?`) +[ + ({ a = 1; } ? a == true) + ({ a = 1; } ? "a" == true) + ({ a = 1; } ? b == false) + ({ a = 1; } ? "b" == false) + ({ a.foo = 1; } ? a.foo == true) + ({ a.foo = 1; } ? a."foo" == true) + ({ a.foo = 1; } ? "a.foo" == false) + ({ a.foo = 1; } ? "a".foo == true) + ({ a.foo = 1; } ? a == true) +] From f46365b193dc76393f308053b7775cca2a1a9d22 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Tue, 24 Jan 2023 11:17:47 +0100 Subject: [PATCH 60/73] fix evaluation of fieldpaths. Identifiers require a position. So, we added it --- src/nix.rs | 48 ++++++++++++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/src/nix.rs b/src/nix.rs index 60dc3bca94..4a1de4f367 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -8,17 +8,20 @@ use crate::term::make::{self, if_then_else}; use crate::term::{record::RecordData, BinaryOp, UnaryOp}; use crate::term::{MergePriority, MetaValue, RichTerm, Term}; use codespan::FileId; -use rnix::ast::{BinOp as NixBinOp, Str as NixStr, UnaryOp as NixUniOp, Attr as NixAttr}; +use rnix::ast::{ + Attr as NixAttr, BinOp as NixBinOp, Ident as NixIdent, Str as NixStr, UnaryOp as NixUniOp, +}; +use rowan::ast::AstNode; use std::collections::HashMap; -use std::iter::repeat_with; -fn path_elem_from_nix(attr: NixAttr, state: &State) -> FieldPathElem { - match attr { - NixAttr::Ident(id) => FieldPathElem::Ident(id.to_string().into()), - NixAttr::Str(s) => FieldPathElem::Expr(s.translate(state)), - NixAttr::Dynamic(d) => FieldPathElem::Expr(d.expr().unwrap().translate(state)), - } -} +// kept for future use. +//fn path_elem_from_nix(attr: NixAttr, state: &State) -> FieldPathElem { +// match attr { +// NixAttr::Ident(id) => FieldPathElem::Ident(id.to_string().into()), +// NixAttr::Str(s) => FieldPathElem::Expr(s.translate(state)), +// NixAttr::Dynamic(d) => FieldPathElem::Expr(d.expr().unwrap().translate(state)), +// } +//} fn path_elem_rt(attr: NixAttr, state: &State) -> RichTerm { match attr { @@ -30,11 +33,17 @@ fn path_elem_rt(attr: NixAttr, state: &State) -> RichTerm { fn path_rts_from_nix(n: rnix::ast::Attrpath, state: &State) -> T where -T: FromIterator + T: FromIterator, { n.attrs().map(|a| path_elem_rt(a, state)).collect() } +fn id_from_nix(id: NixIdent, state: &State) -> Ident { + let pos = id.syntax().text_range(); + let span = mk_span(state.file_id, pos.start().into(), pos.end().into()); + Ident::new_with_pos(id.to_string(), crate::position::TermPos::Original(span)) +} + impl ToNickel for NixStr { fn translate(self, state: &State) -> RichTerm { use rnix::ast::InterpolPart; @@ -96,7 +105,6 @@ impl ToNickel for NixBinOp { impl ToNickel for rnix::ast::Expr { fn translate(self, state: &State) -> RichTerm { use rnix::ast::Expr; - use rowan::ast::AstNode; let pos = self.syntax().text_range(); let file_id = state.file_id; let span = mk_span(file_id, pos.start().into(), pos.end().into()); @@ -125,7 +133,7 @@ impl ToNickel for rnix::ast::Expr { ) .into(), Expr::AttrSet(n) => { - use crate::parser::utils::{build_record, elaborate_field_path, FieldPathElem}; + use crate::parser::utils::{build_record, elaborate_field_path}; use rnix::ast::HasEntry; let fields: Vec<(_, _)> = n .attrpath_values() @@ -137,7 +145,7 @@ impl ToNickel for rnix::ast::Expr { .attrs() .map(|p| match p { rnix::ast::Attr::Ident(id) => { - FieldPathElem::Ident(id.to_string().into()) + FieldPathElem::Ident(id_from_nix(id, state)) } rnix::ast::Attr::Dynamic(d) => { FieldPathElem::Expr(d.expr().unwrap().translate(state)) @@ -158,13 +166,13 @@ impl ToNickel for rnix::ast::Expr { "true" => Term::Bool(true), "false" => Term::Bool(false), "null" => Term::Null, - id => { - if state.env.contains(id) || state.with.is_empty() { - Term::Var(id.into()) + id_str => { + if state.env.contains(id_str) || state.with.is_empty() { + Term::Var(id_from_nix(id, state)) } else { Term::App( crate::stdlib::compat::with(state.with.clone().into_iter().collect()), - Term::Str(id.into()).into(), + Term::Str(id.to_string()).into(), ) } } @@ -294,7 +302,11 @@ impl ToNickel for rnix::ast::Expr { Expr::HasAttr(n) => { let path = path_rts_from_nix(n.attrpath().unwrap(), state); let path = Term::Array(path, Default::default()); - mk_app!(crate::stdlib::compat::has_field_path(), path, n.expr().unwrap().translate(state)) + mk_app!( + crate::stdlib::compat::has_field_path(), + path, + n.expr().unwrap().translate(state) + ) } Expr::Path(_) => unimplemented!(), // TODO: What is the diff with select? } From dda02d828b0250047bcc0c62a05b4bd39b380be9 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Mon, 23 Jan 2023 15:06:37 +0100 Subject: [PATCH 61/73] Nix evaluator is now an optional compilation feature --- Cargo.toml | 7 +++++-- src/bin/nickel.rs | 47 ++++++++++++++++++++++++++--------------------- src/cache.rs | 34 +++++++++++++++++++++++++++++----- src/lib.rs | 1 + tests/nix.rs | 2 ++ 5 files changed, 63 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 22a124e931..31679c7f0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ markdown = ["termimad"] repl = ["rustyline", "rustyline-derive", "ansi_term"] repl-wasm = ["wasm-bindgen", "js-sys", "serde_repr"] doc = [ "comrak" ] +nix = [ "rnix", "rowan" ] [build-dependencies] lalrpop = "0.19.6" @@ -63,8 +64,10 @@ pretty = "0.11.3" comrak = { version = "0.12.1", optional = true, features = [] } once_cell = "1.14.0" typed-arena = "2.0.1" -rnix = "0.11" -rowan = "0.15.10" + +# needed dependencies to be able to interpret nix expr with nickel +rnix = { version = "0.11", optional = true } +rowan = { version = "0.15.10", optional = true } [dev-dependencies] pretty_assertions = "1.2.1" diff --git a/src/bin/nickel.rs b/src/bin/nickel.rs index 1aa7cf70c2..f577394712 100644 --- a/src/bin/nickel.rs +++ b/src/bin/nickel.rs @@ -114,28 +114,33 @@ fn main() { #[cfg(not(feature = "repl"))] eprintln!("error: this executable was not compiled with REPL support"); } else if let Some(Command::Nixin) = opts.command { - use nickel_lang::cache::Cache; - use nickel_lang::cache::ErrorTolerance; - use nickel_lang::pretty::*; - use pretty::BoxAllocator; + #[cfg(feature = "nix")] + { + use nickel_lang::cache::Cache; + use nickel_lang::cache::ErrorTolerance; + use nickel_lang::pretty::*; + use pretty::BoxAllocator; - let mut buf = String::new(); - let mut cache = Cache::new(ErrorTolerance::Strict); - let mut out: Vec = Vec::new(); - opts.file - .map(std::fs::File::open) - .map(|f| f.and_then(|mut f| f.read_to_string(&mut buf))) - .unwrap_or_else(|| std::io::stdin().read_to_string(&mut buf)) - .unwrap_or_else(|err| { - eprintln!("Error when reading input: {}", err); - process::exit(1) - }); - let allocator = BoxAllocator; - let file_id = cache.add_source(".nix", buf.as_bytes()).unwrap(); - let rt = nickel_lang::nix::parse(&cache, file_id).unwrap(); - let doc: DocBuilder<_, ()> = rt.pretty(&allocator); - doc.render(80, &mut out).unwrap(); - println!("{}", String::from_utf8_lossy(&out).as_ref()); + let mut buf = String::new(); + let mut cache = Cache::new(ErrorTolerance::Strict); + let mut out: Vec = Vec::new(); + opts.file + .map(std::fs::File::open) + .map(|f| f.and_then(|mut f| f.read_to_string(&mut buf))) + .unwrap_or(std::io::stdin().read_to_string(&mut buf)) + .unwrap_or_else(|err| { + eprintln!("Error when reading input: {}", err); + process::exit(1) + }); + let allocator = BoxAllocator; + let file_id = cache.add_source(".nix", buf.as_bytes()).unwrap(); + let rt = nickel_lang::nix::parse(&cache, file_id).unwrap(); + let doc: DocBuilder<_, ()> = rt.pretty(&allocator); + doc.render(80, &mut out).unwrap(); + println!("{}", String::from_utf8_lossy(&out).as_ref()); + } + #[cfg(not(feature = "nix"))] + eprintln!("error: this executable was not compiled with Nix evaluation support"); } else { let mut program = opts .file diff --git a/src/cache.rs b/src/cache.rs index b96f19475b..f02e7e937c 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -38,7 +38,19 @@ impl InputFormat { fn from_path_buf(path_buf: &Path) -> Option { match path_buf.extension().and_then(OsStr::to_str) { Some("ncl") => Some(InputFormat::Nickel), - Some("nix") => Some(InputFormat::Nix), + Some("nix") => { + #[cfg(feature = "nix")] + { + Some(InputFormat::Nix) + } + #[cfg(not(feature = "nix"))] + { + eprintln!( + "error: this executable was not compiled with Nix evaluation support" + ); + None + } + } Some("json") => Some(InputFormat::Json), Some("yaml") | Some("yml") => Some(InputFormat::Yaml), Some("toml") => Some(InputFormat::Toml), @@ -459,10 +471,22 @@ impl Cache { Ok((t, parse_errs)) } - InputFormat::Nix => Ok(( - crate::nix::parse(self, file_id).unwrap(), - ParseErrors::default(), - )), + // TODO: Error management for parse errors. + // May be better to throw an error instead of panicing if nickel has been compiled + // without Nix support + InputFormat::Nix => { + #[cfg(feature = "nix")] + { + Ok(( + crate::nix::parse(&self, file_id).unwrap(), + ParseErrors::default(), + )) + } + #[cfg(not(feature = "nix"))] + { + panic!("error: this executable was not compiled with Nix evaluation support") + } + } InputFormat::Json => serde_json::from_str(self.files.source(file_id)) .map(|t| (t, ParseErrors::default())) .map_err(|err| ParseError::from_serde_json(err, file_id, &self.files)), diff --git a/src/lib.rs b/src/lib.rs index 23aa04ae7d..8701ba07bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ pub mod error; pub mod eval; pub mod identifier; pub mod label; +#[cfg(feature = "nix")] pub mod nix; pub mod parser; pub mod position; diff --git a/tests/nix.rs b/tests/nix.rs index ed0c25160b..1ab743b688 100644 --- a/tests/nix.rs +++ b/tests/nix.rs @@ -1,3 +1,5 @@ +#![cfg(feature = "nix")] + use nickel_lang::term::Term; use nickel_lang_utilities::eval; From 164f24f8a9e1f595e159493850de16d90d4da012 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Tue, 24 Jan 2023 15:22:28 +0100 Subject: [PATCH 62/73] clippy fixes --- src/bin/nickel.rs | 5 +++-- src/cache.rs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/bin/nickel.rs b/src/bin/nickel.rs index f577394712..e5367ff9be 100644 --- a/src/bin/nickel.rs +++ b/src/bin/nickel.rs @@ -7,10 +7,11 @@ use nickel_lang::repl::query_print; use nickel_lang::repl::rustyline_frontend; use nickel_lang::term::{RichTerm, Term}; use nickel_lang::{serialize, serialize::ExportFormat}; +#[cfg(feature = "nix")] +use std::io::Read; use std::path::{Path, PathBuf}; use std::{ fs::{self, File}, - io::Read, process, }; // use std::ffi::OsStr; @@ -127,7 +128,7 @@ fn main() { opts.file .map(std::fs::File::open) .map(|f| f.and_then(|mut f| f.read_to_string(&mut buf))) - .unwrap_or(std::io::stdin().read_to_string(&mut buf)) + .unwrap_or_else(|| std::io::stdin().read_to_string(&mut buf)) .unwrap_or_else(|err| { eprintln!("Error when reading input: {}", err); process::exit(1) diff --git a/src/cache.rs b/src/cache.rs index f02e7e937c..5d2e458f81 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -478,7 +478,7 @@ impl Cache { #[cfg(feature = "nix")] { Ok(( - crate::nix::parse(&self, file_id).unwrap(), + crate::nix::parse(self, file_id).unwrap(), ParseErrors::default(), )) } From fe664c61328249182b14d2536ebfc9ea91c5cb23 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Mon, 23 Jan 2023 10:56:46 +0100 Subject: [PATCH 63/73] first test for `with` keyword --- tests/nix.rs | 5 +++++ tests/nix/with.nix | 27 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 tests/nix/with.nix diff --git a/tests/nix.rs b/tests/nix.rs index 1ab743b688..c16c1307a3 100644 --- a/tests/nix.rs +++ b/tests/nix.rs @@ -24,3 +24,8 @@ fn basics_nix() { fn records_nix() { run("records.nix"); } + +#[test] +fn with_nix() { + run("with.nix"); +} diff --git a/tests/nix/with.nix b/tests/nix/with.nix new file mode 100644 index 0000000000..f6d6fef3aa --- /dev/null +++ b/tests/nix/with.nix @@ -0,0 +1,27 @@ +let + r1 = { a = "a1"; b = "b1"; c = "c1"; }; + r2 = { a = "a2"; b = "b2"; }; +in +[ + # simple with + (with r1; [ a b c ] == [ r1.a r1.b r1.c ]) + + # with does not shadow staticaly defined vars + ( + let + a = 11; + in + with r1; a == 11 + ) + + # with shadow previous with + (with r1; with r2; [ b c ] == [ r2.b r1.c ]) + + # complex shadowing + ( + let + a = 11; + in + with r1; with r2; [ a b c ] == [ 11 r2.b r1.c ] + ) +] From ab8a8d1499c3d3bf7884f59a14c1e500dce09288 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Tue, 24 Jan 2023 16:43:20 +0100 Subject: [PATCH 64/73] more tests for Nix `with` - complex shadowing - nested record combined with a with - with and recursive record which does not pass for now (check todos in `tests/nix/with.nix`) --- tests/nix/with.nix | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/tests/nix/with.nix b/tests/nix/with.nix index f6d6fef3aa..c73dfbed3d 100644 --- a/tests/nix/with.nix +++ b/tests/nix/with.nix @@ -1,6 +1,7 @@ let r1 = { a = "a1"; b = "b1"; c = "c1"; }; r2 = { a = "a2"; b = "b2"; }; + r3 = { a = { a = "raa"; }; }; in [ # simple with @@ -24,4 +25,28 @@ in in with r1; with r2; [ a b c ] == [ 11 r2.b r1.c ] ) + ( + let + a = 11; + in + with r1; let + b = 22; + in + [ a b c ] == [ 11 22 r1.c ] + ) + + # with and nested records + ( + with r3; a == r3.a && (with a; a == r3.a.a) + ) + + # with and rec records + # TODO: this test is disabled because, now, when we are in a rec record, we don't populate the environment with it's fields. See how it's done for `let in` blocks. + # ( + # with r1; rec { + # a = 11; + # v = a + 11; + # s = b + c; + # } == { a = 11; v = 22; s = "b1c1";} + # ) ] From 26346bc9d6486c3db8e001df765c3094e7360e8e Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Tue, 24 Jan 2023 17:10:56 +0100 Subject: [PATCH 65/73] tests for Nix `with` inside a function body. --- tests/nix/with.nix | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/nix/with.nix b/tests/nix/with.nix index c73dfbed3d..ef5e4b7cc4 100644 --- a/tests/nix/with.nix +++ b/tests/nix/with.nix @@ -41,7 +41,7 @@ in ) # with and rec records - # TODO: this test is disabled because, now, when we are in a rec record, we don't populate the environment with it's fields. See how it's done for `let in` blocks. + # TODO: this test is disabled because, now, when we are in a rec record, we don't populate the environment with it's fields. See how it's done for `let in` blocks in `src/nix.rs`. # ( # with r1; rec { # a = 11; @@ -49,4 +49,18 @@ in # s = b + c; # } == { a = 11; v = 22; s = "b1c1";} # ) + + # with inside a function body + # TODO: this test is disabled because, now, when we are in a function body, we don't populate the environment with it's fields. See how it's done for `let in` blocks in `src/nix.rs`. + # ( + # let + # f = a: with r1; [a b]; + # in f 11 == [11 r1.b] + # ) + # Don't forget to manage the patern matched arguments. + # ( + # let + # f = {a, b}: with r1; [a b c]; + # in f r2 == [r2.a r2.b r1.c] + # ) ] From eb88acfae8f9226263551cc301c2563e7c549dcb Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Fri, 27 Jan 2023 15:48:47 +0100 Subject: [PATCH 66/73] Small code improvments, comments and documentation for nix/nickel --- src/conversion.rs | 4 ++ src/error.rs | 2 + src/nix.rs | 123 +++++++++++++++++++++++++++++++++------------- 3 files changed, 96 insertions(+), 33 deletions(-) diff --git a/src/conversion.rs b/src/conversion.rs index 5c04a5d961..26512f1bf1 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -17,6 +17,7 @@ pub struct State { pub file_id: FileId, /// Variables in scope. pub env: HashSet, + /// With scope. List of the records on which has been applied a with in the current scope. pub with: Vec, } @@ -30,5 +31,8 @@ pub trait ToNickel: Sized { }; self.translate(&state) } + + /// Use to convert an expression or a term of the source language to nickel. + /// For a complet exemple, you can see `crate::nix`, the convertion from Nix to Nickel. fn translate(self, state: &State) -> RichTerm; } diff --git a/src/error.rs b/src/error.rs index ee59680444..b49ea269f4 100644 --- a/src/error.rs +++ b/src/error.rs @@ -314,6 +314,7 @@ impl IntoDiagnostics for ParseErrors { /// An error occurring during parsing. #[derive(Debug, PartialEq, Eq, Clone)] pub enum ParseError { + #[cfg(feature = "nix")] /// Temporary Nix error variant. /// TODO: because parsing errors are almost the same between Nix and Nickel, Have a seamless /// convertion from Nix errors to Nickel ones could be a good improvement. @@ -1404,6 +1405,7 @@ impl IntoDiagnostics for ParseError { ) -> Vec> { let diagnostic = match self { // TODO: improve error management for nix parser. + #[cfg(feature = "nix")] ParseError::NixParseError(file_id) => { let end = files.source_span(*file_id).end(); let start = files.source_span(*file_id).start(); diff --git a/src/nix.rs b/src/nix.rs index 4a1de4f367..659fc18967 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -14,14 +14,13 @@ use rnix::ast::{ use rowan::ast::AstNode; use std::collections::HashMap; -// kept for future use. -//fn path_elem_from_nix(attr: NixAttr, state: &State) -> FieldPathElem { -// match attr { -// NixAttr::Ident(id) => FieldPathElem::Ident(id.to_string().into()), -// NixAttr::Str(s) => FieldPathElem::Expr(s.translate(state)), -// NixAttr::Dynamic(d) => FieldPathElem::Expr(d.expr().unwrap().translate(state)), -// } -//} +fn path_elem_from_nix(attr: NixAttr, state: &State) -> FieldPathElem { + match attr { + NixAttr::Ident(id) => FieldPathElem::Ident(id_from_nix(id, state)), + NixAttr::Str(s) => FieldPathElem::Expr(s.translate(state)), + NixAttr::Dynamic(d) => FieldPathElem::Expr(d.expr().unwrap().translate(state)), + } +} fn path_elem_rt(attr: NixAttr, state: &State) -> RichTerm { match attr { @@ -80,8 +79,11 @@ impl ToNickel for NixBinOp { let rhs = self.rhs().unwrap().translate(state); match self.operator().unwrap() { Concat => make::op2(BinaryOp::ArrayConcat(), lhs, rhs), + // TODO: the Nix `//` operator. Update => unimplemented!(), + // Use a compatibility function to be able to merge strings with the same operator used + // for addition. Add => mk_app!(crate::stdlib::compat::add(), lhs, rhs), Sub => make::op2(BinaryOp::Sub(), lhs, rhs), Mul => make::op2(BinaryOp::Mult(), lhs, rhs), @@ -94,8 +96,12 @@ impl ToNickel for NixBinOp { MoreOrEq => make::op2(BinaryOp::GreaterOrEq(), lhs, rhs), NotEqual => make::op1(UnaryOp::BoolNot(), make::op2(BinaryOp::Eq(), lhs, rhs)), + // the Nix `->` operator. + // if the lhs is true, then it return the boolean value of rhs. If lhs is false, the + // implication is alwais true. Implication => if_then_else(lhs, rhs, Term::Bool(true)), + // In Nickel as oposit to Nix, the `&&` and `||` operators are unary operators. And => mk_app!(Term::Op1(UnaryOp::BoolAnd(), lhs), rhs), Or => mk_app!(Term::Op1(UnaryOp::BoolOr(), lhs), rhs), } @@ -110,22 +116,42 @@ impl ToNickel for rnix::ast::Expr { let span = mk_span(file_id, pos.start().into(), pos.end().into()); println!("{:?}: {}", self, self); match self { + // This is a parse error of the nix code. + // it's translated to a Nickel internal error specific for nix code (`NixParseError`) + // May not be the better way to do, but this version of the code does not realy have + // error management for the nix side. Expr::Error(_) => { Term::ParseError(crate::error::ParseError::NixParseError(file_id)).into() // TODO: Improve error management } + // The Root of a file. generaly, this field is not matched because the common way to + // translate is as we do in `parse` function below. Like this, we pass a actual `Expr` + // to this function and not the `Root` wrapper. + // Anyway we prefer to manage it, in case the caller pass a `Expr` casted from + // `rowan::AstNode`. Expr::Root(n) => n.expr().unwrap().translate(state), Expr::Paren(n) => n.expr().unwrap().translate(state), + // TODO: Will we have an `Assert` contract at some point in the stdlib or do we implement it + // in `stdlib::compat`? Expr::Assert(_) => unimplemented!(), + // Some specificity around Nix literals or better said, on how `rnix` parse the + // literals: + // - It differenciate floats and integers. We then convertboth to floats. + // - For some reason, `Uri`s are concidered literals, but `Str` and `Path` are not. Expr::Literal(n) => match n.kind() { rnix::ast::LiteralKind::Float(v) => Term::Num(v.value().unwrap()), rnix::ast::LiteralKind::Integer(v) => Term::Num(v.value().unwrap() as f64), - // TODO: How to manage Paths in nickel? + // TODO: How to manage Uris in nickel? + // What should be the nickel internal representation? + // String could be ok, but what if we give it back to a Nix expr? + // Apologise, not sure of the output of `Uri::to_string` rnix::ast::LiteralKind::Uri(v) => Term::Str(v.to_string()), } .into(), + // That's what we call a multiline string in Nickel. Nix don't have the concept of + // string literal (e.g.: `Term::Str` of Nickel) Expr::Str(n) => n.translate(state), Expr::List(n) => Term::Array( n.items().map(|elm| elm.translate(state)).collect(), @@ -135,6 +161,9 @@ impl ToNickel for rnix::ast::Expr { Expr::AttrSet(n) => { use crate::parser::utils::{build_record, elaborate_field_path}; use rnix::ast::HasEntry; + // TODO: Before that, we have to fill `state.env` when the attrset is recursive. + // As it is now, a with brought value take precedence over the fields of the + // recursive record which is incorrect. let fields: Vec<(_, _)> = n .attrpath_values() .map(|kv| { @@ -143,15 +172,7 @@ impl ToNickel for rnix::ast::Expr { .attrpath() .unwrap() .attrs() - .map(|p| match p { - rnix::ast::Attr::Ident(id) => { - FieldPathElem::Ident(id_from_nix(id, state)) - } - rnix::ast::Attr::Dynamic(d) => { - FieldPathElem::Expr(d.expr().unwrap().translate(state)) - } - rnix::ast::Attr::Str(s) => FieldPathElem::Expr(s.translate(state)), - }) + .map(|e| path_elem_from_nix(e, state)) .collect(); elaborate_field_path(p, val) }) @@ -167,6 +188,9 @@ impl ToNickel for rnix::ast::Expr { "false" => Term::Bool(false), "null" => Term::Null, id_str => { + // Compatibility with the Nix `with` construct. It look if the identifier has + // been staticaly defined and if not, it look for it in the `with` broughts + // identifiers. if state.env.contains(id_str) || state.with.is_empty() { Term::Var(id_from_nix(id, state)) } else { @@ -194,7 +218,8 @@ impl ToNickel for rnix::ast::Expr { })); for kv in n.attrpath_values() { // In `let` blocks, the key is suposed to be a single ident so `Path` exactly one - // element. + // element. TODO: check nix realy don't support attrpath on the lhs in a let + // block. let id = kv.attrpath().unwrap().attrs().next().unwrap(); // Check we don't try to redefine builtin values. Even if it's possible in Nix, // we don't suport it. @@ -203,7 +228,12 @@ impl ToNickel for rnix::ast::Expr { "`let {}` is forbiden. Can not redifine `true`, `false` or `nul`", id ), - s => s.into(), + s => { + let pos = id.syntax().text_range(); + let span = mk_span(state.file_id, pos.start().into(), pos.end().into()); + // give a position to the identifier. + Ident::new_with_pos(s, crate::position::TermPos::Original(span)) + } }; let rt = kv.value().unwrap().translate(&state); destruct_vec.push(destruct::Match::Simple(id, Default::default())); @@ -223,25 +253,38 @@ impl ToNickel for rnix::ast::Expr { } Expr::With(n) => { let mut state = state.clone(); + // we push in a vec the term passed to the with (e.g.: `with t; ...` we push the + // term `t`) we push a term because it does not to have a variable, it can be any + // expretion evaluated to a record. state.with.push(n.namespace().unwrap().translate(&state)); + // In the Nickel AST, a with don't realy exist. It's translated to its body. That's + // only when we will parse a variable access that we will take care of the `with`s. + // See the `Expr::Identifier` of the current `match`. n.body().unwrap().translate(&state) } + // a lambda or a function definition. Expr::Lambda(n) => { match n.param().unwrap() { + // the simple case in which the param of the lambda is an identifier as in + // `f = x: ...` x is an identifier. rnix::ast::Param::IdentParam(idp) => Term::Fun( - idp.ident().unwrap().to_string().into(), + id_from_nix(idp.ident().unwrap(), state), + // TODO: add the `id` to `state.env` n.body().unwrap().translate(state), ), + // the param is a pattern as we generaly see in NixOS modules (`{pkgs, lib, + // ...}:` rnix::ast::Param::Pattern(pat) => { + // TODO: add the matched identifiers to `state.env` use crate::destruct::*; let at = pat .pat_bind() - .map(|id| id.ident().unwrap().to_string().into()); - // TODO: manage default values: + .map(|id| id_from_nix(id.ident().unwrap(), state)); let matches = pat .pat_entries() .map(|e| { + // manage default values: let mv = if let Some(def) = e.default() { MetaValue { value: Some(def.translate(state)), @@ -249,9 +292,11 @@ impl ToNickel for rnix::ast::Expr { ..Default::default() } } else { + // the value does not has default. So we construct an empty + // metavalue without any priority annotation. Default::default() }; - Match::Simple(e.ident().unwrap().to_string().into(), mv) + Match::Simple(id_from_nix(e.ident().unwrap(), state), mv) }) .collect(); let dest = Destruct::Record { @@ -266,6 +311,7 @@ impl ToNickel for rnix::ast::Expr { } .into(), + // function application. Expr::Apply(n) => Term::App( n.lambda().unwrap().translate(state), n.argument().unwrap().translate(state), @@ -278,14 +324,20 @@ impl ToNickel for rnix::ast::Expr { ), Expr::BinOp(n) => n.translate(state), Expr::UnaryOp(n) => n.translate(state), - Expr::Select(n) => { - n.attrpath() - .unwrap() - .attrs() - .fold(n.expr().unwrap().translate(state), |acc, i| { + + // static or dynamic records field access. + Expr::Select(n) => n + .attrpath() + .unwrap() + .attrs() + // a nested access is an iterator on attrs from left to right. + .fold( + n.expr().unwrap().translate(state), // the fold is initialized with the + // record accessed. + |acc, i| { match i { rnix::ast::Attr::Ident(id) => { - Term::Op1(UnaryOp::StaticAccess(id.to_string().into()), acc) + Term::Op1(UnaryOp::StaticAccess(id_from_nix(id, state)), acc) } rnix::ast::Attr::Dynamic(d) => Term::Op2( BinaryOp::DynAccess(), @@ -297,8 +349,10 @@ impl ToNickel for rnix::ast::Expr { } } .into() - }) - } + }, + ), + + // The Nix `?` operator. Expr::HasAttr(n) => { let path = path_rts_from_nix(n.attrpath().unwrap(), state); let path = Term::Array(path, Default::default()); @@ -308,12 +362,15 @@ impl ToNickel for rnix::ast::Expr { n.expr().unwrap().translate(state) ) } - Expr::Path(_) => unimplemented!(), // TODO: What is the diff with select? + Expr::Path(_) => unimplemented!(), } + // set the position in the AST to try to have some sort of debuging support. .with_pos(crate::position::TermPos::Original(span)) } } +/// the main entry of this module. It parse a Nix file pointed by `file_id` into a Nickel +/// AST/Richterm. pub fn parse(cache: &Cache, file_id: FileId) -> Result { let source = cache.files().source(file_id); let root = rnix::Root::parse(source).ok()?; // TODO: we could return a list of errors calling From 58c24df048a6aa664c2d48c30c7b91b4294a3e14 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Fri, 27 Jan 2023 16:34:46 +0100 Subject: [PATCH 67/73] add some tests for let blocks of Nix --- tests/nix.rs | 5 +++++ tests/nix/lets.nix | 6 ++++++ 2 files changed, 11 insertions(+) create mode 100644 tests/nix/lets.nix diff --git a/tests/nix.rs b/tests/nix.rs index c16c1307a3..e14a684f04 100644 --- a/tests/nix.rs +++ b/tests/nix.rs @@ -20,6 +20,11 @@ fn basics_nix() { run("basics.nix"); } +#[test] +fn lets_nix() { + run("lets.nix"); +} + #[test] fn records_nix() { run("records.nix"); diff --git a/tests/nix/lets.nix b/tests/nix/lets.nix new file mode 100644 index 0000000000..a734fd3ae8 --- /dev/null +++ b/tests/nix/lets.nix @@ -0,0 +1,6 @@ +[ + (let a = "a"; in a == "a") + (let a = "a"; b = "b"; in [ a b ] == [ "a" "b" ]) + (let a = c; b = a + c; c = 1; in [ a b c ] == [ 1 2 1 ]) + (let a = 1; in let b = a; in a + b == 2) +] From eb4a9598835a6c2eae9096846780aa6ef0dcc212 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Fri, 27 Jan 2023 17:13:05 +0100 Subject: [PATCH 68/73] add the `or ` form for a field access in Nix --- src/nix.rs | 73 +++++++++++++++++++++++++++++-------------- tests/nix/records.nix | 7 +++++ 2 files changed, 56 insertions(+), 24 deletions(-) diff --git a/src/nix.rs b/src/nix.rs index 659fc18967..7fe9ae3e49 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -326,31 +326,56 @@ impl ToNickel for rnix::ast::Expr { Expr::UnaryOp(n) => n.translate(state), // static or dynamic records field access. - Expr::Select(n) => n - .attrpath() - .unwrap() - .attrs() - // a nested access is an iterator on attrs from left to right. - .fold( - n.expr().unwrap().translate(state), // the fold is initialized with the - // record accessed. - |acc, i| { - match i { - rnix::ast::Attr::Ident(id) => { - Term::Op1(UnaryOp::StaticAccess(id_from_nix(id, state)), acc) + Expr::Select(n) => { + let select = n + .attrpath() + .unwrap() + .attrs() + // a nested access is an iterator on attrs from left to right. + .fold( + n.expr().unwrap().translate(state), // the fold is initialized with the + // record accessed. + |acc, i| { + match i { + rnix::ast::Attr::Ident(id) => { + Term::Op1(UnaryOp::StaticAccess(id_from_nix(id, state)), acc) + } + rnix::ast::Attr::Dynamic(d) => Term::Op2( + BinaryOp::DynAccess(), + d.expr().unwrap().translate(state), + acc, + ), + rnix::ast::Attr::Str(s) => { + Term::Op2(BinaryOp::DynAccess(), s.translate(state), acc) + } } - rnix::ast::Attr::Dynamic(d) => Term::Op2( - BinaryOp::DynAccess(), - d.expr().unwrap().translate(state), - acc, - ), - rnix::ast::Attr::Str(s) => { - Term::Op2(BinaryOp::DynAccess(), s.translate(state), acc) - } - } - .into() - }, - ), + .into() + }, + ); + // if the selection contains a `... or ` suffix + if let Some(def) = n.default_expr() { + let path = path_rts_from_nix(n.attrpath().unwrap(), state); + let path = Term::Array(path, Default::default()); + // we transform it to something like the following pseudo code: + // + // ``` + // if has_field_path + // then . + // else + // ``` + if_then_else( + mk_app!( + crate::stdlib::compat::has_field_path(), + path, + n.expr().unwrap().translate(state) + ), + select, + def.translate(state), + ) + } else { + select + } + } // The Nix `?` operator. Expr::HasAttr(n) => { diff --git a/tests/nix/records.nix b/tests/nix/records.nix index ec597a7e06..ee730f1999 100644 --- a/tests/nix/records.nix +++ b/tests/nix/records.nix @@ -9,4 +9,11 @@ ({ a.foo = 1; } ? "a.foo" == false) ({ a.foo = 1; } ? "a".foo == true) ({ a.foo = 1; } ? a == true) + + # field access or default + ({ a = "a"; }.a or "x" == "a") + ({ a = "a"; }.b or "x" == "x") + ({ a.b = "ab"; }.a or "x" == { b = "ab"; }) + ({ a.b = "ab"; }.a.b or "x" == "ab") + ({ a.b = "ab"; }.a.c or "x" == "x") ] From cf22ab7ef68c39bdf3e7d0d20c76859f0039f425 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Fri, 27 Jan 2023 18:28:17 +0100 Subject: [PATCH 69/73] Implementation of the update operator for nix/nickel --- src/nix.rs | 2 +- src/stdlib.rs | 8 ++++++++ stdlib/compat.ncl | 8 ++++++++ tests/nix/records.nix | 5 +++++ 4 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/nix.rs b/src/nix.rs index 7fe9ae3e49..7dc6a656b1 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -80,7 +80,7 @@ impl ToNickel for NixBinOp { match self.operator().unwrap() { Concat => make::op2(BinaryOp::ArrayConcat(), lhs, rhs), // TODO: the Nix `//` operator. - Update => unimplemented!(), + Update => mk_app!(crate::stdlib::compat::update(), lhs, rhs), // Use a compatibility function to be able to merge strings with the same operator used // for addition. diff --git a/src/stdlib.rs b/src/stdlib.rs index d6f896c90c..04b73f3114 100644 --- a/src/stdlib.rs +++ b/src/stdlib.rs @@ -150,6 +150,14 @@ pub mod compat { use crate::term::make::op1; use crate::term::{array::Array, Term, UnaryOp}; + /// helper function to perform a Nix like update (`//` operator). + pub fn update() -> RichTerm { + op1( + UnaryOp::StaticAccess("update_from_rec".into()), + Term::Var("compat".into()), + ) + } + /// helper function to check if a record has a nested field. pub fn has_field_path() -> RichTerm { op1( diff --git a/stdlib/compat.ncl b/stdlib/compat.ncl index 05f08f110e..db458a6060 100644 --- a/stdlib/compat.ncl +++ b/stdlib/compat.ncl @@ -9,6 +9,14 @@ compat | doc "Nix compatibility layer. This library should not be used by Nickel then a ++ b else a + b, + # The update operator of Nix `//`. It's a "general form" of the `record.update` of Nickel. + # TODO: May be intresting to be addapted and integrated the the actual Nickel stdlib. + update_from_rec = fun r1 r2 => + array.fold + (fun key acc => record.update key r2."%{key}" acc) + r1 + (record.fields r2), + has_field_path = fun fields record => %length% fields == 0 || # Because it's only used by generated code, this length will never be initialy 0. So if it's 0, it mean the end of the path. ( diff --git a/tests/nix/records.nix b/tests/nix/records.nix index ee730f1999..ada318e437 100644 --- a/tests/nix/records.nix +++ b/tests/nix/records.nix @@ -16,4 +16,9 @@ ({ a.b = "ab"; }.a or "x" == { b = "ab"; }) ({ a.b = "ab"; }.a.b or "x" == "ab") ({ a.b = "ab"; }.a.c or "x" == "x") + + # the '//' update operator. + ({ a = 1; b = 2; } // { b = 3; c = 4; } == { a = 1; b = 3; c = 4; }) + (let r = { a = 1; }; in r // { } == r) + (let r = { a = 1; }; in { } // r == r) ] From 690224157e03bb8fa559ad9cac7e0f5697269d17 Mon Sep 17 00:00:00 2001 From: francois-caddet Date: Mon, 30 Jan 2023 16:29:17 +0100 Subject: [PATCH 70/73] function renaming in `stdlib/compat` Nickel lib --- src/stdlib.rs | 2 +- stdlib/compat.ncl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/stdlib.rs b/src/stdlib.rs index 04b73f3114..5b252aacd0 100644 --- a/src/stdlib.rs +++ b/src/stdlib.rs @@ -153,7 +153,7 @@ pub mod compat { /// helper function to perform a Nix like update (`//` operator). pub fn update() -> RichTerm { op1( - UnaryOp::StaticAccess("update_from_rec".into()), + UnaryOp::StaticAccess("update_all".into()), Term::Var("compat".into()), ) } diff --git a/stdlib/compat.ncl b/stdlib/compat.ncl index db458a6060..8ae5228d21 100644 --- a/stdlib/compat.ncl +++ b/stdlib/compat.ncl @@ -11,7 +11,7 @@ compat | doc "Nix compatibility layer. This library should not be used by Nickel # The update operator of Nix `//`. It's a "general form" of the `record.update` of Nickel. # TODO: May be intresting to be addapted and integrated the the actual Nickel stdlib. - update_from_rec = fun r1 r2 => + update_all = fun r1 r2 => array.fold (fun key acc => record.update key r2."%{key}" acc) r1 From ddeff4cd158f5f5b6cc0cc5cd9f127325f82b9f3 Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Wed, 8 Mar 2023 18:36:43 +0100 Subject: [PATCH 71/73] Fix clippy warnings, formatting --- lsp/nls/src/requests/completion.rs | 4 ++-- src/lib.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lsp/nls/src/requests/completion.rs b/lsp/nls/src/requests/completion.rs index bcbe5e33ba..fa0017351a 100644 --- a/lsp/nls/src/requests/completion.rs +++ b/lsp/nls/src/requests/completion.rs @@ -90,7 +90,7 @@ impl IdentWithType { if name.is_ascii() { String::from(name) } else { - format!("\"{}\"", name) + format!("\"{name}\"") } } let doc = || { @@ -814,7 +814,7 @@ mod tests { let actual = get_identifier_path(input); let expected: Option> = expected.map(|path| path.iter().map(|s| String::from(*s)).collect()); - assert_eq!(actual, expected, "test failed: {}", case_name) + assert_eq!(actual, expected, "test failed: {case_name}") } } diff --git a/src/lib.rs b/src/lib.rs index 8701ba07bb..2a07d4cddb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,7 +1,7 @@ pub mod cache; +pub mod conversion; pub mod deserialize; pub mod destructuring; -pub mod conversion; pub mod environment; pub mod error; pub mod eval; From cad83f08835b1844c05581fbe47dd87ef2ad88cf Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Thu, 9 Mar 2023 11:54:36 +0100 Subject: [PATCH 72/73] Fix compilation and clippy warnings; tests still fail --- src/bin/nickel.rs | 2 +- src/error.rs | 8 +++---- src/nix.rs | 53 ++++++++++++++++++++++++++++------------------- tests/nix.rs | 5 ++--- 4 files changed, 39 insertions(+), 29 deletions(-) diff --git a/src/bin/nickel.rs b/src/bin/nickel.rs index e5367ff9be..438bb74db8 100644 --- a/src/bin/nickel.rs +++ b/src/bin/nickel.rs @@ -130,7 +130,7 @@ fn main() { .map(|f| f.and_then(|mut f| f.read_to_string(&mut buf))) .unwrap_or_else(|| std::io::stdin().read_to_string(&mut buf)) .unwrap_or_else(|err| { - eprintln!("Error when reading input: {}", err); + eprintln!("Error when reading input: {err}"); process::exit(1) }); let allocator = BoxAllocator; diff --git a/src/error.rs b/src/error.rs index b49ea269f4..fabda766d2 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1407,17 +1407,17 @@ impl IntoDiagnostics for ParseError { // TODO: improve error management for nix parser. #[cfg(feature = "nix")] ParseError::NixParseError(file_id) => { - let end = files.source_span(*file_id).end(); - let start = files.source_span(*file_id).start(); + let end = files.source_span(file_id).end(); + let start = files.source_span(file_id).start(); Diagnostic::error() .with_message(format!( "error parsing nix file {}", - files.name(*file_id).to_string_lossy() + files.name(file_id).to_string_lossy() )) .with_labels(vec![primary(&RawSpan { start, end, - src_id: *file_id, + src_id: file_id, })]) } ParseError::UnexpectedEOF(file_id, _expected) => { diff --git a/src/nix.rs b/src/nix.rs index 7dc6a656b1..b6d3a2cff8 100644 --- a/src/nix.rs +++ b/src/nix.rs @@ -4,9 +4,13 @@ pub use crate::conversion::ToNickel; use crate::identifier::Ident; use crate::mk_app; use crate::parser::utils::{mk_span, FieldPathElem}; +use crate::position::TermPos; use crate::term::make::{self, if_then_else}; use crate::term::{record::RecordData, BinaryOp, UnaryOp}; -use crate::term::{MergePriority, MetaValue, RichTerm, Term}; +use crate::term::{ + record::{Field, FieldMetadata}, + MergePriority, RichTerm, Term, +}; use codespan::FileId; use rnix::ast::{ Attr as NixAttr, BinOp as NixBinOp, Ident as NixIdent, Str as NixStr, UnaryOp as NixUniOp, @@ -114,7 +118,7 @@ impl ToNickel for rnix::ast::Expr { let pos = self.syntax().text_range(); let file_id = state.file_id; let span = mk_span(file_id, pos.start().into(), pos.end().into()); - println!("{:?}: {}", self, self); + println!("{self:?}: {self}"); match self { // This is a parse error of the nix code. // it's translated to a Nickel internal error specific for nix code (`NixParseError`) @@ -159,7 +163,7 @@ impl ToNickel for rnix::ast::Expr { ) .into(), Expr::AttrSet(n) => { - use crate::parser::utils::{build_record, elaborate_field_path}; + use crate::parser::utils::{build_record, FieldDef}; use rnix::ast::HasEntry; // TODO: Before that, we have to fill `state.env` when the attrset is recursive. // As it is now, a with brought value take precedence over the fields of the @@ -168,13 +172,18 @@ impl ToNickel for rnix::ast::Expr { .attrpath_values() .map(|kv| { let val = kv.value().unwrap().translate(state); - let p: Vec<_> = kv + let path: Vec<_> = kv .attrpath() .unwrap() .attrs() .map(|e| path_elem_from_nix(e, state)) .collect(); - elaborate_field_path(p, val) + let field_def = FieldDef { + path, + field: Field::from(val), + pos: TermPos::None, + }; + field_def.elaborate() }) .collect(); build_record(fields, Default::default()).into() @@ -202,12 +211,12 @@ impl ToNickel for rnix::ast::Expr { } } .into(), - Expr::LegacyLet(_) => panic!("Legacy let form is not supported"), // Probably useless to suport it in a short term. + Expr::LegacyLet(_) => panic!("Legacy let form is not supported"), // Probably useless to support it in a short term. // `let ... in` blocks are recursive in Nix and not in Nickel. To emulate this, we use // a `let = in`. The record provide recursivity then the values // are destructured by the pattern. Expr::LetIn(n) => { - use crate::destruct; + use crate::destructuring; use rnix::ast::HasEntry; let mut destruct_vec = Vec::new(); let mut fields = HashMap::new(); @@ -217,16 +226,15 @@ impl ToNickel for rnix::ast::Expr { // it possible in Nix? })); for kv in n.attrpath_values() { - // In `let` blocks, the key is suposed to be a single ident so `Path` exactly one - // element. TODO: check nix realy don't support attrpath on the lhs in a let + // In `let` blocks, the key is supposed to be a single ident so `Path` exactly one + // element. TODO: check nix really don't support attrpath on the lhs in a let // block. let id = kv.attrpath().unwrap().attrs().next().unwrap(); // Check we don't try to redefine builtin values. Even if it's possible in Nix, // we don't suport it. let id: Ident = match id.to_string().as_str() { "true" | "false" | "null" => panic!( - "`let {}` is forbiden. Can not redifine `true`, `false` or `nul`", - id + "`let {id}` is forbidden. Can not redefine `true`, `false` or `null`" ), s => { let pos = id.syntax().text_range(); @@ -236,18 +244,18 @@ impl ToNickel for rnix::ast::Expr { } }; let rt = kv.value().unwrap().translate(&state); - destruct_vec.push(destruct::Match::Simple(id, Default::default())); + destruct_vec.push(destructuring::Match::Simple(id, Default::default())); fields.insert(id, rt); } make::let_pat::( None, - destruct::Destruct::Record { + destructuring::RecordPattern { matches: destruct_vec, open: false, rest: None, span, }, - Term::RecRecord(RecordData::with_fields(fields), vec![], None), + Term::RecRecord(RecordData::with_field_values(fields), Vec::new(), None), n.body().unwrap().translate(&state), ) } @@ -277,7 +285,7 @@ impl ToNickel for rnix::ast::Expr { // ...}:` rnix::ast::Param::Pattern(pat) => { // TODO: add the matched identifiers to `state.env` - use crate::destruct::*; + use crate::destructuring::*; let at = pat .pat_bind() .map(|id| id_from_nix(id.ident().unwrap(), state)); @@ -285,21 +293,24 @@ impl ToNickel for rnix::ast::Expr { .pat_entries() .map(|e| { // manage default values: - let mv = if let Some(def) = e.default() { - MetaValue { + let field = if let Some(def) = e.default() { + Field { value: Some(def.translate(state)), - priority: MergePriority::Bottom, - ..Default::default() + metadata: FieldMetadata { + priority: MergePriority::Bottom, + ..Default::default() + }, + pending_contracts: Vec::new(), } } else { // the value does not has default. So we construct an empty // metavalue without any priority annotation. Default::default() }; - Match::Simple(id_from_nix(e.ident().unwrap(), state), mv) + Match::Simple(id_from_nix(e.ident().unwrap(), state), field) }) .collect(); - let dest = Destruct::Record { + let dest = RecordPattern { matches, open: pat.ellipsis_token().is_some(), rest: None, diff --git a/tests/nix.rs b/tests/nix.rs index e14a684f04..77f0356160 100644 --- a/tests/nix.rs +++ b/tests/nix.rs @@ -5,12 +5,11 @@ use nickel_lang_utilities::eval; fn run(path: &str) { eval(format!( - "let t = import \"{}/tests/nix/{}\" in array.fold (fun x acc => acc && x) true t", + "let t = import \"{}/tests/nix/{path}\" in array.fold (fun x acc => acc && x) true t", env!("CARGO_MANIFEST_DIR"), - path )) .map(|term| { - assert_eq!(term, Term::Bool(true), "error in test {}", path,); + assert_eq!(term, Term::Bool(true), "error in test {path}"); }) .unwrap(); } From 8a8ef4a291f3f715e39568474b31c1e4738aaa62 Mon Sep 17 00:00:00 2001 From: Yann Hamdaoui Date: Thu, 9 Mar 2023 12:26:56 +0100 Subject: [PATCH 73/73] Update compat.ncl, fix tests after rebase --- stdlib/compat.ncl | 88 +++++++++++++++++++++++++++++------------------ tests/nix.rs | 2 +- 2 files changed, 56 insertions(+), 34 deletions(-) diff --git a/stdlib/compat.ncl b/stdlib/compat.ncl index 8ae5228d21..9baa694ba7 100644 --- a/stdlib/compat.ncl +++ b/stdlib/compat.ncl @@ -1,38 +1,60 @@ { -compat | doc "Nix compatibility layer. This library should not be used by Nickel program." -= { - # Addition in Nix is overloaded to work both as number addition and string concatenation. - # There is no such operator in Nickel. This function implement the equivalent of the Nix primitive operator by dynamically - # dispatching between addition and concatenation, based on the runtime type of its arguments. - add = fun a b => - if %typeof% a == `Str && %typeof% b == `Str - then a ++ b - else a + b, + compat + | doc m%" + Nix compatibility layer. - # The update operator of Nix `//`. It's a "general form" of the `record.update` of Nickel. - # TODO: May be intresting to be addapted and integrated the the actual Nickel stdlib. - update_all = fun r1 r2 => - array.fold - (fun key acc => record.update key r2."%{key}" acc) - r1 - (record.fields r2), + This library is used by program transpiled from Nix code to Nickel. This + library should'nt usually be used directly by Nickel program. The API + isn't stable and no backward-compatibility guarantees exist at this + point + "% + = { + # Addition in Nix is overloaded to work both as number addition and string + # concatenation. There is no such operator in Nickel. This function + # implement the equivalent of the Nix primitive operator by dynamically + # dispatching between addition and concatenation, based on the runtime type + # of its arguments. + add = fun a b => + if %typeof% a == `Str && %typeof% b == `Str + then + a ++ b + else + a + b, - has_field_path = fun fields record => - %length% fields == 0 || # Because it's only used by generated code, this length will never be initialy 0. So if it's 0, it mean the end of the path. - ( - %has_field% (%head% fields) record && - has_field_path (%tail% fields) record."%{%head% fields}" - ), + # The update operator of Nix `//`. It's a "general form" of the + # `record.update` of Nickel. + # + # TODO: May be interesting to be adapted and integrated to the actual Nickel + # stdlib. + update_all = fun r1 r2 => + r2 + |> record.fields + |> array.fold_left (fun acc key => record.update key r2."%{key}" acc) r1, - with = - let AssertFound = fun l val => if val.found - then val.value - else %blame% l in - fun envs field => ( - array.fold (fun current acc => - if !acc.found && record.has_field field current - then { value = current."%{field}", found = true} - else acc - ) {value = null, found = false} envs) | AssertFound -} + has_field_path = fun fields record => + # Because it's only used by generated code, this length will never be + # initially 0. So if it's 0, it mean the end of the path. + let head = %head% fields in + %length% fields == 0 || + ( + %has_field% head record && + has_field_path (%tail% fields) record."%{head}" + ), + + with = + let AssertFound = fun label value => + if value.found then + value.value + else + %blame% label + in + + fun envs field => ( + array.fold_right (fun current acc => + if !acc.found && record.has_field field current + then { value = current."%{field}", found = true} + else acc + ) {value = null, found = false} envs + ) | AssertFound + } } diff --git a/tests/nix.rs b/tests/nix.rs index 77f0356160..39ec9dcf23 100644 --- a/tests/nix.rs +++ b/tests/nix.rs @@ -5,7 +5,7 @@ use nickel_lang_utilities::eval; fn run(path: &str) { eval(format!( - "let t = import \"{}/tests/nix/{path}\" in array.fold (fun x acc => acc && x) true t", + "import \"{}/tests/nix/{path}\" |> array.all function.id", env!("CARGO_MANIFEST_DIR"), )) .map(|term| {