Compare commits

..

7 Commits

24 changed files with 599 additions and 506 deletions

3
.gitignore vendored
View File

@@ -7,3 +7,6 @@ flamegraph*.svg
perf.data* perf.data*
profile.json.gz profile.json.gz
prof.json prof.json
*.cpuprofile
*.cpuprofile.gz
*v8.log*

8
Cargo.lock generated
View File

@@ -1986,7 +1986,7 @@ dependencies = [
[[package]] [[package]]
name = "nix-compat" name = "nix-compat"
version = "0.1.0" 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 = [ dependencies = [
"bitflags", "bitflags",
"bstr", "bstr",
@@ -2009,7 +2009,7 @@ dependencies = [
[[package]] [[package]]
name = "nix-compat-derive" name = "nix-compat-derive"
version = "0.1.0" 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 = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -3271,9 +3271,9 @@ dependencies = [
[[package]] [[package]]
name = "syn-match" name = "syn-match"
version = "0.3.0" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "783c4140d7ed89f37116e865b49e5a9fdd28608b9071a9dd1e158b50fc0a31fc" checksum = "54b8f0a9004d6aafa6a588602a1119e6cdaacec9921aa1605383e6e7d6258fd6"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@@ -21,3 +21,11 @@
[no-exit-message] [no-exit-message]
@evali expr: @evali expr:
cargo run --release --features inspector -- --inspect-brk 127.0.0.1:9229 eval --expr '{{expr}}' cargo run --release --features inspector -- --inspect-brk 127.0.0.1:9229 eval --expr '{{expr}}'
[no-exit-message]
@replp:
cargo run --release --features prof -- repl
[no-exit-message]
@evalp expr:
cargo run --release --features prof -- eval --expr '{{expr}}'

View File

@@ -76,6 +76,7 @@ uuid = { version = "1", features = ["v4"], optional = true }
[features] [features]
inspector = ["dep:fastwebsockets", "dep:hyper", "dep:hyper-util", "dep:http-body-util", "dep:http", "dep:uuid"] inspector = ["dep:fastwebsockets", "dep:hyper", "dep:hyper-util", "dep:http-body-util", "dep:http", "dep:uuid"]
prof = []
[dev-dependencies] [dev-dependencies]
criterion = { version = "0.8", features = ["html_reports"] } criterion = { version = "0.8", features = ["html_reports"] }

View File

@@ -154,6 +154,6 @@ export const unsafeGetAttrPos =
return null; return null;
} }
const span = positions.get(name) as string; const span = positions.get(name) as number;
return mkPos(span); return mkPos(span);
}; };

View File

@@ -2,11 +2,16 @@
* Conversion and serialization builtin functions * Conversion and serialization builtin functions
*/ */
import { addBuiltContext, mkStringWithContext, type NixStringContext } from "../string-context"; import {
addBuiltContext,
mkStringWithContext,
type NixStringContext,
StringWithContext,
} from "../string-context";
import { force, isThunk } from "../thunk"; import { force, isThunk } from "../thunk";
import { forceFunction, forceStringNoCtx } from "../type-assert"; import { forceFunction, forceStringNoCtx } from "../type-assert";
import type { NixString, NixValue } from "../types"; import type { NixString, NixValue } from "../types";
import { HAS_CONTEXT, IS_PATH, isNixPath, isStringWithContext } from "../types"; import { isNixPath, isStringWithContext, NixPath } from "../types";
import { isAttrs, isPath, typeOf } from "./type-check"; import { isAttrs, isPath, typeOf } from "./type-check";
export const fromJSON = (e: NixValue): NixValue => { export const fromJSON = (e: NixValue): NixValue => {
@@ -306,13 +311,13 @@ export const nixValueToJson = (
if (typeof v === "number") return v; if (typeof v === "number") return v;
if (typeof v === "boolean") return v; if (typeof v === "boolean") return v;
if (typeof v === "string") return v; if (typeof v === "string") return v;
if (typeof v === "object" && HAS_CONTEXT in v) { if (v instanceof StringWithContext) {
for (const elem of v.context) { for (const elem of v.context) {
outContext.add(elem); outContext.add(elem);
} }
return v.value; return v.value;
} }
if (typeof v === "object" && IS_PATH in v) { if (v instanceof NixPath) {
if (copyToStore) { if (copyToStore) {
const storePath = Deno.core.ops.op_copy_path_to_store(v.value); const storePath = Deno.core.ops.op_copy_path_to_store(v.value);
outContext.add(storePath); outContext.add(storePath);

View File

@@ -377,16 +377,16 @@ export const derivationStrict = (args: NixValue): NixAttrs => {
const rustResult: { const rustResult: {
drvPath: string; drvPath: string;
outputs: [string, string][]; outputs: [string, string][];
} = Deno.core.ops.op_finalize_derivation({ } = Deno.core.ops.op_finalize_derivation(
name: drvName, drvName,
builder, builder,
platform, platform,
outputs, outputs,
args: drvArgs, drvArgs,
env: envEntries, envEntries,
context: contextArray, contextArray,
fixedOutput: fixedOutputInfo, fixedOutputInfo,
}); );
const result: NixAttrs = new Map(); const result: NixAttrs = new Map();

View File

@@ -30,9 +30,5 @@ export const convertHash = (args: NixValue): string => {
const toHashFormat = forceStringNoCtx(select(attrs, ["toHashFormat"])); const toHashFormat = forceStringNoCtx(select(attrs, ["toHashFormat"]));
return Deno.core.ops.op_convert_hash({ return Deno.core.ops.op_convert_hash(hash, hashAlgo, toHashFormat);
hash,
hashAlgo,
toHashFormat,
});
}; };

View File

@@ -11,8 +11,8 @@ import {
forceStringNoCtx, forceStringNoCtx,
forceStringValue, forceStringValue,
} from "../type-assert"; } from "../type-assert";
import type { NixAttrs, NixPath, NixString, NixValue } from "../types"; import type { NixAttrs, NixString, NixValue } from "../types";
import { CatchableError, IS_PATH, isNixPath } from "../types"; import { CatchableError, isNixPath, NixPath } from "../types";
import { coerceToPath, coerceToString, StringCoercionMode } from "./conversion"; import { coerceToPath, coerceToString, StringCoercionMode } from "./conversion";
import { baseNameOf } from "./path"; import { baseNameOf } from "./path";
import { isAttrs, isPath, isString } from "./type-check"; import { isAttrs, isPath, isString } from "./type-check";
@@ -49,8 +49,7 @@ export const importFunc = (path: NixValue): NixValue => {
return cached; return cached;
} }
const code = Deno.core.ops.op_import(pathStr); const result = Deno.core.ops.op_import(pathStr);
const result = Function(`return (${code})`)();
importCache.set(pathStr, result); importCache.set(pathStr, result);
return result; return result;
@@ -85,24 +84,24 @@ export const fetchClosure = (_args: NixValue): never => {
}; };
export interface FetchUrlResult { export interface FetchUrlResult {
store_path: string; storePath: string;
hash: string; hash: string;
} }
export interface FetchTarballResult { export interface FetchTarballResult {
store_path: string; storePath: string;
nar_hash: string; narHash: string;
} }
export interface FetchGitResult { export interface FetchGitResult {
out_path: string; outPath: string;
rev: string; rev: string;
short_rev: string; shortRev: string;
rev_count: number; revCount: number;
last_modified: number; lastModified: number;
last_modified_date: string; lastModifiedDate: string;
submodules: boolean; submodules: boolean;
nar_hash: string | null; narHash: string | null;
} }
const normalizeUrlInput = ( const normalizeUrlInput = (
@@ -154,16 +153,16 @@ export const fetchurl = (args: NixValue): NixString => {
executable ?? false, executable ?? false,
); );
const context: NixStringContext = new Set(); const context: NixStringContext = new Set();
addOpaqueContext(context, result.store_path); addOpaqueContext(context, result.storePath);
return mkStringWithContext(result.store_path, context); return mkStringWithContext(result.storePath, context);
}; };
export const fetchTarball = (args: NixValue): NixString => { export const fetchTarball = (args: NixValue): NixString => {
const { url, name, sha256 } = normalizeTarballInput(args); const { url, name, sha256 } = normalizeTarballInput(args);
const result: FetchTarballResult = Deno.core.ops.op_fetch_tarball(url, name ?? null, sha256 ?? null); const result: FetchTarballResult = Deno.core.ops.op_fetch_tarball(url, name ?? null, sha256 ?? null);
const context: NixStringContext = new Set(); const context: NixStringContext = new Set();
addOpaqueContext(context, result.store_path); addOpaqueContext(context, result.storePath);
return mkStringWithContext(result.store_path, context); return mkStringWithContext(result.storePath, context);
}; };
export const fetchGit = (args: NixValue): NixAttrs => { export const fetchGit = (args: NixValue): NixAttrs => {
@@ -173,16 +172,16 @@ export const fetchGit = (args: NixValue): NixAttrs => {
const url = coerceToString(forced, StringCoercionMode.Base, false, disposedContext); const url = coerceToString(forced, StringCoercionMode.Base, false, disposedContext);
const result = Deno.core.ops.op_fetch_git(url, null, null, false, false, false, null); const result = Deno.core.ops.op_fetch_git(url, null, null, false, false, false, null);
const outContext: NixStringContext = new Set(); const outContext: NixStringContext = new Set();
addOpaqueContext(outContext, result.out_path); addOpaqueContext(outContext, result.outPath);
return new Map<string, NixValue>([ return new Map<string, NixValue>([
["outPath", mkStringWithContext(result.out_path, outContext)], ["outPath", mkStringWithContext(result.outPath, outContext)],
["rev", result.rev], ["rev", result.rev],
["shortRev", result.short_rev], ["shortRev", result.shortRev],
["revCount", BigInt(result.rev_count)], ["revCount", BigInt(result.revCount)],
["lastModified", BigInt(result.last_modified)], ["lastModified", BigInt(result.lastModified)],
["lastModifiedDate", result.last_modified_date], ["lastModifiedDate", result.lastModifiedDate],
["submodules", result.submodules], ["submodules", result.submodules],
["narHash", result.nar_hash], ["narHash", result.narHash],
]); ]);
} }
const attrs = forceAttrs(args); const attrs = forceAttrs(args);
@@ -205,16 +204,16 @@ export const fetchGit = (args: NixValue): NixAttrs => {
); );
const outContext: NixStringContext = new Set(); const outContext: NixStringContext = new Set();
addOpaqueContext(outContext, result.out_path); addOpaqueContext(outContext, result.outPath);
return new Map<string, NixValue>([ return new Map<string, NixValue>([
["outPath", mkStringWithContext(result.out_path, outContext)], ["outPath", mkStringWithContext(result.outPath, outContext)],
["rev", result.rev], ["rev", result.rev],
["shortRev", result.short_rev], ["shortRev", result.shortRev],
["revCount", BigInt(result.rev_count)], ["revCount", BigInt(result.revCount)],
["lastModified", BigInt(result.last_modified)], ["lastModified", BigInt(result.lastModified)],
["lastModifiedDate", result.last_modified_date], ["lastModifiedDate", result.lastModifiedDate],
["submodules", result.submodules], ["submodules", result.submodules],
["narHash", result.nar_hash], ["narHash", result.narHash],
]); ]);
}; };
@@ -307,12 +306,7 @@ const autoDetectAndFetch = (attrs: NixAttrs): NixAttrs => {
export const readDir = (path: NixValue): NixAttrs => { export const readDir = (path: NixValue): NixAttrs => {
const pathStr = realisePath(path); const pathStr = realisePath(path);
const entries: Record<string, string> = Deno.core.ops.op_read_dir(pathStr); return Deno.core.ops.op_read_dir(pathStr);
const result: NixAttrs = new Map();
for (const [name, type] of Object.entries(entries)) {
result.set(name, type);
}
return result;
}; };
export const readFile = (path: NixValue): string => { export const readFile = (path: NixValue): string => {
@@ -475,13 +469,13 @@ export const findFile =
suffix.length > 0 ? Deno.core.ops.op_resolve_path(suffix, resolvedPath) : resolvedPath; suffix.length > 0 ? Deno.core.ops.op_resolve_path(suffix, resolvedPath) : resolvedPath;
if (Deno.core.ops.op_path_exists(candidatePath)) { if (Deno.core.ops.op_path_exists(candidatePath)) {
return { [IS_PATH]: true, value: candidatePath }; return new NixPath(candidatePath);
} }
} }
if (lookupPathStr.startsWith("nix/")) { if (lookupPathStr.startsWith("nix/")) {
// FIXME: special path type // FIXME: special path type
return { [IS_PATH]: true, value: `<${lookupPathStr}>` }; return new NixPath(`<${lookupPathStr}>`);
} }
throw new CatchableError(`file '${lookupPathStr}' was not found in the Nix search path`); throw new CatchableError(`file '${lookupPathStr}' was not found in the Nix search path`);

View File

@@ -8,7 +8,7 @@ import type { NixAttrs, NixBool, NixPath, NixString, NixValue } from "./types";
import { CatchableError, isNixPath } from "./types"; import { CatchableError, isNixPath } from "./types";
interface StackFrame { interface StackFrame {
span: string; span: number;
message: string; message: string;
} }
@@ -32,7 +32,7 @@ function enrichError(error: unknown): Error {
return err; return err;
} }
const pushContext = (message: string, span: string): void => { const pushContext = (message: string, span: number): void => {
if (callStack.length >= MAX_STACK_DEPTH) { if (callStack.length >= MAX_STACK_DEPTH) {
callStack.shift(); callStack.shift();
} }
@@ -43,7 +43,7 @@ const popContext = (): void => {
callStack.pop(); callStack.pop();
}; };
export const withContext = <T>(message: string, span: string, fn: () => T): T => { export const withContext = <T>(message: string, span: number, fn: () => T): T => {
pushContext(message, span); pushContext(message, span);
try { try {
return fn(); return fn();
@@ -142,19 +142,22 @@ export const resolvePath = (currentDir: string, path: NixValue): NixPath => {
return mkPath(resolved); return mkPath(resolved);
}; };
export const select = (obj: NixValue, attrpath: NixValue[], span?: string): NixValue => { export const select = (obj: NixValue, attrpath: NixValue[], span?: number): NixValue => {
if (span) { if (span !== undefined) {
const pathStrings = attrpath.map((a) => forceStringValue(a));
const path = pathStrings.join(".");
const message = path ? `while selecting attribute [${path}]` : "while selecting attribute";
if (callStack.length >= MAX_STACK_DEPTH) { if (callStack.length >= MAX_STACK_DEPTH) {
callStack.shift(); callStack.shift();
} }
callStack.push({ span, message }); const frame: StackFrame = { span, message: "while selecting attribute" };
callStack.push(frame);
try { try {
return selectImpl(obj, attrpath); return selectImpl(obj, attrpath);
} catch (error) { } catch (error) {
try {
const path = attrpath.map((a) => forceStringValue(a)).join(".");
if (path) frame.message = `while selecting attribute [${path}]`;
} catch {
throw enrichError(error);
}
throw enrichError(error); throw enrichError(error);
} finally { } finally {
callStack.pop(); callStack.pop();
@@ -167,8 +170,8 @@ export const select = (obj: NixValue, attrpath: NixValue[], span?: string): NixV
function selectImpl(obj: NixValue, attrpath: NixValue[]): NixValue { function selectImpl(obj: NixValue, attrpath: NixValue[]): NixValue {
let attrs = forceAttrs(obj); let attrs = forceAttrs(obj);
for (const attr of attrpath.slice(0, -1)) { for (let i = 0; i < attrpath.length - 1; i++) {
const key = forceStringValue(attr); const key = forceStringValue(attrpath[i]);
if (!attrs.has(key)) { if (!attrs.has(key)) {
throw new Error(`Attribute '${key}' not found`); throw new Error(`Attribute '${key}' not found`);
} }
@@ -187,20 +190,23 @@ export const selectWithDefault = (
obj: NixValue, obj: NixValue,
attrpath: NixValue[], attrpath: NixValue[],
defaultVal: NixValue, defaultVal: NixValue,
span?: string, span?: number,
): NixValue => { ): NixValue => {
if (span) { if (span !== undefined) {
const pathStrings = attrpath.map((a) => forceStringValue(a));
const path = pathStrings.join(".");
const message = path ? `while selecting attribute [${path}]` : "while selecting attribute";
if (callStack.length >= MAX_STACK_DEPTH) { if (callStack.length >= MAX_STACK_DEPTH) {
callStack.shift(); callStack.shift();
} }
callStack.push({ span, message }); const frame: StackFrame = { span, message: "while selecting attribute" };
callStack.push(frame);
try { try {
return selectWithDefaultImpl(obj, attrpath, defaultVal); return selectWithDefaultImpl(obj, attrpath, defaultVal);
} catch (error) { } catch (error) {
try {
const path = attrpath.map((a) => forceStringValue(a)).join(".");
if (path) frame.message = `while selecting attribute [${path}]`;
} catch {
throw enrichError(error);
}
throw enrichError(error); throw enrichError(error);
} finally { } finally {
callStack.pop(); callStack.pop();
@@ -216,8 +222,8 @@ function selectWithDefaultImpl(obj: NixValue, attrpath: NixValue[], defaultVal:
return defaultVal; return defaultVal;
} }
for (const attr of attrpath.slice(0, -1)) { for (let i = 0; i < attrpath.length - 1; i++) {
const key = forceStringValue(attr); const key = forceStringValue(attrpath[i]);
if (!attrs.has(key)) { if (!attrs.has(key)) {
return defaultVal; return defaultVal;
} }
@@ -242,8 +248,8 @@ export const hasAttr = (obj: NixValue, attrpath: NixValue[]): NixBool => {
} }
let attrs = forced; let attrs = forced;
for (const attr of attrpath.slice(0, -1)) { for (let i = 0; i < attrpath.length - 1; i++) {
const key = forceStringNoCtx(attr); const key = forceStringNoCtx(attrpath[i]);
if (!attrs.has(key)) { if (!attrs.has(key)) {
return false; return false;
} }
@@ -257,8 +263,8 @@ export const hasAttr = (obj: NixValue, attrpath: NixValue[]): NixBool => {
return attrs.has(forceStringValue(attrpath[attrpath.length - 1])); return attrs.has(forceStringValue(attrpath[attrpath.length - 1]));
}; };
export const call = (func: NixValue, arg: NixValue, span?: string): NixValue => { export const call = (func: NixValue, arg: NixValue, span?: number): NixValue => {
if (span) { if (span !== undefined) {
if (callStack.length >= MAX_STACK_DEPTH) { if (callStack.length >= MAX_STACK_DEPTH) {
callStack.shift(); callStack.shift();
} }
@@ -276,19 +282,19 @@ export const call = (func: NixValue, arg: NixValue, span?: string): NixValue =>
}; };
function callImpl(func: NixValue, arg: NixValue): NixValue { function callImpl(func: NixValue, arg: NixValue): NixValue {
const forcedFunc = force(func); const forced = force(func);
if (typeof forcedFunc === "function") { if (typeof forced === "function") {
forcedFunc.args?.check(arg); forced.args?.check(arg);
return forcedFunc(arg); return forced(arg);
} }
if (forcedFunc instanceof Map && forcedFunc.has("__functor")) { if (forced instanceof Map && forced.has("__functor")) {
const functor = forceFunction(forcedFunc.get("__functor") as NixValue); const functor = forceFunction(forced.get("__functor") as NixValue);
return call(functor(forcedFunc), arg); 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)) { if (forceBool(assertion)) {
return expr; return expr;
} }
@@ -298,15 +304,8 @@ export const assert = (assertion: NixValue, expr: NixValue, assertionRaw: string
throw "unreachable"; throw "unreachable";
}; };
export const ifFunc = (cond: NixValue, consq: NixValue, alter: NixValue) => { export const mkPos = (span: number): NixAttrs => {
if (forceBool(cond)) { return Deno.core.ops.op_decode_span(span);
return consq;
}
return alter;
};
export const mkPos = (span: string): NixAttrs => {
return new Map(Object.entries(Deno.core.ops.op_decode_span(span)));
}; };
interface WithScope { interface WithScope {

View File

@@ -5,7 +5,6 @@
*/ */
import { builtins, PRIMOP_METADATA } from "./builtins"; import { builtins, PRIMOP_METADATA } from "./builtins";
import { coerceToString, StringCoercionMode } from "./builtins/conversion";
import { import {
assert, assert,
call, call,
@@ -16,40 +15,31 @@ import {
resolvePath, resolvePath,
select, select,
selectWithDefault, selectWithDefault,
withContext,
} from "./helpers"; } from "./helpers";
import { op } from "./operators"; import { op } from "./operators";
import { HAS_CONTEXT } from "./string-context"; import { HAS_CONTEXT } from "./string-context";
import { import { createThunk, DEBUG_THUNKS, force, forceDeep, forceShallow, IS_CYCLE, IS_THUNK } from "./thunk";
createThunk,
DEBUG_THUNKS,
force,
forceDeep,
forceShallow,
IS_CYCLE,
IS_THUNK,
isThunk,
} from "./thunk";
import { forceBool } from "./type-assert"; 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; export type NixRuntime = typeof Nix;
const replBindings: Map<string, NixValue> = new Map; const replBindings: Map<string, NixValue> = new Map();
export const Nix = { export const Nix = {
createThunk,
force,
forceShallow,
forceDeep,
forceBool,
isThunk,
IS_THUNK, IS_THUNK,
IS_CYCLE, IS_CYCLE,
HAS_CONTEXT, HAS_CONTEXT,
IS_PATH, IS_PATH,
PRIMOP_METADATA,
DEBUG_THUNKS, DEBUG_THUNKS,
createThunk,
force,
forceBool,
forceShallow,
forceDeep,
assert, assert,
call, call,
hasAttr, hasAttr,
@@ -57,20 +47,13 @@ export const Nix = {
selectWithDefault, selectWithDefault,
lookupWith, lookupWith,
resolvePath, resolvePath,
coerceToString,
concatStringsWithContext, concatStringsWithContext,
StringCoercionMode,
mkAttrs, mkAttrs,
mkAttrsWithPos,
mkFunction, mkFunction,
mkPos, mkPos,
ATTR_POSITIONS,
withContext,
op, op,
builtins, builtins,
PRIMOP_METADATA,
replBindings, replBindings,
setReplBinding: (name: string, value: NixValue) => { setReplBinding: (name: string, value: NixValue) => {
@@ -80,3 +63,29 @@ export const Nix = {
}; };
globalThis.Nix = 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;

View File

@@ -1,4 +1,4 @@
import { IS_PATH, type NixPath } from "./types"; import { NixPath } from "./types";
const canonicalizePath = (path: string): string => { const canonicalizePath = (path: string): string => {
const parts: string[] = []; const parts: string[] = [];
@@ -30,7 +30,7 @@ const canonicalizePath = (path: string): string => {
}; };
export const mkPath = (value: string): NixPath => { export const mkPath = (value: string): NixPath => {
return { [IS_PATH]: true, value: canonicalizePath(value) }; return new NixPath(canonicalizePath(value));
}; };
export const getPathValue = (p: NixPath): string => { export const getPathValue = (p: NixPath): string => {

View File

@@ -22,18 +22,22 @@ export type StringContextElem = StringContextOpaque | StringContextDrvDeep | Str
export type NixStringContext = Set<string>; export type NixStringContext = Set<string>;
export interface StringWithContext { export class StringWithContext {
readonly [HAS_CONTEXT]: true; readonly [HAS_CONTEXT] = true as const;
value: string; value: string;
context: NixStringContext; context: NixStringContext;
constructor(value: string, context: NixStringContext) {
this.value = value;
this.context = context;
}
} }
export const isStringWithContext = (v: NixStrictValue): v is StringWithContext => { export const isStringWithContext = (v: NixStrictValue): v is StringWithContext => {
return typeof v === "object" && v !== null && HAS_CONTEXT in v; return v instanceof StringWithContext;
}; };
export const mkStringWithContext = (value: string, context: NixStringContext): StringWithContext => { export const mkStringWithContext = (value: string, context: NixStringContext): StringWithContext => {
return { [HAS_CONTEXT]: true, value, context }; return new StringWithContext(value, context);
}; };
export const mkPlainString = (value: string): string => value; export const mkPlainString = (value: string): string => value;
@@ -45,11 +49,12 @@ export const getStringValue = (s: string | StringWithContext): string => {
return s; return s;
}; };
const emptyContext: NixStringContext = new Set();
export const getStringContext = (s: string | StringWithContext): NixStringContext => { export const getStringContext = (s: string | StringWithContext): NixStringContext => {
if (isStringWithContext(s)) { if (isStringWithContext(s)) {
return s.context; return s.context;
} }
return new Set(); return emptyContext;
}; };
export const mergeContexts = (...contexts: NixStringContext[]): NixStringContext => { export const mergeContexts = (...contexts: NixStringContext[]): NixStringContext => {

View File

@@ -1,7 +1,7 @@
import { isAttrs, isList } from "./builtins/type-check"; import { isAttrs, isList } from "./builtins/type-check";
import { HAS_CONTEXT } from "./string-context"; import { StringWithContext } from "./string-context";
import type { NixAttrs, NixStrictValue, NixThunkInterface, NixValue } from "./types"; import type { NixAttrs, NixStrictValue, NixValue } from "./types";
import { IS_PATH } from "./types"; import { NixPath } from "./types";
export const IS_THUNK = Symbol("is_thunk"); export const IS_THUNK = Symbol("is_thunk");
@@ -21,8 +21,7 @@ export const DEBUG_THUNKS = { enabled: true };
* - Evaluating (blackhole): func is undefined, result is undefined * - Evaluating (blackhole): func is undefined, result is undefined
* - Evaluated: func is undefined, result is defined * - Evaluated: func is undefined, result is defined
*/ */
export class NixThunk implements NixThunkInterface { export class NixThunk {
[key: symbol]: unknown;
readonly [IS_THUNK] = true as const; readonly [IS_THUNK] = true as const;
func: (() => NixValue) | undefined; func: (() => NixValue) | undefined;
result: NixStrictValue | undefined; result: NixStrictValue | undefined;
@@ -42,8 +41,8 @@ export class NixThunk implements NixThunkInterface {
} }
} }
export const isThunk = (value: NixValue): value is NixThunkInterface => { export const isThunk = (value: NixValue): value is NixThunk => {
return value !== null && typeof value === "object" && IS_THUNK in value && value[IS_THUNK] === true; return value instanceof NixThunk;
}; };
/** /**
@@ -112,7 +111,7 @@ export const force = (value: NixValue): NixStrictValue => {
} }
}; };
export const createThunk = (func: () => NixValue, label?: string): NixThunkInterface => { export const createThunk = (func: () => NixValue, label?: string): NixThunk => {
return new NixThunk(func, label); return new NixThunk(func, label);
}; };
@@ -142,7 +141,7 @@ export const forceDeep = (value: NixValue, seen: WeakSet<object> = new WeakSet()
seen.add(forced); seen.add(forced);
} }
if (HAS_CONTEXT in forced || IS_PATH in forced) { if (forced instanceof StringWithContext || forced instanceof NixPath) {
return forced; return forced;
} }

View File

@@ -1,18 +1,21 @@
import { HAS_CONTEXT, isStringWithContext, type StringWithContext } from "./string-context"; import { HAS_CONTEXT, isStringWithContext, type StringWithContext } from "./string-context";
import { type CYCLE_MARKER, force, IS_THUNK } from "./thunk"; import { type CYCLE_MARKER, force, type NixThunk } from "./thunk";
import { forceAttrs, forceStringNoCtx } from "./type-assert"; import { forceAttrs, forceStringNoCtx } from "./type-assert";
export { HAS_CONTEXT, isStringWithContext }; export { HAS_CONTEXT, isStringWithContext };
export type { StringWithContext }; export type { StringWithContext };
export const IS_PATH = Symbol("IS_PATH"); export const IS_PATH = Symbol("IS_PATH");
export interface NixPath { export class NixPath {
readonly [IS_PATH]: true; readonly [IS_PATH] = true as const;
value: string; value: string;
constructor(value: string) {
this.value = value;
}
} }
export const isNixPath = (v: NixStrictValue): v is NixPath => { export const isNixPath = (v: NixStrictValue): v is NixPath => {
return typeof v === "object" && v !== null && IS_PATH in v; return v instanceof NixPath;
}; };
export type NixInt = bigint; export type NixInt = bigint;
@@ -24,18 +27,18 @@ export type NixNull = null;
export const ATTR_POSITIONS = Symbol("attrPositions"); export const ATTR_POSITIONS = Symbol("attrPositions");
export type NixList = NixValue[]; export type NixList = NixValue[];
export type NixAttrs = Map<string, NixValue> & { [ATTR_POSITIONS]?: Map<string, string> }; export type NixAttrs = Map<string, NixValue> & { [ATTR_POSITIONS]?: Map<string, number> };
export type NixFunction = ((arg: NixValue) => NixValue) & { args?: NixArgs }; export type NixFunction = ((arg: NixValue) => NixValue) & { args?: NixArgs };
export class NixArgs { export class NixArgs {
required: string[]; required: string[];
optional: string[]; optional: string[];
allowed: Set<string>; allowed: Set<string>;
ellipsis: boolean; ellipsis: boolean;
positions: Map<string, string>; positions: Map<string, number>;
constructor(required: string[], optional: string[], positions: Record<string, string>, ellipsis: boolean) { constructor(required: string[], optional: string[], positions: Map<string, number>, ellipsis: boolean) {
this.required = required; this.required = required;
this.optional = optional; this.optional = optional;
this.positions = new Map(Object.entries(positions)); this.positions = positions;
this.ellipsis = ellipsis; this.ellipsis = ellipsis;
this.allowed = new Set(required.concat(optional)); this.allowed = new Set(required.concat(optional));
} }
@@ -61,7 +64,7 @@ export const mkFunction = (
f: (arg: NixValue) => NixValue, f: (arg: NixValue) => NixValue,
required: string[], required: string[],
optional: string[], optional: string[],
positions: Record<string, string>, positions: Map<string, number>,
ellipsis: boolean, ellipsis: boolean,
): NixFunction => { ): NixFunction => {
const func: NixFunction = f; const func: NixFunction = f;
@@ -69,26 +72,11 @@ export const mkFunction = (
return func; return func;
}; };
export const mkAttrs = (attrs: NixAttrs, keys: NixValue[], values: NixValue[]): NixAttrs => { export const mkAttrs = (
const len = keys.length; attrs: NixAttrs,
for (let i = 0; i < len; i++) { positions: Map<string, number>,
const key = force(keys[i]); dyns?: { dynKeys: NixValue[]; dynVals: NixValue[]; dynSpans: number[] },
if (key === null) {
continue;
}
const str = forceStringNoCtx(key);
attrs.set(str, values[i]);
}
return attrs;
};
export const mkAttrsWithPos = (
obj: Record<string, NixValue>,
positions: Record<string, string>,
dyns?: { dynKeys: NixValue[]; dynVals: NixValue[]; dynSpans: string[] },
): NixAttrs => { ): NixAttrs => {
const attrs: NixAttrs = new Map(Object.entries(obj));
if (dyns) { if (dyns) {
const len = dyns.dynKeys.length; const len = dyns.dynKeys.length;
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
@@ -98,23 +86,17 @@ export const mkAttrsWithPos = (
} }
const str = forceStringNoCtx(key); const str = forceStringNoCtx(key);
attrs.set(str, dyns.dynVals[i]); attrs.set(str, dyns.dynVals[i]);
positions[str] = dyns.dynSpans[i]; positions.set(str, dyns.dynSpans[i]);
} }
} }
if (Object.keys(positions).length > 0) { if (positions.size > 0) {
attrs[ATTR_POSITIONS] = new Map(Object.entries(positions)); attrs[ATTR_POSITIONS] = positions;
} }
return attrs; return attrs;
}; };
export interface NixThunkInterface {
readonly [IS_THUNK]: true;
func: (() => NixValue) | undefined;
result: NixStrictValue | undefined;
}
export type NixPrimitive = NixNull | NixBool | NixInt | NixFloat | NixString; export type NixPrimitive = NixNull | NixBool | NixInt | NixFloat | NixString;
export type NixValue = export type NixValue =
| NixPrimitive | NixPrimitive
@@ -122,8 +104,8 @@ export type NixValue =
| NixList | NixList
| NixAttrs | NixAttrs
| NixFunction | NixFunction
| NixThunkInterface | NixThunk
| typeof CYCLE_MARKER; | typeof CYCLE_MARKER;
export type NixStrictValue = Exclude<NixValue, NixThunkInterface>; export type NixStrictValue = Exclude<NixValue, NixThunk>;
export class CatchableError extends Error {} export class CatchableError extends Error {}

View File

@@ -1,30 +1,68 @@
import type { NixRuntime } from ".."; import type { NixRuntime } from "..";
import type { builtins } from "../builtins";
import type { FetchGitResult, FetchTarballResult, FetchUrlResult } from "../builtins/io"; 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, NixAttrs } from "../types";
declare global { declare global {
var Nix: NixRuntime; 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 Deno {
namespace core { namespace core {
namespace ops { namespace ops {
function op_import(path: string): string; function op_import(path: string): NixValue;
function op_scoped_import(path: string, scopeKeys: string[]): string; function op_scoped_import(path: string, scopeKeys: string[]): string;
function op_resolve_path(currentDir: string, path: string): string; function op_resolve_path(currentDir: string, path: string): string;
function op_read_file(path: string): string; function op_read_file(path: string): string;
function op_read_file_type(path: string): string; function op_read_file_type(path: string): string;
function op_read_dir(path: string): Record<string, string>; function op_read_dir(path: string): Map<string, string>;
function op_path_exists(path: string): boolean; function op_path_exists(path: string): boolean;
function op_walk_dir(path: string): [string, string][]; function op_walk_dir(path: string): [string, string][];
function op_make_placeholder(output: string): string; function op_make_placeholder(output: string): string;
function op_store_path(path: string): string; function op_store_path(path: string): string;
function op_convert_hash(input: { function op_convert_hash(hash: string, hashAlgo: string | null, toHashFormat: string): string;
hash: string;
hashAlgo: string | null;
toHashFormat: string;
}): string;
function op_hash_string(algo: string, data: string): string; function op_hash_string(algo: string, data: string): string;
function op_hash_file(algo: string, path: string): string; function op_hash_file(algo: string, path: string): string;
function op_parse_hash(hashStr: string, algo: string | null): { hex: string; algo: string }; function op_parse_hash(hashStr: string, algo: string | null): { hex: string; algo: string };
@@ -43,11 +81,7 @@ declare global {
includePaths: string[], includePaths: string[],
): string; ): string;
function op_decode_span(span: string): { function op_decode_span(span: number): NixAttrs;
file: string | null;
line: number | null;
column: number | null;
};
function op_to_file(name: string, contents: string, references: string[]): string; function op_to_file(name: string, contents: string, references: string[]): string;
@@ -62,16 +96,16 @@ declare global {
function op_from_toml(toml: string): unknown; function op_from_toml(toml: string): unknown;
function op_to_xml(e: NixValue): [string, string[]]; function op_to_xml(e: NixValue): [string, string[]];
function op_finalize_derivation(input: { function op_finalize_derivation(
name: string; name: string,
builder: string; builder: string,
platform: string; platform: string,
outputs: string[]; outputs: string[],
args: string[]; args: string[],
env: [string, string][]; env: [string, string][],
context: string[]; context: string[],
fixedOutput: { hashAlgo: string; hash: string; hashMode: string } | null; fixedOutput: { hashAlgo: string; hash: string; hashMode: string } | null,
}): { drvPath: string; outputs: [string, string][] }; ): { drvPath: string; outputs: [string, string][] };
function op_fetch_url( function op_fetch_url(
url: string, url: string,

View File

@@ -31,14 +31,10 @@ pub(crate) fn compile(expr: &Ir, ctx: &impl CodegenContext) -> String {
code!(&mut buf, ctx; "(()=>{"); 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; code!(&mut buf, ctx;
"const __currentDir=" "const _d="
quoted(&ctx.get_current_dir().display().to_string()) quoted(&ctx.get_current_dir().display().to_string())
";const __with=null;return " ",_w=null;return "
expr 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 { pub(crate) fn compile_scoped(expr: &Ir, ctx: &impl CodegenContext) -> String {
let mut buf = CodeBuffer::with_capacity(8192); let mut buf = CodeBuffer::with_capacity(8192);
code!(&mut buf, ctx; "((__scope)=>{"); code!(&mut buf, ctx; "((_s)=>{");
if std::env::var("NIX_JS_DEBUG_THUNKS").is_ok() {
code!(&mut buf, ctx; "Nix.DEBUG_THUNKS.enabled=true;");
}
code!(&mut buf, ctx; code!(&mut buf, ctx;
"const __currentDir=" "const _d="
quoted(&ctx.get_current_dir().display().to_string()) quoted(&ctx.get_current_dir().display().to_string())
";return " ",_w=null;return "
expr expr
"})" "})"
); );
@@ -191,13 +183,7 @@ where
impl<Ctx: CodegenContext> Compile<Ctx> for rnix::TextRange { impl<Ctx: CodegenContext> Compile<Ctx> for rnix::TextRange {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
code!( code!(buf, "{}", ctx.register_span(*self));
buf,
"\"{}:{}:{}\"",
ctx.get_current_source_id(),
usize::from(self.start()),
usize::from(self.end())
);
} }
} }
@@ -207,7 +193,7 @@ pub(crate) trait CodegenContext {
fn get_current_dir(&self) -> &Path; fn get_current_dir(&self) -> &Path;
fn get_store_dir(&self) -> &str; fn get_store_dir(&self) -> &str;
fn get_current_source_id(&self) -> usize; fn get_current_source_id(&self) -> usize;
fn get_current_source(&self) -> crate::error::Source; fn register_span(&self, range: rnix::TextRange) -> usize;
} }
impl<Ctx: CodegenContext> Compile<Ctx> for ExprId { impl<Ctx: CodegenContext> Compile<Ctx> for ExprId {
@@ -241,7 +227,8 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
code!(buf, ctx; quoted(&s.val)); code!(buf, ctx; quoted(&s.val));
} }
Ir::Path(p) => { 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::If(x) => x.compile(ctx, buf),
Ir::BinOp(x) => x.compile(ctx, buf), Ir::BinOp(x) => x.compile(ctx, buf),
@@ -259,11 +246,13 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
code!(buf, "expr{}", expr_id.0); code!(buf, "expr{}", expr_id.0);
} }
Ir::Builtins(_) => { Ir::Builtins(_) => {
code!(buf, ctx; "Nix.builtins"); // Nix.builtins
code!(buf, ctx; "$b");
} }
&Ir::Builtin(Builtin { inner: name, .. }) => { &Ir::Builtin(Builtin { inner: name, .. }) => {
// Nix.builtins
code!(buf, ctx; code!(buf, ctx;
"Nix.builtins.get(" "$b.get("
ctx.get_sym(name) ctx.get_sym(name)
")" ")"
); );
@@ -276,15 +265,13 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
ref assertion_raw, ref assertion_raw,
span: assert_span, span: assert_span,
}) => { }) => {
let assertion_ir = ctx.get_ir(assertion); let assertion = ctx.get_ir(assertion);
let assertion_span = assertion_ir.span();
// Nix.assert
code!(buf, ctx; code!(buf, ctx;
"Nix.assert(Nix.withContext(\"while evaluating the condition of the assert statement\"," "$a("
assertion_span assertion
",()=>(" ","
assertion_ir
")),"
ctx.get_ir(expr) ctx.get_ir(expr)
"," ","
quoted(assertion_raw) quoted(assertion_raw)
@@ -294,32 +281,35 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
); );
} }
Ir::CurPos(cur_pos) => { Ir::CurPos(cur_pos) => {
// Nix.mkPos
code!(buf, ctx; code!(buf, ctx;
"Nix.mkPos(" "$mp("
cur_pos.span cur_pos.span
")" ")"
); );
} }
&Ir::ReplBinding(ReplBinding { inner: name, .. }) => { &Ir::ReplBinding(ReplBinding { inner: name, .. }) => {
// Nix.getReplBinding
code!(buf, ctx; code!(buf, ctx;
"Nix.getReplBinding(" "$gb("
ctx.get_sym(name) ctx.get_sym(name)
")" ")"
); );
} }
&Ir::ScopedImportBinding(ScopedImportBinding { inner: name, .. }) => { &Ir::ScopedImportBinding(ScopedImportBinding { inner: name, .. }) => {
code!(buf, ctx; code!(buf, ctx;
"__scope.get(" "_s.get("
ctx.get_sym(name) ctx.get_sym(name)
")" ")"
); );
} }
Ir::WithExpr(x) => x.compile(ctx, buf), Ir::WithExpr(x) => x.compile(ctx, buf),
&Ir::WithLookup(WithLookup { inner: name, .. }) => { &Ir::WithLookup(WithLookup { inner: name, .. }) => {
// Nix.lookupWith
code!(buf, ctx; code!(buf, ctx;
"Nix.lookupWith(" "$l("
ctx.get_sym(name) ctx.get_sym(name)
",__with)" ",_w)"
); );
} }
} }
@@ -334,13 +324,10 @@ impl<Ctx: CodegenContext> Compile<Ctx> for If {
alter, alter,
span: _, span: _,
} = self; } = self;
let cond_ir = ctx.get_ir(cond); let cond = ctx.get_ir(cond);
let cond_span = cond_ir.span();
code!(buf, ctx; // Nix.forceBool
"(Nix.withContext(\"while evaluating a branch condition\"," cond_span ",()=>Nix.forceBool(" cond_ir ")))" code!(buf, ctx; "$fb(" cond ")?(" consq "):(" alter ")");
"?(" consq "):(" alter ")"
);
} }
} }
@@ -353,65 +340,50 @@ impl<Ctx: CodegenContext> Compile<Ctx> for BinOp {
match self.kind { match self.kind {
Add | Sub | Mul | Div | Eq | Neq | Lt | Gt | Leq | Geq | Con | Upd => { 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 { let op_func = match self.kind {
Add => "Nix.op.add", Add => "$oa",
Sub => "Nix.op.sub", Sub => "$os",
Mul => "Nix.op.mul", Mul => "$om",
Div => "Nix.op.div", Div => "$od",
Eq => "Nix.op.eq", Eq => "$oe",
Neq => "Nix.op.neq", Neq => "!$oe",
Lt => "Nix.op.lt", Lt => "$ol",
Gt => "Nix.op.gt", Gt => "$og",
Leq => "Nix.op.lte", Leq => "!$og",
Geq => "Nix.op.gte", Geq => "!$ol",
Con => "Nix.op.concat", Con => "$oc",
Upd => "Nix.op.update", Upd => "$ou",
_ => unreachable!(), _ => unreachable!(),
}; };
code!( code!(
buf, ctx; buf, ctx;
"Nix.withContext(\"while evaluating the " op_name " operator\"," self.span ",()=>(" op_func "(" lhs "," rhs ")))" op_func "(" lhs "," rhs ")"
); );
} }
And => { And => {
code!( code!(
buf, ctx; buf, ctx;
"Nix.withContext(\"while evaluating the && operator\"," self.span ",()=>(Nix.forceBool(" lhs ")&&Nix.forceBool(" rhs ")))" "$fb(" lhs ")" "&&" "$fb(" rhs ")"
); );
} }
Or => { Or => {
code!( code!(
buf, ctx; buf, ctx;
"Nix.withContext(\"while evaluating the || operator\"," self.span ",()=>(Nix.forceBool(" lhs ")||Nix.forceBool(" rhs ")))" "$fb(" lhs ")" "||" "$fb(" rhs ")"
); );
} }
Impl => { Impl => {
code!( code!(
buf, ctx; buf, ctx;
"Nix.withContext(\"while evaluating the -> operator\"," self.span ",()=>(!Nix.forceBool(" lhs ")||Nix.forceBool(" rhs ")))" "!$fb(" lhs ")" "||" "$fb(" rhs ")"
); );
} }
PipeL => { PipeL => {
code!(buf, ctx; "Nix.call(" rhs "," lhs ")"); code!(buf, ctx; "$c(" rhs "," lhs ")");
} }
PipeR => { PipeR => {
code!(buf, ctx; "Nix.call(" lhs "," rhs ")"); code!(buf, ctx; "$c(" lhs "," rhs ")");
} }
} }
} }
@@ -423,10 +395,10 @@ impl<Ctx: CodegenContext> Compile<Ctx> for UnOp {
let rhs = ctx.get_ir(self.rhs); let rhs = ctx.get_ir(self.rhs);
match self.kind { match self.kind {
Neg => { Neg => {
code!(buf, ctx; "Nix.op.sub(0n," rhs ")"); code!(buf, ctx; "$os(0n," rhs ")");
} }
Not => { Not => {
code!(buf, ctx; "Nix.op.bnot(" ctx.get_ir(self.rhs) ")"); code!(buf, ctx; "!$fb(" ctx.get_ir(self.rhs) ")");
} }
} }
} }
@@ -444,7 +416,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Func {
ellipsis, ellipsis,
}) = &self.param }) = &self.param
{ {
code!(buf, "Nix.mkFunction(arg{}=>", id); code!(buf, "$mf(arg{}=>", id);
if has_thunks { if has_thunks {
code!(buf, ctx; "{" self.thunks "return " self.body "}"); code!(buf, ctx; "{" self.thunks "return " self.body "}");
} else { } else {
@@ -459,11 +431,11 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Func {
joined(optional.iter(), ",", |ctx: &Ctx, buf, &(sym, _)| { joined(optional.iter(), ",", |ctx: &Ctx, buf, &(sym, _)| {
code!(buf, ctx; ctx.get_sym(sym)); code!(buf, ctx; ctx.get_sym(sym));
}) })
"],{" "],new Map(["
joined(required.iter().chain(optional.iter()), ",", |ctx: &Ctx, buf, &(sym, span)| { joined(required.iter().chain(optional.iter()), ",", |ctx: &Ctx, buf, &(sym, span)| {
code!(buf, ctx; ctx.get_sym(sym) ":" span); code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]");
}) })
"}," "]),"
ellipsis ellipsis
")" ")"
); );
@@ -481,7 +453,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Func {
impl<Ctx: CodegenContext> Compile<Ctx> for Call { impl<Ctx: CodegenContext> Compile<Ctx> for Call {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
code!(buf, ctx; code!(buf, ctx;
"Nix.call(" "$c("
ctx.get_ir(self.func) ctx.get_ir(self.func)
"," ","
ctx.get_ir(self.arg) ctx.get_ir(self.arg)
@@ -500,16 +472,10 @@ impl<Ctx: CodegenContext> Compile<Ctx> for [(ExprId, ExprId)] {
for &(slot, inner) in self { for &(slot, inner) in self {
let inner_ir = ctx.get_ir(inner); let inner_ir = ctx.get_ir(inner);
let inner_span = inner_ir.span();
code!( code!(
buf, ctx; buf, ctx;
"let expr" slot.0 "=Nix.createThunk(()=>(" inner_ir ")," "let expr" slot.0 "=$t(()=>(" inner_ir "),"
"\"expr" slot.0 " " "\"expr" slot.0 "\");"
ctx.get_current_source().get_name() ":"
usize::from(inner_span.start()) ":"
usize::from(inner_span.end())
"\");"
); );
} }
} }
@@ -532,9 +498,9 @@ impl<Ctx: CodegenContext> Compile<Ctx> for WithExpr {
let body = ctx.get_ir(self.body); let body = ctx.get_ir(self.body);
let has_thunks = !self.thunks.is_empty(); let has_thunks = !self.thunks.is_empty();
if has_thunks { 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 { } else {
code!(buf, ctx; "((__with)=>(" body "))({env:" namespace ",last:__with})"); code!(buf, ctx; "((_w)=>(" body "))({env:" namespace ",last:_w})");
} }
} }
} }
@@ -543,7 +509,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Select {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
if let Some(default) = self.default { if let Some(default) = self.default {
code!(buf, ctx; code!(buf, ctx;
"Nix.selectWithDefault(" "$sd("
ctx.get_ir(self.expr) ctx.get_ir(self.expr)
",[" ",["
joined(self.attrpath.iter(), ",", |ctx: &Ctx, buf, attr| { joined(self.attrpath.iter(), ",", |ctx: &Ctx, buf, attr| {
@@ -560,7 +526,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Select {
); );
} else { } else {
code!(buf, ctx; code!(buf, ctx;
"Nix.select(" "$s("
ctx.get_ir(self.expr) ctx.get_ir(self.expr)
",[" ",["
joined(self.attrpath.iter(), ",", |ctx: &Ctx, buf, attr| { joined(self.attrpath.iter(), ",", |ctx: &Ctx, buf, attr| {
@@ -581,31 +547,28 @@ impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
if !self.dyns.is_empty() { if !self.dyns.is_empty() {
code!(buf, ctx; code!(buf, ctx;
"Nix.mkAttrsWithPos({" "$ma(new Map(["
joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(expr, _))| { joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(expr, _))| {
let key = ctx.get_sym(sym); let key = ctx.get_sym(sym);
let val = ctx.get_ir(expr); let val = ctx.get_ir(expr);
code!( code!(
buf, ctx; buf, ctx;
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))| { joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| {
code!(buf, ctx; ctx.get_sym(sym) ":" span); code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]");
}) })
"},{dynKeys:[" "]),{dynKeys:["
joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (key, _, _)| { joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (key, _, _)| {
code!(buf, ctx; ctx.get_ir(*key)); code!(buf, ctx; ctx.get_ir(*key));
}) })
"],dynVals:[" "],dynVals:["
joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (_, val, _)| { joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (_, val, _)| {
let val = ctx.get_ir(*val); let val = ctx.get_ir(*val);
code!( code!(buf, ctx; val);
buf, ctx;
"Nix.withContext(\"while evaluating a dynamic attribute\"," val.span() ",()=>(" val "))"
);
}) })
"],dynSpans:[" "],dynSpans:["
joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (_, _, attr_span)| { joined(self.dyns.iter(), ",", |ctx: &Ctx, buf, (_, _, attr_span)| {
@@ -615,21 +578,21 @@ impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet {
); );
} else if !self.stcs.is_empty() { } else if !self.stcs.is_empty() {
code!(buf, ctx; code!(buf, ctx;
"Nix.mkAttrsWithPos({" "$ma(new Map(["
joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(expr, _))| { joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(expr, _))| {
let key = ctx.get_sym(sym); let key = ctx.get_sym(sym);
let val = ctx.get_ir(expr); let val = ctx.get_ir(expr);
code!( code!(
buf, ctx; buf, ctx;
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))| { joined(self.stcs.iter(), ",", |ctx: &Ctx, buf, (&sym, &(_, span))| {
code!(buf, ctx; ctx.get_sym(sym) ":" span); code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]");
}) })
"})" "]))"
); );
} else { } else {
code!(buf, ctx; "new Map()"); code!(buf, ctx; "new Map()");
@@ -641,12 +604,9 @@ impl<Ctx: CodegenContext> Compile<Ctx> for List {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
code!(buf, ctx; 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); let item = ctx.get_ir(*item);
code!( code!(buf, ctx; item);
buf, ctx;
"Nix.withContext(\"while evaluating list element " idx "\"," item.span() ",()=>(" item "))"
);
}) })
"]" "]"
); );
@@ -656,13 +616,10 @@ impl<Ctx: CodegenContext> Compile<Ctx> for List {
impl<Ctx: CodegenContext> Compile<Ctx> for ConcatStrings { impl<Ctx: CodegenContext> Compile<Ctx> for ConcatStrings {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
code!(buf, ctx; code!(buf, ctx;
"Nix.concatStringsWithContext([" "$cs(["
joined(self.parts.iter(), ",", |ctx: &Ctx, buf, part| { joined(self.parts.iter(), ",", |ctx: &Ctx, buf, part| {
let part = ctx.get_ir(*part); let part = ctx.get_ir(*part);
code!( code!(buf, ctx; part);
buf, ctx;
"Nix.withContext(\"while evaluating a path segment\"," part.span() ",()=>(" part "))"
);
}) })
"]," self.force_string ")" "]," self.force_string ")"
); );
@@ -672,7 +629,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for ConcatStrings {
impl<Ctx: CodegenContext> Compile<Ctx> for HasAttr { impl<Ctx: CodegenContext> Compile<Ctx> for HasAttr {
fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) { fn compile(&self, ctx: &Ctx, buf: &mut CodeBuffer) {
code!(buf, ctx; code!(buf, ctx;
"Nix.hasAttr(" "$h("
ctx.get_ir(self.lhs) ctx.get_ir(self.lhs)
",[" ",["
joined(self.rhs.iter(), ",", |ctx: &Ctx, buf, attr| { joined(self.rhs.iter(), ",", |ctx: &Ctx, buf, attr| {

View File

@@ -1,3 +1,4 @@
use std::cell::UnsafeCell;
use std::path::Path; use std::path::Path;
use std::ptr::NonNull; use std::ptr::NonNull;
@@ -123,9 +124,14 @@ impl Context {
let code = self.ctx.compile(source, None)?; let code = self.ctx.compile(source, None)?;
self.runtime.eval( self.runtime.eval(
format!( format!(
"Nix.builtins.set('derivation',({}));Nix.builtins.set('storeDir','{}')", "Nix.builtins.set('derivation',({}));Nix.builtins.set('storeDir','{}');{}0n",
code, 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, &mut self.ctx,
)?; )?;
@@ -182,6 +188,7 @@ pub(crate) struct Ctx {
global: NonNull<HashMap<SymId, ExprId>>, global: NonNull<HashMap<SymId, ExprId>>,
sources: Vec<Source>, sources: Vec<Source>,
store: DaemonStore, store: DaemonStore,
spans: UnsafeCell<Vec<(usize, TextRange)>>,
} }
impl Ctx { impl Ctx {
@@ -278,6 +285,7 @@ impl Ctx {
global: unsafe { NonNull::new_unchecked(Box::leak(Box::new(global))) }, global: unsafe { NonNull::new_unchecked(Box::leak(Box::new(global))) },
sources: Vec::new(), sources: Vec::new(),
store, store,
spans: UnsafeCell::new(Vec::new()),
}) })
} }
@@ -363,12 +371,15 @@ impl CodegenContext for Ctx {
.checked_sub(1) .checked_sub(1)
.expect("current_source not set") .expect("current_source not set")
} }
fn get_current_source(&self) -> crate::error::Source {
self.sources.last().expect("current_source not set").clone()
}
fn get_store_dir(&self) -> &str { fn get_store_dir(&self) -> &str {
self.store.get_store_dir() 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 { impl RuntimeContext for Ctx {
@@ -390,6 +401,10 @@ impl RuntimeContext for Ctx {
fn get_store(&self) -> &DaemonStore { fn get_store(&self) -> &DaemonStore {
&self.store &self.store
} }
fn get_span(&self, id: usize) -> (usize, rnix::TextRange) {
let spans = unsafe { &*self.spans.get() };
spans[id]
}
} }
enum Scope<'ctx> { enum Scope<'ctx> {

View File

@@ -292,43 +292,32 @@ fn parse_frames(stack: &str, ctx: &impl RuntimeContext) -> Vec<NixStackFrame> {
let mut frames = Vec::new(); let mut frames = Vec::new();
for line in stack.lines() { 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 { let Some(rest) = line.strip_prefix("NIX_STACK_FRAME:") else {
continue; 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; continue;
} }
let src = match parts[0].parse() { let span_id: usize = match parts[0].parse() {
Ok(id) => ctx.get_source(id), Ok(id) => 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,
Err(_) => continue, 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() == 2 {
parts[1].to_string()
let message = {
if parts.len() == 4 {
parts[3].to_string()
} else { } else {
String::new() String::new()
}
}; };
frames.push(NixStackFrame { span, message, src }); frames.push(NixStackFrame { span, message, src });
} }
// Deduplicate consecutive identical frames
frames.dedup_by(|a, b| a.span == b.span && a.message == b.message); frames.dedup_by(|a, b| a.span == b.span && a.message == b.message);
frames frames

View File

@@ -1,8 +1,8 @@
use deno_core::OpState; use deno_core::OpState;
use deno_core::ToV8;
use deno_core::op2; use deno_core::op2;
use nix_compat::nixhash::HashAlgo; use nix_compat::nixhash::HashAlgo;
use nix_compat::nixhash::NixHash; use nix_compat::nixhash::NixHash;
use serde::Serialize;
use tracing::{debug, info, warn}; use tracing::{debug, info, warn};
use crate::runtime::OpStateExt; use crate::runtime::OpStateExt;
@@ -22,19 +22,19 @@ pub use metadata_cache::MetadataCache;
use crate::nar; use crate::nar;
use crate::runtime::NixRuntimeError; use crate::runtime::NixRuntimeError;
#[derive(Serialize)] #[derive(ToV8)]
pub struct FetchUrlResult { pub struct FetchUrlResult {
pub store_path: String, pub store_path: String,
pub hash: String, pub hash: String,
} }
#[derive(Serialize)] #[derive(ToV8)]
pub struct FetchTarballResult { pub struct FetchTarballResult {
pub store_path: String, pub store_path: String,
pub nar_hash: String, pub nar_hash: String,
} }
#[derive(Serialize)] #[derive(ToV8)]
pub struct FetchGitResult { pub struct FetchGitResult {
pub out_path: String, pub out_path: String,
pub rev: String, pub rev: String,
@@ -47,7 +47,6 @@ pub struct FetchGitResult {
} }
#[op2] #[op2]
#[serde]
pub fn op_fetch_url<Ctx: RuntimeContext>( pub fn op_fetch_url<Ctx: RuntimeContext>(
state: &mut OpState, state: &mut OpState,
#[string] url: String, #[string] url: String,
@@ -152,7 +151,6 @@ pub fn op_fetch_url<Ctx: RuntimeContext>(
} }
#[op2] #[op2]
#[serde]
pub fn op_fetch_tarball<Ctx: RuntimeContext>( pub fn op_fetch_tarball<Ctx: RuntimeContext>(
state: &mut OpState, state: &mut OpState,
#[string] url: String, #[string] url: String,
@@ -266,7 +264,6 @@ pub fn op_fetch_tarball<Ctx: RuntimeContext>(
} }
#[op2] #[op2]
#[serde]
pub fn op_fetch_git<Ctx: RuntimeContext>( pub fn op_fetch_git<Ctx: RuntimeContext>(
state: &mut OpState, state: &mut OpState,
#[string] url: String, #[string] url: String,

View File

@@ -26,6 +26,12 @@ struct Cli {
#[derive(Subcommand)] #[derive(Subcommand)]
enum Command { enum Command {
Compile {
#[clap(flatten)]
source: ExprSource,
#[arg(long)]
silent: bool
},
Eval { Eval {
#[clap(flatten)] #[clap(flatten)]
source: ExprSource, source: ExprSource,
@@ -63,6 +69,30 @@ fn create_context(#[cfg(feature = "inspector")] cli: &Cli) -> Result<Context> {
Ok(Context::new()?) Ok(Context::new()?)
} }
fn run_compile(context: &mut Context, src: ExprSource, silent: bool) -> Result<()> {
let src = if let Some(expr) = src.expr {
Source::new_eval(expr)?
} else if let Some(file) = src.file {
Source::new_file(file)?
} else {
unreachable!()
};
match context.compile(src) {
Ok(compiled) => {
if !silent {
println!("{compiled}");
}
}
Err(err) => {
eprintln!("{:?}", miette::Report::new(*err));
exit(1);
}
};
#[cfg(feature = "inspector")]
context.wait_for_inspector_disconnect();
Ok(())
}
fn run_eval(context: &mut Context, src: ExprSource) -> Result<()> { fn run_eval(context: &mut Context, src: ExprSource) -> Result<()> {
let src = if let Some(expr) = src.expr { let src = if let Some(expr) = src.expr {
Source::new_eval(expr)? Source::new_eval(expr)?
@@ -150,6 +180,7 @@ fn main() -> Result<()> {
)?; )?;
match cli.command { match cli.command {
Command::Compile { source , silent } => run_compile(&mut context, source, silent),
Command::Eval { source } => run_eval(&mut context, source), Command::Eval { source } => run_eval(&mut context, source),
Command::Repl => run_repl(&mut context), Command::Repl => run_repl(&mut context),
} }

View File

@@ -26,6 +26,7 @@ pub(crate) trait RuntimeContext: 'static {
fn compile_scoped(&mut self, source: Source, scope: Vec<String>) -> Result<String>; fn compile_scoped(&mut self, source: Source, scope: Vec<String>) -> Result<String>;
fn get_source(&self, id: usize) -> Source; fn get_source(&self, id: usize) -> Source;
fn get_store(&self) -> &DaemonStore; fn get_store(&self) -> &DaemonStore;
fn get_span(&self, id: usize) -> (usize, rnix::TextRange);
} }
pub(crate) trait OpStateExt<Ctx: RuntimeContext> { pub(crate) trait OpStateExt<Ctx: RuntimeContext> {
@@ -115,6 +116,7 @@ pub(crate) use private::NixRuntimeError;
pub(crate) struct Runtime<Ctx: RuntimeContext> { pub(crate) struct Runtime<Ctx: RuntimeContext> {
js_runtime: JsRuntime, js_runtime: JsRuntime,
#[cfg(feature = "inspector")]
rt: tokio::runtime::Runtime, rt: tokio::runtime::Runtime,
#[cfg(feature = "inspector")] #[cfg(feature = "inspector")]
wait_for_inspector: bool, wait_for_inspector: bool,
@@ -142,7 +144,12 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
static INIT: Once = Once::new(); static INIT: Once = Once::new();
INIT.call_once(|| { INIT.call_once(|| {
assert_eq!( assert_eq!(
deno_core::v8_set_flags(vec!["".into(), format!("--stack-size={}", 8 * 1024)]), deno_core::v8_set_flags(vec![
"".into(),
format!("--stack-size={}", 8 * 1024),
#[cfg(feature = "prof")]
("--prof".into())
]),
[""] [""]
); );
JsRuntime::init_platform(Some(v8::new_default_platform(0, false).make_shared())); JsRuntime::init_platform(Some(v8::new_default_platform(0, false).make_shared()));
@@ -172,6 +179,7 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
Ok(Self { Ok(Self {
js_runtime, js_runtime,
#[cfg(feature = "inspector")]
rt: tokio::runtime::Builder::new_current_thread() rt: tokio::runtime::Builder::new_current_thread()
.enable_all() .enable_all()
.build() .build()
@@ -224,22 +232,6 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
crate::error::parse_js_error(error, ctx) crate::error::parse_js_error(error, ctx)
})?; })?;
let global_value = self
.rt
.block_on(self.js_runtime.resolve(global_value))
.map_err(|error| {
let op_state = self.js_runtime.op_state();
let op_state_borrow = op_state.borrow();
let ctx: &Ctx = op_state_borrow.get_ctx();
crate::error::parse_js_error(error, ctx)
})?;
#[cfg(feature = "inspector")]
{
let _ = self
.rt
.block_on(self.js_runtime.run_event_loop(Default::default()));
}
// Retrieve scope from JsRuntime // Retrieve scope from JsRuntime
deno_core::scope!(scope, self.js_runtime); deno_core::scope!(scope, self.js_runtime);

View File

@@ -21,10 +21,10 @@ use deno_core::url::Url;
use fastwebsockets::Frame; use fastwebsockets::Frame;
use fastwebsockets::OpCode; use fastwebsockets::OpCode;
use fastwebsockets::WebSocket; use fastwebsockets::WebSocket;
use hashbrown::HashMap;
use hyper::body::Bytes; use hyper::body::Bytes;
use hyper_util::rt::TokioIo; use hyper_util::rt::TokioIo;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::pin::pin; use std::pin::pin;
use std::process; use std::process;

View File

@@ -1,9 +1,11 @@
use std::collections::BTreeMap;
use std::convert::Infallible;
use std::path::{Component, Path, PathBuf}; use std::path::{Component, Path, PathBuf};
use std::str::FromStr; use std::str::FromStr;
use hashbrown::hash_map::{Entry, HashMap}; use deno_core::error::JsError;
use deno_core::{FromV8, OpState, ToV8, v8};
use deno_core::{FromV8, OpState, v8}; use hashbrown::{HashMap, HashSet, hash_map::Entry};
use regex::Regex; use regex::Regex;
use rust_embed::Embed; use rust_embed::Embed;
@@ -36,16 +38,84 @@ impl RegexCache {
} }
} }
pub(super) struct Map<K, V>(HashMap<K, V>);
impl<'a, K, V> ToV8<'a> for Map<K, V>
where
K: ToV8<'a>,
K::Error: ToString,
V: ToV8<'a>,
V::Error: ToString,
{
type Error = NixRuntimeError;
fn to_v8<'i>(self, scope: &mut v8::PinScope<'a, 'i>) -> Result<v8::Local<'a, v8::Value>> {
let map = v8::Map::new(scope);
for (k, v) in self.0 {
let k = k.to_v8(scope).map_err(|err| err.to_string())?;
let v = v.to_v8(scope).map_err(|err| err.to_string())?;
map.set(scope, k, v).ok_or("Failed to set V8 Map KV")?;
}
Ok(map.into())
}
}
#[derive(Embed)] #[derive(Embed)]
#[folder = "src/runtime/corepkgs"] #[folder = "src/runtime/corepkgs"]
pub(crate) struct CorePkgs; pub(crate) struct CorePkgs;
#[deno_core::op2] fn new_simple_jserror(msg: String) -> Box<JsError> {
#[string] JsError {
message: Some(msg.clone()),
name: None,
stack: None,
cause: None,
exception_message: msg,
frames: Vec::new(),
source_line: None,
source_line_frame_index: None,
aggregated: None,
additional_properties: Vec::new(),
}
.into()
}
struct Compiled(String);
impl<'a> ToV8<'a> for Compiled {
type Error = Box<JsError>;
fn to_v8<'i>(
self,
scope: &mut v8::PinScope<'a, 'i>,
) -> std::result::Result<v8::Local<'a, v8::Value>, Self::Error> {
let Ok(script) = self.0.to_v8(scope);
let Some(source) = script.to_string(scope) else {
unsafe { std::hint::unreachable_unchecked() }
};
let tc = std::pin::pin!(v8::TryCatch::new(scope));
let mut scope = tc.init();
let Some(compiled) = v8::Script::compile(&scope, source, None) else {
let msg = scope
.exception()
.map(|e| e.to_rust_string_lossy(&scope))
.unwrap_or_else(|| "failed to compile code".into());
return Err(new_simple_jserror(msg));
};
match compiled.run(&scope) {
Some(val) => Ok(val),
None => Err(scope
.exception()
.map(|e| JsError::from_v8_exception(&mut scope, e))
.unwrap_or_else(|| {
new_simple_jserror("script execution failed unexpectedly".into())
})),
}
}
}
#[deno_core::op2(reentrant)]
pub(super) fn op_import<Ctx: RuntimeContext>( pub(super) fn op_import<Ctx: RuntimeContext>(
state: &mut OpState, state: &mut OpState,
#[string] path: String, #[string] path: String,
) -> Result<String> { ) -> Result<Compiled> {
let _span = tracing::info_span!("op_import", path = %path).entered(); let _span = tracing::info_span!("op_import", path = %path).entered();
let ctx: &mut Ctx = state.get_ctx_mut(); let ctx: &mut Ctx = state.get_ctx_mut();
@@ -61,7 +131,8 @@ pub(super) fn op_import<Ctx: RuntimeContext>(
.into(), .into(),
); );
ctx.add_source(source.clone()); ctx.add_source(source.clone());
return Ok(ctx.compile(source).map_err(|err| err.to_string())?); let code = ctx.compile(source).map_err(|err| err.to_string())?;
return Ok(Compiled(code));
} else { } else {
return Err(format!("Corepkg not found: {}", corepkg_name).into()); return Err(format!("Corepkg not found: {}", corepkg_name).into());
} }
@@ -85,7 +156,8 @@ pub(super) fn op_import<Ctx: RuntimeContext>(
tracing::debug!("Compiling file"); tracing::debug!("Compiling file");
ctx.add_source(source.clone()); ctx.add_source(source.clone());
Ok(ctx.compile(source).map_err(|err| err.to_string())?) let code = ctx.compile(source).map_err(|err| err.to_string())?;
Ok(Compiled(code))
} }
#[deno_core::op2] #[deno_core::op2]
@@ -93,7 +165,7 @@ pub(super) fn op_import<Ctx: RuntimeContext>(
pub(super) fn op_scoped_import<Ctx: RuntimeContext>( pub(super) fn op_scoped_import<Ctx: RuntimeContext>(
state: &mut OpState, state: &mut OpState,
#[string] path: String, #[string] path: String,
#[serde] scope: Vec<String>, #[scoped] scope: Vec<String>,
) -> Result<String> { ) -> Result<String> {
let _span = tracing::info_span!("op_scoped_import", path = %path).entered(); let _span = tracing::info_span!("op_scoped_import", path = %path).entered();
let ctx: &mut Ctx = state.get_ctx_mut(); let ctx: &mut Ctx = state.get_ctx_mut();
@@ -161,10 +233,7 @@ pub(super) fn op_read_file_type(#[string] path: String) -> Result<String> {
} }
#[deno_core::op2] #[deno_core::op2]
#[serde] pub(super) fn op_read_dir(#[string] path: String) -> Result<Map<String, &'static str>> {
pub(super) fn op_read_dir(
#[string] path: String,
) -> Result<std::collections::HashMap<String, String>> {
let path = Path::new(&path); let path = Path::new(&path);
if !path.is_dir() { if !path.is_dir() {
@@ -174,7 +243,7 @@ pub(super) fn op_read_dir(
let entries = std::fs::read_dir(path) let entries = std::fs::read_dir(path)
.map_err(|e| format!("Failed to read directory {}: {}", path.display(), e))?; .map_err(|e| format!("Failed to read directory {}: {}", path.display(), e))?;
let mut result = std::collections::HashMap::new(); let mut result = HashMap::new();
for entry in entries { for entry in entries {
let entry = entry.map_err(|e| format!("Failed to read directory entry: {}", e))?; let entry = entry.map_err(|e| format!("Failed to read directory entry: {}", e))?;
@@ -198,10 +267,10 @@ pub(super) fn op_read_dir(
"unknown" "unknown"
}; };
result.insert(file_name, type_str.to_string()); result.insert(file_name, type_str);
} }
Ok(result) Ok(Map(result))
} }
#[deno_core::op2] #[deno_core::op2]
@@ -255,35 +324,40 @@ pub(super) fn op_make_placeholder(#[string] output: String) -> String {
format!("/{}", encoded) format!("/{}", encoded)
} }
#[deno_core::op2] enum StringOrU32 {
#[serde] String(String),
pub(super) fn op_decode_span<Ctx: RuntimeContext>( U32(u32),
state: &mut OpState, }
#[string] span_str: String, impl<'a> ToV8<'a> for StringOrU32 {
) -> Result<serde_json::Value> { type Error = Infallible;
let parts: Vec<&str> = span_str.split(':').collect(); fn to_v8<'i>(
if parts.len() != 3 { self,
return Ok(serde_json::json!({ scope: &mut v8::PinScope<'a, 'i>,
"file": serde_json::Value::Null, ) -> std::result::Result<v8::Local<'a, v8::Value>, Self::Error> {
"line": serde_json::Value::Null, match self {
"column": serde_json::Value::Null Self::String(x) => x.to_v8(scope),
})); Self::U32(x) => x.to_v8(scope),
}
}
} }
let source_id: usize = parts[0].parse().map_err(|_| "Invalid source ID")?; #[deno_core::op2]
let start: u32 = parts[1].parse().map_err(|_| "Invalid start offset")?; pub(super) fn op_decode_span<Ctx: RuntimeContext>(
state: &mut OpState,
#[smi] span_id: u32,
) -> Map<&'static str, StringOrU32> {
let ctx: &Ctx = state.get_ctx(); 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 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!({ Map(HashMap::from([
"file": source.get_name(), ("file", StringOrU32::String(source.get_name())),
"line": line, ("line", StringOrU32::U32(line)),
"column": column ("column", StringOrU32::U32(column)),
})) ]))
} }
fn byte_offset_to_line_col(content: &str, offset: usize) -> (u32, u32) { fn byte_offset_to_line_col(content: &str, offset: usize) -> (u32, u32) {
@@ -305,14 +379,18 @@ fn byte_offset_to_line_col(content: &str, offset: usize) -> (u32, u32) {
(line, col) (line, col)
} }
#[derive(serde::Serialize)] mod private {
use deno_core::ToV8;
#[derive(ToV8)]
pub(super) struct ParsedHash { pub(super) struct ParsedHash {
hex: String, pub(super) hex: String,
algo: String, pub(super) algo: String,
} }
}
use private::*;
#[deno_core::op2] #[deno_core::op2]
#[serde]
pub(super) fn op_parse_hash( pub(super) fn op_parse_hash(
#[string] hash_str: String, #[string] hash_str: String,
#[string] algo: Option<String>, #[string] algo: Option<String>,
@@ -444,7 +522,7 @@ pub(super) fn op_to_file<Ctx: RuntimeContext>(
state: &mut OpState, state: &mut OpState,
#[string] name: String, #[string] name: String,
#[string] contents: String, #[string] contents: String,
#[serde] references: Vec<String>, #[scoped] references: Vec<String>,
) -> Result<String> { ) -> Result<String> {
let ctx: &Ctx = state.get_ctx(); let ctx: &Ctx = state.get_ctx();
let store = ctx.get_store(); let store = ctx.get_store();
@@ -498,7 +576,6 @@ pub(super) fn op_get_env(#[string] key: String) -> Result<String> {
} }
#[deno_core::op2] #[deno_core::op2]
#[serde]
pub(super) fn op_walk_dir(#[string] path: String) -> Result<Vec<(String, String)>> { pub(super) fn op_walk_dir(#[string] path: String) -> Result<Vec<(String, String)>> {
fn walk_recursive( fn walk_recursive(
base: &Path, base: &Path,
@@ -560,7 +637,7 @@ pub(super) fn op_add_filtered_path<Ctx: RuntimeContext>(
#[string] name: Option<String>, #[string] name: Option<String>,
recursive: bool, recursive: bool,
#[string] sha256: Option<String>, #[string] sha256: Option<String>,
#[serde] include_paths: Vec<String>, #[scoped] include_paths: Vec<String>,
) -> Result<String> { ) -> Result<String> {
use nix_compat::nixhash::{HashAlgo, NixHash}; use nix_compat::nixhash::{HashAlgo, NixHash};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
@@ -659,7 +736,6 @@ pub(super) fn op_add_filtered_path<Ctx: RuntimeContext>(
} }
#[deno_core::op2] #[deno_core::op2]
#[serde]
pub(super) fn op_match( pub(super) fn op_match(
state: &mut OpState, state: &mut OpState,
#[string] regex: String, #[string] regex: String,
@@ -684,7 +760,6 @@ pub(super) fn op_match(
} }
#[deno_core::op2] #[deno_core::op2]
#[serde]
pub(super) fn op_split( pub(super) fn op_split(
state: &mut OpState, state: &mut OpState,
#[string] regex: String, #[string] regex: String,
@@ -720,13 +795,30 @@ pub(super) fn op_split(
Ok(ret) Ok(ret)
} }
#[derive(serde::Serialize)]
#[serde(untagged)]
pub(super) enum SplitResult { pub(super) enum SplitResult {
Text(String), Text(String),
Captures(Vec<Option<String>>), Captures(Vec<Option<String>>),
} }
impl<'a> ToV8<'a> for SplitResult {
type Error = Infallible;
fn to_v8<'i>(
self,
scope: &mut v8::PinScope<'a, 'i>,
) -> std::result::Result<v8::Local<'a, v8::Value>, Self::Error> {
Ok(match self {
Self::Text(text) => {
let Ok(value) = text.to_v8(scope);
value
}
Self::Captures(captures) => {
let Ok(value) = captures.to_v8(scope);
value
}
})
}
}
pub(super) enum NixJsonValue { pub(super) enum NixJsonValue {
Null, Null,
Bool(bool), Bool(bool),
@@ -847,34 +939,24 @@ pub(super) fn op_from_toml(#[string] toml_str: String) -> Result<NixJsonValue> {
toml_to_nix(parsed) toml_to_nix(parsed)
} }
#[derive(serde::Deserialize)] mod scope {
use deno_core::{FromV8, ToV8};
#[derive(FromV8)]
pub(super) struct FixedOutputInput { pub(super) struct FixedOutputInput {
#[serde(rename = "hashAlgo")] pub(super) hash_algo: String,
hash_algo: String, pub(super) hash: String,
hash: String, pub(super) hash_mode: String,
#[serde(rename = "hashMode")]
hash_mode: String,
} }
#[derive(serde::Deserialize)] #[derive(ToV8)]
pub(super) struct FinalizeDerivationInput {
name: String,
builder: String,
platform: String,
outputs: Vec<String>,
args: Vec<String>,
env: Vec<(String, String)>,
context: Vec<String>,
#[serde(rename = "fixedOutput")]
fixed_output: Option<FixedOutputInput>,
}
#[derive(serde::Serialize)]
pub(super) struct FinalizeDerivationOutput { pub(super) struct FinalizeDerivationOutput {
#[serde(rename = "drvPath")] // renamed to `drvPath` automatically
drv_path: String, pub(super) drv_path: String,
outputs: Vec<(String, String)>, pub(super) outputs: Vec<(String, String)>,
} }
}
use scope::*;
fn output_path_name(drv_name: &str, output: &str) -> String { fn output_path_name(drv_name: &str, output: &str) -> String {
if output == "out" { if output == "out" {
@@ -885,10 +967,16 @@ fn output_path_name(drv_name: &str, output: &str) -> String {
} }
#[deno_core::op2] #[deno_core::op2]
#[serde]
pub(super) fn op_finalize_derivation<Ctx: RuntimeContext>( pub(super) fn op_finalize_derivation<Ctx: RuntimeContext>(
state: &mut OpState, state: &mut OpState,
#[serde] input: FinalizeDerivationInput, #[string] name: String,
#[string] builder: String,
#[string] platform: String,
#[scoped] outputs: Vec<String>,
#[scoped] args: Vec<String>,
#[scoped] env: Vec<(String, String)>,
#[scoped] context: Vec<String>,
#[scoped] fixed_output: Option<FixedOutputInput>,
) -> Result<FinalizeDerivationOutput> { ) -> Result<FinalizeDerivationOutput> {
use crate::derivation::{DerivationData, OutputInfo}; use crate::derivation::{DerivationData, OutputInfo};
use crate::string_context::extract_input_drvs_and_srcs; use crate::string_context::extract_input_drvs_and_srcs;
@@ -898,15 +986,15 @@ pub(super) fn op_finalize_derivation<Ctx: RuntimeContext>(
let store_dir = store.get_store_dir().to_string(); let store_dir = store.get_store_dir().to_string();
let (input_drvs, input_srcs) = let (input_drvs, input_srcs) =
extract_input_drvs_and_srcs(&input.context).map_err(NixRuntimeError::from)?; extract_input_drvs_and_srcs(&context).map_err(NixRuntimeError::from)?;
let env: std::collections::BTreeMap<String, String> = input.env.into_iter().collect(); let env: BTreeMap<String, String> = env.into_iter().collect();
let drv_path; let drv_path;
let output_paths: Vec<(String, String)>; let output_paths: Vec<(String, String)>;
if let Some(fixed) = &input.fixed_output { if let Some(fixed) = &fixed_output {
let path_name = output_path_name(&input.name, "out"); let path_name = output_path_name(&name, "out");
let out_path = crate::runtime::ops::op_make_fixed_output_path_impl( let out_path = crate::runtime::ops::op_make_fixed_output_path_impl(
&store_dir, &store_dir,
&fixed.hash_algo, &fixed.hash_algo,
@@ -921,7 +1009,7 @@ pub(super) fn op_finalize_derivation<Ctx: RuntimeContext>(
"" ""
}; };
let mut final_outputs = std::collections::BTreeMap::new(); let mut final_outputs = BTreeMap::new();
final_outputs.insert( final_outputs.insert(
"out".to_string(), "out".to_string(),
OutputInfo { OutputInfo {
@@ -935,13 +1023,13 @@ pub(super) fn op_finalize_derivation<Ctx: RuntimeContext>(
final_env.insert("out".to_string(), out_path.clone()); final_env.insert("out".to_string(), out_path.clone());
let drv = DerivationData { let drv = DerivationData {
name: input.name.clone(), name: name.clone(),
outputs: final_outputs, outputs: final_outputs,
input_drvs: input_drvs.clone(), input_drvs: input_drvs.clone(),
input_srcs: input_srcs.clone(), input_srcs: input_srcs.clone(),
platform: input.platform, platform,
builder: input.builder, builder,
args: input.args, args,
env: final_env, env: final_env,
}; };
@@ -949,7 +1037,7 @@ pub(super) fn op_finalize_derivation<Ctx: RuntimeContext>(
let references = drv.collect_references(); let references = drv.collect_references();
drv_path = store drv_path = store
.add_text_to_store(&format!("{}.drv", input.name), &final_aterm, references) .add_text_to_store(&format!("{}.drv", name), &final_aterm, references)
.map_err(|e| NixRuntimeError::from(format!("failed to write derivation: {}", e)))?; .map_err(|e| NixRuntimeError::from(format!("failed to write derivation: {}", e)))?;
let fixed_hash_fingerprint = format!( let fixed_hash_fingerprint = format!(
@@ -963,8 +1051,7 @@ pub(super) fn op_finalize_derivation<Ctx: RuntimeContext>(
output_paths = vec![("out".to_string(), out_path)]; output_paths = vec![("out".to_string(), out_path)];
} else { } else {
let masked_outputs: std::collections::BTreeMap<String, OutputInfo> = input let masked_outputs: std::collections::BTreeMap<String, OutputInfo> = outputs
.outputs
.iter() .iter()
.map(|o| { .map(|o| {
( (
@@ -979,18 +1066,18 @@ pub(super) fn op_finalize_derivation<Ctx: RuntimeContext>(
.collect(); .collect();
let mut masked_env = env.clone(); let mut masked_env = env.clone();
for output in &input.outputs { for output in &outputs {
masked_env.insert(output.clone(), String::new()); masked_env.insert(output.clone(), String::new());
} }
let masked_drv = DerivationData { let masked_drv = DerivationData {
name: input.name.clone(), name: name.clone(),
outputs: masked_outputs, outputs: masked_outputs,
input_drvs: input_drvs.clone(), input_drvs: input_drvs.clone(),
input_srcs: input_srcs.clone(), input_srcs: input_srcs.clone(),
platform: input.platform.clone(), platform: platform.clone(),
builder: input.builder.clone(), builder: builder.clone(),
args: input.args.clone(), args: args.clone(),
env: masked_env, env: masked_env,
}; };
@@ -1018,8 +1105,8 @@ pub(super) fn op_finalize_derivation<Ctx: RuntimeContext>(
let mut final_env = env; let mut final_env = env;
let mut result_output_paths = Vec::new(); let mut result_output_paths = Vec::new();
for output_name in &input.outputs { for output_name in &outputs {
let path_name = output_path_name(&input.name, output_name); let path_name = output_path_name(&name, output_name);
let out_path = crate::nix_utils::make_store_path( let out_path = crate::nix_utils::make_store_path(
&store_dir, &store_dir,
&format!("output:{}", output_name), &format!("output:{}", output_name),
@@ -1039,13 +1126,13 @@ pub(super) fn op_finalize_derivation<Ctx: RuntimeContext>(
} }
let final_drv = DerivationData { let final_drv = DerivationData {
name: input.name, name,
outputs: final_outputs, outputs: final_outputs,
input_drvs, input_drvs,
input_srcs, input_srcs,
platform: input.platform, platform,
builder: input.builder, builder,
args: input.args, args,
env: final_env, env: final_env,
}; };
@@ -1172,21 +1259,21 @@ pub(super) fn op_hash_file(#[string] algo: String, #[string] path: String) -> Re
#[deno_core::op2] #[deno_core::op2]
#[string] #[string]
pub(super) fn op_convert_hash(#[serde] input: ConvertHashInput) -> Result<String> { pub(super) fn op_convert_hash(
#[string] hash: &str,
#[string] algo: Option<String>,
#[string] format: &str,
) -> Result<String> {
use nix_compat::nixhash::{HashAlgo, NixHash}; use nix_compat::nixhash::{HashAlgo, NixHash};
let hash_algo = input let hash_algo = algo.as_deref().and_then(|a| HashAlgo::from_str(a).ok());
.hash_algo
.as_deref()
.and_then(|a| HashAlgo::from_str(a).ok());
let hash = NixHash::from_str(&input.hash, hash_algo).map_err(|e| { let hash = NixHash::from_str(hash, hash_algo)
NixRuntimeError::from(format!("cannot convert hash '{}': {}", input.hash, e)) .map_err(|e| NixRuntimeError::from(format!("cannot convert hash '{}': {}", hash, e)))?;
})?;
let bytes = hash.digest_as_bytes(); let bytes = hash.digest_as_bytes();
match input.to_format.as_str() { match format {
"base16" => Ok(hex::encode(bytes)), "base16" => Ok(hex::encode(bytes)),
"nix32" | "base32" => Ok(nix_compat::nixbase32::encode(bytes)), "nix32" | "base32" => Ok(nix_compat::nixbase32::encode(bytes)),
"base64" => { "base64" => {
@@ -1199,20 +1286,11 @@ pub(super) fn op_convert_hash(#[serde] input: ConvertHashInput) -> Result<String
})), })),
_ => Err(NixRuntimeError::from(format!( _ => Err(NixRuntimeError::from(format!(
"unknown hash format '{}'", "unknown hash format '{}'",
input.to_format format
))), ))),
} }
} }
#[derive(serde::Deserialize)]
pub(super) struct ConvertHashInput {
hash: String,
#[serde(rename = "hashAlgo")]
hash_algo: Option<String>,
#[serde(rename = "toHashFormat")]
to_format: String,
}
struct XmlCtx<'s> { struct XmlCtx<'s> {
force_fn: v8::Local<'s, v8::Function>, force_fn: v8::Local<'s, v8::Function>,
is_thunk: v8::Local<'s, v8::Symbol>, is_thunk: v8::Local<'s, v8::Symbol>,
@@ -1254,7 +1332,7 @@ impl<'s> XmlCtx<'s> {
struct XmlWriter { struct XmlWriter {
buf: String, buf: String,
context: Vec<String>, context: Vec<String>,
drvs_seen: hashbrown::HashSet<String>, drvs_seen: HashSet<String>,
} }
impl XmlWriter { impl XmlWriter {
@@ -1262,7 +1340,7 @@ impl XmlWriter {
Self { Self {
buf: String::with_capacity(4096), buf: String::with_capacity(4096),
context: Vec::new(), context: Vec::new(),
drvs_seen: hashbrown::HashSet::new(), drvs_seen: HashSet::new(),
} }
} }
@@ -1753,7 +1831,6 @@ impl<'a> FromV8<'a> for ToXmlResult {
} }
#[deno_core::op2] #[deno_core::op2]
#[serde]
pub(super) fn op_to_xml(#[scoped] value: ToXmlResult) -> (String, Vec<String>) { pub(super) fn op_to_xml(#[scoped] value: ToXmlResult) -> (String, Vec<String>) {
(value.xml, value.context) (value.xml, value.context)
} }