diff --git a/Cargo.lock b/Cargo.lock index 14a0d32..73873a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -490,6 +490,12 @@ version = "3.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dea2df4cf52843e0452895c455a1a2cfbb842a1e7329671acf418fdc53ed4c59" +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + [[package]] name = "fd-lock" version = "4.0.4" @@ -497,7 +503,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", - "rustix 1.0.8", + "rustix 1.1.3", "windows-sys 0.59.0", ] @@ -627,6 +633,18 @@ dependencies = [ "slab", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + [[package]] name = "glob" version = "0.3.3" @@ -889,9 +907,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.174" +version = "0.2.179" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" +checksum = "c5a2d376baa530d1238d133232d15e239abad80d05838b4b59354e5268af431f" [[package]] name = "libloading" @@ -927,9 +945,9 @@ checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" -version = "0.9.4" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "litemap" @@ -1039,8 +1057,8 @@ dependencies = [ "rnix", "rustyline", "string-interner", + "tempfile", "thiserror", - "v8", ] [[package]] @@ -1210,6 +1228,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "radium" version = "0.7.0" @@ -1347,15 +1371,15 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.8" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys 0.9.4", - "windows-sys 0.60.2", + "linux-raw-sys 0.11.0", + "windows-sys 0.61.2", ] [[package]] @@ -1629,6 +1653,19 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tempfile" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix 1.1.3", + "windows-sys 0.61.2", +] + [[package]] name = "temporal_capi" version = "0.1.2" @@ -1832,6 +1869,15 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.106" @@ -2098,6 +2144,12 @@ version = "0.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + [[package]] name = "writeable" version = "0.6.2" diff --git a/nix-js/Cargo.toml b/nix-js/Cargo.toml index 2ac741c..2d308f3 100644 --- a/nix-js/Cargo.toml +++ b/nix-js/Cargo.toml @@ -18,10 +18,12 @@ thiserror = "2" string-interner = "0.19" itertools = "0.14" -v8 = "142.2" deno_core = "0.376" deno_error = "0.7" rnix = "0.12" nix-js-macros = { path = "../nix-js-macros" } + +[dev-dependencies] +tempfile = "3.24" diff --git a/nix-js/src/bin/eval.rs b/nix-js/src/bin/eval.rs index 2675413..1acf282 100644 --- a/nix-js/src/bin/eval.rs +++ b/nix-js/src/bin/eval.rs @@ -10,7 +10,7 @@ fn main() -> Result<()> { } args.next(); let expr = args.next().unwrap(); - match Context::new().eval(&expr) { + match Context::new().eval_code(&expr) { Ok(value) => { println!("{value}"); Ok(()) diff --git a/nix-js/src/bin/repl.rs b/nix-js/src/bin/repl.rs index f94522d..de7d3bc 100644 --- a/nix-js/src/bin/repl.rs +++ b/nix-js/src/bin/repl.rs @@ -30,7 +30,7 @@ fn main() -> Result<()> { eprintln!("Error: {}", err); } */ } else { - match context.eval(&line) { + match context.eval_code(&line) { Ok(value) => println!("{value}"), Err(err) => eprintln!("Error: {err}"), } diff --git a/nix-js/src/context.rs b/nix-js/src/context.rs index 89ff87d..8cd276f 100644 --- a/nix-js/src/context.rs +++ b/nix-js/src/context.rs @@ -1,3 +1,4 @@ +use std::path::PathBuf; use std::ptr::NonNull; use hashbrown::HashMap; @@ -17,6 +18,7 @@ pub struct Context { irs: Vec, symbols: DefaultStringInterner, global: NonNull>, + path_stack: Vec, } impl Drop for Context { @@ -27,6 +29,32 @@ impl Drop for Context { } } +pub struct PathDropGuard<'ctx> { + ctx: &'ctx mut Context, +} + +impl<'ctx> PathDropGuard<'ctx> { + pub fn new(path: PathBuf, ctx: &'ctx mut Context) -> Self { + ctx.path_stack.push(path); + Self { ctx } + } + pub fn new_cwd(ctx: &'ctx mut Context) -> Self { + let cwd = std::env::current_dir().unwrap(); + let virtual_file = cwd.join("__eval__.nix"); + ctx.path_stack.push(virtual_file); + Self { ctx } + } + pub fn as_ctx(&mut self) -> &mut Context { + self.ctx + } +} + +impl Drop for PathDropGuard<'_> { + fn drop(&mut self) { + self.ctx.path_stack.pop(); + } +} + impl Default for Context { fn default() -> Self { use crate::ir::{Attr, Builtins, Select, ToIr}; @@ -82,6 +110,7 @@ impl Default for Context { symbols, irs, global: unsafe { NonNull::new_unchecked(Box::leak(Box::new(global))) }, + path_stack: Vec::new(), } } } @@ -97,21 +126,26 @@ impl Context { DowngradeCtx::new(self, global_ref) } - pub fn eval(&mut self, expr: &str) -> Result { - // Initialize IMPORT_PATH_STACK with current directory for relative path resolution - let _path_guard = crate::runtime::ImportPathGuard::push_cwd(); + pub fn eval_code(&mut self, expr: &str) -> Result { + // Initialize `path_stack` with current directory for relative path resolution + let mut guard = PathDropGuard::new_cwd(self); + let ctx = guard.as_ctx(); let root = rnix::Root::parse(expr); if !root.errors().is_empty() { return Err(Error::parse_error(root.errors().iter().join("; "))); } - let root = self + let root = ctx .downgrade_ctx() .downgrade(root.tree().expr().unwrap())?; - let code = self.get_ir(root).compile(self); + let code = ctx.get_ir(root).compile(ctx); let code = format!("Nix.force({})", code); println!("[DEBUG] generated code: {}", &code); - crate::runtime::run(code, self) + crate::runtime::run(code, ctx) + } + + pub fn get_current_dir(&self) -> PathBuf { + self.path_stack.last().unwrap().parent().unwrap().to_path_buf() } } @@ -135,32 +169,32 @@ mod test { #[test] fn basic_eval() { assert_eq!( - Context::new().eval("1 + 1").unwrap(), + Context::new().eval_code("1 + 1").unwrap(), Value::Const(Const::Int(2)) ); assert_eq!( - Context::new().eval("(x: x) 1").unwrap(), + Context::new().eval_code("(x: x) 1").unwrap(), Value::Const(Const::Int(1)) ); assert_eq!( - Context::new().eval("(x: y: x - y) 2 1").unwrap(), + Context::new().eval_code("(x: y: x - y) 2 1").unwrap(), Value::Const(Const::Int(1)) ); assert_eq!( - Context::new().eval("rec { b = a; a = 1; }.b").unwrap(), + Context::new().eval_code("rec { b = a; a = 1; }.b").unwrap(), Value::Const(Const::Int(1)) ); assert_eq!( - Context::new().eval("let b = a; a = 1; in b").unwrap(), + Context::new().eval_code("let b = a; a = 1; in b").unwrap(), Value::Const(Const::Int(1)) ); assert_eq!( - Context::new().eval("let fib = n: if n == 1 || n == 2 then 1 else (fib (n - 1)) + (fib (n - 2)); in fib 30").unwrap(), + 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)) ); assert_eq!( Context::new() - .eval("((f: let x = f x; in x)(self: { x = 1; y = self.x + 1; })).y") + .eval_code("((f: let x = f x; in x)(self: { x = 1; y = self.x + 1; })).y") .unwrap(), Value::Const(Const::Int(2)) ); @@ -203,7 +237,7 @@ mod test { ), ]; for (expr, expected) in tests { - assert_eq!(Context::new().eval(expr).unwrap(), expected); + assert_eq!(Context::new().eval_code(expr).unwrap(), expected); } } @@ -212,19 +246,19 @@ mod test { // Test function with required parameters assert_eq!( Context::new() - .eval("({ a, b }: a + b) { a = 1; b = 2; }") + .eval_code("({ a, b }: a + b) { a = 1; b = 2; }") .unwrap(), Value::Const(Const::Int(3)) ); // Test missing required parameter should fail - let result = Context::new().eval("({ a, b }: a + b) { a = 1; }"); + let result = Context::new().eval_code("({ a, b }: a + b) { a = 1; }"); assert!(result.is_err()); // Test all required parameters present assert_eq!( Context::new() - .eval("({ x, y, z }: x + y + z) { x = 1; y = 2; z = 3; }") + .eval_code("({ x, y, z }: x + y + z) { x = 1; y = 2; z = 3; }") .unwrap(), Value::Const(Const::Int(6)) ); @@ -233,13 +267,13 @@ mod test { #[test] fn test_param_check_allowed() { // Test function without ellipsis - should reject unexpected arguments - let result = Context::new().eval("({ a, b }: a + b) { a = 1; b = 2; c = 3; }"); + let result = Context::new().eval_code("({ a, b }: a + b) { a = 1; b = 2; c = 3; }"); assert!(result.is_err()); // Test function with ellipsis - should accept extra arguments assert_eq!( Context::new() - .eval("({ a, b, ... }: a + b) { a = 1; b = 2; c = 3; }") + .eval_code("({ a, b, ... }: a + b) { a = 1; b = 2; c = 3; }") .unwrap(), Value::Const(Const::Int(3)) ); @@ -250,7 +284,7 @@ mod test { // Test function with default parameters assert_eq!( Context::new() - .eval("({ a, b ? 5 }: a + b) { a = 1; }") + .eval_code("({ a, b ? 5 }: a + b) { a = 1; }") .unwrap(), Value::Const(Const::Int(6)) ); @@ -258,7 +292,7 @@ mod test { // Test overriding default parameter assert_eq!( Context::new() - .eval("({ a, b ? 5 }: a + b) { a = 1; b = 10; }") + .eval_code("({ a, b ? 5 }: a + b) { a = 1; b = 10; }") .unwrap(), Value::Const(Const::Int(11)) ); @@ -269,7 +303,7 @@ mod test { // Test function with @ pattern (alias) assert_eq!( Context::new() - .eval("(args@{ a, b }: args.a + args.b) { a = 1; b = 2; }") + .eval_code("(args@{ a, b }: args.a + args.b) { a = 1; b = 2; }") .unwrap(), Value::Const(Const::Int(3)) ); @@ -280,14 +314,14 @@ mod test { // Test simple parameter (no pattern) should not have validation assert_eq!( Context::new() - .eval("(x: x.a + x.b) { a = 1; b = 2; }") + .eval_code("(x: x.a + x.b) { a = 1; b = 2; }") .unwrap(), Value::Const(Const::Int(3)) ); // Simple parameter accepts any argument assert_eq!( - Context::new().eval("(x: x) 42").unwrap(), + Context::new().eval_code("(x: x) 42").unwrap(), Value::Const(Const::Int(42)) ); } @@ -295,7 +329,7 @@ mod test { #[test] fn test_builtins_basic_access() { // Test that builtins identifier is accessible - let result = Context::new().eval("builtins").unwrap(); + let result = Context::new().eval_code("builtins").unwrap(); // Should return an AttrSet with builtin functions assert!(matches!(result, Value::AttrSet(_))); } @@ -303,7 +337,7 @@ mod test { #[test] fn test_builtins_self_reference() { // Test builtins.builtins (self-reference as thunk) - let result = Context::new().eval("builtins.builtins").unwrap(); + let result = Context::new().eval_code("builtins.builtins").unwrap(); assert!(matches!(result, Value::AttrSet(_))); } @@ -311,7 +345,7 @@ mod test { fn test_builtin_function_add() { // Test calling builtin function: builtins.add 1 2 assert_eq!( - Context::new().eval("builtins.add 1 2").unwrap(), + Context::new().eval_code("builtins.add 1 2").unwrap(), Value::Const(Const::Int(3)) ); } @@ -320,7 +354,7 @@ mod 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(), + Context::new().eval_code("builtins.length [1 2 3]").unwrap(), Value::Const(Const::Int(3)) ); } @@ -329,7 +363,7 @@ mod test { fn test_builtin_function_map() { // Test higher-order builtin: map (x: x * 2) [1 2 3] assert_eq!( - Context::new().eval("map (x: x * 2) [1 2 3]").unwrap(), + 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)), @@ -343,7 +377,7 @@ mod test { // Test predicate builtin: builtins.filter (x: x > 1) [1 2 3] assert_eq!( Context::new() - .eval("builtins.filter (x: x > 1) [1 2 3]") + .eval_code("builtins.filter (x: x > 1) [1 2 3]") .unwrap(), Value::List(List::new(vec![ Value::Const(Const::Int(2)), @@ -356,7 +390,7 @@ mod test { fn test_builtin_function_attrnames() { // Test builtins.attrNames { a = 1; b = 2; } let result = Context::new() - .eval("builtins.attrNames { a = 1; b = 2; }") + .eval_code("builtins.attrNames { a = 1; b = 2; }") .unwrap(); // Should return a list of attribute names assert!(matches!(result, Value::List(_))); @@ -370,7 +404,7 @@ mod test { fn test_builtin_function_head() { // Test builtins.head [1 2 3] assert_eq!( - Context::new().eval("builtins.head [1 2 3]").unwrap(), + Context::new().eval_code("builtins.head [1 2 3]").unwrap(), Value::Const(Const::Int(1)) ); } @@ -379,7 +413,7 @@ mod test { fn test_builtin_function_tail() { // Test builtins.tail [1 2 3] assert_eq!( - Context::new().eval("builtins.tail [1 2 3]").unwrap(), + 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)), @@ -392,7 +426,7 @@ mod test { // Test builtins in let binding assert_eq!( Context::new() - .eval("let b = builtins; in b.add 5 3") + .eval_code("let b = builtins; in b.add 5 3") .unwrap(), Value::Const(Const::Int(8)) ); @@ -402,7 +436,7 @@ mod test { fn test_builtin_in_with() { // Test builtins with 'with' expression assert_eq!( - Context::new().eval("with builtins; add 10 20").unwrap(), + Context::new().eval_code("with builtins; add 10 20").unwrap(), Value::Const(Const::Int(30)) ); } @@ -412,7 +446,7 @@ mod test { // Test nested function calls with builtins assert_eq!( Context::new() - .eval("builtins.add (builtins.mul 2 3) (builtins.sub 10 5)") + .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 ); @@ -422,23 +456,23 @@ mod test { fn test_builtin_type_checks() { // Test type checking functions assert_eq!( - Context::new().eval("builtins.isList [1 2 3]").unwrap(), + Context::new().eval_code("builtins.isList [1 2 3]").unwrap(), Value::Const(Const::Bool(true)) ); assert_eq!( - Context::new().eval("builtins.isAttrs { a = 1; }").unwrap(), + Context::new().eval_code("builtins.isAttrs { a = 1; }").unwrap(), Value::Const(Const::Bool(true)) ); assert_eq!( - Context::new().eval("builtins.isFunction (x: x)").unwrap(), + Context::new().eval_code("builtins.isFunction (x: x)").unwrap(), Value::Const(Const::Bool(true)) ); assert_eq!( - Context::new().eval("builtins.isNull null").unwrap(), + Context::new().eval_code("builtins.isNull null").unwrap(), Value::Const(Const::Bool(true)) ); assert_eq!( - Context::new().eval("builtins.isBool true").unwrap(), + Context::new().eval_code("builtins.isBool true").unwrap(), Value::Const(Const::Bool(true)) ); } @@ -448,7 +482,7 @@ mod test { // 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") + .eval_code("let builtins = { add = x: y: x - y; }; in builtins.add 5 3") .unwrap(), Value::Const(Const::Int(2)) // Uses shadowed version ); @@ -459,7 +493,7 @@ mod test { // Test that builtins.builtins is lazy (thunk) // This should not cause infinite recursion let result = Context::new() - .eval("builtins.builtins.builtins.add 1 1") + .eval_code("builtins.builtins.builtins.add 1 1") .unwrap(); assert_eq!(result, Value::Const(Const::Int(2))); } @@ -468,7 +502,7 @@ mod test { #[test] fn test_free_global_true() { assert_eq!( - Context::new().eval("true").unwrap(), + Context::new().eval_code("true").unwrap(), Value::Const(Const::Bool(true)) ); } @@ -476,7 +510,7 @@ mod test { #[test] fn test_free_global_false() { assert_eq!( - Context::new().eval("false").unwrap(), + Context::new().eval_code("false").unwrap(), Value::Const(Const::Bool(false)) ); } @@ -484,7 +518,7 @@ mod test { #[test] fn test_free_global_null() { assert_eq!( - Context::new().eval("null").unwrap(), + Context::new().eval_code("null").unwrap(), Value::Const(Const::Null) ); } @@ -493,7 +527,7 @@ mod 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(), + 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)), @@ -506,11 +540,11 @@ mod test { fn test_free_global_isnull() { // Test isNull function assert_eq!( - Context::new().eval("isNull null").unwrap(), + Context::new().eval_code("isNull null").unwrap(), Value::Const(Const::Bool(true)) ); assert_eq!( - Context::new().eval("isNull 5").unwrap(), + Context::new().eval_code("isNull 5").unwrap(), Value::Const(Const::Bool(false)) ); } @@ -519,12 +553,12 @@ mod test { fn test_free_global_shadowing() { // Test shadowing of free globals assert_eq!( - Context::new().eval("let true = false; in true").unwrap(), + Context::new().eval_code("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") + .eval_code("let map = x: y: x; in map 1 2") .unwrap(), Value::Const(Const::Int(1)) ); @@ -535,7 +569,7 @@ mod test { // Test mixing free globals in expressions assert_eq!( Context::new() - .eval("if true then map (x: x + 1) [1 2] else []") + .eval_code("if true then map (x: x + 1) [1 2] else []") .unwrap(), Value::List(List::new(vec![ Value::Const(Const::Int(2)), @@ -549,7 +583,7 @@ mod test { // Test free globals in let bindings assert_eq!( Context::new() - .eval("let x = true; y = false; in x && y") + .eval_code("let x = true; y = false; in x && y") .unwrap(), Value::Const(Const::Bool(false)) ); @@ -562,20 +596,20 @@ mod test { // Test large i64 values assert_eq!( - ctx.eval("9223372036854775807").unwrap(), + ctx.eval_code("9223372036854775807").unwrap(), Value::Const(Const::Int(9223372036854775807)) ); // Test negative large value // Can't use -9223372036854775808 since unary minus is actually desugared to (0 - ) assert_eq!( - ctx.eval("-9223372036854775807").unwrap(), + ctx.eval_code("-9223372036854775807").unwrap(), Value::Const(Const::Int(-9223372036854775807)) ); // Test large number arithmetic assert_eq!( - ctx.eval("5000000000000000000 + 3000000000000000000") + ctx.eval_code("5000000000000000000 + 3000000000000000000") .unwrap(), Value::Const(Const::Int(8000000000000000000i64)) ); @@ -587,45 +621,45 @@ mod test { // isInt tests assert_eq!( - ctx.eval("builtins.isInt 42").unwrap(), + ctx.eval_code("builtins.isInt 42").unwrap(), Value::Const(Const::Bool(true)) ); assert_eq!( - ctx.eval("builtins.isInt 42.0").unwrap(), + ctx.eval_code("builtins.isInt 42.0").unwrap(), Value::Const(Const::Bool(false)) ); // isFloat tests assert_eq!( - ctx.eval("builtins.isFloat 42").unwrap(), + ctx.eval_code("builtins.isFloat 42").unwrap(), Value::Const(Const::Bool(false)) ); assert_eq!( - ctx.eval("builtins.isFloat 42.5").unwrap(), + ctx.eval_code("builtins.isFloat 42.5").unwrap(), Value::Const(Const::Bool(true)) ); assert_eq!( - ctx.eval("builtins.isFloat 1.0").unwrap(), + ctx.eval_code("builtins.isFloat 1.0").unwrap(), Value::Const(Const::Bool(true)) ); // typeOf tests assert_eq!( - ctx.eval("builtins.typeOf 1").unwrap(), + ctx.eval_code("builtins.typeOf 1").unwrap(), Value::String("int".to_string()) ); assert_eq!( - ctx.eval("builtins.typeOf 1.0").unwrap(), + ctx.eval_code("builtins.typeOf 1.0").unwrap(), Value::String("float".to_string()) ); assert_eq!( - ctx.eval("builtins.typeOf 3.14").unwrap(), + ctx.eval_code("builtins.typeOf 3.14").unwrap(), Value::String("float".to_string()) ); // literal tests - assert_eq!(ctx.eval("1").unwrap(), Value::Const(Const::Int(1))); - assert_eq!(ctx.eval("1.").unwrap(), Value::Const(Const::Float(1.))) + assert_eq!(ctx.eval_code("1").unwrap(), Value::Const(Const::Int(1))); + assert_eq!(ctx.eval_code("1.").unwrap(), Value::Const(Const::Float(1.))) } #[test] @@ -634,25 +668,25 @@ mod test { // int + int = int assert_eq!( - ctx.eval("builtins.typeOf (1 + 2)").unwrap(), + ctx.eval_code("builtins.typeOf (1 + 2)").unwrap(), Value::String("int".to_string()) ); // int + float = float assert_eq!( - ctx.eval("builtins.typeOf (1 + 2.0)").unwrap(), + ctx.eval_code("builtins.typeOf (1 + 2.0)").unwrap(), Value::String("float".to_string()) ); // int * int = int assert_eq!( - ctx.eval("builtins.typeOf (3 * 4)").unwrap(), + ctx.eval_code("builtins.typeOf (3 * 4)").unwrap(), Value::String("int".to_string()) ); // int * float = float assert_eq!( - ctx.eval("builtins.typeOf (3 * 4.0)").unwrap(), + ctx.eval_code("builtins.typeOf (3 * 4.0)").unwrap(), Value::String("float".to_string()) ); } @@ -661,24 +695,24 @@ mod test { fn test_integer_division() { let mut ctx = Context::new(); - assert_eq!(ctx.eval("5 / 2").unwrap(), Value::Const(Const::Int(2))); + assert_eq!(ctx.eval_code("5 / 2").unwrap(), Value::Const(Const::Int(2))); - assert_eq!(ctx.eval("7 / 3").unwrap(), Value::Const(Const::Int(2))); + assert_eq!(ctx.eval_code("7 / 3").unwrap(), Value::Const(Const::Int(2))); - assert_eq!(ctx.eval("10 / 3").unwrap(), Value::Const(Const::Int(3))); + assert_eq!(ctx.eval_code("10 / 3").unwrap(), Value::Const(Const::Int(3))); // Float division returns float assert_eq!( - ctx.eval("5 / 2.0").unwrap(), + ctx.eval_code("5 / 2.0").unwrap(), Value::Const(Const::Float(2.5)) ); assert_eq!( - ctx.eval("7.0 / 2").unwrap(), + ctx.eval_code("7.0 / 2").unwrap(), Value::Const(Const::Float(3.5)) ); - assert_eq!(ctx.eval("(-7) / 3").unwrap(), Value::Const(Const::Int(-2))); + assert_eq!(ctx.eval_code("(-7) / 3").unwrap(), Value::Const(Const::Int(-2))); } #[test] @@ -687,101 +721,66 @@ mod test { // Test builtin add with large numbers assert_eq!( - ctx.eval("builtins.add 5000000000000000000 3000000000000000000") + ctx.eval_code("builtins.add 5000000000000000000 3000000000000000000") .unwrap(), Value::Const(Const::Int(8000000000000000000i64)) ); // Test builtin mul with large numbers assert_eq!( - ctx.eval("builtins.mul 1000000000 1000000000").unwrap(), + ctx.eval_code("builtins.mul 1000000000 1000000000").unwrap(), Value::Const(Const::Int(1000000000000000000i64)) ); } #[test] fn test_import_absolute_path() { - use std::io::Write; - let mut ctx = Context::new(); - // Create temporary file - let temp_dir = std::env::temp_dir(); - let lib_path = temp_dir.join("nix_test_lib.nix"); + let temp_dir = tempfile::tempdir().unwrap(); + let lib_path = temp_dir.path().join("nix_test_lib.nix"); - let mut file = std::fs::File::create(&lib_path).unwrap(); - file.write_all(b"{ add = a: b: a + b; }").unwrap(); - drop(file); + std::fs::write(&lib_path, "{ add = a: b: a + b; }").unwrap(); - // Test import with absolute path string let expr = format!(r#"(import "{}").add 3 5"#, lib_path.display()); - assert_eq!(ctx.eval(&expr).unwrap(), Value::Const(Const::Int(8))); - - // Cleanup - std::fs::remove_file(&lib_path).ok(); + assert_eq!(ctx.eval_code(&expr).unwrap(), Value::Const(Const::Int(8))); } #[test] fn test_import_nested() { - use std::io::Write; - let mut ctx = Context::new(); - // Create temporary directory structure - let temp_dir = std::env::temp_dir().join("nix_test_nested"); - std::fs::create_dir_all(&temp_dir).unwrap(); + let temp_dir = tempfile::tempdir().unwrap(); - // Create lib.nix - let lib_path = temp_dir.join("lib.nix"); - let mut file = std::fs::File::create(&lib_path).unwrap(); - file.write_all(b"{ add = a: b: a + b; }").unwrap(); - drop(file); + let lib_path = temp_dir.path().join("lib.nix"); + std::fs::write(&lib_path, "{ add = a: b: a + b; }").unwrap(); - // Create main.nix that imports lib.nix - let main_path = temp_dir.join("main.nix"); + let main_path = temp_dir.path().join("main.nix"); let main_content = format!( r#"let lib = import {}; in {{ result = lib.add 10 20; }}"#, lib_path.display() ); - let mut file = std::fs::File::create(&main_path).unwrap(); - file.write_all(main_content.as_bytes()).unwrap(); - drop(file); + std::fs::write(&main_path, main_content).unwrap(); - // Test nested import let expr = format!(r#"(import "{}").result"#, main_path.display()); - assert_eq!(ctx.eval(&expr).unwrap(), Value::Const(Const::Int(30))); - - // Cleanup - std::fs::remove_file(&lib_path).ok(); - std::fs::remove_file(&main_path).ok(); - std::fs::remove_dir(&temp_dir).ok(); + assert_eq!(ctx.eval_code(&expr).unwrap(), Value::Const(Const::Int(30))); } #[test] fn test_import_relative_path() { - use std::io::Write; - let mut ctx = Context::new(); - // Create temporary directory structure - let temp_dir = std::env::temp_dir().join("nix_test_relative"); - let subdir = temp_dir.join("subdir"); + let temp_dir = tempfile::tempdir().unwrap(); + let subdir = temp_dir.path().join("subdir"); std::fs::create_dir_all(&subdir).unwrap(); - // Create lib.nix - let lib_path = temp_dir.join("lib.nix"); - let mut file = std::fs::File::create(&lib_path).unwrap(); - file.write_all(b"{ multiply = a: b: a * b; }").unwrap(); - drop(file); + let lib_path = temp_dir.path().join("lib.nix"); + std::fs::write(&lib_path, "{ multiply = a: b: a * b; }").unwrap(); - // Create subdir/helper.nix let helper_path = subdir.join("helper.nix"); - let mut file = std::fs::File::create(&helper_path).unwrap(); - file.write_all(b"{ subtract = a: b: a - b; }").unwrap(); - drop(file); + std::fs::write(&helper_path, "{ subtract = a: b: a - b; }").unwrap(); - // Create main.nix with relative path imports - let main_path = temp_dir.join("main.nix"); + let main_path = temp_dir.path().join("main.nix"); let main_content = r#" let lib = import ./lib.nix; @@ -791,44 +790,24 @@ in { result2 = helper.subtract 10 3; } "#; - let mut file = std::fs::File::create(&main_path).unwrap(); - file.write_all(main_content.as_bytes()).unwrap(); - drop(file); + std::fs::write(&main_path, main_content).unwrap(); - // Test relative path imports let expr = format!(r#"let x = import "{}"; in x.result1"#, main_path.display()); - assert_eq!(ctx.eval(&expr).unwrap(), Value::Const(Const::Int(12))); + assert_eq!(ctx.eval_code(&expr).unwrap(), Value::Const(Const::Int(12))); let expr = format!(r#"let x = import "{}"; in x.result2"#, main_path.display()); - assert_eq!(ctx.eval(&expr).unwrap(), Value::Const(Const::Int(7))); - - // Cleanup - std::fs::remove_file(&lib_path).ok(); - std::fs::remove_file(&helper_path).ok(); - std::fs::remove_file(&main_path).ok(); - std::fs::remove_dir(&subdir).ok(); - std::fs::remove_dir(&temp_dir).ok(); + assert_eq!(ctx.eval_code(&expr).unwrap(), Value::Const(Const::Int(7))); } #[test] fn test_import_returns_function() { - use std::io::Write; - let mut ctx = Context::new(); - // Create temporary file that exports a function - let temp_dir = std::env::temp_dir(); - let func_path = temp_dir.join("nix_test_func.nix"); + let temp_dir = tempfile::tempdir().unwrap(); + let func_path = temp_dir.path().join("nix_test_func.nix"); + std::fs::write(&func_path, "x: x * 2").unwrap(); - let mut file = std::fs::File::create(&func_path).unwrap(); - file.write_all(b"x: x * 2").unwrap(); - drop(file); - - // Test importing a function let expr = format!(r#"(import "{}") 5"#, func_path.display()); - assert_eq!(ctx.eval(&expr).unwrap(), Value::Const(Const::Int(10))); - - // Cleanup - std::fs::remove_file(&func_path).ok(); + assert_eq!(ctx.eval_code(&expr).unwrap(), Value::Const(Const::Int(10))); } } diff --git a/nix-js/src/context/downgrade.rs b/nix-js/src/context/downgrade.rs index 6afe43d..851298b 100644 --- a/nix-js/src/context/downgrade.rs +++ b/nix-js/src/context/downgrade.rs @@ -164,4 +164,8 @@ impl DowngradeContext for DowngradeCtx<'_> { let mut guard = ScopeGuard { ctx: self }; f(guard.as_ctx()) } + + fn get_current_dir(&self) -> std::path::PathBuf { + self.ctx.get_current_dir() + } } diff --git a/nix-js/src/ir.rs b/nix-js/src/ir.rs index 389e3f7..4b75333 100644 --- a/nix-js/src/ir.rs +++ b/nix-js/src/ir.rs @@ -37,6 +37,8 @@ pub trait DowngradeContext { fn with_with_scope(&mut self, namespace: ExprId, f: F) -> R where F: FnOnce(&mut Self) -> R; + + fn get_current_dir(&self) -> std::path::PathBuf; } ir! { diff --git a/nix-js/src/ir/downgrade.rs b/nix-js/src/ir/downgrade.rs index 8c0581e..d042847 100644 --- a/nix-js/src/ir/downgrade.rs +++ b/nix-js/src/ir/downgrade.rs @@ -55,14 +55,13 @@ impl Downgrade for ast::IfElse { impl Downgrade for ast::Path { fn downgrade(self, ctx: &mut Ctx) -> Result { - // Collect all parts and check if there are any interpolations let parts_ast: Vec<_> = self.parts().collect(); let has_interpolation = parts_ast .iter() .any(|part| matches!(part, ast::InterpolPart::Interpolation(_))); let parts = if !has_interpolation { - // Pure literal path - resolve at compile time + // Resolve at compile time let path_str: String = parts_ast .into_iter() .filter_map(|part| match part { @@ -71,20 +70,10 @@ impl Downgrade for ast::Path { }) .collect(); - // Resolve relative paths at compile time let resolved_path = if path_str.starts_with('/') { - // Absolute path - use as is path_str } else { - // Relative path - resolve against current file directory - let current_dir = crate::runtime::IMPORT_PATH_STACK.with(|stack| { - stack - .borrow() - .last() - .and_then(|p| p.parent()) - .map(|p| p.to_path_buf()) - .unwrap_or_else(|| std::env::current_dir().unwrap()) - }); + let current_dir = ctx.get_current_dir(); current_dir .join(&path_str) @@ -99,16 +88,13 @@ impl Downgrade for ast::Path { .to_string() }; - // Return single string part with resolved path vec![ctx.new_expr(Str { val: resolved_path }.to_ir())] } else { - // Path with interpolation - do NOT resolve at compile time - // Keep literal parts as-is and defer resolution to runtime + // Resolve at runtime parts_ast .into_iter() .map(|part| match part { ast::InterpolPart::Literal(lit) => { - // Keep literal as-is (don't resolve) Ok(ctx.new_expr( Str { val: lit.to_string(), @@ -183,9 +169,7 @@ impl Downgrade for ast::AttrSet { } // rec { a = 1; b = a; } => let a = 1; b = a; in { inherit a b; } - let entries: Vec<_> = self.entries().collect(); - let (bindings, body) = downgrade_let_bindings(entries, ctx, |ctx, binding_keys| { // Create plain attrset as body with inherit let mut attrs = AttrSet { @@ -201,7 +185,6 @@ impl Downgrade for ast::AttrSet { Ok(ctx.new_expr(attrs.to_ir())) })?; - // Create Let expression Ok(ctx.new_expr(Let { bindings, body }.to_ir())) } } diff --git a/nix-js/src/runtime.rs b/nix-js/src/runtime.rs index 7ab3c7e..4a59b5d 100644 --- a/nix-js/src/runtime.rs +++ b/nix-js/src/runtime.rs @@ -1,81 +1,47 @@ +use std::borrow::Cow; use std::cell::RefCell; -use std::path::PathBuf; use std::ptr::NonNull; use std::sync::Once; -use deno_core::{JsRuntime, RuntimeOptions}; +use deno_core::{ + Extension, ExtensionFileSource, JsRuntime, OpState, RuntimeOptions, v8 +}; use deno_error::js_error_wrapper; use crate::codegen::{CodegenContext, Compile}; -use crate::context::Context; +use crate::context::{Context, PathDropGuard}; use crate::error::{Error, Result}; use crate::ir::DowngradeContext; use crate::value::{AttrSet, Const, List, Symbol, Value}; -static INIT: Once = Once::new(); - -thread_local! { - static CONTEXT_HOLDER: RefCell>> = const { RefCell::new(None) }; +pub trait RuntimeContext { + fn split(&mut self) -> (&mut JsRuntime, &mut Ctx); } -// for relative path resolution -thread_local! { - pub(crate) static IMPORT_PATH_STACK: RefCell> = const { RefCell::new(Vec::new()) }; -} +fn nix_runtime(ctx: &mut Context) -> Extension { + const ESM: &[ExtensionFileSource] = + &deno_core::include_js_files!(nix_runtime dir "runtime-ts/dist", "runtime.js"); -struct ContextGuard; - -impl ContextGuard { - fn set(ctx: &mut Context) -> Self { - CONTEXT_HOLDER.with(|holder| { - let ptr = NonNull::new(ctx as *mut Context).unwrap(); - *holder.borrow_mut() = Some(ptr); - }); - Self + // TODO: SAFETY + let ptr = unsafe { NonNull::new_unchecked(ctx) }; + Extension { + name: "nix_runtime", + esm_files: Cow::Borrowed(ESM), + esm_entry_point: Some("ext:nix_runtime/runtime.js"), + ops: Cow::Owned(vec![ + op_import(), + op_read_file(), + op_path_exists(), + op_resolve_path(), + ]), + op_state_fn: Some(Box::new(move |state| { + state.put(RefCell::new(ptr)); + })), + enabled: true, + ..Default::default() } } -impl Drop for ContextGuard { - fn drop(&mut self) { - CONTEXT_HOLDER.with(|holder| { - *holder.borrow_mut() = None; - }); - } -} - -pub struct ImportPathGuard; - -impl ImportPathGuard { - pub fn push_cwd() -> Self { - // Push a virtual file path in cwd so .parent() returns cwd - let cwd = std::env::current_dir().unwrap(); - let virtual_file = cwd.join("__eval__.nix"); - IMPORT_PATH_STACK.with(|stack| stack.borrow_mut().push(virtual_file)); - Self - } - - pub fn push(path: PathBuf) -> Self { - IMPORT_PATH_STACK.with(|stack| stack.borrow_mut().push(path)); - Self - } -} - -impl Drop for ImportPathGuard { - fn drop(&mut self) { - IMPORT_PATH_STACK.with(|stack| stack.borrow_mut().pop()); - } -} - -// injects to Deno.core.ops -deno_core::extension!( - nix_ops, - ops = [op_import, op_read_file, op_path_exists, op_resolve_path] -); - -fn nix_extension() -> deno_core::Extension { - nix_ops::init() -} - #[derive(Debug)] pub struct SimpleErrorWrapper(pub String); @@ -107,62 +73,51 @@ js_error_wrapper!(SimpleErrorWrapper, NixError, "EvalError"); #[deno_core::op2] #[string] -fn op_import(#[string] path: String) -> std::result::Result { - CONTEXT_HOLDER.with(|holder| { - let mut ptr = holder - .borrow() - .ok_or_else(|| -> NixError { "No context available".to_string().into() })?; - let ctx = unsafe { ptr.as_mut() }; +fn op_import(state: &mut OpState, #[string] path: String) -> std::result::Result { + let mut ptr = state.borrow::>>().borrow_mut(); + let ctx = unsafe { ptr.as_mut() }; - // 1. Resolve path relative to current file (or CWD if top-level) - let current_dir = IMPORT_PATH_STACK.with(|stack| { - stack - .borrow() - .last() - .and_then(|p| p.parent()) - .map(|p| p.to_path_buf()) - .unwrap_or_else(|| std::env::current_dir().unwrap()) - }); - - let absolute_path = current_dir - .join(&path) - .canonicalize() - .map_err(|e| -> NixError { - format!("Failed to resolve path {}: {}", path, e).into() - })?; - - // 2. Push to stack for nested imports (RAII guard ensures pop on drop) - let _guard = ImportPathGuard::push(absolute_path.clone()); - - // 3. Read file - let content = std::fs::read_to_string(&absolute_path).map_err(|e| -> NixError { - format!("Failed to read {}: {}", absolute_path.display(), e).into() + // 1. Resolve path relative to current file (or CWD if top-level) + let current_dir = ctx.get_current_dir(); + let absolute_path = current_dir + .join(&path) + .canonicalize() + .map_err(|e| -> NixError { + format!("Failed to resolve path {}: {}", path, e).into() })?; - // 4. Parse - let root = rnix::Root::parse(&content); - if !root.errors().is_empty() { - return Err(format!( - "Parse error in {}: {:?}", - absolute_path.display(), - root.errors() - ) - .into()); - } + // 2. Psh to stack for nested imports (RAII guard ensures pop on drop) + let mut guard = PathDropGuard::new(absolute_path.clone(), ctx); + let ctx = guard.as_ctx(); - // 5. Downgrade to IR - let expr = root - .tree() - .expr() - .ok_or_else(|| -> NixError { "No expression in file".to_string().into() })?; - let expr_id = ctx - .downgrade_ctx() - .downgrade(expr) - .map_err(|e| -> NixError { format!("Downgrade error: {}", e).into() })?; + // 3. Read file + let content = std::fs::read_to_string(&absolute_path).map_err(|e| -> NixError { + format!("Failed to read {}: {}", absolute_path.display(), e).into() + })?; - // 6. Codegen - returns JS code string - Ok(ctx.get_ir(expr_id).compile(ctx)) - }) + // 4. Parse + let root = rnix::Root::parse(&content); + if !root.errors().is_empty() { + return Err(format!( + "Parse error in {}: {:?}", + absolute_path.display(), + root.errors() + ) + .into()); + } + + // 5. Downgrade to IR + let expr = root + .tree() + .expr() + .ok_or_else(|| -> NixError { "No expression in file".to_string().into() })?; + let expr_id = ctx + .downgrade_ctx() + .downgrade(expr) + .map_err(|e| -> NixError { format!("Downgrade error: {}", e).into() })?; + + // 6. Codegen - returns JS code string + Ok(ctx.get_ir(expr_id).compile(ctx)) } #[deno_core::op2] @@ -179,21 +134,17 @@ fn op_path_exists(#[string] path: String) -> bool { #[deno_core::op2] #[string] -fn op_resolve_path(#[string] path: String) -> std::result::Result { +fn op_resolve_path(state: &mut OpState, #[string] path: String) -> std::result::Result { + let ptr = state.borrow::>>().borrow(); + let ctx = unsafe { ptr.as_ref() }; + // If already absolute, return as-is if path.starts_with('/') { return Ok(path); } // Resolve relative path against current file directory (or CWD) - let current_dir = IMPORT_PATH_STACK.with(|stack| { - stack - .borrow() - .last() - .and_then(|p| p.parent()) - .map(|p| p.to_path_buf()) - .unwrap_or_else(|| std::env::current_dir().unwrap()) - }); + let current_dir = ctx.get_current_dir(); current_dir .join(&path) @@ -203,13 +154,13 @@ fn op_resolve_path(#[string] path: String) -> std::result::Result { +struct RuntimeCtx<'a, 'b> { scope: &'a v8::PinnedRef<'a, v8::HandleScope<'b>>, is_thunk_symbol: Option>, primop_metadata_symbol: Option>, } -impl<'a, 'b> RuntimeContext<'a, 'b> { +impl<'a, 'b> RuntimeCtx<'a, 'b> { fn new(scope: &'a v8::PinnedRef<'a, v8::HandleScope<'b>>) -> Self { let is_thunk_symbol = Self::get_is_thunk_symbol(scope); let primop_metadata_symbol = Self::get_primop_metadata_symbol(scope); @@ -255,11 +206,9 @@ impl<'a, 'b> RuntimeContext<'a, 'b> { } } -// Main entry point -pub fn run(script: String, ctx: &mut Context) -> Result { - let _guard = ContextGuard::set(ctx); - +pub fn new_js_runtime(ctx: &mut Context) -> JsRuntime { // Initialize V8 once + static INIT: Once = Once::new(); INIT.call_once(|| { JsRuntime::init_platform( Some(v8::new_default_platform(0, false).make_shared()), @@ -267,31 +216,30 @@ pub fn run(script: String, ctx: &mut Context) -> Result { ); }); - // Create a new JsRuntime for each evaluation to avoid state issues - let mut runtime = JsRuntime::new(RuntimeOptions { - extensions: vec![nix_extension()], + JsRuntime::new(RuntimeOptions { + extensions: vec![nix_runtime(ctx)], ..Default::default() - }); + }) +} - // Load runtime.js - let runtime_code = include_str!("../runtime-ts/dist/runtime.js"); - runtime - .execute_script("", runtime_code) - .map_err(|e| Error::eval_error(format!("Failed to load runtime: {:?}", e)))?; +// Main entry point +pub fn run(script: String, ctx: &mut Context) -> Result { + let mut runtime = new_js_runtime(ctx); // Execute user script let global_value = runtime .execute_script("", script) .map_err(|e| Error::eval_error(format!("Execution error: {:?}", e)))?; + // Retrieve scope from JsRuntime deno_core::scope!(scope, runtime); let local_value = v8::Local::new(scope, &global_value); - let runtime_ctx = RuntimeContext::new(scope); + let runtime_ctx = RuntimeCtx::new(scope); Ok(to_value(local_value, &runtime_ctx)) } -fn to_value<'a, 'b>(val: v8::Local<'a, v8::Value>, ctx: &RuntimeContext<'a, 'b>) -> Value { +fn to_value<'a, 'b>(val: v8::Local<'a, v8::Value>, ctx: &RuntimeCtx<'a, 'b>) -> Value { let scope = ctx.scope; match () { _ if val.is_big_int() => { @@ -357,7 +305,7 @@ fn to_value<'a, 'b>(val: v8::Local<'a, v8::Value>, ctx: &RuntimeContext<'a, 'b>) } } -fn is_thunk<'a, 'b>(val: v8::Local<'a, v8::Value>, ctx: &RuntimeContext<'a, 'b>) -> bool { +fn is_thunk<'a, 'b>(val: v8::Local<'a, v8::Value>, ctx: &RuntimeCtx<'a, 'b>) -> bool { if !val.is_object() { return false; } @@ -376,7 +324,7 @@ fn is_thunk<'a, 'b>(val: v8::Local<'a, v8::Value>, ctx: &RuntimeContext<'a, 'b>) /// Check if a function is a primop fn primop_name<'a, 'b>( val: v8::Local<'a, v8::Value>, - ctx: &RuntimeContext<'a, 'b>, + ctx: &RuntimeCtx<'a, 'b>, ) -> Option { if !val.is_function() { return None; @@ -402,7 +350,7 @@ fn primop_name<'a, 'b>( /// Check if a primop is partially applied (has applied > 0) fn primop_app_name<'a, 'b>( val: v8::Local<'a, v8::Value>, - ctx: &RuntimeContext<'a, 'b>, + ctx: &RuntimeCtx<'a, 'b>, ) -> Option { let name = primop_name(val, ctx)?;