refactor: flatten Ir::Const & Value::Const; add Ir::Builtin to represent
globally available builtins
This commit is contained in:
@@ -2,11 +2,11 @@ use itertools::Itertools as _;
|
||||
|
||||
use crate::ir::*;
|
||||
|
||||
pub trait Compile<Ctx: CodegenContext> {
|
||||
pub(crate) trait Compile<Ctx: CodegenContext> {
|
||||
fn compile(&self, ctx: &Ctx) -> String;
|
||||
}
|
||||
|
||||
pub trait CodegenContext {
|
||||
pub(crate) trait CodegenContext {
|
||||
fn get_ir(&self, id: ExprId) -> &Ir;
|
||||
fn get_sym(&self, id: SymId) -> &str;
|
||||
}
|
||||
@@ -14,12 +14,8 @@ pub trait CodegenContext {
|
||||
impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
|
||||
fn compile(&self, ctx: &Ctx) -> String {
|
||||
match self {
|
||||
Ir::Const(Const { val }) => match val {
|
||||
crate::value::Const::Null => "null".to_string(),
|
||||
crate::value::Const::Int(val) => format!("{}n", val), // Generate BigInt literal
|
||||
crate::value::Const::Float(val) => val.to_string(),
|
||||
crate::value::Const::Bool(val) => val.to_string(),
|
||||
},
|
||||
Ir::Int(int) => format!("{int}n"), // Generate BigInt literal
|
||||
Ir::Float(float) => float.to_string(),
|
||||
Ir::Str(s) => {
|
||||
// Escape string for JavaScript
|
||||
let escaped = s
|
||||
@@ -63,6 +59,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
|
||||
format!("expr{}", expr_id.0)
|
||||
}
|
||||
Ir::Builtins(_) => "Nix.builtins".to_string(),
|
||||
&Ir::Builtin(Builtin(name)) => format!("Nix.builtins[\"{}\"]", ctx.get_sym(name)),
|
||||
Ir::ConcatStrings(x) => x.compile(ctx),
|
||||
Ir::HasAttr(x) => x.compile(ctx),
|
||||
&Ir::Assert(Assert { assertion, expr }) => {
|
||||
|
||||
@@ -8,7 +8,7 @@ use string_interner::DefaultStringInterner;
|
||||
|
||||
use crate::codegen::{CodegenContext, Compile};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::ir::{DowngradeContext, ExprId, Ir, SymId};
|
||||
use crate::ir::{Builtin, DowngradeContext, ExprId, Ir, SymId};
|
||||
use crate::runtime::Runtime;
|
||||
use crate::value::Value;
|
||||
|
||||
@@ -118,7 +118,7 @@ impl Drop for PathDropGuard<'_> {
|
||||
|
||||
impl Default for Ctx {
|
||||
fn default() -> Self {
|
||||
use crate::ir::{Attr, Builtins, Select, ToIr};
|
||||
use crate::ir::{Builtins, ToIr as _};
|
||||
|
||||
let mut symbols = DefaultStringInterner::new();
|
||||
let mut irs = Vec::new();
|
||||
@@ -157,14 +157,9 @@ impl Default for Ctx {
|
||||
|
||||
for name in free_globals {
|
||||
let name_sym = symbols.get_or_intern(name);
|
||||
let select_ir = Select {
|
||||
expr: builtins_expr,
|
||||
attrpath: vec![Attr::Str(name_sym)],
|
||||
default: None,
|
||||
};
|
||||
let select_expr = ExprId(irs.len());
|
||||
irs.push(select_ir.to_ir());
|
||||
global.insert(name_sym, select_expr);
|
||||
let id = ExprId(irs.len());
|
||||
irs.push(Builtin(name_sym).to_ir());
|
||||
global.insert(name_sym, id);
|
||||
}
|
||||
|
||||
Self {
|
||||
@@ -178,16 +173,16 @@ impl Default for Ctx {
|
||||
}
|
||||
|
||||
impl Ctx {
|
||||
pub fn new() -> Self {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn downgrade_ctx<'a>(self: Pin<&'a mut Self>) -> DowngradeCtx<'a> {
|
||||
pub(crate) fn downgrade_ctx<'a>(self: Pin<&'a mut Self>) -> DowngradeCtx<'a> {
|
||||
let global_ref = unsafe { self.global.as_ref() };
|
||||
DowngradeCtx::new(self, global_ref)
|
||||
}
|
||||
|
||||
pub fn get_current_dir(&self) -> PathBuf {
|
||||
pub(crate) fn get_current_dir(&self) -> PathBuf {
|
||||
self.path_stack
|
||||
.last()
|
||||
.unwrap()
|
||||
@@ -212,63 +207,55 @@ mod test {
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use super::*;
|
||||
use crate::value::{AttrSet, Const, List, Symbol};
|
||||
use crate::value::{AttrSet, List, Symbol};
|
||||
|
||||
#[test]
|
||||
fn basic_eval() {
|
||||
assert_eq!(
|
||||
Context::new().eval_code("1 + 1").unwrap(),
|
||||
Value::Const(Const::Int(2))
|
||||
);
|
||||
assert_eq!(
|
||||
Context::new().eval_code("(x: x) 1").unwrap(),
|
||||
Value::Const(Const::Int(1))
|
||||
);
|
||||
assert_eq!(Context::new().eval_code("1 + 1").unwrap(), Value::Int(2));
|
||||
assert_eq!(Context::new().eval_code("(x: x) 1").unwrap(), Value::Int(1));
|
||||
assert_eq!(
|
||||
Context::new().eval_code("(x: y: x - y) 2 1").unwrap(),
|
||||
Value::Const(Const::Int(1))
|
||||
Value::Int(1)
|
||||
);
|
||||
assert_eq!(
|
||||
Context::new().eval_code("rec { b = a; a = 1; }.b").unwrap(),
|
||||
Value::Const(Const::Int(1))
|
||||
Value::Int(1)
|
||||
);
|
||||
assert_eq!(
|
||||
Context::new().eval_code("let b = a; a = 1; in b").unwrap(),
|
||||
Value::Const(Const::Int(1))
|
||||
Value::Int(1)
|
||||
);
|
||||
assert_eq!(
|
||||
Context::new().eval_code("let fib = n: if n == 1 || n == 2 then 1 else (fib (n - 1)) + (fib (n - 2)); in fib 30").unwrap(),
|
||||
Value::Const(Const::Int(832040))
|
||||
Value::Int(832040)
|
||||
);
|
||||
assert_eq!(
|
||||
Context::new()
|
||||
.eval_code("((f: let x = f x; in x)(self: { x = 1; y = self.x + 1; })).y")
|
||||
.unwrap(),
|
||||
Value::Const(Const::Int(2))
|
||||
Value::Int(2)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_binop() {
|
||||
let tests = [
|
||||
("1 + 1", Value::Const(Const::Int(2))),
|
||||
("2 - 1", Value::Const(Const::Int(1))),
|
||||
("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", Value::Int(2)),
|
||||
("2 - 1", Value::Int(1)),
|
||||
("1. * 1", Value::Float(1.)),
|
||||
("1 / 1.", Value::Float(1.)),
|
||||
("1 == 1", Value::Bool(true)),
|
||||
("1 != 1", Value::Bool(false)),
|
||||
("2 < 1", Value::Bool(false)),
|
||||
("2 > 1", Value::Bool(true)),
|
||||
("1 <= 1", Value::Bool(true)),
|
||||
("1 >= 1", Value::Bool(true)),
|
||||
// Short-circuit evaluation: true || <expr> should not evaluate <expr>
|
||||
("true || (1 / 0)", Value::Const(Const::Bool(true))),
|
||||
("true && 1 == 0", Value::Const(Const::Bool(false))),
|
||||
("true || (1 / 0)", Value::Bool(true)),
|
||||
("true && 1 == 0", Value::Bool(false)),
|
||||
(
|
||||
"[ 1 2 3 ] ++ [ 4 5 6 ]",
|
||||
Value::List(List::new(
|
||||
(1..=6).map(Const::Int).map(Value::Const).collect(),
|
||||
)),
|
||||
Value::List(List::new((1..=6).map(Value::Int).collect())),
|
||||
),
|
||||
(
|
||||
"{ a.b = 1; b = 2; } // { a.c = 2; }",
|
||||
@@ -277,10 +264,10 @@ mod test {
|
||||
Symbol::from("a"),
|
||||
Value::AttrSet(AttrSet::new(BTreeMap::from([(
|
||||
Symbol::from("c"),
|
||||
Value::Const(Const::Int(2)),
|
||||
Value::Int(2),
|
||||
)]))),
|
||||
),
|
||||
(Symbol::from("b"), Value::Const(Const::Int(2))),
|
||||
(Symbol::from("b"), Value::Int(2)),
|
||||
]))),
|
||||
),
|
||||
];
|
||||
@@ -296,7 +283,7 @@ mod test {
|
||||
Context::new()
|
||||
.eval_code("({ a, b }: a + b) { a = 1; b = 2; }")
|
||||
.unwrap(),
|
||||
Value::Const(Const::Int(3))
|
||||
Value::Int(3)
|
||||
);
|
||||
|
||||
// Test missing required parameter should fail
|
||||
@@ -308,7 +295,7 @@ mod test {
|
||||
Context::new()
|
||||
.eval_code("({ x, y, z }: x + y + z) { x = 1; y = 2; z = 3; }")
|
||||
.unwrap(),
|
||||
Value::Const(Const::Int(6))
|
||||
Value::Int(6)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -323,7 +310,7 @@ mod test {
|
||||
Context::new()
|
||||
.eval_code("({ a, b, ... }: a + b) { a = 1; b = 2; c = 3; }")
|
||||
.unwrap(),
|
||||
Value::Const(Const::Int(3))
|
||||
Value::Int(3)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -334,7 +321,7 @@ mod test {
|
||||
Context::new()
|
||||
.eval_code("({ a, b ? 5 }: a + b) { a = 1; }")
|
||||
.unwrap(),
|
||||
Value::Const(Const::Int(6))
|
||||
Value::Int(6)
|
||||
);
|
||||
|
||||
// Test overriding default parameter
|
||||
@@ -342,7 +329,7 @@ mod test {
|
||||
Context::new()
|
||||
.eval_code("({ a, b ? 5 }: a + b) { a = 1; b = 10; }")
|
||||
.unwrap(),
|
||||
Value::Const(Const::Int(11))
|
||||
Value::Int(11)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -353,7 +340,7 @@ mod test {
|
||||
Context::new()
|
||||
.eval_code("(args@{ a, b }: args.a + args.b) { a = 1; b = 2; }")
|
||||
.unwrap(),
|
||||
Value::Const(Const::Int(3))
|
||||
Value::Int(3)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -364,13 +351,13 @@ mod test {
|
||||
Context::new()
|
||||
.eval_code("(x: x.a + x.b) { a = 1; b = 2; }")
|
||||
.unwrap(),
|
||||
Value::Const(Const::Int(3))
|
||||
Value::Int(3)
|
||||
);
|
||||
|
||||
// Simple parameter accepts any argument
|
||||
assert_eq!(
|
||||
Context::new().eval_code("(x: x) 42").unwrap(),
|
||||
Value::Const(Const::Int(42))
|
||||
Value::Int(42)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -394,7 +381,7 @@ mod test {
|
||||
// Test calling builtin function: builtins.add 1 2
|
||||
assert_eq!(
|
||||
Context::new().eval_code("builtins.add 1 2").unwrap(),
|
||||
Value::Const(Const::Int(3))
|
||||
Value::Int(3)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -403,7 +390,7 @@ mod test {
|
||||
// Test builtin with list: builtins.length [1 2 3]
|
||||
assert_eq!(
|
||||
Context::new().eval_code("builtins.length [1 2 3]").unwrap(),
|
||||
Value::Const(Const::Int(3))
|
||||
Value::Int(3)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -412,11 +399,9 @@ mod test {
|
||||
// Test higher-order builtin: map (x: x * 2) [1 2 3]
|
||||
assert_eq!(
|
||||
Context::new().eval_code("map (x: x * 2) [1 2 3]").unwrap(),
|
||||
Value::List(List::new(vec![
|
||||
Value::Const(Const::Int(2)),
|
||||
Value::Const(Const::Int(4)),
|
||||
Value::Const(Const::Int(6)),
|
||||
]))
|
||||
Value::List(List::new(
|
||||
vec![Value::Int(2), Value::Int(4), Value::Int(6),]
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -427,10 +412,7 @@ mod test {
|
||||
Context::new()
|
||||
.eval_code("builtins.filter (x: x > 1) [1 2 3]")
|
||||
.unwrap(),
|
||||
Value::List(List::new(vec![
|
||||
Value::Const(Const::Int(2)),
|
||||
Value::Const(Const::Int(3)),
|
||||
]))
|
||||
Value::List(List::new(vec![Value::Int(2), Value::Int(3),]))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -453,7 +435,7 @@ mod test {
|
||||
// Test builtins.head [1 2 3]
|
||||
assert_eq!(
|
||||
Context::new().eval_code("builtins.head [1 2 3]").unwrap(),
|
||||
Value::Const(Const::Int(1))
|
||||
Value::Int(1)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -462,10 +444,7 @@ mod test {
|
||||
// Test builtins.tail [1 2 3]
|
||||
assert_eq!(
|
||||
Context::new().eval_code("builtins.tail [1 2 3]").unwrap(),
|
||||
Value::List(List::new(vec![
|
||||
Value::Const(Const::Int(2)),
|
||||
Value::Const(Const::Int(3)),
|
||||
]))
|
||||
Value::List(List::new(vec![Value::Int(2), Value::Int(3),]))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -476,7 +455,7 @@ mod test {
|
||||
Context::new()
|
||||
.eval_code("let b = builtins; in b.add 5 3")
|
||||
.unwrap(),
|
||||
Value::Const(Const::Int(8))
|
||||
Value::Int(8)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -487,7 +466,7 @@ mod test {
|
||||
Context::new()
|
||||
.eval_code("with builtins; add 10 20")
|
||||
.unwrap(),
|
||||
Value::Const(Const::Int(30))
|
||||
Value::Int(30)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -498,7 +477,7 @@ mod test {
|
||||
Context::new()
|
||||
.eval_code("builtins.add (builtins.mul 2 3) (builtins.sub 10 5)")
|
||||
.unwrap(),
|
||||
Value::Const(Const::Int(11)) // (2*3) + (10-5) = 6 + 5 = 11
|
||||
Value::Int(11) // (2*3) + (10-5 = 6 + 5 = 11
|
||||
);
|
||||
}
|
||||
|
||||
@@ -507,27 +486,27 @@ mod test {
|
||||
// Test type checking functions
|
||||
assert_eq!(
|
||||
Context::new().eval_code("builtins.isList [1 2 3]").unwrap(),
|
||||
Value::Const(Const::Bool(true))
|
||||
Value::Bool(true)
|
||||
);
|
||||
assert_eq!(
|
||||
Context::new()
|
||||
.eval_code("builtins.isAttrs { a = 1; }")
|
||||
.unwrap(),
|
||||
Value::Const(Const::Bool(true))
|
||||
Value::Bool(true)
|
||||
);
|
||||
assert_eq!(
|
||||
Context::new()
|
||||
.eval_code("builtins.isFunction (x: x)")
|
||||
.unwrap(),
|
||||
Value::Const(Const::Bool(true))
|
||||
Value::Bool(true)
|
||||
);
|
||||
assert_eq!(
|
||||
Context::new().eval_code("builtins.isNull null").unwrap(),
|
||||
Value::Const(Const::Bool(true))
|
||||
Value::Bool(true)
|
||||
);
|
||||
assert_eq!(
|
||||
Context::new().eval_code("builtins.isBool true").unwrap(),
|
||||
Value::Const(Const::Bool(true))
|
||||
Value::Bool(true)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -538,7 +517,7 @@ mod test {
|
||||
Context::new()
|
||||
.eval_code("let builtins = { add = x: y: x - y; }; in builtins.add 5 3")
|
||||
.unwrap(),
|
||||
Value::Const(Const::Int(2)) // Uses shadowed version
|
||||
Value::Int(2) // Uses shadowed version
|
||||
);
|
||||
}
|
||||
|
||||
@@ -549,32 +528,26 @@ mod test {
|
||||
let result = Context::new()
|
||||
.eval_code("builtins.builtins.builtins.add 1 1")
|
||||
.unwrap();
|
||||
assert_eq!(result, Value::Const(Const::Int(2)));
|
||||
assert_eq!(result, Value::Int(2));
|
||||
}
|
||||
|
||||
// Free globals tests
|
||||
#[test]
|
||||
fn test_free_global_true() {
|
||||
assert_eq!(
|
||||
Context::new().eval_code("true").unwrap(),
|
||||
Value::Const(Const::Bool(true))
|
||||
);
|
||||
assert_eq!(Context::new().eval_code("true").unwrap(), Value::Bool(true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_free_global_false() {
|
||||
assert_eq!(
|
||||
Context::new().eval_code("false").unwrap(),
|
||||
Value::Const(Const::Bool(false))
|
||||
Value::Bool(false)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_free_global_null() {
|
||||
assert_eq!(
|
||||
Context::new().eval_code("null").unwrap(),
|
||||
Value::Const(Const::Null)
|
||||
);
|
||||
assert_eq!(Context::new().eval_code("null").unwrap(), Value::Null);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -582,11 +555,9 @@ mod test {
|
||||
// Test free global function: map (x: x * 2) [1 2 3]
|
||||
assert_eq!(
|
||||
Context::new().eval_code("map (x: x * 2) [1 2 3]").unwrap(),
|
||||
Value::List(List::new(vec![
|
||||
Value::Const(Const::Int(2)),
|
||||
Value::Const(Const::Int(4)),
|
||||
Value::Const(Const::Int(6)),
|
||||
]))
|
||||
Value::List(List::new(
|
||||
vec![Value::Int(2), Value::Int(4), Value::Int(6),]
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -595,11 +566,11 @@ mod test {
|
||||
// Test isNull function
|
||||
assert_eq!(
|
||||
Context::new().eval_code("isNull null").unwrap(),
|
||||
Value::Const(Const::Bool(true))
|
||||
Value::Bool(true)
|
||||
);
|
||||
assert_eq!(
|
||||
Context::new().eval_code("isNull 5").unwrap(),
|
||||
Value::Const(Const::Bool(false))
|
||||
Value::Bool(false)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -610,13 +581,13 @@ mod test {
|
||||
Context::new()
|
||||
.eval_code("let true = false; in true")
|
||||
.unwrap(),
|
||||
Value::Const(Const::Bool(false))
|
||||
Value::Bool(false)
|
||||
);
|
||||
assert_eq!(
|
||||
Context::new()
|
||||
.eval_code("let map = x: y: x; in map 1 2")
|
||||
.unwrap(),
|
||||
Value::Const(Const::Int(1))
|
||||
Value::Int(1)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -627,10 +598,7 @@ mod test {
|
||||
Context::new()
|
||||
.eval_code("if true then map (x: x + 1) [1 2] else []")
|
||||
.unwrap(),
|
||||
Value::List(List::new(vec![
|
||||
Value::Const(Const::Int(2)),
|
||||
Value::Const(Const::Int(3)),
|
||||
]))
|
||||
Value::List(List::new(vec![Value::Int(2), Value::Int(3),]))
|
||||
);
|
||||
}
|
||||
|
||||
@@ -641,7 +609,7 @@ mod test {
|
||||
Context::new()
|
||||
.eval_code("let x = true; y = false; in x && y")
|
||||
.unwrap(),
|
||||
Value::Const(Const::Bool(false))
|
||||
Value::Bool(false)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -653,21 +621,21 @@ mod test {
|
||||
// Test large i64 values
|
||||
assert_eq!(
|
||||
ctx.eval_code("9223372036854775807").unwrap(),
|
||||
Value::Const(Const::Int(9223372036854775807))
|
||||
Value::Int(9223372036854775807)
|
||||
);
|
||||
|
||||
// Test negative large value
|
||||
// Can't use -9223372036854775808 since unary minus is actually desugared to (0 - <expr>)
|
||||
assert_eq!(
|
||||
ctx.eval_code("-9223372036854775807").unwrap(),
|
||||
Value::Const(Const::Int(-9223372036854775807))
|
||||
Value::Int(-9223372036854775807)
|
||||
);
|
||||
|
||||
// Test large number arithmetic
|
||||
assert_eq!(
|
||||
ctx.eval_code("5000000000000000000 + 3000000000000000000")
|
||||
.unwrap(),
|
||||
Value::Const(Const::Int(8000000000000000000i64))
|
||||
Value::Int(8000000000000000000i64)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -678,25 +646,25 @@ mod test {
|
||||
// isInt tests
|
||||
assert_eq!(
|
||||
ctx.eval_code("builtins.isInt 42").unwrap(),
|
||||
Value::Const(Const::Bool(true))
|
||||
Value::Bool(true)
|
||||
);
|
||||
assert_eq!(
|
||||
ctx.eval_code("builtins.isInt 42.0").unwrap(),
|
||||
Value::Const(Const::Bool(false))
|
||||
Value::Bool(false)
|
||||
);
|
||||
|
||||
// isFloat tests
|
||||
assert_eq!(
|
||||
ctx.eval_code("builtins.isFloat 42").unwrap(),
|
||||
Value::Const(Const::Bool(false))
|
||||
Value::Bool(false)
|
||||
);
|
||||
assert_eq!(
|
||||
ctx.eval_code("builtins.isFloat 42.5").unwrap(),
|
||||
Value::Const(Const::Bool(true))
|
||||
Value::Bool(true)
|
||||
);
|
||||
assert_eq!(
|
||||
ctx.eval_code("builtins.isFloat 1.0").unwrap(),
|
||||
Value::Const(Const::Bool(true))
|
||||
Value::Bool(true)
|
||||
);
|
||||
|
||||
// typeOf tests
|
||||
@@ -714,8 +682,8 @@ mod test {
|
||||
);
|
||||
|
||||
// literal tests
|
||||
assert_eq!(ctx.eval_code("1").unwrap(), Value::Const(Const::Int(1)));
|
||||
assert_eq!(ctx.eval_code("1.").unwrap(), Value::Const(Const::Float(1.)))
|
||||
assert_eq!(ctx.eval_code("1").unwrap(), Value::Int(1));
|
||||
assert_eq!(ctx.eval_code("1.").unwrap(), Value::Float(1.))
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -751,30 +719,18 @@ mod test {
|
||||
fn test_integer_division() {
|
||||
let mut ctx = Context::new();
|
||||
|
||||
assert_eq!(ctx.eval_code("5 / 2").unwrap(), Value::Const(Const::Int(2)));
|
||||
assert_eq!(ctx.eval_code("5 / 2").unwrap(), Value::Int(2));
|
||||
|
||||
assert_eq!(ctx.eval_code("7 / 3").unwrap(), Value::Const(Const::Int(2)));
|
||||
assert_eq!(ctx.eval_code("7 / 3").unwrap(), Value::Int(2));
|
||||
|
||||
assert_eq!(
|
||||
ctx.eval_code("10 / 3").unwrap(),
|
||||
Value::Const(Const::Int(3))
|
||||
);
|
||||
assert_eq!(ctx.eval_code("10 / 3").unwrap(), Value::Int(3));
|
||||
|
||||
// Float division returns float
|
||||
assert_eq!(
|
||||
ctx.eval_code("5 / 2.0").unwrap(),
|
||||
Value::Const(Const::Float(2.5))
|
||||
);
|
||||
assert_eq!(ctx.eval_code("5 / 2.0").unwrap(), Value::Float(2.5));
|
||||
|
||||
assert_eq!(
|
||||
ctx.eval_code("7.0 / 2").unwrap(),
|
||||
Value::Const(Const::Float(3.5))
|
||||
);
|
||||
assert_eq!(ctx.eval_code("7.0 / 2").unwrap(), Value::Float(3.5));
|
||||
|
||||
assert_eq!(
|
||||
ctx.eval_code("(-7) / 3").unwrap(),
|
||||
Value::Const(Const::Int(-2))
|
||||
);
|
||||
assert_eq!(ctx.eval_code("(-7) / 3").unwrap(), Value::Int(-2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -785,13 +741,13 @@ mod test {
|
||||
assert_eq!(
|
||||
ctx.eval_code("builtins.add 5000000000000000000 3000000000000000000")
|
||||
.unwrap(),
|
||||
Value::Const(Const::Int(8000000000000000000i64))
|
||||
Value::Int(8000000000000000000i64)
|
||||
);
|
||||
|
||||
// Test builtin mul with large numbers
|
||||
assert_eq!(
|
||||
ctx.eval_code("builtins.mul 1000000000 1000000000").unwrap(),
|
||||
Value::Const(Const::Int(1000000000000000000i64))
|
||||
Value::Int(1000000000000000000i64)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -805,7 +761,7 @@ mod test {
|
||||
std::fs::write(&lib_path, "{ add = a: b: a + b; }").unwrap();
|
||||
|
||||
let expr = format!(r#"(import "{}").add 3 5"#, lib_path.display());
|
||||
assert_eq!(ctx.eval_code(&expr).unwrap(), Value::Const(Const::Int(8)));
|
||||
assert_eq!(ctx.eval_code(&expr).unwrap(), Value::Int(8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -825,7 +781,7 @@ mod test {
|
||||
std::fs::write(&main_path, main_content).unwrap();
|
||||
|
||||
let expr = format!(r#"(import "{}").result"#, main_path.display());
|
||||
assert_eq!(ctx.eval_code(&expr).unwrap(), Value::Const(Const::Int(30)));
|
||||
assert_eq!(ctx.eval_code(&expr).unwrap(), Value::Int(30));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -855,10 +811,10 @@ mod test {
|
||||
std::fs::write(&main_path, main_content).unwrap();
|
||||
|
||||
let expr = format!(r#"let x = import "{}"; in x.result1"#, main_path.display());
|
||||
assert_eq!(ctx.eval_code(&expr).unwrap(), Value::Const(Const::Int(12)));
|
||||
assert_eq!(ctx.eval_code(&expr).unwrap(), Value::Int(12));
|
||||
|
||||
let expr = format!(r#"let x = import "{}"; in x.result2"#, main_path.display());
|
||||
assert_eq!(ctx.eval_code(&expr).unwrap(), Value::Const(Const::Int(7)));
|
||||
assert_eq!(ctx.eval_code(&expr).unwrap(), Value::Int(7));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -870,6 +826,6 @@ mod test {
|
||||
std::fs::write(&func_path, "x: x * 2").unwrap();
|
||||
|
||||
let expr = format!(r#"(import "{}") 5"#, func_path.display());
|
||||
assert_eq!(ctx.eval_code(&expr).unwrap(), Value::Const(Const::Int(10)));
|
||||
assert_eq!(ctx.eval_code(&expr).unwrap(), Value::Int(10));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ use rnix::ast;
|
||||
use string_interner::symbol::SymbolU32;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::value::Const as PubConst;
|
||||
use crate::value::format_symbol;
|
||||
use nix_js_macros::ir;
|
||||
|
||||
@@ -44,8 +43,12 @@ pub trait DowngradeContext {
|
||||
ir! {
|
||||
Ir,
|
||||
|
||||
Int(i64),
|
||||
Float(f64),
|
||||
Str,
|
||||
AttrSet,
|
||||
List,
|
||||
|
||||
HasAttr,
|
||||
BinOp,
|
||||
UnOp,
|
||||
@@ -54,8 +57,6 @@ ir! {
|
||||
Call,
|
||||
Assert,
|
||||
ConcatStrings,
|
||||
Const,
|
||||
Str,
|
||||
Path,
|
||||
Func,
|
||||
Let,
|
||||
@@ -63,6 +64,7 @@ ir! {
|
||||
ExprRef(ExprId),
|
||||
Thunk(ExprId),
|
||||
Builtins,
|
||||
Builtin,
|
||||
}
|
||||
|
||||
impl AttrSet {
|
||||
@@ -354,18 +356,6 @@ pub struct ConcatStrings {
|
||||
pub parts: Vec<ExprId>,
|
||||
}
|
||||
|
||||
/// Represents a constant value (e.g., integer, float, boolean, null).
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Const {
|
||||
pub val: PubConst,
|
||||
}
|
||||
|
||||
impl<T: Into<PubConst>> From<T> for Const {
|
||||
fn from(value: T) -> Self {
|
||||
Self { val: value.into() }
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a simple, non-interpolated string literal.
|
||||
#[derive(Debug)]
|
||||
pub struct Str {
|
||||
@@ -386,4 +376,4 @@ pub struct Builtins;
|
||||
|
||||
/// Represents an attribute in `builtins`.
|
||||
#[derive(Debug)]
|
||||
pub struct Builtin(pub String);
|
||||
pub struct Builtin(pub SymId);
|
||||
|
||||
@@ -139,8 +139,8 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Str {
|
||||
impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Literal {
|
||||
fn downgrade(self, ctx: &mut Ctx) -> Result<ExprId> {
|
||||
Ok(ctx.new_expr(match self.kind() {
|
||||
ast::LiteralKind::Integer(int) => Const::from(int.value().unwrap()).to_ir(),
|
||||
ast::LiteralKind::Float(float) => Const::from(float.value().unwrap()).to_ir(),
|
||||
ast::LiteralKind::Integer(int) => Ir::Int(int.value().unwrap()),
|
||||
ast::LiteralKind::Float(float) => Ir::Float(float.value().unwrap()),
|
||||
ast::LiteralKind::Uri(uri) => Str {
|
||||
val: uri.to_string(),
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
pub mod codegen;
|
||||
mod codegen;
|
||||
pub mod context;
|
||||
pub mod error;
|
||||
pub mod ir;
|
||||
pub mod runtime;
|
||||
mod runtime;
|
||||
pub mod value;
|
||||
|
||||
#[global_allocator]
|
||||
|
||||
@@ -3,13 +3,12 @@ use std::pin::Pin;
|
||||
use std::sync::Once;
|
||||
|
||||
use deno_core::{Extension, ExtensionFileSource, JsRuntime, OpDecl, OpState, RuntimeOptions, v8};
|
||||
use deno_error::js_error_wrapper;
|
||||
|
||||
use crate::codegen::{CodegenContext, Compile};
|
||||
use crate::context::{CtxPtr, PathDropGuard};
|
||||
use crate::error::{Error, Result};
|
||||
use crate::ir::DowngradeContext;
|
||||
use crate::value::{AttrSet, Const, List, Symbol, Value};
|
||||
use crate::value::{AttrSet, List, Symbol, Value};
|
||||
|
||||
type ScopeRef<'p, 's> = v8::PinnedRef<'p, v8::HandleScope<'s>>;
|
||||
type LocalValue<'a> = v8::Local<'a, v8::Value>;
|
||||
@@ -38,33 +37,32 @@ fn runtime_extension(ctx: CtxPtr) -> Extension {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SimpleErrorWrapper(String);
|
||||
|
||||
impl std::fmt::Display for SimpleErrorWrapper {
|
||||
mod private {
|
||||
use deno_error::js_error_wrapper;
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub struct SimpleErrorWrapper(pub(crate) String);
|
||||
impl std::fmt::Display for SimpleErrorWrapper {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::Debug::fmt(self, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
impl std::error::Error for SimpleErrorWrapper {}
|
||||
|
||||
impl std::error::Error for SimpleErrorWrapper {
|
||||
fn cause(&self) -> Option<&dyn std::error::Error> {
|
||||
None
|
||||
}
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
None
|
||||
}
|
||||
fn description(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
js_error_wrapper!(SimpleErrorWrapper, NixError, "EvalError");
|
||||
|
||||
js_error_wrapper!(SimpleErrorWrapper, NixError, "EvalError");
|
||||
impl From<String> for NixError {
|
||||
impl From<String> for NixError {
|
||||
fn from(value: String) -> Self {
|
||||
NixError(SimpleErrorWrapper(value))
|
||||
}
|
||||
}
|
||||
impl From<&str> for NixError {
|
||||
fn from(value: &str) -> Self {
|
||||
NixError(SimpleErrorWrapper(value.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
use private::NixError;
|
||||
|
||||
#[deno_core::op2]
|
||||
#[string]
|
||||
@@ -76,14 +74,13 @@ fn op_import(state: &mut OpState, #[string] path: String) -> std::result::Result
|
||||
let absolute_path = current_dir
|
||||
.join(&path)
|
||||
.canonicalize()
|
||||
.map_err(|e| -> NixError { format!("Failed to resolve path {}: {}", path, e).into() })?;
|
||||
.map_err(|e| format!("Failed to resolve path {}: {}", path, e))?;
|
||||
|
||||
let mut guard = PathDropGuard::new(absolute_path.clone(), ctx);
|
||||
let ctx = guard.as_ctx();
|
||||
|
||||
let content = std::fs::read_to_string(&absolute_path).map_err(|e| -> NixError {
|
||||
format!("Failed to read {}: {}", absolute_path.display(), e).into()
|
||||
})?;
|
||||
let content = std::fs::read_to_string(&absolute_path)
|
||||
.map_err(|e| format!("Failed to read {}: {}", absolute_path.display(), e))?;
|
||||
|
||||
let root = rnix::Root::parse(&content);
|
||||
if !root.errors().is_empty() {
|
||||
@@ -95,15 +92,12 @@ fn op_import(state: &mut OpState, #[string] path: String) -> std::result::Result
|
||||
.into());
|
||||
}
|
||||
|
||||
let expr = root
|
||||
.tree()
|
||||
.expr()
|
||||
.ok_or_else(|| -> NixError { "No expression in file".to_string().into() })?;
|
||||
let expr = root.tree().expr().ok_or("No expression in file")?;
|
||||
let expr_id = ctx
|
||||
.as_mut()
|
||||
.downgrade_ctx()
|
||||
.downgrade(expr)
|
||||
.map_err(|e| -> NixError { format!("Downgrade error: {}", e).into() })?;
|
||||
.map_err(|e| format!("Downgrade error: {}", e))?;
|
||||
|
||||
Ok(ctx.get_ir(expr_id).compile(Pin::get_ref(ctx.as_ref())))
|
||||
}
|
||||
@@ -111,8 +105,7 @@ fn op_import(state: &mut OpState, #[string] path: String) -> std::result::Result
|
||||
#[deno_core::op2]
|
||||
#[string]
|
||||
fn op_read_file(#[string] path: String) -> std::result::Result<String, NixError> {
|
||||
std::fs::read_to_string(&path)
|
||||
.map_err(|e| -> NixError { format!("Failed to read {}: {}", path, e).into() })
|
||||
Ok(std::fs::read_to_string(&path).map_err(|e| format!("Failed to read {}: {}", path, e))?)
|
||||
}
|
||||
|
||||
#[deno_core::op2(fast)]
|
||||
@@ -137,11 +130,11 @@ fn op_resolve_path(
|
||||
// Resolve relative path against current file directory (or CWD)
|
||||
let current_dir = ctx.get_current_dir();
|
||||
|
||||
current_dir
|
||||
Ok(current_dir
|
||||
.join(&path)
|
||||
.canonicalize()
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.map_err(|e| -> NixError { format!("Failed to resolve path {}: {}", path, e).into() })
|
||||
.map_err(|e| format!("Failed to resolve path {}: {}", path, e))?)
|
||||
}
|
||||
|
||||
pub(crate) struct Runtime {
|
||||
@@ -234,16 +227,16 @@ fn to_value<'a>(
|
||||
if !lossless {
|
||||
panic!("BigInt value out of i64 range: conversion lost precision");
|
||||
}
|
||||
Value::Const(Const::Int(val))
|
||||
Value::Int(val)
|
||||
}
|
||||
_ if val.is_number() => {
|
||||
let val = val.to_number(scope).unwrap().value();
|
||||
// number is always NixFloat
|
||||
Value::Const(Const::Float(val))
|
||||
Value::Float(val)
|
||||
}
|
||||
_ if val.is_true() => Value::Const(Const::Bool(true)),
|
||||
_ if val.is_false() => Value::Const(Const::Bool(false)),
|
||||
_ if val.is_null() => Value::Const(Const::Null),
|
||||
_ if val.is_true() => Value::Bool(true),
|
||||
_ if val.is_false() => Value::Bool(false),
|
||||
_ if val.is_null() => Value::Null,
|
||||
_ if val.is_string() => {
|
||||
let val = val.to_string(scope).unwrap();
|
||||
Value::String(val.to_rust_string_lossy(scope))
|
||||
@@ -349,10 +342,10 @@ mod test {
|
||||
Value::AttrSet(AttrSet::new(std::collections::BTreeMap::from([(
|
||||
Symbol::from("test"),
|
||||
Value::List(List::new(vec![
|
||||
Value::Const(Const::Float(1.)),
|
||||
Value::Const(Const::Int(9223372036854775807)),
|
||||
Value::Const(Const::Bool(true)),
|
||||
Value::Const(Const::Bool(false)),
|
||||
Value::Float(1.),
|
||||
Value::Int(9223372036854775807),
|
||||
Value::Bool(true),
|
||||
Value::Bool(false),
|
||||
Value::String("hello world!".to_string())
|
||||
]))
|
||||
)])))
|
||||
|
||||
@@ -9,49 +9,6 @@ use std::sync::LazyLock;
|
||||
use derive_more::{Constructor, IsVariant, Unwrap};
|
||||
use regex::Regex;
|
||||
|
||||
/// Represents a constant, primitive value in Nix.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, IsVariant, Unwrap)]
|
||||
pub enum Const {
|
||||
/// A boolean value (`true` or `false`).
|
||||
Bool(bool),
|
||||
/// A 64-bit signed integer.
|
||||
Int(i64),
|
||||
/// A 64-bit floating-point number.
|
||||
Float(f64),
|
||||
/// The `null` value.
|
||||
Null,
|
||||
}
|
||||
|
||||
impl Display for Const {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
use Const::*;
|
||||
match self {
|
||||
Int(x) => write!(f, "{x}"),
|
||||
Float(x) => write!(f, "{x}"),
|
||||
Bool(x) => write!(f, "{x}"),
|
||||
Null => write!(f, "null"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<bool> for Const {
|
||||
fn from(value: bool) -> Self {
|
||||
Const::Bool(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for Const {
|
||||
fn from(value: i64) -> Self {
|
||||
Const::Int(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f64> for Const {
|
||||
fn from(value: f64) -> Self {
|
||||
Const::Float(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a Nix symbol, which is used as a key in attribute sets.
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Constructor)]
|
||||
pub struct Symbol(String);
|
||||
@@ -172,8 +129,14 @@ impl Display for List {
|
||||
/// Represents any possible Nix value that can be returned from an evaluation.
|
||||
#[derive(IsVariant, Unwrap, Clone, Debug, PartialEq)]
|
||||
pub enum Value {
|
||||
/// A constant value (int, float, bool, null).
|
||||
Const(Const),
|
||||
/// An integer value.
|
||||
Int(i64),
|
||||
/// An floating-point value.
|
||||
Float(f64),
|
||||
/// An boolean value.
|
||||
Bool(bool),
|
||||
/// An null value.
|
||||
Null,
|
||||
/// A string value.
|
||||
String(String),
|
||||
/// An attribute set.
|
||||
@@ -197,7 +160,10 @@ impl Display for Value {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||
use Value::*;
|
||||
match self {
|
||||
Const(x) => write!(f, "{x}"),
|
||||
&Int(x) => write!(f, "{x}"),
|
||||
&Float(x) => write!(f, "{x}"),
|
||||
&Bool(x) => write!(f, "{x}"),
|
||||
Null => write!(f, "null"),
|
||||
String(x) => write!(f, r#""{x}""#),
|
||||
AttrSet(x) => write!(f, "{x}"),
|
||||
List(x) => write!(f, "{x}"),
|
||||
|
||||
Reference in New Issue
Block a user