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*
profile.json.gz
prof.json
*.cpuprofile
*.cpuprofile.gz
*v8.log*

8
Cargo.lock generated
View File

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

View File

@@ -21,3 +21,11 @@
[no-exit-message]
@evali 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]
inspector = ["dep:fastwebsockets", "dep:hyper", "dep:hyper-util", "dep:http-body-util", "dep:http", "dep:uuid"]
prof = []
[dev-dependencies]
criterion = { version = "0.8", features = ["html_reports"] }

View File

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

View File

@@ -2,11 +2,16 @@
* 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 { forceFunction, forceStringNoCtx } from "../type-assert";
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";
export const fromJSON = (e: NixValue): NixValue => {
@@ -306,13 +311,13 @@ export const nixValueToJson = (
if (typeof v === "number") return v;
if (typeof v === "boolean") 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) {
outContext.add(elem);
}
return v.value;
}
if (typeof v === "object" && IS_PATH in v) {
if (v instanceof NixPath) {
if (copyToStore) {
const storePath = Deno.core.ops.op_copy_path_to_store(v.value);
outContext.add(storePath);

View File

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

View File

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

View File

@@ -11,8 +11,8 @@ import {
forceStringNoCtx,
forceStringValue,
} from "../type-assert";
import type { NixAttrs, NixPath, NixString, NixValue } from "../types";
import { CatchableError, IS_PATH, isNixPath } from "../types";
import type { NixAttrs, NixString, NixValue } from "../types";
import { CatchableError, isNixPath, NixPath } from "../types";
import { coerceToPath, coerceToString, StringCoercionMode } from "./conversion";
import { baseNameOf } from "./path";
import { isAttrs, isPath, isString } from "./type-check";
@@ -49,8 +49,7 @@ export const importFunc = (path: NixValue): NixValue => {
return cached;
}
const code = Deno.core.ops.op_import(pathStr);
const result = Function(`return (${code})`)();
const result = Deno.core.ops.op_import(pathStr);
importCache.set(pathStr, result);
return result;
@@ -85,24 +84,24 @@ export const fetchClosure = (_args: NixValue): never => {
};
export interface FetchUrlResult {
store_path: string;
storePath: string;
hash: string;
}
export interface FetchTarballResult {
store_path: string;
nar_hash: string;
storePath: string;
narHash: string;
}
export interface FetchGitResult {
out_path: string;
outPath: string;
rev: string;
short_rev: string;
rev_count: number;
last_modified: number;
last_modified_date: string;
shortRev: string;
revCount: number;
lastModified: number;
lastModifiedDate: string;
submodules: boolean;
nar_hash: string | null;
narHash: string | null;
}
const normalizeUrlInput = (
@@ -154,16 +153,16 @@ export const fetchurl = (args: NixValue): NixString => {
executable ?? false,
);
const context: NixStringContext = new Set();
addOpaqueContext(context, result.store_path);
return mkStringWithContext(result.store_path, context);
addOpaqueContext(context, result.storePath);
return mkStringWithContext(result.storePath, context);
};
export const fetchTarball = (args: NixValue): NixString => {
const { url, name, sha256 } = normalizeTarballInput(args);
const result: FetchTarballResult = Deno.core.ops.op_fetch_tarball(url, name ?? null, sha256 ?? null);
const context: NixStringContext = new Set();
addOpaqueContext(context, result.store_path);
return mkStringWithContext(result.store_path, context);
addOpaqueContext(context, result.storePath);
return mkStringWithContext(result.storePath, context);
};
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 result = Deno.core.ops.op_fetch_git(url, null, null, false, false, false, null);
const outContext: NixStringContext = new Set();
addOpaqueContext(outContext, result.out_path);
addOpaqueContext(outContext, result.outPath);
return new Map<string, NixValue>([
["outPath", mkStringWithContext(result.out_path, outContext)],
["outPath", mkStringWithContext(result.outPath, outContext)],
["rev", result.rev],
["shortRev", result.short_rev],
["revCount", BigInt(result.rev_count)],
["lastModified", BigInt(result.last_modified)],
["lastModifiedDate", result.last_modified_date],
["shortRev", result.shortRev],
["revCount", BigInt(result.revCount)],
["lastModified", BigInt(result.lastModified)],
["lastModifiedDate", result.lastModifiedDate],
["submodules", result.submodules],
["narHash", result.nar_hash],
["narHash", result.narHash],
]);
}
const attrs = forceAttrs(args);
@@ -205,16 +204,16 @@ export const fetchGit = (args: NixValue): NixAttrs => {
);
const outContext: NixStringContext = new Set();
addOpaqueContext(outContext, result.out_path);
addOpaqueContext(outContext, result.outPath);
return new Map<string, NixValue>([
["outPath", mkStringWithContext(result.out_path, outContext)],
["outPath", mkStringWithContext(result.outPath, outContext)],
["rev", result.rev],
["shortRev", result.short_rev],
["revCount", BigInt(result.rev_count)],
["lastModified", BigInt(result.last_modified)],
["lastModifiedDate", result.last_modified_date],
["shortRev", result.shortRev],
["revCount", BigInt(result.revCount)],
["lastModified", BigInt(result.lastModified)],
["lastModifiedDate", result.lastModifiedDate],
["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 => {
const pathStr = realisePath(path);
const entries: Record<string, string> = 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;
return Deno.core.ops.op_read_dir(pathStr);
};
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;
if (Deno.core.ops.op_path_exists(candidatePath)) {
return { [IS_PATH]: true, value: candidatePath };
return new NixPath(candidatePath);
}
}
if (lookupPathStr.startsWith("nix/")) {
// 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`);

View File

@@ -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 = <T>(message: string, span: string, fn: () => T): T => {
export const withContext = <T>(message: string, span: number, fn: () => T): T => {
pushContext(message, span);
try {
return fn();
@@ -142,19 +142,22 @@ export const resolvePath = (currentDir: string, path: NixValue): NixPath => {
return mkPath(resolved);
};
export const select = (obj: NixValue, attrpath: NixValue[], span?: string): NixValue => {
if (span) {
const pathStrings = attrpath.map((a) => forceStringValue(a));
const path = pathStrings.join(".");
const message = path ? `while selecting attribute [${path}]` : "while selecting attribute";
export const select = (obj: NixValue, attrpath: NixValue[], span?: number): NixValue => {
if (span !== undefined) {
if (callStack.length >= MAX_STACK_DEPTH) {
callStack.shift();
}
callStack.push({ span, message });
const frame: StackFrame = { span, message: "while selecting attribute" };
callStack.push(frame);
try {
return selectImpl(obj, attrpath);
} 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);
} finally {
callStack.pop();
@@ -167,8 +170,8 @@ export const select = (obj: NixValue, attrpath: NixValue[], span?: string): NixV
function selectImpl(obj: NixValue, attrpath: NixValue[]): NixValue {
let attrs = forceAttrs(obj);
for (const attr of attrpath.slice(0, -1)) {
const key = forceStringValue(attr);
for (let i = 0; i < attrpath.length - 1; i++) {
const key = forceStringValue(attrpath[i]);
if (!attrs.has(key)) {
throw new Error(`Attribute '${key}' not found`);
}
@@ -187,20 +190,23 @@ export const selectWithDefault = (
obj: NixValue,
attrpath: NixValue[],
defaultVal: NixValue,
span?: string,
span?: number,
): NixValue => {
if (span) {
const pathStrings = attrpath.map((a) => forceStringValue(a));
const path = pathStrings.join(".");
const message = path ? `while selecting attribute [${path}]` : "while selecting attribute";
if (span !== undefined) {
if (callStack.length >= MAX_STACK_DEPTH) {
callStack.shift();
}
callStack.push({ span, message });
const frame: StackFrame = { span, message: "while selecting attribute" };
callStack.push(frame);
try {
return selectWithDefaultImpl(obj, attrpath, defaultVal);
} 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);
} finally {
callStack.pop();
@@ -216,8 +222,8 @@ function selectWithDefaultImpl(obj: NixValue, attrpath: NixValue[], defaultVal:
return defaultVal;
}
for (const attr of attrpath.slice(0, -1)) {
const key = forceStringValue(attr);
for (let i = 0; i < attrpath.length - 1; i++) {
const key = forceStringValue(attrpath[i]);
if (!attrs.has(key)) {
return defaultVal;
}
@@ -242,8 +248,8 @@ export const hasAttr = (obj: NixValue, attrpath: NixValue[]): NixBool => {
}
let attrs = forced;
for (const attr of attrpath.slice(0, -1)) {
const key = forceStringNoCtx(attr);
for (let i = 0; i < attrpath.length - 1; i++) {
const key = forceStringNoCtx(attrpath[i]);
if (!attrs.has(key)) {
return false;
}
@@ -257,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();
}
@@ -276,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;
}
@@ -298,15 +304,8 @@ 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 => {
return new Map(Object.entries(Deno.core.ops.op_decode_span(span)));
export const mkPos = (span: number): NixAttrs => {
return Deno.core.ops.op_decode_span(span);
};
interface WithScope {

View File

@@ -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<string, NixValue> = new Map;
const replBindings: Map<string, NixValue> = 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;

View File

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

View File

@@ -22,18 +22,22 @@ export type StringContextElem = StringContextOpaque | StringContextDrvDeep | Str
export type NixStringContext = Set<string>;
export interface StringWithContext {
readonly [HAS_CONTEXT]: true;
export class StringWithContext {
readonly [HAS_CONTEXT] = true as const;
value: string;
context: NixStringContext;
constructor(value: string, context: NixStringContext) {
this.value = value;
this.context = context;
}
}
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 => {
return { [HAS_CONTEXT]: true, value, context };
return new StringWithContext(value, context);
};
export const mkPlainString = (value: string): string => value;
@@ -45,11 +49,12 @@ export const getStringValue = (s: string | StringWithContext): string => {
return s;
};
const emptyContext: NixStringContext = new Set();
export const getStringContext = (s: string | StringWithContext): NixStringContext => {
if (isStringWithContext(s)) {
return s.context;
}
return new Set();
return emptyContext;
};
export const mergeContexts = (...contexts: NixStringContext[]): NixStringContext => {

View File

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

View File

@@ -1,18 +1,21 @@
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";
export { HAS_CONTEXT, isStringWithContext };
export type { StringWithContext };
export const IS_PATH = Symbol("IS_PATH");
export interface NixPath {
readonly [IS_PATH]: true;
export class NixPath {
readonly [IS_PATH] = true as const;
value: string;
constructor(value: string) {
this.value = value;
}
}
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;
@@ -24,18 +27,18 @@ export type NixNull = null;
export const ATTR_POSITIONS = Symbol("attrPositions");
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 class NixArgs {
required: string[];
optional: string[];
allowed: Set<string>;
ellipsis: boolean;
positions: Map<string, string>;
constructor(required: string[], optional: string[], positions: Record<string, string>, ellipsis: boolean) {
positions: Map<string, number>;
constructor(required: string[], optional: string[], positions: Map<string, number>, ellipsis: boolean) {
this.required = required;
this.optional = optional;
this.positions = new Map(Object.entries(positions));
this.positions = positions;
this.ellipsis = ellipsis;
this.allowed = new Set(required.concat(optional));
}
@@ -61,7 +64,7 @@ export const mkFunction = (
f: (arg: NixValue) => NixValue,
required: string[],
optional: string[],
positions: Record<string, string>,
positions: Map<string, number>,
ellipsis: boolean,
): NixFunction => {
const func: NixFunction = f;
@@ -69,26 +72,11 @@ 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 = (
obj: Record<string, NixValue>,
positions: Record<string, string>,
dyns?: { dynKeys: NixValue[]; dynVals: NixValue[]; dynSpans: string[] },
export const mkAttrs = (
attrs: NixAttrs,
positions: Map<string, number>,
dyns?: { dynKeys: NixValue[]; dynVals: NixValue[]; dynSpans: number[] },
): NixAttrs => {
const attrs: NixAttrs = new Map(Object.entries(obj));
if (dyns) {
const len = dyns.dynKeys.length;
for (let i = 0; i < len; i++) {
@@ -98,23 +86,17 @@ export const mkAttrsWithPos = (
}
const str = forceStringNoCtx(key);
attrs.set(str, dyns.dynVals[i]);
positions[str] = dyns.dynSpans[i];
positions.set(str, dyns.dynSpans[i]);
}
}
if (Object.keys(positions).length > 0) {
attrs[ATTR_POSITIONS] = new Map(Object.entries(positions));
if (positions.size > 0) {
attrs[ATTR_POSITIONS] = positions;
}
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 NixValue =
| NixPrimitive
@@ -122,8 +104,8 @@ export type NixValue =
| NixList
| NixAttrs
| NixFunction
| NixThunkInterface
| NixThunk
| typeof CYCLE_MARKER;
export type NixStrictValue = Exclude<NixValue, NixThunkInterface>;
export type NixStrictValue = Exclude<NixValue, NixThunk>;
export class CatchableError extends Error {}

View File

@@ -1,30 +1,68 @@
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, NixAttrs } 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 {
function op_import(path: string): string;
function op_import(path: string): NixValue;
function op_scoped_import(path: string, scopeKeys: string[]): string;
function op_resolve_path(currentDir: string, path: string): string;
function op_read_file(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_walk_dir(path: string): [string, string][];
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,11 +81,7 @@ declare global {
includePaths: string[],
): string;
function op_decode_span(span: string): {
file: string | null;
line: number | null;
column: number | null;
};
function op_decode_span(span: number): NixAttrs;
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_to_xml(e: NixValue): [string, string[]];
function op_finalize_derivation(input: {
name: string;
builder: string;
platform: string;
outputs: string[];
args: string[];
env: [string, string][];
context: string[];
fixedOutput: { hashAlgo: string; hash: string; hashMode: string } | null;
}): { drvPath: string; outputs: [string, string][] };
function op_finalize_derivation(
name: string,
builder: string,
platform: string,
outputs: string[],
args: string[],
env: [string, string][],
context: string[],
fixedOutput: { hashAlgo: string; hash: string; hashMode: string } | null,
): { drvPath: string; outputs: [string, string][] };
function op_fetch_url(
url: string,

View File

@@ -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<Ctx: CodegenContext> Compile<Ctx> 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,7 +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 get_current_source(&self) -> crate::error::Source;
fn register_span(&self, range: rnix::TextRange) -> usize;
}
impl<Ctx: CodegenContext> Compile<Ctx> for ExprId {
@@ -241,7 +227,8 @@ impl<Ctx: CodegenContext> Compile<Ctx> 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),
@@ -259,11 +246,13 @@ impl<Ctx: CodegenContext> Compile<Ctx> 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)
")"
);
@@ -276,15 +265,13 @@ impl<Ctx: CodegenContext> Compile<Ctx> 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
")),"
"$a("
assertion
","
ctx.get_ir(expr)
","
quoted(assertion_raw)
@@ -294,32 +281,35 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
);
}
Ir::CurPos(cur_pos) => {
// Nix.mkPos
code!(buf, ctx;
"Nix.mkPos("
"$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)"
);
}
}
@@ -334,13 +324,10 @@ impl<Ctx: CodegenContext> Compile<Ctx> 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 ")");
}
}
@@ -353,65 +340,50 @@ impl<Ctx: CodegenContext> Compile<Ctx> 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 ")");
}
}
}
@@ -423,10 +395,10 @@ impl<Ctx: CodegenContext> Compile<Ctx> 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) ")");
}
}
}
@@ -444,7 +416,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> 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 {
@@ -459,11 +431,11 @@ impl<Ctx: CodegenContext> Compile<Ctx> 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; ctx.get_sym(sym) ":" span);
code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]");
})
"},"
"]),"
ellipsis
")"
);
@@ -481,7 +453,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Func {
impl<Ctx: CodegenContext> Compile<Ctx> 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)
@@ -500,16 +472,10 @@ impl<Ctx: CodegenContext> Compile<Ctx> for [(ExprId, ExprId)] {
for &(slot, inner) in self {
let inner_ir = ctx.get_ir(inner);
let inner_span = inner_ir.span();
code!(
buf, ctx;
"let expr" slot.0 "=Nix.createThunk(()=>(" inner_ir "),"
"\"expr" slot.0 " "
ctx.get_current_source().get_name() ":"
usize::from(inner_span.start()) ":"
usize::from(inner_span.end())
"\");"
"let expr" slot.0 "=$t(()=>(" inner_ir "),"
"\"expr" slot.0 "\");"
);
}
}
@@ -532,9 +498,9 @@ impl<Ctx: CodegenContext> Compile<Ctx> 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})");
}
}
}
@@ -543,7 +509,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> 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| {
@@ -560,7 +526,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Select {
);
} else {
code!(buf, ctx;
"Nix.select("
"$s("
ctx.get_ir(self.expr)
",["
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) {
if !self.dyns.is_empty() {
code!(buf, ctx;
"Nix.mkAttrsWithPos({"
"$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;
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; ctx.get_sym(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)| {
@@ -615,21 +578,21 @@ impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet {
);
} else if !self.stcs.is_empty() {
code!(buf, ctx;
"Nix.mkAttrsWithPos({"
"$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;
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; ctx.get_sym(sym) ":" span);
code!(buf, ctx; "[" ctx.get_sym(sym) "," span "]");
})
"})"
"]))"
);
} else {
code!(buf, ctx; "new Map()");
@@ -641,12 +604,9 @@ impl<Ctx: CodegenContext> Compile<Ctx> 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);
})
"]"
);
@@ -656,13 +616,10 @@ impl<Ctx: CodegenContext> Compile<Ctx> for List {
impl<Ctx: CodegenContext> Compile<Ctx> 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 ")"
);
@@ -672,7 +629,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for ConcatStrings {
impl<Ctx: CodegenContext> Compile<Ctx> 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| {

View File

@@ -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<HashMap<SymId, ExprId>>,
sources: Vec<Source>,
store: DaemonStore,
spans: UnsafeCell<Vec<(usize, TextRange)>>,
}
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()),
})
}
@@ -363,12 +371,15 @@ impl CodegenContext for Ctx {
.checked_sub(1)
.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 {
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 {
@@ -390,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> {

View File

@@ -292,43 +292,32 @@ fn parse_frames(stack: &str, ctx: &impl RuntimeContext) -> Vec<NixStackFrame> {
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()
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

View File

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

View File

@@ -26,6 +26,12 @@ struct Cli {
#[derive(Subcommand)]
enum Command {
Compile {
#[clap(flatten)]
source: ExprSource,
#[arg(long)]
silent: bool
},
Eval {
#[clap(flatten)]
source: ExprSource,
@@ -63,6 +69,30 @@ fn create_context(#[cfg(feature = "inspector")] cli: &Cli) -> Result<Context> {
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<()> {
let src = if let Some(expr) = src.expr {
Source::new_eval(expr)?
@@ -150,6 +180,7 @@ fn main() -> Result<()> {
)?;
match cli.command {
Command::Compile { source , silent } => run_compile(&mut context, source, silent),
Command::Eval { source } => run_eval(&mut context, source),
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 get_source(&self, id: usize) -> Source;
fn get_store(&self) -> &DaemonStore;
fn get_span(&self, id: usize) -> (usize, rnix::TextRange);
}
pub(crate) trait OpStateExt<Ctx: RuntimeContext> {
@@ -115,6 +116,7 @@ pub(crate) use private::NixRuntimeError;
pub(crate) struct Runtime<Ctx: RuntimeContext> {
js_runtime: JsRuntime,
#[cfg(feature = "inspector")]
rt: tokio::runtime::Runtime,
#[cfg(feature = "inspector")]
wait_for_inspector: bool,
@@ -142,7 +144,12 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
static INIT: Once = Once::new();
INIT.call_once(|| {
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()));
@@ -172,6 +179,7 @@ impl<Ctx: RuntimeContext> Runtime<Ctx> {
Ok(Self {
js_runtime,
#[cfg(feature = "inspector")]
rt: tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
@@ -224,22 +232,6 @@ impl<Ctx: RuntimeContext> Runtime<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
deno_core::scope!(scope, self.js_runtime);

View File

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

View File

@@ -1,9 +1,11 @@
use std::collections::BTreeMap;
use std::convert::Infallible;
use std::path::{Component, Path, PathBuf};
use std::str::FromStr;
use hashbrown::hash_map::{Entry, HashMap};
use deno_core::{FromV8, OpState, v8};
use deno_core::error::JsError;
use deno_core::{FromV8, OpState, ToV8, v8};
use hashbrown::{HashMap, HashSet, hash_map::Entry};
use regex::Regex;
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)]
#[folder = "src/runtime/corepkgs"]
pub(crate) struct CorePkgs;
#[deno_core::op2]
#[string]
fn new_simple_jserror(msg: String) -> Box<JsError> {
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>(
state: &mut OpState,
#[string] path: String,
) -> Result<String> {
) -> Result<Compiled> {
let _span = tracing::info_span!("op_import", path = %path).entered();
let ctx: &mut Ctx = state.get_ctx_mut();
@@ -61,7 +131,8 @@ pub(super) fn op_import<Ctx: RuntimeContext>(
.into(),
);
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 {
return Err(format!("Corepkg not found: {}", corepkg_name).into());
}
@@ -85,7 +156,8 @@ pub(super) fn op_import<Ctx: RuntimeContext>(
tracing::debug!("Compiling file");
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]
@@ -93,7 +165,7 @@ pub(super) fn op_import<Ctx: RuntimeContext>(
pub(super) fn op_scoped_import<Ctx: RuntimeContext>(
state: &mut OpState,
#[string] path: String,
#[serde] scope: Vec<String>,
#[scoped] scope: Vec<String>,
) -> Result<String> {
let _span = tracing::info_span!("op_scoped_import", path = %path).entered();
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]
#[serde]
pub(super) fn op_read_dir(
#[string] path: String,
) -> Result<std::collections::HashMap<String, String>> {
pub(super) fn op_read_dir(#[string] path: String) -> Result<Map<String, &'static str>> {
let path = Path::new(&path);
if !path.is_dir() {
@@ -174,7 +243,7 @@ pub(super) fn op_read_dir(
let entries = std::fs::read_dir(path)
.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 {
let entry = entry.map_err(|e| format!("Failed to read directory entry: {}", e))?;
@@ -198,10 +267,10 @@ pub(super) fn op_read_dir(
"unknown"
};
result.insert(file_name, type_str.to_string());
result.insert(file_name, type_str);
}
Ok(result)
Ok(Map(result))
}
#[deno_core::op2]
@@ -255,35 +324,40 @@ pub(super) fn op_make_placeholder(#[string] output: String) -> String {
format!("/{}", encoded)
}
#[deno_core::op2]
#[serde]
pub(super) fn op_decode_span<Ctx: RuntimeContext>(
state: &mut OpState,
#[string] span_str: String,
) -> Result<serde_json::Value> {
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
}));
enum StringOrU32 {
String(String),
U32(u32),
}
impl<'a> ToV8<'a> for StringOrU32 {
type Error = Infallible;
fn to_v8<'i>(
self,
scope: &mut v8::PinScope<'a, 'i>,
) -> std::result::Result<v8::Local<'a, v8::Value>, Self::Error> {
match self {
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")?;
let start: u32 = parts[1].parse().map_err(|_| "Invalid start offset")?;
#[deno_core::op2]
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 (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(),
"line": line,
"column": column
}))
Map(HashMap::from([
("file", StringOrU32::String(source.get_name())),
("line", StringOrU32::U32(line)),
("column", StringOrU32::U32(column)),
]))
}
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)
}
#[derive(serde::Serialize)]
mod private {
use deno_core::ToV8;
#[derive(ToV8)]
pub(super) struct ParsedHash {
hex: String,
algo: String,
pub(super) hex: String,
pub(super) algo: String,
}
}
use private::*;
#[deno_core::op2]
#[serde]
pub(super) fn op_parse_hash(
#[string] hash_str: String,
#[string] algo: Option<String>,
@@ -444,7 +522,7 @@ pub(super) fn op_to_file<Ctx: RuntimeContext>(
state: &mut OpState,
#[string] name: String,
#[string] contents: String,
#[serde] references: Vec<String>,
#[scoped] references: Vec<String>,
) -> Result<String> {
let ctx: &Ctx = state.get_ctx();
let store = ctx.get_store();
@@ -498,7 +576,6 @@ pub(super) fn op_get_env(#[string] key: String) -> Result<String> {
}
#[deno_core::op2]
#[serde]
pub(super) fn op_walk_dir(#[string] path: String) -> Result<Vec<(String, String)>> {
fn walk_recursive(
base: &Path,
@@ -560,7 +637,7 @@ pub(super) fn op_add_filtered_path<Ctx: RuntimeContext>(
#[string] name: Option<String>,
recursive: bool,
#[string] sha256: Option<String>,
#[serde] include_paths: Vec<String>,
#[scoped] include_paths: Vec<String>,
) -> Result<String> {
use nix_compat::nixhash::{HashAlgo, NixHash};
use sha2::{Digest, Sha256};
@@ -659,7 +736,6 @@ pub(super) fn op_add_filtered_path<Ctx: RuntimeContext>(
}
#[deno_core::op2]
#[serde]
pub(super) fn op_match(
state: &mut OpState,
#[string] regex: String,
@@ -684,7 +760,6 @@ pub(super) fn op_match(
}
#[deno_core::op2]
#[serde]
pub(super) fn op_split(
state: &mut OpState,
#[string] regex: String,
@@ -720,13 +795,30 @@ pub(super) fn op_split(
Ok(ret)
}
#[derive(serde::Serialize)]
#[serde(untagged)]
pub(super) enum SplitResult {
Text(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 {
Null,
Bool(bool),
@@ -847,34 +939,24 @@ pub(super) fn op_from_toml(#[string] toml_str: String) -> Result<NixJsonValue> {
toml_to_nix(parsed)
}
#[derive(serde::Deserialize)]
mod scope {
use deno_core::{FromV8, ToV8};
#[derive(FromV8)]
pub(super) struct FixedOutputInput {
#[serde(rename = "hashAlgo")]
hash_algo: String,
hash: String,
#[serde(rename = "hashMode")]
hash_mode: String,
pub(super) hash_algo: String,
pub(super) hash: String,
pub(super) hash_mode: String,
}
#[derive(serde::Deserialize)]
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)]
#[derive(ToV8)]
pub(super) struct FinalizeDerivationOutput {
#[serde(rename = "drvPath")]
drv_path: String,
outputs: Vec<(String, String)>,
// renamed to `drvPath` automatically
pub(super) drv_path: String,
pub(super) outputs: Vec<(String, String)>,
}
}
use scope::*;
fn output_path_name(drv_name: &str, output: &str) -> String {
if output == "out" {
@@ -885,10 +967,16 @@ fn output_path_name(drv_name: &str, output: &str) -> String {
}
#[deno_core::op2]
#[serde]
pub(super) fn op_finalize_derivation<Ctx: RuntimeContext>(
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> {
use crate::derivation::{DerivationData, OutputInfo};
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 (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 output_paths: Vec<(String, String)>;
if let Some(fixed) = &input.fixed_output {
let path_name = output_path_name(&input.name, "out");
if let Some(fixed) = &fixed_output {
let path_name = output_path_name(&name, "out");
let out_path = crate::runtime::ops::op_make_fixed_output_path_impl(
&store_dir,
&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(
"out".to_string(),
OutputInfo {
@@ -935,13 +1023,13 @@ pub(super) fn op_finalize_derivation<Ctx: RuntimeContext>(
final_env.insert("out".to_string(), out_path.clone());
let drv = DerivationData {
name: input.name.clone(),
name: name.clone(),
outputs: final_outputs,
input_drvs: input_drvs.clone(),
input_srcs: input_srcs.clone(),
platform: input.platform,
builder: input.builder,
args: input.args,
platform,
builder,
args,
env: final_env,
};
@@ -949,7 +1037,7 @@ pub(super) fn op_finalize_derivation<Ctx: RuntimeContext>(
let references = drv.collect_references();
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)))?;
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)];
} else {
let masked_outputs: std::collections::BTreeMap<String, OutputInfo> = input
.outputs
let masked_outputs: std::collections::BTreeMap<String, OutputInfo> = outputs
.iter()
.map(|o| {
(
@@ -979,18 +1066,18 @@ pub(super) fn op_finalize_derivation<Ctx: RuntimeContext>(
.collect();
let mut masked_env = env.clone();
for output in &input.outputs {
for output in &outputs {
masked_env.insert(output.clone(), String::new());
}
let masked_drv = DerivationData {
name: input.name.clone(),
name: name.clone(),
outputs: masked_outputs,
input_drvs: input_drvs.clone(),
input_srcs: input_srcs.clone(),
platform: input.platform.clone(),
builder: input.builder.clone(),
args: input.args.clone(),
platform: platform.clone(),
builder: builder.clone(),
args: args.clone(),
env: masked_env,
};
@@ -1018,8 +1105,8 @@ pub(super) fn op_finalize_derivation<Ctx: RuntimeContext>(
let mut final_env = env;
let mut result_output_paths = Vec::new();
for output_name in &input.outputs {
let path_name = output_path_name(&input.name, output_name);
for output_name in &outputs {
let path_name = output_path_name(&name, output_name);
let out_path = crate::nix_utils::make_store_path(
&store_dir,
&format!("output:{}", output_name),
@@ -1039,13 +1126,13 @@ pub(super) fn op_finalize_derivation<Ctx: RuntimeContext>(
}
let final_drv = DerivationData {
name: input.name,
name,
outputs: final_outputs,
input_drvs,
input_srcs,
platform: input.platform,
builder: input.builder,
args: input.args,
platform,
builder,
args,
env: final_env,
};
@@ -1172,21 +1259,21 @@ pub(super) fn op_hash_file(#[string] algo: String, #[string] path: String) -> Re
#[deno_core::op2]
#[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};
let hash_algo = input
.hash_algo
.as_deref()
.and_then(|a| HashAlgo::from_str(a).ok());
let hash_algo = algo.as_deref().and_then(|a| HashAlgo::from_str(a).ok());
let hash = NixHash::from_str(&input.hash, hash_algo).map_err(|e| {
NixRuntimeError::from(format!("cannot convert hash '{}': {}", input.hash, e))
})?;
let hash = NixHash::from_str(hash, hash_algo)
.map_err(|e| NixRuntimeError::from(format!("cannot convert hash '{}': {}", hash, e)))?;
let bytes = hash.digest_as_bytes();
match input.to_format.as_str() {
match format {
"base16" => Ok(hex::encode(bytes)),
"nix32" | "base32" => Ok(nix_compat::nixbase32::encode(bytes)),
"base64" => {
@@ -1199,20 +1286,11 @@ pub(super) fn op_convert_hash(#[serde] input: ConvertHashInput) -> Result<String
})),
_ => Err(NixRuntimeError::from(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> {
force_fn: v8::Local<'s, v8::Function>,
is_thunk: v8::Local<'s, v8::Symbol>,
@@ -1254,7 +1332,7 @@ impl<'s> XmlCtx<'s> {
struct XmlWriter {
buf: String,
context: Vec<String>,
drvs_seen: hashbrown::HashSet<String>,
drvs_seen: HashSet<String>,
}
impl XmlWriter {
@@ -1262,7 +1340,7 @@ impl XmlWriter {
Self {
buf: String::with_capacity(4096),
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]
#[serde]
pub(super) fn op_to_xml(#[scoped] value: ToXmlResult) -> (String, Vec<String>) {
(value.xml, value.context)
}