feat: builtins

This commit is contained in:
2026-01-02 16:29:17 +08:00
parent bd77cde867
commit d2ed3935ca
9 changed files with 757 additions and 52 deletions

View File

@@ -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}"))
}, }
} }
} }

View File

@@ -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}]")
} }
} }

View File

@@ -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))
);
}
} }

View File

@@ -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> {

View File

@@ -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;

View File

@@ -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(),
);
} }
} }

View File

@@ -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};

View File

@@ -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![

View File

@@ -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];
@@ -134,15 +126,356 @@ const Nix = (() => {
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 {
create_thunk, create_thunk,
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;