feat: builtins
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
use std::process::exit;
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use nix_js::context::Context;
|
use nix_js::context::Context;
|
||||||
|
use std::process::exit;
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let mut args = std::env::args();
|
let mut args = std::env::args();
|
||||||
@@ -14,10 +14,10 @@ fn main() -> Result<()> {
|
|||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
println!("{value}");
|
println!("{value}");
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("Error: {err}");
|
eprintln!("Error: {err}");
|
||||||
Err(anyhow::anyhow!("{err}"))
|
Err(anyhow::anyhow!("{err}"))
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
|
|||||||
&Ir::ExprRef(expr_id) => {
|
&Ir::ExprRef(expr_id) => {
|
||||||
format!("expr{}", expr_id.0)
|
format!("expr{}", expr_id.0)
|
||||||
}
|
}
|
||||||
|
Ir::Builtin(_) => "Nix.builtins".to_string(),
|
||||||
ir => todo!("{ir:?}"),
|
ir => todo!("{ir:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,12 +85,11 @@ impl<Ctx: CodegenContext> Compile<Ctx> for UnOp {
|
|||||||
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!("Nix.op.sub(0,{rhs})"),
|
Neg => format!("Nix.op.sub(0,{rhs})"),
|
||||||
Not => format!("Nix.op.bnot({rhs})")
|
Not => format!("Nix.op.bnot({rhs})"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl<Ctx: CodegenContext> Compile<Ctx> for Func {
|
impl<Ctx: CodegenContext> Compile<Ctx> for Func {
|
||||||
fn compile(&self, ctx: &Ctx) -> String {
|
fn compile(&self, ctx: &Ctx) -> String {
|
||||||
let id = ctx.get_ir(self.arg).as_ref().unwrap_arg().0;
|
let id = ctx.get_ir(self.arg).as_ref().unwrap_arg().0;
|
||||||
@@ -189,7 +189,10 @@ 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!("Nix.select_with_default({}, \"{}\", {})", result, key, default_val)
|
format!(
|
||||||
|
"Nix.select_with_default({}, \"{}\", {})",
|
||||||
|
result, key, default_val
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
format!("Nix.select({}, \"{}\")", result, key)
|
format!("Nix.select({}, \"{}\")", result, key)
|
||||||
}
|
}
|
||||||
@@ -198,7 +201,10 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Select {
|
|||||||
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!("Nix.select_with_default({}, {}, {})", result, key, default_val)
|
format!(
|
||||||
|
"Nix.select_with_default({}, {}, {})",
|
||||||
|
result, key, default_val
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
format!("Nix.select({}, {})", result, key)
|
format!("Nix.select({}, {})", result, key)
|
||||||
}
|
}
|
||||||
@@ -232,7 +238,11 @@ impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet {
|
|||||||
|
|
||||||
impl<Ctx: CodegenContext> Compile<Ctx> for List {
|
impl<Ctx: CodegenContext> Compile<Ctx> for List {
|
||||||
fn compile(&self, ctx: &Ctx) -> String {
|
fn compile(&self, ctx: &Ctx) -> String {
|
||||||
let list = self.items.iter().map(|item| ctx.get_ir(*item).compile(ctx)).join(",");
|
let list = self
|
||||||
|
.items
|
||||||
|
.iter()
|
||||||
|
.map(|item| ctx.get_ir(*item).compile(ctx))
|
||||||
|
.join(",");
|
||||||
format!("[{list}]")
|
format!("[{list}]")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,10 +28,60 @@ impl Drop for Context {
|
|||||||
|
|
||||||
impl Default for Context {
|
impl Default for Context {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
use crate::ir::{Attr, Builtin, Select, ToIr};
|
||||||
|
|
||||||
|
let mut symbols = DefaultStringInterner::new();
|
||||||
|
let mut irs = Vec::new();
|
||||||
|
let mut global = HashMap::new();
|
||||||
|
|
||||||
|
irs.push(Builtin.to_ir());
|
||||||
|
let builtins_expr = ExprId(0);
|
||||||
|
|
||||||
|
let builtins_sym = symbols.get_or_intern("builtins");
|
||||||
|
global.insert(builtins_sym, builtins_expr);
|
||||||
|
|
||||||
|
let free_globals = [
|
||||||
|
"true",
|
||||||
|
"false",
|
||||||
|
"null",
|
||||||
|
|
||||||
|
"abort",
|
||||||
|
"baseNameOf",
|
||||||
|
"break",
|
||||||
|
"dirOf",
|
||||||
|
"derivation",
|
||||||
|
"derivationStrict",
|
||||||
|
"fetchGit",
|
||||||
|
"fetchMercurial",
|
||||||
|
"fetchTarball",
|
||||||
|
"fetchTree",
|
||||||
|
"fromTOML",
|
||||||
|
"import",
|
||||||
|
"isNull",
|
||||||
|
"map",
|
||||||
|
"placeholder",
|
||||||
|
"removeAttrs",
|
||||||
|
"scopedImport",
|
||||||
|
"throw",
|
||||||
|
"toString",
|
||||||
|
];
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
symbols: DefaultStringInterner::new(),
|
symbols,
|
||||||
irs: Vec::new(),
|
irs,
|
||||||
global: unsafe { NonNull::new_unchecked(Box::leak(Box::new(HashMap::new()))) },
|
global: unsafe { NonNull::new_unchecked(Box::leak(Box::new(global))) },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -134,10 +184,8 @@ mod test {
|
|||||||
("2 > 1", Value::Const(Const::Bool(true))),
|
("2 > 1", Value::Const(Const::Bool(true))),
|
||||||
("1 <= 1", Value::Const(Const::Bool(true))),
|
("1 <= 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))),
|
("true || (1 / 0)", Value::Const(Const::Bool(true))),
|
||||||
("1 == 1 && 1 == 0", Value::Const(Const::Bool(false))),
|
("true && 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 ]",
|
"[ 1 2 3 ] ++ [ 4 5 6 ]",
|
||||||
Value::List(List::new(
|
Value::List(List::new(
|
||||||
@@ -247,4 +295,269 @@ mod test {
|
|||||||
Value::Const(Const::Int(42))
|
Value::Const(Const::Int(42))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_builtins_basic_access() {
|
||||||
|
// Test that builtins identifier is accessible
|
||||||
|
let result = Context::new().eval("builtins").unwrap();
|
||||||
|
// Should return an AttrSet with builtin functions
|
||||||
|
assert!(matches!(result, Value::AttrSet(_)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_builtins_self_reference() {
|
||||||
|
// Test builtins.builtins (self-reference as thunk)
|
||||||
|
let result = Context::new().eval("builtins.builtins").unwrap();
|
||||||
|
assert!(matches!(result, Value::AttrSet(_)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_builtin_function_add() {
|
||||||
|
// Test calling builtin function: builtins.add 1 2
|
||||||
|
assert_eq!(
|
||||||
|
Context::new().eval("builtins.add 1 2").unwrap(),
|
||||||
|
Value::Const(Const::Int(3))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_builtin_function_length() {
|
||||||
|
// Test builtin with list: builtins.length [1 2 3]
|
||||||
|
assert_eq!(
|
||||||
|
Context::new().eval("builtins.length [1 2 3]").unwrap(),
|
||||||
|
Value::Const(Const::Int(3))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_builtin_function_map() {
|
||||||
|
// Test higher-order builtin: builtins.map (x: x * 2) [1 2 3]
|
||||||
|
assert_eq!(
|
||||||
|
Context::new()
|
||||||
|
.eval("builtins.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)),
|
||||||
|
]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_builtin_function_filter() {
|
||||||
|
// Test predicate builtin: builtins.filter (x: x > 1) [1 2 3]
|
||||||
|
assert_eq!(
|
||||||
|
Context::new()
|
||||||
|
.eval("builtins.filter (x: x > 1) [1 2 3]")
|
||||||
|
.unwrap(),
|
||||||
|
Value::List(List::new(vec![
|
||||||
|
Value::Const(Const::Int(2)),
|
||||||
|
Value::Const(Const::Int(3)),
|
||||||
|
]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_builtin_function_attrnames() {
|
||||||
|
// Test builtins.attrNames { a = 1; b = 2; }
|
||||||
|
let result = Context::new()
|
||||||
|
.eval("builtins.attrNames { a = 1; b = 2; }")
|
||||||
|
.unwrap();
|
||||||
|
// Should return a list of attribute names
|
||||||
|
assert!(matches!(result, Value::List(_)));
|
||||||
|
if let Value::List(list) = result {
|
||||||
|
// List should contain 2 elements
|
||||||
|
assert_eq!(format!("{:?}", list).matches(',').count() + 1, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_builtin_function_head() {
|
||||||
|
// Test builtins.head [1 2 3]
|
||||||
|
assert_eq!(
|
||||||
|
Context::new().eval("builtins.head [1 2 3]").unwrap(),
|
||||||
|
Value::Const(Const::Int(1))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_builtin_function_tail() {
|
||||||
|
// Test builtins.tail [1 2 3]
|
||||||
|
assert_eq!(
|
||||||
|
Context::new().eval("builtins.tail [1 2 3]").unwrap(),
|
||||||
|
Value::List(List::new(vec![
|
||||||
|
Value::Const(Const::Int(2)),
|
||||||
|
Value::Const(Const::Int(3)),
|
||||||
|
]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_builtin_in_let() {
|
||||||
|
// Test builtins in let binding
|
||||||
|
assert_eq!(
|
||||||
|
Context::new()
|
||||||
|
.eval("let b = builtins; in b.add 5 3")
|
||||||
|
.unwrap(),
|
||||||
|
Value::Const(Const::Int(8))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_builtin_in_with() {
|
||||||
|
// Test builtins with 'with' expression
|
||||||
|
assert_eq!(
|
||||||
|
Context::new().eval("with builtins; add 10 20").unwrap(),
|
||||||
|
Value::Const(Const::Int(30))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_builtin_nested_access() {
|
||||||
|
// Test nested function calls with builtins
|
||||||
|
assert_eq!(
|
||||||
|
Context::new()
|
||||||
|
.eval("builtins.add (builtins.mul 2 3) (builtins.sub 10 5)")
|
||||||
|
.unwrap(),
|
||||||
|
Value::Const(Const::Int(11)) // (2*3) + (10-5) = 6 + 5 = 11
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_builtin_type_checks() {
|
||||||
|
// Test type checking functions
|
||||||
|
assert_eq!(
|
||||||
|
Context::new().eval("builtins.isList [1 2 3]").unwrap(),
|
||||||
|
Value::Const(Const::Bool(true))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Context::new().eval("builtins.isAttrs { a = 1; }").unwrap(),
|
||||||
|
Value::Const(Const::Bool(true))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Context::new().eval("builtins.isFunction (x: x)").unwrap(),
|
||||||
|
Value::Const(Const::Bool(true))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Context::new().eval("builtins.isNull null").unwrap(),
|
||||||
|
Value::Const(Const::Bool(true))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Context::new().eval("builtins.isBool true").unwrap(),
|
||||||
|
Value::Const(Const::Bool(true))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_builtin_shadowing() {
|
||||||
|
// Test that user can shadow builtins (Nix allows this)
|
||||||
|
assert_eq!(
|
||||||
|
Context::new()
|
||||||
|
.eval("let builtins = { add = x: y: x - y; }; in builtins.add 5 3")
|
||||||
|
.unwrap(),
|
||||||
|
Value::Const(Const::Int(2)) // Uses shadowed version
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_builtin_lazy_evaluation() {
|
||||||
|
// Test that builtins.builtins is lazy (thunk)
|
||||||
|
// This should not cause infinite recursion
|
||||||
|
let result = Context::new()
|
||||||
|
.eval("builtins.builtins.builtins.add 1 1")
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(result, Value::Const(Const::Int(2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free globals tests
|
||||||
|
#[test]
|
||||||
|
fn test_free_global_true() {
|
||||||
|
assert_eq!(
|
||||||
|
Context::new().eval("true").unwrap(),
|
||||||
|
Value::Const(Const::Bool(true))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_free_global_false() {
|
||||||
|
assert_eq!(
|
||||||
|
Context::new().eval("false").unwrap(),
|
||||||
|
Value::Const(Const::Bool(false))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_free_global_null() {
|
||||||
|
assert_eq!(
|
||||||
|
Context::new().eval("null").unwrap(),
|
||||||
|
Value::Const(Const::Null)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_free_global_map() {
|
||||||
|
// Test free global function: map (x: x * 2) [1 2 3]
|
||||||
|
assert_eq!(
|
||||||
|
Context::new().eval("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)),
|
||||||
|
]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_free_global_isnull() {
|
||||||
|
// Test isNull function
|
||||||
|
assert_eq!(
|
||||||
|
Context::new().eval("isNull null").unwrap(),
|
||||||
|
Value::Const(Const::Bool(true))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Context::new().eval("isNull 5").unwrap(),
|
||||||
|
Value::Const(Const::Bool(false))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_free_global_shadowing() {
|
||||||
|
// Test shadowing of free globals
|
||||||
|
assert_eq!(
|
||||||
|
Context::new().eval("let true = false; in true").unwrap(),
|
||||||
|
Value::Const(Const::Bool(false))
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
Context::new()
|
||||||
|
.eval("let map = x: y: x; in map 1 2")
|
||||||
|
.unwrap(),
|
||||||
|
Value::Const(Const::Int(1))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_free_global_mixed_usage() {
|
||||||
|
// Test mixing free globals in expressions
|
||||||
|
assert_eq!(
|
||||||
|
Context::new()
|
||||||
|
.eval("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)),
|
||||||
|
]))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_free_global_in_let() {
|
||||||
|
// Test free globals in let bindings
|
||||||
|
assert_eq!(
|
||||||
|
Context::new()
|
||||||
|
.eval("let x = true; y = false; in x && y")
|
||||||
|
.unwrap(),
|
||||||
|
Value::Const(Const::Bool(false))
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,17 +114,20 @@ impl DowngradeContext for DowngradeCtx<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn extract_expr(&mut self, id: ExprId) -> Ir {
|
fn extract_expr(&mut self, id: ExprId) -> Ir {
|
||||||
self.irs.get_mut(id.0).unwrap().take().unwrap()
|
let local_id = id.0 - self.ctx.irs.len();
|
||||||
|
self.irs.get_mut(local_id).unwrap().take().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace_expr(&mut self, id: ExprId, expr: Ir) {
|
fn replace_expr(&mut self, id: ExprId, expr: Ir) {
|
||||||
let _ = self.irs.get_mut(id.0).unwrap().insert(expr);
|
let local_id = id.0 - self.ctx.irs.len();
|
||||||
|
let _ = self.irs.get_mut(local_id).unwrap().insert(expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(refining_impl_trait)]
|
#[allow(refining_impl_trait)]
|
||||||
fn reserve_slots(&mut self, slots: usize) -> impl Iterator<Item = ExprId> + Clone + use<> {
|
fn reserve_slots(&mut self, slots: usize) -> impl Iterator<Item = ExprId> + Clone + use<> {
|
||||||
|
let start = self.ctx.irs.len() + self.irs.len();
|
||||||
self.irs.extend(std::iter::repeat_with(|| None).take(slots));
|
self.irs.extend(std::iter::repeat_with(|| None).take(slots));
|
||||||
(self.irs.len() - slots..self.irs.len()).map(ExprId)
|
(start..start + slots).map(ExprId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn downgrade(mut self, root: rnix::ast::Expr) -> Result<ExprId> {
|
fn downgrade(mut self, root: rnix::ast::Expr) -> Result<ExprId> {
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ ir! {
|
|||||||
PrimOp(PrimOpId),
|
PrimOp(PrimOpId),
|
||||||
ExprRef(ExprId),
|
ExprRef(ExprId),
|
||||||
Thunk(ExprId),
|
Thunk(ExprId),
|
||||||
|
Builtin,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AttrSet {
|
impl AttrSet {
|
||||||
@@ -391,3 +392,8 @@ pub struct Path {
|
|||||||
/// This can be a simple `Str` or a `ConcatStrings` for interpolated paths.
|
/// This can be a simple `Str` or a `ConcatStrings` for interpolated paths.
|
||||||
pub expr: ExprId,
|
pub expr: ExprId,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Represents the special `builtins` global object.
|
||||||
|
/// This is a unit struct with no fields as it maps directly to the runtime builtins.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Builtin;
|
||||||
|
|||||||
@@ -250,9 +250,8 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::LetIn {
|
|||||||
let entries: Vec<_> = self.entries().collect();
|
let entries: Vec<_> = self.entries().collect();
|
||||||
let body_expr = self.body().unwrap();
|
let body_expr = self.body().unwrap();
|
||||||
|
|
||||||
let (bindings, body) = downgrade_let_bindings(entries, ctx, |ctx, _binding_keys| {
|
let (bindings, body) =
|
||||||
body_expr.downgrade(ctx)
|
downgrade_let_bindings(entries, ctx, |ctx, _binding_keys| body_expr.downgrade(ctx))?;
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(ctx.new_expr(Let { bindings, body }.to_ir()))
|
Ok(ctx.new_expr(Let { bindings, body }.to_ir()))
|
||||||
}
|
}
|
||||||
@@ -357,13 +356,17 @@ impl<Ctx: DowngradeContext> Downgrade<Ctx> for ast::Lambda {
|
|||||||
|
|
||||||
// Downgrade body in Let scope and create Let expression
|
// Downgrade body in Let scope and create Let expression
|
||||||
let bindings_vec: Vec<ExprId> = bindings.values().copied().collect();
|
let bindings_vec: Vec<ExprId> = bindings.values().copied().collect();
|
||||||
let inner_body = ctx.with_let_scope(bindings, |ctx| self.body().unwrap().downgrade(ctx))?;
|
let inner_body =
|
||||||
|
ctx.with_let_scope(bindings, |ctx| self.body().unwrap().downgrade(ctx))?;
|
||||||
|
|
||||||
// Create Let expression to wrap the bindings
|
// Create Let expression to wrap the bindings
|
||||||
body = ctx.new_expr(Let {
|
body = ctx.new_expr(
|
||||||
|
Let {
|
||||||
bindings: bindings_vec,
|
bindings: bindings_vec,
|
||||||
body: inner_body,
|
body: inner_body,
|
||||||
}.to_ir());
|
}
|
||||||
|
.to_ir(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use hashbrown::{HashMap, HashSet};
|
|
||||||
use hashbrown::hash_map::Entry;
|
use hashbrown::hash_map::Entry;
|
||||||
|
use hashbrown::{HashMap, HashSet};
|
||||||
use rnix::ast;
|
use rnix::ast;
|
||||||
|
|
||||||
use crate::error::{Error, Result};
|
use crate::error::{Error, Result};
|
||||||
|
|||||||
@@ -31,7 +31,9 @@ fn run_impl(script: &str, isolate: &mut v8::Isolate) -> Result<Value> {
|
|||||||
let runtime_script = v8::Script::compile(scope, runtime_source, None).unwrap();
|
let runtime_script = v8::Script::compile(scope, runtime_source, None).unwrap();
|
||||||
|
|
||||||
if runtime_script.run(scope).is_none() {
|
if runtime_script.run(scope).is_none() {
|
||||||
return Err(Error::eval_error("Failed to initialize runtime".to_string()));
|
return Err(Error::eval_error(
|
||||||
|
"Failed to initialize runtime".to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let source = v8::String::new(scope, script).unwrap();
|
let source = v8::String::new(scope, script).unwrap();
|
||||||
@@ -47,7 +49,10 @@ fn run_impl(script: &str, isolate: &mut v8::Isolate) -> Result<Value> {
|
|||||||
.to_string(try_catch)
|
.to_string(try_catch)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_rust_string_lossy(try_catch);
|
.to_rust_string_lossy(try_catch);
|
||||||
return Err(Error::eval_error(format!("Compilation error: {}", exception_string)));
|
return Err(Error::eval_error(format!(
|
||||||
|
"Compilation error: {}",
|
||||||
|
exception_string
|
||||||
|
)));
|
||||||
} else {
|
} else {
|
||||||
return Err(Error::eval_error("Unknown compilation error".to_string()));
|
return Err(Error::eval_error("Unknown compilation error".to_string()));
|
||||||
}
|
}
|
||||||
@@ -62,7 +67,10 @@ fn run_impl(script: &str, isolate: &mut v8::Isolate) -> Result<Value> {
|
|||||||
.to_string(try_catch)
|
.to_string(try_catch)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.to_rust_string_lossy(try_catch);
|
.to_rust_string_lossy(try_catch);
|
||||||
Err(Error::eval_error(format!("Runtime error: {}", exception_string)))
|
Err(Error::eval_error(format!(
|
||||||
|
"Runtime error: {}",
|
||||||
|
exception_string
|
||||||
|
)))
|
||||||
} else {
|
} else {
|
||||||
Err(Error::eval_error("Unknown runtime error".to_string()))
|
Err(Error::eval_error("Unknown runtime error".to_string()))
|
||||||
}
|
}
|
||||||
@@ -91,7 +99,7 @@ fn to_value<'a>(
|
|||||||
}
|
}
|
||||||
_ if val.is_true() => Value::Const(Const::Bool(true)),
|
_ if val.is_true() => Value::Const(Const::Bool(true)),
|
||||||
_ if val.is_false() => Value::Const(Const::Bool(false)),
|
_ if val.is_false() => Value::Const(Const::Bool(false)),
|
||||||
_ if val.is_null() => Value::Const(Const::Bool(true)),
|
_ if val.is_null() => Value::Const(Const::Null),
|
||||||
_ if val.is_string() => {
|
_ if val.is_string() => {
|
||||||
let val = val.to_string(scope).unwrap();
|
let val = val.to_string(scope).unwrap();
|
||||||
Value::String(val.to_rust_string_lossy(scope))
|
Value::String(val.to_rust_string_lossy(scope))
|
||||||
@@ -107,7 +115,12 @@ fn to_value<'a>(
|
|||||||
.collect();
|
.collect();
|
||||||
Value::List(List::new(list))
|
Value::List(List::new(list))
|
||||||
}
|
}
|
||||||
|
_ if val.is_function() => Value::Func,
|
||||||
_ if val.is_object() => {
|
_ if val.is_object() => {
|
||||||
|
if is_thunk(val, scope) {
|
||||||
|
return Value::Thunk;
|
||||||
|
}
|
||||||
|
|
||||||
let val = val.to_object(scope).unwrap();
|
let val = val.to_object(scope).unwrap();
|
||||||
let keys = val
|
let keys = val
|
||||||
.get_own_property_names(scope, v8::GetPropertyNamesArgsBuilder::new().build())
|
.get_own_property_names(scope, v8::GetPropertyNamesArgsBuilder::new().build())
|
||||||
@@ -123,18 +136,42 @@ fn to_value<'a>(
|
|||||||
.collect();
|
.collect();
|
||||||
Value::AttrSet(AttrSet::new(attrs))
|
Value::AttrSet(AttrSet::new(attrs))
|
||||||
}
|
}
|
||||||
_ if val.is_function_template() => Value::PrimOp,
|
|
||||||
_ if val.is_function() => Value::Func,
|
|
||||||
_ => todo!("{}", val.type_repr()),
|
_ => todo!("{}", val.type_repr()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_thunk<'a>(
|
||||||
|
val: v8::Local<'a, v8::Value>,
|
||||||
|
scope: &v8::PinnedRef<'a, v8::HandleScope>,
|
||||||
|
) -> bool {
|
||||||
|
if !val.is_object() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let global = scope.get_current_context().global(scope);
|
||||||
|
let nix_key = v8::String::new(scope, "Nix").unwrap();
|
||||||
|
let nix_obj = match global.get(scope, nix_key.into()) {
|
||||||
|
Some(obj) if obj.is_object() => obj.to_object(scope).unwrap(),
|
||||||
|
_ => return false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let is_thunk_sym_key = v8::String::new(scope, "IS_THUNK").unwrap();
|
||||||
|
let is_thunk_sym = match nix_obj.get(scope, is_thunk_sym_key.into()) {
|
||||||
|
Some(sym) if sym.is_symbol() => sym,
|
||||||
|
_ => return false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let obj = val.to_object(scope).unwrap();
|
||||||
|
matches!(obj.get(scope, is_thunk_sym), Some(v) if v.is_true())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn to_value_working() {
|
fn to_value_working() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
run("({
|
run("({
|
||||||
test: [1, 9223372036854775807n, true, false, 'hello world!']
|
test: [1, 9223372036854775807n, true, false, 'hello world!']
|
||||||
})").unwrap(),
|
})")
|
||||||
|
.unwrap(),
|
||||||
Value::AttrSet(AttrSet::new(std::collections::BTreeMap::from([(
|
Value::AttrSet(AttrSet::new(std::collections::BTreeMap::from([(
|
||||||
Symbol::from("test"),
|
Symbol::from("test"),
|
||||||
Value::List(List::new(vec![
|
Value::List(List::new(vec![
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ const Nix = (() => {
|
|||||||
constructor(func) {
|
constructor(func) {
|
||||||
this[IS_THUNK] = true;
|
this[IS_THUNK] = true;
|
||||||
this.func = func;
|
this.func = func;
|
||||||
this.is_forced = false;
|
|
||||||
this.result = null;
|
this.result = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -19,14 +18,12 @@ const Nix = (() => {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.is_forced) {
|
if (value.func === null) {
|
||||||
return value.result;
|
return value.result;
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = force(value.func());
|
const result = force(value.func());
|
||||||
value.result = result;
|
value.result = result;
|
||||||
value.is_forced = true;
|
|
||||||
|
|
||||||
value.func = null;
|
value.func = null;
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -53,11 +50,6 @@ const Nix = (() => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const trace = (msg, value) => {
|
|
||||||
console.log(`[TRACE] ${msg}`);
|
|
||||||
return force(value);
|
|
||||||
};
|
|
||||||
|
|
||||||
const select = (obj, key) => {
|
const select = (obj, key) => {
|
||||||
const forced_obj = force(obj);
|
const forced_obj = force(obj);
|
||||||
const forced_key = force(key);
|
const forced_key = force(key);
|
||||||
@@ -78,11 +70,11 @@ const Nix = (() => {
|
|||||||
const forced_key = force(key);
|
const forced_key = force(key);
|
||||||
|
|
||||||
if (forced_obj === null || forced_obj === undefined) {
|
if (forced_obj === null || forced_obj === undefined) {
|
||||||
return force(default_val);
|
return default_val;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(forced_key in forced_obj)) {
|
if (!(forced_key in forced_obj)) {
|
||||||
return force(default_val);
|
return default_val;
|
||||||
}
|
}
|
||||||
|
|
||||||
return forced_obj[forced_key];
|
return forced_obj[forced_key];
|
||||||
@@ -131,7 +123,344 @@ const Nix = (() => {
|
|||||||
bnot: (a) => !force(a),
|
bnot: (a) => !force(a),
|
||||||
|
|
||||||
concat: (a, b) => Array.prototype.concat.apply(force(a), force(b)),
|
concat: (a, b) => Array.prototype.concat.apply(force(a), force(b)),
|
||||||
update: (a, b) => ({...force(a), ...force(b)}),
|
update: (a, b) => ({ ...force(a), ...force(b) }),
|
||||||
|
};
|
||||||
|
|
||||||
|
const builtins = {
|
||||||
|
add: (a) => (b) => force(a) + force(b),
|
||||||
|
sub: (a) => (b) => force(a) - force(b),
|
||||||
|
mul: (a) => (b) => force(a) * force(b),
|
||||||
|
div: (a) => (b) => force(a) / force(b),
|
||||||
|
bitAnd: (a) => (b) => force(a) & force(b),
|
||||||
|
bitOr: (a) => (b) => force(a) | force(b),
|
||||||
|
bitXor: (a) => (b) => force(a) ^ force(b),
|
||||||
|
lessThan: (a) => (b) => force(a) < force(b),
|
||||||
|
|
||||||
|
ceil: (x) => Math.ceil(force(x)),
|
||||||
|
floor: (x) => Math.floor(force(x)),
|
||||||
|
|
||||||
|
deepSeq: (e1) => (e2) => {
|
||||||
|
throw "Not implemented: deepSeq"
|
||||||
|
},
|
||||||
|
seq: (e1) => (e2) => {
|
||||||
|
force(e1);
|
||||||
|
return e2;
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchClosure: (args) => {
|
||||||
|
throw "Not implemented: fetchClosure"
|
||||||
|
},
|
||||||
|
fetchGit: (args) => {
|
||||||
|
throw "Not implemented: fetchGit"
|
||||||
|
},
|
||||||
|
fetchTarball: (args) => {
|
||||||
|
throw "Not implemented: fetchTarball"
|
||||||
|
},
|
||||||
|
fetchTree: (args) => {
|
||||||
|
throw "Not implemented: fetchTree"
|
||||||
|
},
|
||||||
|
fetchurl: (args) => {
|
||||||
|
throw "Not implemented: fetchurl"
|
||||||
|
},
|
||||||
|
|
||||||
|
fromJSON: (e) => {
|
||||||
|
throw "Not implemented: fromJSON"
|
||||||
|
},
|
||||||
|
fromTOML: (e) => {
|
||||||
|
throw "Not implemented: fromTOML"
|
||||||
|
},
|
||||||
|
toJSON: (e) => {
|
||||||
|
throw "Not implemented: toJSON"
|
||||||
|
},
|
||||||
|
toXML: (e) => {
|
||||||
|
throw "Not implemented: toXML"
|
||||||
|
},
|
||||||
|
|
||||||
|
getContext: (s) => {
|
||||||
|
throw "Not implemented: getContext"
|
||||||
|
},
|
||||||
|
hasContext: (s) => {
|
||||||
|
throw "Not implemented: hasContext"
|
||||||
|
},
|
||||||
|
|
||||||
|
hashFile: (type) => (p) => {
|
||||||
|
throw "Not implemented: hashFile"
|
||||||
|
},
|
||||||
|
hashString: (type) => (p) => {
|
||||||
|
throw "Not implemented: hashString"
|
||||||
|
},
|
||||||
|
convertHash: (args) => {
|
||||||
|
throw "Not implemented: convertHash"
|
||||||
|
},
|
||||||
|
|
||||||
|
isAttrs: (e) => {
|
||||||
|
const val = force(e);
|
||||||
|
return typeof val === "object" && !Array.isArray(val) && val !== null;
|
||||||
|
},
|
||||||
|
isBool: (e) => typeof force(e) === "boolean",
|
||||||
|
isFloat: (e) => {
|
||||||
|
throw "Not implemented: isFloat"
|
||||||
|
},
|
||||||
|
isFunction: (e) => typeof force(e) === "function",
|
||||||
|
isInt: (e) => {
|
||||||
|
throw "Not implemented: isInt"
|
||||||
|
},
|
||||||
|
isList: (e) => Array.isArray(force(e)),
|
||||||
|
isNull: (e) => force(e) === null,
|
||||||
|
isPath: (e) => {
|
||||||
|
throw "Not implemented: isPath"
|
||||||
|
},
|
||||||
|
isString: (e) => typeof force(e) === "string",
|
||||||
|
typeOf: (e) => {
|
||||||
|
throw "Not implemented: typeOf"
|
||||||
|
},
|
||||||
|
|
||||||
|
import: (path) => {
|
||||||
|
throw "Not implemented: import"
|
||||||
|
},
|
||||||
|
scopedImport: (scope) => (path) => {
|
||||||
|
throw "Not implemented: scopedImport"
|
||||||
|
},
|
||||||
|
|
||||||
|
unsafeDiscardOutputDependency: (s) => {
|
||||||
|
throw "Not implemented: unsafeDiscardOutputDependency"
|
||||||
|
},
|
||||||
|
unsafeDiscardStringContext: (s) => {
|
||||||
|
throw "Not implemented: unsafeDiscardStringContext"
|
||||||
|
},
|
||||||
|
unsafeGetAttrPos: (s) => {
|
||||||
|
throw "Not implemented: unsafeGetAttrPos"
|
||||||
|
},
|
||||||
|
|
||||||
|
abort: (s) => {
|
||||||
|
throw `evaluation aborted with the following error message: '${force(s)}'`
|
||||||
|
},
|
||||||
|
addDrvOutputDependencies: (s) => {
|
||||||
|
throw "Not implemented: addDrvOutputDependencies"
|
||||||
|
},
|
||||||
|
all: (pred) => (list) => Array.prototype.every.call(force(list), force(pred)),
|
||||||
|
any: (pred) => (list) => Array.prototype.some.call(force(list), force(pred)),
|
||||||
|
attrNames: (set) => Object.keys(force(set)),
|
||||||
|
attrValues: (set) => Object.values(force(set)),
|
||||||
|
baseNameOf: (x) => {
|
||||||
|
const str = force(x);
|
||||||
|
if (str.length === 0)
|
||||||
|
return "";
|
||||||
|
let last = str.length - 1;
|
||||||
|
if (str[last] === "/" && last > 0)
|
||||||
|
last -= 1;
|
||||||
|
let pos = last;
|
||||||
|
while (pos >= 0 && str[pos] !== "/")
|
||||||
|
pos -= 1;
|
||||||
|
if (pos !== 0 || (pos === 0 && str[pos] === "/")) {
|
||||||
|
pos += 1;
|
||||||
|
}
|
||||||
|
return String.prototype.substring.call(str, pos, last + 1);
|
||||||
|
},
|
||||||
|
break: (v) => v,
|
||||||
|
catAttrs: (attr) => (list) => {
|
||||||
|
const key = force(attr);
|
||||||
|
return force(list).map((set) => force(set)[key]).filter((val) => val !== undefined);
|
||||||
|
},
|
||||||
|
compareVersions: (s1) => (s2) => {
|
||||||
|
throw "Not implemented: compareVersions"
|
||||||
|
},
|
||||||
|
concatLists: (lists) => Array.prototype.reduce.call(force(lists), (acc, cur) => Array.prototype.concat(acc, force(cur)), []),
|
||||||
|
concatMap: (f) => (lists) => {
|
||||||
|
const fn = force(f);
|
||||||
|
return Array.prototype.reduce.call(force(lists), (acc, cur) => Array.prototype.concat(acc, force(fn(cur))), []);
|
||||||
|
},
|
||||||
|
concatStringsSep: (sep) => (list) => Array.prototype.join.call(force(list), force(sep)),
|
||||||
|
dirOf: (s) => {
|
||||||
|
throw "Not implemented: dirOf"
|
||||||
|
},
|
||||||
|
elem: (x) => (xs) => Array.prototype.includes.call(force(xs), force(x)),
|
||||||
|
elemAt: (xs) => (n) => force(xs)[force(n)],
|
||||||
|
filter: (f) => (list) => Array.prototype.filter.call(force(list), force(f)),
|
||||||
|
filterSource: (args) => {
|
||||||
|
throw "Not implemented: filterSource"
|
||||||
|
},
|
||||||
|
findFile: (search) => (lookup) => {
|
||||||
|
throw "Not implemented: findFile"
|
||||||
|
},
|
||||||
|
flakeRefToString: (attrs) => {
|
||||||
|
throw "Not implemented: flakeRefToString"
|
||||||
|
},
|
||||||
|
["foldl'"]:(op_fn) => (nul) => (list) => {
|
||||||
|
const forced_op = force(op_fn);
|
||||||
|
return Array.prototype.reduce.call(force(list), (acc, cur) => forced_op(acc)(cur), nul);
|
||||||
|
},
|
||||||
|
functionArgs: (f) => {
|
||||||
|
throw "Not implemented: functionArgs"
|
||||||
|
},
|
||||||
|
genList: (f) => (len) => {
|
||||||
|
const forced_f = force(f);
|
||||||
|
const forced_len = force(len);
|
||||||
|
return [...Array(forced_len).keys()].map(i => forced_f(i));
|
||||||
|
},
|
||||||
|
genericClosure: (args) => {
|
||||||
|
throw "Not implemented: genericClosure"
|
||||||
|
},
|
||||||
|
getAttr: (s) => (set) => force(set)[force(s)],
|
||||||
|
getEnv: (s) => {
|
||||||
|
throw "Not implemented: getEnv"
|
||||||
|
},
|
||||||
|
getFlake: (attrs) => {
|
||||||
|
throw "Not implemented: getFlake"
|
||||||
|
},
|
||||||
|
groupBy: (f) => (list) => {
|
||||||
|
let attrs = {};
|
||||||
|
const forced_f = force(f);
|
||||||
|
const forced_list = force(list);
|
||||||
|
for (const elem of forced_list) {
|
||||||
|
const key = force(forced_f(elem));
|
||||||
|
if (!attrs[key]) attrs[key] = [];
|
||||||
|
attrs[key].push(elem);
|
||||||
|
}
|
||||||
|
return attrs;
|
||||||
|
},
|
||||||
|
hasAttr: (s) => (set) => Object.prototype.hasOwnProperty.call(force(set), force(s)),
|
||||||
|
head: (list) => force(list)[0],
|
||||||
|
intersectAttrs: (e1) => (e2) => {
|
||||||
|
const f1 = force(e1);
|
||||||
|
const f2 = force(e2);
|
||||||
|
let attrs = {};
|
||||||
|
for (const key of Object.keys(f2)) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(f1, key)) {
|
||||||
|
attrs[key] = f2[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return attrs;
|
||||||
|
},
|
||||||
|
length: (e) => force(e).length,
|
||||||
|
listToAttrs: (e) => {
|
||||||
|
let attrs = {};
|
||||||
|
const forced_e = [...force(e)].reverse();
|
||||||
|
for (const obj of forced_e) {
|
||||||
|
const item = force(obj);
|
||||||
|
attrs[force(item.name)] = item.value;
|
||||||
|
}
|
||||||
|
return attrs;
|
||||||
|
},
|
||||||
|
map: (f) => (list) => Array.prototype.map.call(force(list), force(f)),
|
||||||
|
mapAttrs: (f) => (attrs) => {
|
||||||
|
let new_attrs = {};
|
||||||
|
const forced_attrs = force(attrs);
|
||||||
|
const forced_f = force(f);
|
||||||
|
for (const key in forced_attrs) {
|
||||||
|
new_attrs[key] = forced_f(key)(forced_attrs[key]);
|
||||||
|
}
|
||||||
|
return new_attrs;
|
||||||
|
},
|
||||||
|
match: (regex) => (str) => {
|
||||||
|
throw "Not implemented: match"
|
||||||
|
},
|
||||||
|
outputOf: (drv) => (out) => {
|
||||||
|
throw "Not implemented: outputOf"
|
||||||
|
},
|
||||||
|
parseDrvName: (s) => {
|
||||||
|
throw "Not implemented: parseDrvName"
|
||||||
|
},
|
||||||
|
parseFlakeName: (s) => {
|
||||||
|
throw "Not implemented: parseFlakeName"
|
||||||
|
},
|
||||||
|
partition: (pred) => (list) => {
|
||||||
|
const forced_list = force(list);
|
||||||
|
const forced_pred = force(pred);
|
||||||
|
let attrs = {
|
||||||
|
right: [],
|
||||||
|
wrong: [],
|
||||||
|
};
|
||||||
|
for (const elem of forced_list) {
|
||||||
|
if (force(forced_pred(elem))) {
|
||||||
|
attrs.right.push(elem);
|
||||||
|
} else {
|
||||||
|
attrs.wrong.push(elem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return attrs;
|
||||||
|
},
|
||||||
|
path: (args) => {
|
||||||
|
throw "Not implemented: path"
|
||||||
|
},
|
||||||
|
pathExists: (path) => {
|
||||||
|
throw "Not implemented: pathExists"
|
||||||
|
},
|
||||||
|
placeholder: (output) => {
|
||||||
|
throw "Not implemented: placeholder"
|
||||||
|
},
|
||||||
|
readDir: (path) => {
|
||||||
|
throw "Not implemented: readDir"
|
||||||
|
},
|
||||||
|
readFile: (path) => {
|
||||||
|
throw "Not implemented: readFile"
|
||||||
|
},
|
||||||
|
readFileType: (path) => {
|
||||||
|
throw "Not implemented: readFileType"
|
||||||
|
},
|
||||||
|
replaceStrings: (from) => (to) => (s) => {
|
||||||
|
throw "Not implemented: replaceStrings"
|
||||||
|
},
|
||||||
|
sort: (cmp) => (list) => {
|
||||||
|
const forced_list = [...force(list)];
|
||||||
|
const forced_cmp = force(cmp);
|
||||||
|
return forced_list.sort((a, b) => {
|
||||||
|
if (force(forced_cmp(a)(b))) return -1;
|
||||||
|
if (force(forced_cmp(b)(a))) return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
split: (regex, str) => {
|
||||||
|
throw "Not implemented: split"
|
||||||
|
},
|
||||||
|
splitVersion: (s) => {
|
||||||
|
throw "Not implemented: splitVersion"
|
||||||
|
},
|
||||||
|
stringLength: (e) => force(e).length,
|
||||||
|
substring: (start) => (len) => (s) => String.prototype.substring.call(force(s), force(start), force(start) + force(len)),
|
||||||
|
tail: (list) => Array.prototype.slice.call(force(list), 1),
|
||||||
|
throw: (s) => {
|
||||||
|
throw force(s);
|
||||||
|
},
|
||||||
|
toFile: (name, s) => {
|
||||||
|
throw "Not implemented: toFile"
|
||||||
|
},
|
||||||
|
toPath: (name, s) => {
|
||||||
|
throw "Not implemented: toPath"
|
||||||
|
},
|
||||||
|
toString: (name, s) => {
|
||||||
|
throw "Not implemented: toString"
|
||||||
|
},
|
||||||
|
trace: (e1, e2) => {
|
||||||
|
console.log(`trace: ${force(e1)}`);
|
||||||
|
return e2;
|
||||||
|
},
|
||||||
|
traceVerbose: (e1, e2) => {
|
||||||
|
throw "Not implemented: traceVerbose"
|
||||||
|
},
|
||||||
|
tryEval: (e1) => (e2) => {
|
||||||
|
throw "Not implemented: tryEval"
|
||||||
|
},
|
||||||
|
warn: (e1) => (e2) => {
|
||||||
|
console.log(`evaluation warning: ${force(e1)}`);
|
||||||
|
return e2;
|
||||||
|
},
|
||||||
|
zipAttrsWith: (f) => (list) => {
|
||||||
|
throw "Not implemented: zipAttrsWith"
|
||||||
|
},
|
||||||
|
|
||||||
|
builtins: create_thunk(() => builtins),
|
||||||
|
currentSystem: create_thunk(() => {
|
||||||
|
throw "Not implemented: currentSystem"
|
||||||
|
}),
|
||||||
|
currentTime: create_thunk(() => Date.now()),
|
||||||
|
[false]: false,
|
||||||
|
[true]: true,
|
||||||
|
[null]: null,
|
||||||
|
langVersion: 6,
|
||||||
|
nixPath: [],
|
||||||
|
nixVersion: "NIX_JS_VERSION",
|
||||||
|
storeDir: "/nix/store",
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -139,10 +468,14 @@ const Nix = (() => {
|
|||||||
force,
|
force,
|
||||||
is_thunk,
|
is_thunk,
|
||||||
create_lazy_set,
|
create_lazy_set,
|
||||||
trace,
|
|
||||||
select,
|
select,
|
||||||
select_with_default,
|
select_with_default,
|
||||||
validate_params,
|
validate_params,
|
||||||
op
|
op,
|
||||||
|
builtins,
|
||||||
|
IS_THUNK // Export the Symbol for Rust to use
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
// Ensure Nix is available on the global object for Rust access
|
||||||
|
globalThis.Nix = Nix;
|
||||||
|
|||||||
Reference in New Issue
Block a user