feat: list concat & attrs update
This commit is contained in:
16
Cargo.lock
generated
16
Cargo.lock
generated
@@ -69,7 +69,7 @@ dependencies = [
|
|||||||
"bitflags",
|
"bitflags",
|
||||||
"cexpr",
|
"cexpr",
|
||||||
"clang-sys",
|
"clang-sys",
|
||||||
"itertools",
|
"itertools 0.13.0",
|
||||||
"log",
|
"log",
|
||||||
"prettyplease",
|
"prettyplease",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@@ -481,7 +481,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
|
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -856,6 +856,15 @@ dependencies = [
|
|||||||
"either",
|
"either",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itertools"
|
||||||
|
version = "0.14.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||||
|
dependencies = [
|
||||||
|
"either",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.17"
|
version = "1.0.17"
|
||||||
@@ -1022,6 +1031,7 @@ dependencies = [
|
|||||||
"deno_core",
|
"deno_core",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"hashbrown 0.16.1",
|
"hashbrown 0.16.1",
|
||||||
|
"itertools 0.14.0",
|
||||||
"mimalloc",
|
"mimalloc",
|
||||||
"nix-js-macros",
|
"nix-js-macros",
|
||||||
"regex",
|
"regex",
|
||||||
@@ -1344,7 +1354,7 @@ dependencies = [
|
|||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys 0.9.4",
|
"linux-raw-sys 0.9.4",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.60.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ hashbrown = "0.16"
|
|||||||
derive_more = { version = "2", features = ["full"] }
|
derive_more = { version = "2", features = ["full"] }
|
||||||
thiserror = "2"
|
thiserror = "2"
|
||||||
string-interner = "0.19"
|
string-interner = "0.19"
|
||||||
|
itertools = "0.14"
|
||||||
|
|
||||||
v8 = "142.2"
|
v8 = "142.2"
|
||||||
deno_core = "0.376"
|
deno_core = "0.376"
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use itertools::Itertools as _;
|
||||||
|
|
||||||
use crate::ir::*;
|
use crate::ir::*;
|
||||||
|
|
||||||
pub trait Compile<Ctx: CodegenContext> {
|
pub trait Compile<Ctx: CodegenContext> {
|
||||||
@@ -28,17 +30,18 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
|
|||||||
Ir::UnOp(x) => x.compile(ctx),
|
Ir::UnOp(x) => x.compile(ctx),
|
||||||
Ir::Func(x) => x.compile(ctx),
|
Ir::Func(x) => x.compile(ctx),
|
||||||
Ir::AttrSet(x) => x.compile(ctx),
|
Ir::AttrSet(x) => x.compile(ctx),
|
||||||
|
Ir::List(x) => x.compile(ctx),
|
||||||
&Ir::Call(Call { func, arg }) => {
|
&Ir::Call(Call { func, arg }) => {
|
||||||
let func = ctx.get_ir(func).compile(ctx);
|
let func = ctx.get_ir(func).compile(ctx);
|
||||||
let arg = ctx.get_ir(arg).compile(ctx);
|
let arg = ctx.get_ir(arg).compile(ctx);
|
||||||
format!("NixRuntime.force({func})({arg})")
|
format!("Nix.force({func})({arg})")
|
||||||
}
|
}
|
||||||
Ir::Arg(x) => format!("arg{}", x.0),
|
Ir::Arg(x) => format!("arg{}", x.0),
|
||||||
Ir::Let(x) => x.compile(ctx),
|
Ir::Let(x) => x.compile(ctx),
|
||||||
Ir::Select(x) => x.compile(ctx),
|
Ir::Select(x) => x.compile(ctx),
|
||||||
&Ir::Thunk(expr_id) => {
|
&Ir::Thunk(expr_id) => {
|
||||||
let inner = ctx.get_ir(expr_id).compile(ctx);
|
let inner = ctx.get_ir(expr_id).compile(ctx);
|
||||||
format!("NixRuntime.create_thunk(()=>({}))", inner)
|
format!("Nix.create_thunk(()=>({}))", inner)
|
||||||
}
|
}
|
||||||
&Ir::ExprRef(expr_id) => {
|
&Ir::ExprRef(expr_id) => {
|
||||||
format!("expr{}", expr_id.0)
|
format!("expr{}", expr_id.0)
|
||||||
@@ -54,20 +57,23 @@ impl<Ctx: CodegenContext> Compile<Ctx> for BinOp {
|
|||||||
let lhs = ctx.get_ir(self.lhs).compile(ctx);
|
let lhs = ctx.get_ir(self.lhs).compile(ctx);
|
||||||
let rhs = ctx.get_ir(self.rhs).compile(ctx);
|
let rhs = ctx.get_ir(self.rhs).compile(ctx);
|
||||||
match self.kind {
|
match self.kind {
|
||||||
Add => format!("NixRuntime.op.add({},{})", lhs, rhs),
|
Add => format!("Nix.op.add({},{})", lhs, rhs),
|
||||||
Sub => format!("NixRuntime.op.sub({},{})", lhs, rhs),
|
Sub => format!("Nix.op.sub({},{})", lhs, rhs),
|
||||||
Mul => format!("NixRuntime.op.mul({},{})", lhs, rhs),
|
Mul => format!("Nix.op.mul({},{})", lhs, rhs),
|
||||||
Div => format!("NixRuntime.op.div({},{})", lhs, rhs),
|
Div => format!("Nix.op.div({},{})", lhs, rhs),
|
||||||
Eq => format!("NixRuntime.op.eq({},{})", lhs, rhs),
|
Eq => format!("Nix.op.eq({},{})", lhs, rhs),
|
||||||
Neq => format!("NixRuntime.op.neq({},{})", lhs, rhs),
|
Neq => format!("Nix.op.neq({},{})", lhs, rhs),
|
||||||
Lt => format!("NixRuntime.op.lt({},{})", lhs, rhs),
|
Lt => format!("Nix.op.lt({},{})", lhs, rhs),
|
||||||
Gt => format!("NixRuntime.op.gt({},{})", lhs, rhs),
|
Gt => format!("Nix.op.gt({},{})", lhs, rhs),
|
||||||
Leq => format!("NixRuntime.op.lte({},{})", lhs, rhs),
|
Leq => format!("Nix.op.lte({},{})", lhs, rhs),
|
||||||
Geq => format!("NixRuntime.op.gte({},{})", lhs, rhs),
|
Geq => format!("Nix.op.gte({},{})", lhs, rhs),
|
||||||
And => format!("NixRuntime.op.band({},{})", lhs, rhs),
|
And => format!("Nix.op.band({},{})", lhs, rhs),
|
||||||
Or => format!("NixRuntime.op.bor({},{})", lhs, rhs),
|
Or => format!("Nix.op.bor({},{})", lhs, rhs),
|
||||||
Impl => format!("NixRuntime.op.bor(NixRuntime.op.bnot({}),{})", lhs, rhs),
|
Impl => format!("Nix.op.bor(Nix.op.bnot({}),{})", lhs, rhs),
|
||||||
_ => todo!("BinOpKind::{:?}", self.kind),
|
Con => format!("Nix.op.concat({},{})", lhs, rhs),
|
||||||
|
Upd => format!("Nix.op.update({},{})", lhs, rhs),
|
||||||
|
PipeL => format!("Nix.force({})({})", rhs, lhs),
|
||||||
|
PipeR => format!("Nix.force({})({})", lhs, rhs),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,8 +83,8 @@ impl<Ctx: CodegenContext> Compile<Ctx> for UnOp {
|
|||||||
use UnOpKind::*;
|
use UnOpKind::*;
|
||||||
let rhs = ctx.get_ir(self.rhs).compile(ctx);
|
let rhs = ctx.get_ir(self.rhs).compile(ctx);
|
||||||
match self.kind {
|
match self.kind {
|
||||||
Neg => format!("NixRuntime.op.sub(0,{rhs})"),
|
Neg => format!("Nix.op.sub(0,{rhs})"),
|
||||||
Not => format!("NixRuntime.op.bnot({rhs})")
|
Not => format!("Nix.op.bnot({rhs})")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,8 +140,8 @@ impl Func {
|
|||||||
"null".to_string()
|
"null".to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Call NixRuntime.validate_params and store the result
|
// Call Nix.validate_params and store the result
|
||||||
format!("NixRuntime.validate_params(arg{},{},{});", id, required, allowed)
|
format!("Nix.validate_params(arg{},{},{});", id, required, allowed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,18 +189,18 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Select {
|
|||||||
let key = ctx.get_sym(*sym);
|
let key = ctx.get_sym(*sym);
|
||||||
if has_default {
|
if has_default {
|
||||||
let default_val = ctx.get_ir(self.default.unwrap()).compile(ctx);
|
let default_val = ctx.get_ir(self.default.unwrap()).compile(ctx);
|
||||||
format!("NixRuntime.select_with_default({}, \"{}\", {})", result, key, default_val)
|
format!("Nix.select_with_default({}, \"{}\", {})", result, key, default_val)
|
||||||
} else {
|
} else {
|
||||||
format!("NixRuntime.select({}, \"{}\")", result, key)
|
format!("Nix.select({}, \"{}\")", result, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Attr::Dynamic(expr_id) => {
|
Attr::Dynamic(expr_id) => {
|
||||||
let key = ctx.get_ir(*expr_id).compile(ctx);
|
let key = ctx.get_ir(*expr_id).compile(ctx);
|
||||||
if has_default {
|
if has_default {
|
||||||
let default_val = ctx.get_ir(self.default.unwrap()).compile(ctx);
|
let default_val = ctx.get_ir(self.default.unwrap()).compile(ctx);
|
||||||
format!("NixRuntime.select_with_default({}, {}, {})", result, key, default_val)
|
format!("Nix.select_with_default({}, {}, {})", result, key, default_val)
|
||||||
} else {
|
} else {
|
||||||
format!("NixRuntime.select({}, {})", result, key)
|
format!("Nix.select({}, {})", result, key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -223,3 +229,10 @@ impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet {
|
|||||||
format!("{{{}}}", attrs.join(", "))
|
format!("{{{}}}", attrs.join(", "))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<Ctx: CodegenContext> Compile<Ctx> for List {
|
||||||
|
fn compile(&self, ctx: &Ctx) -> String {
|
||||||
|
let list = self.items.iter().map(|item| ctx.get_ir(*item).compile(ctx)).join(",");
|
||||||
|
format!("[{list}]")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ impl Context {
|
|||||||
.downgrade_ctx()
|
.downgrade_ctx()
|
||||||
.downgrade(root.tree().expr().unwrap())?;
|
.downgrade(root.tree().expr().unwrap())?;
|
||||||
let code = self.get_ir(root).compile(self);
|
let code = self.get_ir(root).compile(self);
|
||||||
let code = format!("NixRuntime.force({})", code);
|
let code = format!("Nix.force({})", code);
|
||||||
println!("[DEBUG] generated code: {}", &code);
|
println!("[DEBUG] generated code: {}", &code);
|
||||||
crate::runtime::run(&code)
|
crate::runtime::run(&code)
|
||||||
}
|
}
|
||||||
@@ -81,8 +81,10 @@ impl CodegenContext for Context {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::value::Const;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::value::{AttrSet, Const, List, Symbol};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn basic_eval() {
|
fn basic_eval() {
|
||||||
@@ -118,11 +120,56 @@ mod test {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_binop() {
|
||||||
|
let tests = [
|
||||||
|
("1 + 1", Value::Const(Const::Int(2))),
|
||||||
|
("2 - 1", Value::Const(Const::Int(1))),
|
||||||
|
// FIXME: Floating point
|
||||||
|
// ("1. * 1", Value::Const(Const::Float(1.))),
|
||||||
|
// ("1 / 1.", Value::Const(Const::Float(1.))),
|
||||||
|
("1 == 1", Value::Const(Const::Bool(true))),
|
||||||
|
("1 != 1", Value::Const(Const::Bool(false))),
|
||||||
|
("2 < 1", Value::Const(Const::Bool(false))),
|
||||||
|
("2 > 1", Value::Const(Const::Bool(true))),
|
||||||
|
("1 <= 1", Value::Const(Const::Bool(true))),
|
||||||
|
("1 >= 1", Value::Const(Const::Bool(true))),
|
||||||
|
("1 == 1 || (1 / 0)", Value::Const(Const::Bool(true))),
|
||||||
|
("1 == 1 && 1 == 0", Value::Const(Const::Bool(false))),
|
||||||
|
// ("true || (1 / 0)", Value::Const(Const::Bool(true))),
|
||||||
|
// ("true && 1 == 0", Value::Const(Const::Bool(false))),
|
||||||
|
(
|
||||||
|
"[ 1 2 3 ] ++ [ 4 5 6 ]",
|
||||||
|
Value::List(List::new(
|
||||||
|
(1..=6).map(Const::Int).map(Value::Const).collect(),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"{ a.b = 1; b = 2; } // { a.c = 2; }",
|
||||||
|
Value::AttrSet(AttrSet::new(BTreeMap::from([
|
||||||
|
(
|
||||||
|
Symbol::from("a"),
|
||||||
|
Value::AttrSet(AttrSet::new(BTreeMap::from([(
|
||||||
|
Symbol::from("c"),
|
||||||
|
Value::Const(Const::Int(2)),
|
||||||
|
)]))),
|
||||||
|
),
|
||||||
|
(Symbol::from("b"), Value::Const(Const::Int(2))),
|
||||||
|
]))),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
for (expr, expected) in tests {
|
||||||
|
assert_eq!(Context::new().eval(expr).unwrap(), expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_param_check_required() {
|
fn test_param_check_required() {
|
||||||
// Test function with required parameters
|
// Test function with required parameters
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Context::new().eval("({ a, b }: a + b) { a = 1; b = 2; }").unwrap(),
|
Context::new()
|
||||||
|
.eval("({ a, b }: a + b) { a = 1; b = 2; }")
|
||||||
|
.unwrap(),
|
||||||
Value::Const(Const::Int(3))
|
Value::Const(Const::Int(3))
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -132,7 +179,9 @@ mod test {
|
|||||||
|
|
||||||
// Test all required parameters present
|
// Test all required parameters present
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Context::new().eval("({ x, y, z }: x + y + z) { x = 1; y = 2; z = 3; }").unwrap(),
|
Context::new()
|
||||||
|
.eval("({ x, y, z }: x + y + z) { x = 1; y = 2; z = 3; }")
|
||||||
|
.unwrap(),
|
||||||
Value::Const(Const::Int(6))
|
Value::Const(Const::Int(6))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -145,7 +194,9 @@ mod test {
|
|||||||
|
|
||||||
// Test function with ellipsis - should accept extra arguments
|
// Test function with ellipsis - should accept extra arguments
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Context::new().eval("({ a, b, ... }: a + b) { a = 1; b = 2; c = 3; }").unwrap(),
|
Context::new()
|
||||||
|
.eval("({ a, b, ... }: a + b) { a = 1; b = 2; c = 3; }")
|
||||||
|
.unwrap(),
|
||||||
Value::Const(Const::Int(3))
|
Value::Const(Const::Int(3))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -154,13 +205,17 @@ mod test {
|
|||||||
fn test_param_check_with_default() {
|
fn test_param_check_with_default() {
|
||||||
// Test function with default parameters
|
// Test function with default parameters
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Context::new().eval("({ a, b ? 5 }: a + b) { a = 1; }").unwrap(),
|
Context::new()
|
||||||
|
.eval("({ a, b ? 5 }: a + b) { a = 1; }")
|
||||||
|
.unwrap(),
|
||||||
Value::Const(Const::Int(6))
|
Value::Const(Const::Int(6))
|
||||||
);
|
);
|
||||||
|
|
||||||
// Test overriding default parameter
|
// Test overriding default parameter
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Context::new().eval("({ a, b ? 5 }: a + b) { a = 1; b = 10; }").unwrap(),
|
Context::new()
|
||||||
|
.eval("({ a, b ? 5 }: a + b) { a = 1; b = 10; }")
|
||||||
|
.unwrap(),
|
||||||
Value::Const(Const::Int(11))
|
Value::Const(Const::Int(11))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -169,7 +224,9 @@ mod test {
|
|||||||
fn test_param_check_with_alias() {
|
fn test_param_check_with_alias() {
|
||||||
// Test function with @ pattern (alias)
|
// Test function with @ pattern (alias)
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Context::new().eval("(args@{ a, b }: args.a + args.b) { a = 1; b = 2; }").unwrap(),
|
Context::new()
|
||||||
|
.eval("(args@{ a, b }: args.a + args.b) { a = 1; b = 2; }")
|
||||||
|
.unwrap(),
|
||||||
Value::Const(Const::Int(3))
|
Value::Const(Const::Int(3))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -178,7 +235,9 @@ mod test {
|
|||||||
fn test_simple_param_no_check() {
|
fn test_simple_param_no_check() {
|
||||||
// Test simple parameter (no pattern) should not have validation
|
// Test simple parameter (no pattern) should not have validation
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Context::new().eval("(x: x.a + x.b) { a = 1; b = 2; }").unwrap(),
|
Context::new()
|
||||||
|
.eval("(x: x.a + x.b) { a = 1; b = 2; }")
|
||||||
|
.unwrap(),
|
||||||
Value::Const(Const::Int(3))
|
Value::Const(Const::Int(3))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
const NixRuntime = (() => {
|
const Nix = (() => {
|
||||||
const IS_THUNK = Symbol("is_thunk");
|
const IS_THUNK = Symbol("is_thunk");
|
||||||
|
|
||||||
class NixThunk {
|
class NixThunk {
|
||||||
@@ -128,7 +128,10 @@ const NixRuntime = (() => {
|
|||||||
|
|
||||||
band: (a, b) => force(a) && force(b),
|
band: (a, b) => force(a) && force(b),
|
||||||
bor: (a, b) => force(a) || force(b),
|
bor: (a, b) => force(a) || force(b),
|
||||||
bnot: (a) => !force(a)
|
bnot: (a) => !force(a),
|
||||||
|
|
||||||
|
concat: (a, b) => Array.prototype.concat.apply(force(a), force(b)),
|
||||||
|
update: (a, b) => ({...force(a), ...force(b)}),
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user