diff --git a/.gitignore b/.gitignore index fc5394b..627929b 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,4 @@ profile.json.gz prof.json *.cpuprofile *.cpuprofile.gz -*v8.log +*v8.log* diff --git a/Cargo.lock b/Cargo.lock index 6318f83..9be080f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1986,7 +1986,7 @@ dependencies = [ [[package]] name = "nix-compat" version = "0.1.0" -source = "git+https://git.snix.dev/snix/snix.git#db30e92b30e18ca4d813206ac1b60d1f670adb8c" +source = "git+https://git.snix.dev/snix/snix.git#1b37f68842a7e5e226d9dc009e9a90d400c5fb14" dependencies = [ "bitflags", "bstr", @@ -2009,7 +2009,7 @@ dependencies = [ [[package]] name = "nix-compat-derive" version = "0.1.0" -source = "git+https://git.snix.dev/snix/snix.git#db30e92b30e18ca4d813206ac1b60d1f670adb8c" +source = "git+https://git.snix.dev/snix/snix.git#1b37f68842a7e5e226d9dc009e9a90d400c5fb14" dependencies = [ "proc-macro2", "quote", @@ -3271,9 +3271,9 @@ dependencies = [ [[package]] name = "syn-match" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "783c4140d7ed89f37116e865b49e5a9fdd28608b9071a9dd1e158b50fc0a31fc" +checksum = "54b8f0a9004d6aafa6a588602a1119e6cdaacec9921aa1605383e6e7d6258fd6" dependencies = [ "proc-macro2", "quote", diff --git a/nix-js/runtime-ts/src/builtins/attrs.ts b/nix-js/runtime-ts/src/builtins/attrs.ts index a7721c8..c975eea 100644 --- a/nix-js/runtime-ts/src/builtins/attrs.ts +++ b/nix-js/runtime-ts/src/builtins/attrs.ts @@ -154,6 +154,6 @@ export const unsafeGetAttrPos = return null; } - const span = positions.get(name) as string; + const span = positions.get(name) as number; return mkPos(span); }; diff --git a/nix-js/runtime-ts/src/helpers.ts b/nix-js/runtime-ts/src/helpers.ts index 78df675..cafa473 100644 --- a/nix-js/runtime-ts/src/helpers.ts +++ b/nix-js/runtime-ts/src/helpers.ts @@ -8,7 +8,7 @@ import type { NixAttrs, NixBool, NixPath, NixString, NixValue } from "./types"; import { CatchableError, isNixPath } from "./types"; interface StackFrame { - span: string; + span: number; message: string; } @@ -32,7 +32,7 @@ function enrichError(error: unknown): Error { return err; } -const pushContext = (message: string, span: string): void => { +const pushContext = (message: string, span: number): void => { if (callStack.length >= MAX_STACK_DEPTH) { callStack.shift(); } @@ -43,7 +43,7 @@ const popContext = (): void => { callStack.pop(); }; -export const withContext = (message: string, span: string, fn: () => T): T => { +export const withContext = (message: string, span: number, fn: () => T): T => { pushContext(message, span); try { return fn(); @@ -142,8 +142,8 @@ export const resolvePath = (currentDir: string, path: NixValue): NixPath => { return mkPath(resolved); }; -export const select = (obj: NixValue, attrpath: NixValue[], span?: string): NixValue => { - if (span) { +export const select = (obj: NixValue, attrpath: NixValue[], span?: number): NixValue => { + if (span !== undefined) { if (callStack.length >= MAX_STACK_DEPTH) { callStack.shift(); } @@ -190,9 +190,9 @@ export const selectWithDefault = ( obj: NixValue, attrpath: NixValue[], defaultVal: NixValue, - span?: string, + span?: number, ): NixValue => { - if (span) { + if (span !== undefined) { if (callStack.length >= MAX_STACK_DEPTH) { callStack.shift(); } @@ -263,8 +263,8 @@ export const hasAttr = (obj: NixValue, attrpath: NixValue[]): NixBool => { return attrs.has(forceStringValue(attrpath[attrpath.length - 1])); }; -export const call = (func: NixValue, arg: NixValue, span?: string): NixValue => { - if (span) { +export const call = (func: NixValue, arg: NixValue, span?: number): NixValue => { + if (span !== undefined) { if (callStack.length >= MAX_STACK_DEPTH) { callStack.shift(); } @@ -282,19 +282,19 @@ export const call = (func: NixValue, arg: NixValue, span?: string): NixValue => }; function callImpl(func: NixValue, arg: NixValue): NixValue { - const forcedFunc = force(func); - if (typeof forcedFunc === "function") { - forcedFunc.args?.check(arg); - return forcedFunc(arg); + const forced = force(func); + if (typeof forced === "function") { + forced.args?.check(arg); + return forced(arg); } - if (forcedFunc instanceof Map && forcedFunc.has("__functor")) { - const functor = forceFunction(forcedFunc.get("__functor") as NixValue); - return call(functor(forcedFunc), arg); + if (forced instanceof Map && forced.has("__functor")) { + const functor = forceFunction(forced.get("__functor") as NixValue); + return call(callImpl(functor, forced), arg); } - throw new Error(`attempt to call something which is not a function but ${typeOf(forcedFunc)}`); + throw new Error(`attempt to call something which is not a function but ${typeOf(forced)}`); } -export const assert = (assertion: NixValue, expr: NixValue, assertionRaw: string, span: string): NixValue => { +export const assert = (assertion: NixValue, expr: NixValue, assertionRaw: string, span: number): NixValue => { if (forceBool(assertion)) { return expr; } @@ -304,14 +304,7 @@ export const assert = (assertion: NixValue, expr: NixValue, assertionRaw: string throw "unreachable"; }; -export const ifFunc = (cond: NixValue, consq: NixValue, alter: NixValue) => { - if (forceBool(cond)) { - return consq; - } - return alter; -}; - -export const mkPos = (span: string): NixAttrs => { +export const mkPos = (span: number): NixAttrs => { return new Map(Object.entries(Deno.core.ops.op_decode_span(span))); }; diff --git a/nix-js/runtime-ts/src/index.ts b/nix-js/runtime-ts/src/index.ts index aedec08..3395bf5 100644 --- a/nix-js/runtime-ts/src/index.ts +++ b/nix-js/runtime-ts/src/index.ts @@ -5,7 +5,6 @@ */ import { builtins, PRIMOP_METADATA } from "./builtins"; -import { coerceToString, StringCoercionMode } from "./builtins/conversion"; import { assert, call, @@ -16,40 +15,31 @@ import { resolvePath, select, selectWithDefault, - withContext, } from "./helpers"; import { op } from "./operators"; import { HAS_CONTEXT } from "./string-context"; -import { - createThunk, - DEBUG_THUNKS, - force, - forceDeep, - forceShallow, - IS_CYCLE, - IS_THUNK, - isThunk, -} from "./thunk"; +import { createThunk, DEBUG_THUNKS, force, forceDeep, forceShallow, IS_CYCLE, IS_THUNK } from "./thunk"; import { forceBool } from "./type-assert"; -import { ATTR_POSITIONS, IS_PATH, mkAttrs, mkAttrsWithPos, mkFunction, type NixValue } from "./types"; +import { IS_PATH, mkAttrs, mkFunction, type NixValue } from "./types"; export type NixRuntime = typeof Nix; const replBindings: Map = new Map(); export const Nix = { - createThunk, - force, - forceShallow, - forceDeep, - forceBool, - isThunk, IS_THUNK, IS_CYCLE, HAS_CONTEXT, IS_PATH, + PRIMOP_METADATA, DEBUG_THUNKS, + createThunk, + force, + forceBool, + forceShallow, + forceDeep, + assert, call, hasAttr, @@ -57,20 +47,13 @@ export const Nix = { selectWithDefault, lookupWith, resolvePath, - coerceToString, concatStringsWithContext, - StringCoercionMode, mkAttrs, - mkAttrsWithPos, mkFunction, mkPos, - ATTR_POSITIONS, - - withContext, op, builtins, - PRIMOP_METADATA, replBindings, setReplBinding: (name: string, value: NixValue) => { @@ -80,3 +63,29 @@ export const Nix = { }; globalThis.Nix = Nix; +globalThis.$t = createThunk; +globalThis.$f = force; +globalThis.$fb = forceBool; +globalThis.$a = assert; +globalThis.$c = call; +globalThis.$h = hasAttr; +globalThis.$s = select; +globalThis.$sd = selectWithDefault; +globalThis.$l = lookupWith; +globalThis.$r = resolvePath; +globalThis.$cs = concatStringsWithContext; +globalThis.$ma = mkAttrs; +globalThis.$mf = mkFunction; +globalThis.$mp = mkPos; +globalThis.$gb = Nix.getReplBinding; + +globalThis.$oa = op.add; +globalThis.$os = op.sub; +globalThis.$om = op.mul; +globalThis.$od = op.div; +globalThis.$oe = op.eq; +globalThis.$ol = op.lt; +globalThis.$og = op.gt; +globalThis.$oc = op.concat; +globalThis.$ou = op.update; +globalThis.$b = builtins; diff --git a/nix-js/runtime-ts/src/types.ts b/nix-js/runtime-ts/src/types.ts index a333864..372999a 100644 --- a/nix-js/runtime-ts/src/types.ts +++ b/nix-js/runtime-ts/src/types.ts @@ -27,15 +27,15 @@ export type NixNull = null; export const ATTR_POSITIONS = Symbol("attrPositions"); export type NixList = NixValue[]; -export type NixAttrs = Map & { [ATTR_POSITIONS]?: Map }; +export type NixAttrs = Map & { [ATTR_POSITIONS]?: Map }; export type NixFunction = ((arg: NixValue) => NixValue) & { args?: NixArgs }; export class NixArgs { required: string[]; optional: string[]; allowed: Set; ellipsis: boolean; - positions: Map; - constructor(required: string[], optional: string[], positions: Map, ellipsis: boolean) { + positions: Map; + constructor(required: string[], optional: string[], positions: Map, ellipsis: boolean) { this.required = required; this.optional = optional; this.positions = positions; @@ -64,7 +64,7 @@ export const mkFunction = ( f: (arg: NixValue) => NixValue, required: string[], optional: string[], - positions: Map, + positions: Map, ellipsis: boolean, ): NixFunction => { const func: NixFunction = f; @@ -72,23 +72,10 @@ export const mkFunction = ( return func; }; -export const mkAttrs = (attrs: NixAttrs, keys: NixValue[], values: NixValue[]): NixAttrs => { - const len = keys.length; - for (let i = 0; i < len; i++) { - const key = force(keys[i]); - if (key === null) { - continue; - } - const str = forceStringNoCtx(key); - attrs.set(str, values[i]); - } - return attrs; -}; - -export const mkAttrsWithPos = ( +export const mkAttrs = ( attrs: NixAttrs, - positions: Map, - dyns?: { dynKeys: NixValue[]; dynVals: NixValue[]; dynSpans: string[] }, + positions: Map, + dyns?: { dynKeys: NixValue[]; dynVals: NixValue[]; dynSpans: number[] }, ): NixAttrs => { if (dyns) { const len = dyns.dynKeys.length; diff --git a/nix-js/runtime-ts/src/types/global.d.ts b/nix-js/runtime-ts/src/types/global.d.ts index 7ba3c0d..2ff239d 100644 --- a/nix-js/runtime-ts/src/types/global.d.ts +++ b/nix-js/runtime-ts/src/types/global.d.ts @@ -1,8 +1,50 @@ import type { NixRuntime } from ".."; +import type { builtins } from "../builtins"; import type { FetchGitResult, FetchTarballResult, FetchUrlResult } from "../builtins/io"; +import type { + assert, + call, + concatStringsWithContext, + hasAttr, + lookupWith, + mkPos, + resolvePath, + select, + selectWithDefault, +} from "../helpers"; +import type { op } from "../operators"; +import type { createThunk, force } from "../thunk"; +import type { forceBool } from "../type-assert"; +import type { mkAttrs, mkFunction } from "../types"; declare global { var Nix: NixRuntime; + var $t: typeof createThunk; + var $f: typeof force; + var $fb: typeof forceBool; + var $a: typeof assert; + var $c: typeof call; + var $h: typeof hasAttr; + var $s: typeof select; + var $sd: typeof selectWithDefault; + var $l: typeof lookupWith; + var $r: typeof resolvePath; + var $cs: typeof concatStringsWithContext; + var $ma: typeof mkAttrs; + var $mf: typeof mkFunction; + var $mp: typeof mkPos; + var $oa: typeof op.add; + var $os: typeof op.sub; + var $om: typeof op.mul; + var $od: typeof op.div; + var $oe: typeof op.eq; + var $ol: typeof op.lt; + var $og: typeof op.gt; + var $oc: typeof op.concat; + var $ou: typeof op.update; + var $b: typeof builtins; + var $gb: typeof Nix.getReplBinding; + namespace Deno { namespace core { namespace ops { @@ -20,11 +62,7 @@ declare global { function op_make_placeholder(output: string): string; function op_store_path(path: string): string; - function op_convert_hash(input: { - hash: string; - hashAlgo: string | null; - toHashFormat: string; - }): string; + function op_convert_hash(hash: string, hashAlgo: string | null, toHashFormat: string): string; function op_hash_string(algo: string, data: string): string; function op_hash_file(algo: string, path: string): string; function op_parse_hash(hashStr: string, algo: string | null): { hex: string; algo: string }; @@ -43,7 +81,7 @@ declare global { includePaths: string[], ): string; - function op_decode_span(span: string): { + function op_decode_span(span: number): { file: string | null; line: number | null; column: number | null; diff --git a/nix-js/src/codegen.rs b/nix-js/src/codegen.rs index 4bc935e..4c8f617 100644 --- a/nix-js/src/codegen.rs +++ b/nix-js/src/codegen.rs @@ -31,14 +31,10 @@ pub(crate) fn compile(expr: &Ir, ctx: &impl CodegenContext) -> String { code!(&mut buf, ctx; "(()=>{"); - if std::env::var("NIX_JS_DEBUG_THUNKS").is_ok() { - code!(&mut buf, ctx; "Nix.DEBUG_THUNKS.enabled=true;"); - } - code!(&mut buf, ctx; - "const __currentDir=" + "const _d=" quoted(&ctx.get_current_dir().display().to_string()) - ";const __with=null;return " + ",_w=null;return " expr "})()"); @@ -48,16 +44,12 @@ pub(crate) fn compile(expr: &Ir, ctx: &impl CodegenContext) -> String { pub(crate) fn compile_scoped(expr: &Ir, ctx: &impl CodegenContext) -> String { let mut buf = CodeBuffer::with_capacity(8192); - code!(&mut buf, ctx; "((__scope)=>{"); - - if std::env::var("NIX_JS_DEBUG_THUNKS").is_ok() { - code!(&mut buf, ctx; "Nix.DEBUG_THUNKS.enabled=true;"); - } + code!(&mut buf, ctx; "((_s)=>{"); code!(&mut buf, ctx; - "const __currentDir=" + "const _d=" quoted(&ctx.get_current_dir().display().to_string()) - ";return " + ",_w=null;return " expr "})" ); @@ -191,13 +183,7 @@ where impl Compile for rnix::TextRange { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { - code!( - buf, - "\"{}:{}:{}\"", - ctx.get_current_source_id(), - usize::from(self.start()), - usize::from(self.end()) - ); + code!(buf, "{}", ctx.register_span(*self)); } } @@ -207,6 +193,7 @@ pub(crate) trait CodegenContext { fn get_current_dir(&self) -> &Path; fn get_store_dir(&self) -> &str; fn get_current_source_id(&self) -> usize; + fn register_span(&self, range: rnix::TextRange) -> usize; } impl Compile for ExprId { @@ -240,7 +227,8 @@ impl Compile for Ir { code!(buf, ctx; quoted(&s.val)); } Ir::Path(p) => { - code!(buf, ctx; "Nix.resolvePath(__currentDir," ctx.get_ir(p.expr) ")"); + // Nix.resolvePath + code!(buf, ctx; "$r(_d," ctx.get_ir(p.expr) ")"); } Ir::If(x) => x.compile(ctx, buf), Ir::BinOp(x) => x.compile(ctx, buf), @@ -258,11 +246,13 @@ impl Compile for Ir { code!(buf, "expr{}", expr_id.0); } Ir::Builtins(_) => { - code!(buf, ctx; "Nix.builtins"); + // Nix.builtins + code!(buf, ctx; "$b"); } &Ir::Builtin(Builtin { inner: name, .. }) => { + // Nix.builtins code!(buf, ctx; - "Nix.builtins.get(" + "$b.get(" ctx.get_sym(name) ")" ); @@ -275,50 +265,51 @@ impl Compile for Ir { ref assertion_raw, span: assert_span, }) => { - let assertion_ir = ctx.get_ir(assertion); - let assertion_span = assertion_ir.span(); + let assertion = ctx.get_ir(assertion); + // Nix.assert code!(buf, ctx; - "Nix.assert(Nix.withContext(\"while evaluating the condition of the assert statement\"," - assertion_span - ",()=>(" - assertion_ir - "))," - ctx.get_ir(expr) - "," - quoted(assertion_raw) - "," - assert_span + "$a(" + assertion + "," + ctx.get_ir(expr) + "," + quoted(assertion_raw) + "," + assert_span ")" ); } Ir::CurPos(cur_pos) => { + // Nix.mkPos code!(buf, ctx; - "Nix.mkPos(" - cur_pos.span + "$mp(" + cur_pos.span ")" ); } &Ir::ReplBinding(ReplBinding { inner: name, .. }) => { + // Nix.getReplBinding code!(buf, ctx; - "Nix.getReplBinding(" + "$gb(" ctx.get_sym(name) ")" ); } &Ir::ScopedImportBinding(ScopedImportBinding { inner: name, .. }) => { code!(buf, ctx; - "__scope.get(" + "_s.get(" ctx.get_sym(name) ")" ); } Ir::WithExpr(x) => x.compile(ctx, buf), &Ir::WithLookup(WithLookup { inner: name, .. }) => { + // Nix.lookupWith code!(buf, ctx; - "Nix.lookupWith(" + "$l(" ctx.get_sym(name) - ",__with)" + ",_w)" ); } } @@ -333,13 +324,10 @@ impl Compile for If { alter, span: _, } = self; - let cond_ir = ctx.get_ir(cond); - let cond_span = cond_ir.span(); + let cond = ctx.get_ir(cond); - code!(buf, ctx; - "(Nix.withContext(\"while evaluating a branch condition\"," cond_span ",()=>Nix.forceBool(" cond_ir ")))" - "?(" consq "):(" alter ")" - ); + // Nix.forceBool + code!(buf, ctx; "$fb(" cond ")?(" consq "):(" alter ")"); } } @@ -352,65 +340,50 @@ impl Compile for BinOp { match self.kind { Add | Sub | Mul | Div | Eq | Neq | Lt | Gt | Leq | Geq | Con | Upd => { - let op_name = match self.kind { - Add => "+", - Sub => "-", - Mul => "*", - Div => "/", - Eq => "==", - Neq => "!=", - Lt => "<", - Gt => ">", - Leq => "<=", - Geq => ">=", - Con => "++", - Upd => "//", - _ => unreachable!(), - }; let op_func = match self.kind { - Add => "Nix.op.add", - Sub => "Nix.op.sub", - Mul => "Nix.op.mul", - Div => "Nix.op.div", - Eq => "Nix.op.eq", - Neq => "Nix.op.neq", - Lt => "Nix.op.lt", - Gt => "Nix.op.gt", - Leq => "Nix.op.lte", - Geq => "Nix.op.gte", - Con => "Nix.op.concat", - Upd => "Nix.op.update", + Add => "$oa", + Sub => "$os", + Mul => "$om", + Div => "$od", + Eq => "$oe", + Neq => "!$oe", + Lt => "$ol", + Gt => "$og", + Leq => "!$og", + Geq => "!$ol", + Con => "$oc", + Upd => "$ou", _ => unreachable!(), }; code!( buf, ctx; - "Nix.withContext(\"while evaluating the " op_name " operator\"," self.span ",()=>(" op_func "(" lhs "," rhs ")))" + op_func "(" lhs "," rhs ")" ); } And => { code!( buf, ctx; - "Nix.withContext(\"while evaluating the && operator\"," self.span ",()=>(Nix.forceBool(" lhs ")&&Nix.forceBool(" rhs ")))" + "$fb(" lhs ")" "&&" "$fb(" rhs ")" ); } Or => { code!( buf, ctx; - "Nix.withContext(\"while evaluating the || operator\"," self.span ",()=>(Nix.forceBool(" lhs ")||Nix.forceBool(" rhs ")))" + "$fb(" lhs ")" "||" "$fb(" rhs ")" ); } Impl => { code!( buf, ctx; - "Nix.withContext(\"while evaluating the -> operator\"," self.span ",()=>(!Nix.forceBool(" lhs ")||Nix.forceBool(" rhs ")))" + "!$fb(" lhs ")" "||" "$fb(" rhs ")" ); } PipeL => { - code!(buf, ctx; "Nix.call(" rhs "," lhs ")"); + code!(buf, ctx; "$c(" rhs "," lhs ")"); } PipeR => { - code!(buf, ctx; "Nix.call(" lhs "," rhs ")"); + code!(buf, ctx; "$c(" lhs "," rhs ")"); } } } @@ -422,10 +395,10 @@ impl Compile for UnOp { let rhs = ctx.get_ir(self.rhs); match self.kind { Neg => { - code!(buf, ctx; "Nix.op.sub(0n," rhs ")"); + code!(buf, ctx; "$os(0n," rhs ")"); } Not => { - code!(buf, ctx; "Nix.op.bnot(" ctx.get_ir(self.rhs) ")"); + code!(buf, ctx; "!$fb(" ctx.get_ir(self.rhs) ")"); } } } @@ -443,7 +416,7 @@ impl Compile for Func { ellipsis, }) = &self.param { - code!(buf, "Nix.mkFunction(arg{}=>", id); + code!(buf, "$mf(arg{}=>", id); if has_thunks { code!(buf, ctx; "{" self.thunks "return " self.body "}"); } else { @@ -458,11 +431,11 @@ impl Compile for Func { joined(optional.iter(), ",", |ctx: &Ctx, buf, &(sym, _)| { code!(buf, ctx; ctx.get_sym(sym)); }) - "],new Map()" - joined(required.iter().chain(optional.iter()), "", |ctx: &Ctx, buf, &(sym, span)| { - code!(buf, ctx; ".set(" ctx.get_sym(sym) "," span ")"); + "],new Map([" + joined(required.iter().chain(optional.iter()), ",", |ctx: &Ctx, buf, &(sym, span)| { + code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]"); }) - "," + "])," ellipsis ")" ); @@ -480,7 +453,7 @@ impl Compile for Func { impl Compile for Call { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { code!(buf, ctx; - "Nix.call(" + "$c(" ctx.get_ir(self.func) "," ctx.get_ir(self.arg) @@ -501,7 +474,7 @@ impl Compile for [(ExprId, ExprId)] { let inner_ir = ctx.get_ir(inner); code!( buf, ctx; - "let expr" slot.0 "=Nix.createThunk(()=>(" inner_ir ")," + "let expr" slot.0 "=$t(()=>(" inner_ir ")," "\"expr" slot.0 "\");" ); } @@ -525,9 +498,9 @@ impl Compile for WithExpr { let body = ctx.get_ir(self.body); let has_thunks = !self.thunks.is_empty(); if has_thunks { - code!(buf, ctx; "((__with)=>{" self.thunks "return " body "})({env:" namespace ",last:__with})"); + code!(buf, ctx; "((_w)=>{" self.thunks "return " body "})({env:" namespace ",last:_w})"); } else { - code!(buf, ctx; "((__with)=>(" body "))({env:" namespace ",last:__with})"); + code!(buf, ctx; "((_w)=>(" body "))({env:" namespace ",last:_w})"); } } } @@ -536,7 +509,7 @@ impl Compile for Select { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { if let Some(default) = self.default { code!(buf, ctx; - "Nix.selectWithDefault(" + "$sd(" ctx.get_ir(self.expr) ",[" joined(self.attrpath.iter(), ",", |ctx: &Ctx, buf, attr| { @@ -553,7 +526,7 @@ impl Compile for Select { ); } else { code!(buf, ctx; - "Nix.select(" + "$s(" ctx.get_ir(self.expr) ",[" joined(self.attrpath.iter(), ",", |ctx: &Ctx, buf, attr| { @@ -574,31 +547,28 @@ impl Compile for AttrSet { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { if !self.dyns.is_empty() { code!(buf, ctx; - "Nix.mkAttrsWithPos(new Map()" - joined(self.stcs.iter(), "", |ctx: &Ctx, buf, (&sym, &(expr, _))| { + "$ma(new Map([" + joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(expr, _))| { let key = ctx.get_sym(sym); let val = ctx.get_ir(expr); code!( buf, ctx; - ".set(" key ",Nix.withContext(\"while evaluating the attribute '" escaped(&key) "'\"," val.span() ",()=>(" val ")))" + "[" key "," val "]" ); }) - ",new Map()" - joined(self.stcs.iter(), "", |ctx: &Ctx, buf, (&sym, &(_, span))| { - code!(buf, ctx; ".set(" ctx.get_sym(sym) "," span ")"); + "]),new Map([" + joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| { + code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]"); }) - ",{dynKeys:[" + "]),{dynKeys:[" joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (key, _, _)| { code!(buf, ctx; ctx.get_ir(*key)); }) "],dynVals:[" joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (_, val, _)| { let val = ctx.get_ir(*val); - code!( - buf, ctx; - "Nix.withContext(\"while evaluating a dynamic attribute\"," val.span() ",()=>(" val "))" - ); + code!(buf, ctx; val); }) "],dynSpans:[" joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (_, _, attr_span)| { @@ -608,21 +578,21 @@ impl Compile for AttrSet { ); } else if !self.stcs.is_empty() { code!(buf, ctx; - "Nix.mkAttrsWithPos(new Map()" - joined(self.stcs.iter(), "", |ctx: &Ctx, buf, (&sym, &(expr, _))| { + "$ma(new Map([" + joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(expr, _))| { let key = ctx.get_sym(sym); let val = ctx.get_ir(expr); code!( buf, ctx; - ".set(" key ",Nix.withContext(\"while evaluating the attribute '" escaped(&key) "'\"," val.span() ",()=>(" val ")))" + "[" key "," val "]" ); }) - ",new Map()" - joined(self.stcs.iter(), "", |ctx: &Ctx, buf, (&sym, &(_, span))| { - code!(buf, ctx; ".set(" ctx.get_sym(sym) "," span ")"); + "]),new Map([" + joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| { + code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]"); }) - ")" + "]))" ); } else { code!(buf, ctx; "new Map()"); @@ -634,12 +604,9 @@ impl Compile for List { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { code!(buf, ctx; "[" - joined(self.items.iter().enumerate(), ",", |ctx: &Ctx, buf, (idx, item)| { + joined(self.items.iter(), ",", |ctx: &Ctx, buf, item| { let item = ctx.get_ir(*item); - code!( - buf, ctx; - "Nix.withContext(\"while evaluating list element " idx "\"," item.span() ",()=>(" item "))" - ); + code!(buf, ctx; item); }) "]" ); @@ -649,13 +616,10 @@ impl Compile for List { impl Compile for ConcatStrings { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { code!(buf, ctx; - "Nix.concatStringsWithContext([" + "$cs([" joined(self.parts.iter(), ",", |ctx: &Ctx, buf, part| { let part = ctx.get_ir(*part); - code!( - buf, ctx; - "Nix.withContext(\"while evaluating a path segment\"," part.span() ",()=>(" part "))" - ); + code!(buf, ctx; part); }) "]," self.force_string ")" ); @@ -665,7 +629,7 @@ impl Compile for ConcatStrings { impl Compile for HasAttr { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { code!(buf, ctx; - "Nix.hasAttr(" + "$h(" ctx.get_ir(self.lhs) ",[" joined(self.rhs.iter(), ",", |ctx: &Ctx, buf, attr| { diff --git a/nix-js/src/context.rs b/nix-js/src/context.rs index 6285895..5cb4766 100644 --- a/nix-js/src/context.rs +++ b/nix-js/src/context.rs @@ -1,3 +1,4 @@ +use std::cell::UnsafeCell; use std::path::Path; use std::ptr::NonNull; @@ -123,9 +124,14 @@ impl Context { let code = self.ctx.compile(source, None)?; self.runtime.eval( format!( - "Nix.builtins.set('derivation',({}));Nix.builtins.set('storeDir','{}')", + "Nix.builtins.set('derivation',({}));Nix.builtins.set('storeDir','{}');{}0n", code, - self.get_store_dir() + self.get_store_dir(), + if std::env::var("NIX_JS_DEBUG_THUNKS").is_ok() { + "Nix.DEBUG_THUNKS.enabled=true;" + } else { + "" + } ), &mut self.ctx, )?; @@ -182,6 +188,7 @@ pub(crate) struct Ctx { global: NonNull>, sources: Vec, store: DaemonStore, + spans: UnsafeCell>, } impl Ctx { @@ -278,6 +285,7 @@ impl Ctx { global: unsafe { NonNull::new_unchecked(Box::leak(Box::new(global))) }, sources: Vec::new(), store, + spans: UnsafeCell::new(Vec::new()), }) } @@ -366,6 +374,12 @@ impl CodegenContext for Ctx { fn get_store_dir(&self) -> &str { self.store.get_store_dir() } + fn register_span(&self, range: rnix::TextRange) -> usize { + let spans = unsafe { &mut *self.spans.get() }; + let id = spans.len(); + spans.push((self.get_current_source_id(), range)); + id + } } impl RuntimeContext for Ctx { @@ -387,6 +401,10 @@ impl RuntimeContext for Ctx { fn get_store(&self) -> &DaemonStore { &self.store } + fn get_span(&self, id: usize) -> (usize, rnix::TextRange) { + let spans = unsafe { &*self.spans.get() }; + spans[id] + } } enum Scope<'ctx> { diff --git a/nix-js/src/error.rs b/nix-js/src/error.rs index b7df2d4..9598e01 100644 --- a/nix-js/src/error.rs +++ b/nix-js/src/error.rs @@ -292,43 +292,32 @@ fn parse_frames(stack: &str, ctx: &impl RuntimeContext) -> Vec { let mut frames = Vec::new(); for line in stack.lines() { - // Format: NIX_STACK_FRAME:source_id:start:end[:extra_data] + // Format: NIX_STACK_FRAME:span_id:message let Some(rest) = line.strip_prefix("NIX_STACK_FRAME:") else { continue; }; - let parts: Vec<&str> = rest.splitn(4, ':').collect(); + let parts: Vec<&str> = rest.splitn(2, ':').collect(); - if parts.len() < 3 { + if parts.is_empty() { continue; } - let src = match parts[0].parse() { - Ok(id) => ctx.get_source(id), - Err(_) => continue, - }; - let start: u32 = match parts[1].parse() { - Ok(v) => v, - Err(_) => continue, - }; - let end: u32 = match parts[2].parse() { - Ok(v) => v, + let span_id: usize = match parts[0].parse() { + Ok(id) => id, Err(_) => continue, }; + let (source_id, span) = ctx.get_span(span_id); + let src = ctx.get_source(source_id); - let span = rnix::TextRange::new(rnix::TextSize::from(start), rnix::TextSize::from(end)); - - let message = { - if parts.len() == 4 { - parts[3].to_string() - } else { - String::new() - } + let message = if parts.len() == 2 { + parts[1].to_string() + } else { + String::new() }; frames.push(NixStackFrame { span, message, src }); } - // Deduplicate consecutive identical frames frames.dedup_by(|a, b| a.span == b.span && a.message == b.message); frames diff --git a/nix-js/src/runtime.rs b/nix-js/src/runtime.rs index 0124fed..419375e 100644 --- a/nix-js/src/runtime.rs +++ b/nix-js/src/runtime.rs @@ -26,6 +26,7 @@ pub(crate) trait RuntimeContext: 'static { fn compile_scoped(&mut self, source: Source, scope: Vec) -> Result; fn get_source(&self, id: usize) -> Source; fn get_store(&self) -> &DaemonStore; + fn get_span(&self, id: usize) -> (usize, rnix::TextRange); } pub(crate) trait OpStateExt { diff --git a/nix-js/src/runtime/inspector.rs b/nix-js/src/runtime/inspector.rs index c939e17..3ab8ba9 100644 --- a/nix-js/src/runtime/inspector.rs +++ b/nix-js/src/runtime/inspector.rs @@ -21,10 +21,10 @@ use deno_core::url::Url; use fastwebsockets::Frame; use fastwebsockets::OpCode; use fastwebsockets::WebSocket; +use hashbrown::HashMap; use hyper::body::Bytes; use hyper_util::rt::TokioIo; use std::cell::RefCell; -use std::collections::HashMap; use std::net::SocketAddr; use std::pin::pin; use std::process; diff --git a/nix-js/src/runtime/ops.rs b/nix-js/src/runtime/ops.rs index c91ae04..1b0e786 100644 --- a/nix-js/src/runtime/ops.rs +++ b/nix-js/src/runtime/ops.rs @@ -259,25 +259,14 @@ pub(super) fn op_make_placeholder(#[string] output: String) -> String { #[serde] pub(super) fn op_decode_span( state: &mut OpState, - #[string] span_str: String, + #[smi] span_id: u32, ) -> Result { - let parts: Vec<&str> = span_str.split(':').collect(); - if parts.len() != 3 { - return Ok(serde_json::json!({ - "file": serde_json::Value::Null, - "line": serde_json::Value::Null, - "column": serde_json::Value::Null - })); - } - - let source_id: usize = parts[0].parse().map_err(|_| "Invalid source ID")?; - let start: u32 = parts[1].parse().map_err(|_| "Invalid start offset")?; - let ctx: &Ctx = state.get_ctx(); + let (source_id, range) = ctx.get_span(span_id as usize); let source = ctx.get_source(source_id); - let content = &source.src; + let start = u32::from(range.start()); - let (line, column) = byte_offset_to_line_col(content, start as usize); + let (line, column) = byte_offset_to_line_col(&source.src, start as usize); Ok(serde_json::json!({ "file": source.get_name(),