optimize: use Map to represent NixAttrs

This commit is contained in:
2026-02-16 22:31:31 +08:00
parent 4a885c18b8
commit f49634ccc0
21 changed files with 573 additions and 525 deletions

View File

@@ -4,6 +4,7 @@
"private": true,
"scripts": {
"check": "tsc --noEmit && npx eslint && biome check",
"typecheck": "tsc --noEmit",
"build": "node build.mjs",
"dev": "npm run typecheck && npm run build"
},

View File

@@ -1,12 +1,12 @@
import { mkPos } from "../helpers";
import { mkPos, select } from "../helpers";
import { createThunk } from "../thunk";
import { forceAttrs, forceFunction, forceList, forceStringValue } from "../type-assert";
import { ATTR_POSITIONS, type NixAttrs, type NixList, type NixValue } from "../types";
export const attrNames = (set: NixValue): string[] => Object.keys(forceAttrs(set)).sort();
export const attrNames = (set: NixValue): string[] => Array.from(forceAttrs(set).keys()).sort();
export const attrValues = (set: NixValue): NixValue[] =>
Object.entries(forceAttrs(set))
Array.from(forceAttrs(set).entries())
.sort(([a], [b]) => {
if (a < b) {
return -1;
@@ -21,21 +21,24 @@ export const attrValues = (set: NixValue): NixValue[] =>
export const getAttr =
(s: NixValue) =>
(set: NixValue): NixValue =>
forceAttrs(set)[forceStringValue(s)];
select(forceAttrs(set), [s]);
export const hasAttr =
(s: NixValue) =>
(set: NixValue): boolean =>
Object.hasOwn(forceAttrs(set), forceStringValue(s));
forceAttrs(set).has(forceStringValue(s));
export const mapAttrs =
(f: NixValue) =>
(attrs: NixValue): NixAttrs => {
const forcedAttrs = forceAttrs(attrs);
const forcedF = forceFunction(f);
const newAttrs: NixAttrs = {};
for (const key in forcedAttrs) {
newAttrs[key] = createThunk(() => forceFunction(forcedF(key))(forcedAttrs[key]), "created by mapAttrs");
const newAttrs: NixAttrs = new Map();
for (const [key, val] of forcedAttrs) {
newAttrs.set(
key,
createThunk(() => forceFunction(forcedF(key))(val), "created by mapAttrs"),
);
}
return newAttrs;
};
@@ -43,25 +46,20 @@ export const mapAttrs =
export const removeAttrs =
(attrs: NixValue) =>
(list: NixValue): NixAttrs => {
const newAttrs: NixAttrs = {};
const forcedAttrs = forceAttrs(attrs);
const newAttrs: NixAttrs = new Map(forceAttrs(attrs));
const forcedList = forceList(list);
const keysToRemove = new Set(forcedList.map(forceStringValue));
for (const key in forcedAttrs) {
if (!keysToRemove.has(key)) {
newAttrs[key] = forcedAttrs[key];
}
for (const item of forcedList) {
newAttrs.delete(forceStringValue(item));
}
return newAttrs;
};
export const listToAttrs = (e: NixValue): NixAttrs => {
const attrs: NixAttrs = {};
const attrs: NixAttrs = new Map();
const forcedE = [...forceList(e)].reverse();
for (const obj of forcedE) {
const item = forceAttrs(obj);
attrs[forceStringValue(item.name)] = item.value;
attrs.set(forceStringValue(select(item, ["name"])), select(item, ["value"]));
}
return attrs;
};
@@ -71,21 +69,17 @@ export const intersectAttrs =
(e2: NixValue): NixAttrs => {
const f1 = forceAttrs(e1);
const f2 = forceAttrs(e2);
const attrs: NixAttrs = {};
const k1 = Object.keys(f1);
const k2 = Object.keys(f2);
if (k1.length < k2.length) {
for (let i = 0; i < k1.length; i++) {
const key = k1[i];
if (key in f2) {
attrs[key] = f2[key];
const attrs: NixAttrs = new Map();
if (f1.size < f2.size) {
for (const [key] of f1) {
if (f2.has(key)) {
attrs.set(key, f2.get(key) as NixValue);
}
}
} else {
for (let i = 0; i < k2.length; i++) {
const key = k2[i];
if (key in f1) {
attrs[key] = f2[key];
for (const [key] of f2) {
if (f1.has(key)) {
attrs.set(key, f2.get(key) as NixValue);
}
}
}
@@ -97,20 +91,20 @@ export const catAttrs =
(list: NixValue): NixList => {
const key = forceStringValue(attr);
return forceList(list)
.map((set) => forceAttrs(set)[key])
.filter((val) => val !== undefined);
.map((set) => forceAttrs(set).get(key))
.filter((val) => val !== undefined) as NixList;
};
export const groupBy =
(f: NixValue) =>
(list: NixValue): NixAttrs => {
const attrs: NixAttrs = {};
const attrs: NixAttrs = new Map();
const forcedF = forceFunction(f);
const forcedList = forceList(list);
for (const elem of forcedList) {
const key = forceStringValue(forcedF(elem));
if (!attrs[key]) attrs[key] = [];
(attrs[key] as NixList).push(elem);
if (!attrs.has(key)) attrs.set(key, []);
(attrs.get(key) as NixList).push(elem);
}
return attrs;
};
@@ -125,7 +119,7 @@ export const zipAttrsWith =
for (const item of listForced) {
const attrs = forceAttrs(item);
for (const [key, value] of Object.entries(attrs)) {
for (const [key, value] of attrs) {
if (!attrMap.has(key)) {
attrMap.set(key, []);
}
@@ -133,10 +127,13 @@ export const zipAttrsWith =
}
}
const result: Record<string, NixValue> = {};
const result: NixAttrs = new Map();
for (const [name, values] of attrMap.entries()) {
result[name] = createThunk(() => forceFunction(forceFunction(f)(name))(values));
result.set(
name,
createThunk(() => forceFunction(forceFunction(f)(name))(values)),
);
}
return result;
@@ -148,7 +145,7 @@ export const unsafeGetAttrPos =
const name = forceStringValue(attrName);
const attrs = forceAttrs(attrSet);
if (!(name in attrs)) {
if (!attrs.has(name)) {
return null;
}

View File

@@ -113,20 +113,20 @@ export const getContext = (value: NixValue): NixAttrs => {
const context = getStringContext(s);
const infoMap = parseContextToInfoMap(context);
const result: NixAttrs = {};
const result: NixAttrs = new Map();
for (const [path, info] of infoMap) {
const attrs: NixAttrs = {};
const attrs: NixAttrs = new Map();
if (info.path) {
attrs.path = true;
attrs.set("path", true);
}
if (info.allOutputs) {
attrs.allOutputs = true;
attrs.set("allOutputs", true);
}
if (info.outputs.length > 0) {
attrs.outputs = info.outputs;
attrs.set("outputs", info.outputs);
}
result[path] = attrs;
result.set(path, attrs);
}
return result;
@@ -154,22 +154,22 @@ export const appendContext =
const ctxAttrs = forceAttrs(ctxValue);
const newContext: NixStringContext = new Set(existingContext);
for (const [path, infoVal] of Object.entries(ctxAttrs)) {
for (const [path, infoVal] of ctxAttrs) {
if (!path.startsWith("/nix/store/")) {
throw new Error(`context key '${path}' is not a store path`);
}
const info = forceAttrs(infoVal);
const info = forceAttrs(infoVal as NixValue);
if ("path" in info) {
const pathVal = force(info.path);
if (info.has("path")) {
const pathVal = force(info.get("path") as NixValue);
if (pathVal === true) {
newContext.add(path);
}
}
if ("allOutputs" in info) {
const allOutputs = force(info.allOutputs);
if (info.has("allOutputs")) {
const allOutputs = force(info.get("allOutputs") as NixValue);
if (allOutputs === true) {
if (!path.endsWith(".drv")) {
throw new Error(
@@ -180,8 +180,8 @@ export const appendContext =
}
}
if ("outputs" in info) {
const outputs = forceList(info.outputs);
if (info.has("outputs")) {
const outputs = forceList(info.get("outputs") as NixValue);
if (outputs.length > 0 && !path.endsWith(".drv")) {
throw new Error(
`tried to add derivation output context of ${path}, which is not a derivation, to a string`,

View File

@@ -118,37 +118,31 @@ export const coerceToString = (
}
if (typeof v === "object" && v !== null && !Array.isArray(v)) {
// First, try the __toString method if present
// This allows custom types to define their own string representation
if ("__toString" in v) {
// Force the method in case it's a thunk
const toStringMethod = forceFunction(v.__toString);
const result = force(toStringMethod(v));
// Recursively coerceToString
return coerceToString(result, mode, copyToStore, outContext);
}
// If no __toString, try outPath (used for derivations and store paths)
// This allows derivation objects like { outPath = "/nix/store/..."; } to be coerced
if ("outPath" in v) {
// Recursively coerce the outPath value
const outPath = coerceToString(v.outPath, mode, copyToStore, outContext);
if ("type" in v && v.type === "derivation" && "drvPath" in v && outContext) {
const drvPathValue = force(v.drvPath);
const drvPathStr = isStringWithContext(drvPathValue)
? drvPathValue.value
: typeof drvPathValue === "string"
? drvPathValue
: null;
if (drvPathStr) {
const outputName = "outputName" in v ? String(force(v.outputName)) : "out";
addBuiltContext(outContext, drvPathStr, outputName);
}
if (v instanceof Map) {
if (v.has("__toString")) {
const toStringMethod = forceFunction(v.get("__toString") as NixValue);
const result = force(toStringMethod(v));
return coerceToString(result, mode, copyToStore, outContext);
}
if (v.has("outPath")) {
const outPath = coerceToString(v.get("outPath") as NixValue, mode, copyToStore, outContext);
if (v.has("type") && v.get("type") === "derivation" && v.has("drvPath") && outContext) {
const drvPathValue = force(v.get("drvPath") as NixValue);
const drvPathStr = isStringWithContext(drvPathValue)
? drvPathValue.value
: typeof drvPathValue === "string"
? drvPathValue
: null;
if (drvPathStr) {
const outputName = v.has("outputName") ? String(force(v.get("outputName") as NixValue)) : "out";
addBuiltContext(outContext, drvPathStr, outputName);
}
}
return outPath;
}
return outPath;
}
// Attribute sets without __toString or outPath cannot be coerced
throw new TypeError(`cannot coerce ${typeOf(v)} to a string`);
}
@@ -259,8 +253,8 @@ export const coerceToPath = (value: NixValue, outContext: NixStringContext): str
if (isPath(forced)) {
return forced.value;
}
if (isAttrs(forced) && Object.hasOwn(forced, "__toString")) {
const toStringFunc = forceFunction(forced.__toString);
if (isAttrs(forced) && forced.has("__toString")) {
const toStringFunc = forceFunction(forced.get("__toString") as NixValue);
return coerceToPath(toStringFunc(forced), outContext);
}
@@ -339,30 +333,33 @@ export const nixValueToJson = (
return v.map((item) => nixValueToJson(item, strict, outContext, copyToStore, seen));
}
// NixAttrs
if ("__toString" in v && typeof force(v.__toString) === "function") {
const toStringMethod = force(v.__toString) as (self: typeof v) => NixValue;
const result = force(toStringMethod(v));
if (typeof result === "string") {
return result;
}
if (isStringWithContext(result)) {
for (const elem of result.context) {
outContext.add(elem);
if (v instanceof Map) {
if (v.has("__toString") && typeof force(v.get("__toString") as NixValue) === "function") {
const toStringMethod = force(v.get("__toString") as NixValue) as (self: typeof v) => NixValue;
const result = force(toStringMethod(v));
if (typeof result === "string") {
return result;
}
return result.value;
if (isStringWithContext(result)) {
for (const elem of result.context) {
outContext.add(elem);
}
return result.value;
}
return nixValueToJson(result, strict, outContext, copyToStore, seen);
}
return nixValueToJson(result, strict, outContext, copyToStore, seen);
if (v.has("outPath")) {
return nixValueToJson(v.get("outPath") as NixValue, strict, outContext, copyToStore, seen);
}
const result: Record<string, unknown> = {};
const keys = Array.from(v.keys()).sort();
for (const key of keys) {
result[key] = nixValueToJson(v.get(key) as NixValue, strict, outContext, copyToStore, seen);
}
return result;
}
if ("outPath" in v) {
return nixValueToJson(v.outPath, strict, outContext, copyToStore, seen);
}
const result: Record<string, unknown> = {};
const keys = Object.keys(v).sort();
for (const key of keys) {
result[key] = nixValueToJson(v[key], strict, outContext, copyToStore, seen);
}
return result;
throw new Error(`cannot convert ${typeof v} to JSON`);
};

View File

@@ -116,10 +116,10 @@ export const generateAtermModulo = (drv: DerivationData, inputDrvHashes: Map<str
return `Derive([${outputs}],[${inputDrvs}],[${inputSrcs}],${quoteString(drv.platform)},${escapeString(drv.builder)},[${args}],[${envs}])`;
};
const validateName = (attrs: NixAttrs): string => {
if (!("name" in attrs)) {
if (!attrs.has("name")) {
throw new Error("derivation: missing required attribute 'name'");
}
const name = forceStringValue(attrs.name);
const name = forceStringValue(attrs.get("name") as NixValue);
if (!name) {
throw new Error("derivation: 'name' cannot be empty");
}
@@ -130,17 +130,17 @@ const validateName = (attrs: NixAttrs): string => {
};
const validateBuilder = (attrs: NixAttrs, outContext: NixStringContext): string => {
if (!("builder" in attrs)) {
if (!attrs.has("builder")) {
throw new Error("derivation: missing required attribute 'builder'");
}
return coerceToString(attrs.builder, StringCoercionMode.ToString, true, outContext);
return coerceToString(attrs.get("builder") as NixValue, StringCoercionMode.ToString, true, outContext);
};
const validateSystem = (attrs: NixAttrs): string => {
if (!("system" in attrs)) {
if (!attrs.has("system")) {
throw new Error("derivation: missing required attribute 'system'");
}
return forceStringValue(attrs.system);
return forceStringValue(attrs.get("system") as NixValue);
};
const validateOutputs = (outputs: string[]): void => {
@@ -162,17 +162,25 @@ const validateOutputs = (outputs: string[]): void => {
};
const extractOutputs = (attrs: NixAttrs, structuredAttrs: boolean): string[] => {
if (!("outputs" in attrs)) {
if (!attrs.has("outputs")) {
return ["out"];
}
let outputs: string[];
if (structuredAttrs) {
const outputsList = forceList(attrs.outputs);
const outputsList = forceList(attrs.get("outputs") as NixValue);
outputs = outputsList.map((o) => forceStringValue(o));
} else {
const outputsStr = coerceToString(attrs.outputs, StringCoercionMode.ToString, false, new Set());
outputs = outputsStr.split(/\s+/).filter((s) => s.length > 0);
const outputsStr = coerceToString(
attrs.get("outputs") as NixValue,
StringCoercionMode.ToString,
false,
new Set(),
);
outputs = outputsStr
.trim()
.split(/\s+/)
.filter((s) => s.length > 0);
}
validateOutputs(outputs);
@@ -180,10 +188,10 @@ const extractOutputs = (attrs: NixAttrs, structuredAttrs: boolean): string[] =>
};
const extractArgs = (attrs: NixAttrs, outContext: NixStringContext): string[] => {
if (!("args" in attrs)) {
if (!attrs.has("args")) {
return [];
}
const argsList = forceList(attrs.args);
const argsList = forceList(attrs.get("args") as NixValue);
return argsList.map((a) => coerceToString(a, StringCoercionMode.ToString, true, outContext));
};
@@ -217,13 +225,13 @@ const extractEnv = (
if (structuredAttrs) {
const jsonAttrs: Record<string, unknown> = {};
for (const [key, value] of Object.entries(attrs)) {
for (const [key, value] of attrs) {
if (!structuredAttrsExcludedKeys.has(key)) {
const forcedValue = force(value);
const forcedValue = force(value as NixValue);
if (ignoreNulls && forcedValue === null) {
continue;
}
jsonAttrs[key] = nixValueToJson(value, true, outContext, true);
jsonAttrs[key] = nixValueToJson(value as NixValue, true, outContext, true);
}
if (key === "allowedReferences") {
@@ -271,13 +279,13 @@ const extractEnv = (
}
env.set("__json", sortedJsonStringify(jsonAttrs));
} else {
for (const [key, value] of Object.entries(attrs)) {
for (const [key, value] of attrs) {
if (!specialAttrs.has(key)) {
const forcedValue = force(value);
const forcedValue = force(value as NixValue);
if (ignoreNulls && forcedValue === null) {
continue;
}
env.set(key, coerceToString(value, StringCoercionMode.ToString, true, outContext));
env.set(key, coerceToString(value as NixValue, StringCoercionMode.ToString, true, outContext));
}
}
}
@@ -292,29 +300,29 @@ interface FixedOutputInfo {
}
const extractFixedOutputInfo = (attrs: NixAttrs, ignoreNulls: boolean): FixedOutputInfo | null => {
if (!("outputHash" in attrs)) {
if (!attrs.has("outputHash")) {
return null;
}
const hashValue = force(attrs.outputHash);
const hashValue = force(attrs.get("outputHash") as NixValue);
if (ignoreNulls && hashValue === null) {
return null;
}
const hashRaw = forceStringNoCtx(attrs.outputHash);
const hashRaw = forceStringNoCtx(hashValue);
let hashAlgo = null;
if ("outputHashAlgo" in attrs) {
const algoValue = force(attrs.outputHashAlgo);
if (attrs.has("outputHashAlgo")) {
const algoValue = force(attrs.get("outputHashAlgo") as NixValue);
if (!(ignoreNulls && algoValue === null)) {
hashAlgo = forceStringNoCtx(attrs.outputHashAlgo);
hashAlgo = forceStringNoCtx(algoValue);
}
}
let hashMode = "flat";
if ("outputHashMode" in attrs) {
const modeValue = force(attrs.outputHashMode);
if (attrs.has("outputHashMode")) {
const modeValue = force(attrs.get("outputHashMode") as NixValue);
if (!(ignoreNulls && modeValue === null)) {
hashMode = forceStringValue(attrs.outputHashMode);
hashMode = forceStringValue(modeValue);
}
}
@@ -341,18 +349,22 @@ export const derivationStrict = (args: NixValue): NixAttrs => {
const builder = validateBuilder(attrs, collectedContext);
const platform = validateSystem(attrs);
const structuredAttrs = "__structuredAttrs" in attrs ? force(attrs.__structuredAttrs) === true : false;
const ignoreNulls = "__ignoreNulls" in attrs ? force(attrs.__ignoreNulls) === true : false;
const structuredAttrs = attrs.has("__structuredAttrs")
? force(attrs.get("__structuredAttrs") as NixValue) === true
: false;
const ignoreNulls = attrs.has("__ignoreNulls")
? force(attrs.get("__ignoreNulls") as NixValue) === true
: false;
const outputs = extractOutputs(attrs, structuredAttrs);
const fixedOutputInfo = extractFixedOutputInfo(attrs, ignoreNulls);
validateFixedOutputConstraints(fixedOutputInfo, outputs);
if ("__contentAddressed" in attrs && force(attrs.__contentAddressed) === true) {
if (attrs.has("__contentAddressed") && force(attrs.get("__contentAddressed") as NixValue) === true) {
throw new Error("ca derivations are not supported");
}
if ("__impure" in attrs && force(attrs.__impure) === true) {
if (attrs.has("__impure") && force(attrs.get("__impure") as NixValue) === true) {
throw new Error("impure derivations are not supported");
}
@@ -376,16 +388,16 @@ export const derivationStrict = (args: NixValue): NixAttrs => {
fixedOutput: fixedOutputInfo,
});
const result: NixAttrs = {};
const result: NixAttrs = new Map();
const drvPathContext = new Set<string>();
addDrvDeepContext(drvPathContext, rustResult.drvPath);
result.drvPath = mkStringWithContext(rustResult.drvPath, drvPathContext);
result.set("drvPath", mkStringWithContext(rustResult.drvPath, drvPathContext));
for (const [outputName, outputPath] of rustResult.outputs) {
const outputContext = new Set<string>();
addBuiltContext(outputContext, rustResult.drvPath, outputName);
result[outputName] = mkStringWithContext(outputPath, outputContext);
result.set(outputName, mkStringWithContext(outputPath, outputContext));
}
return result;

View File

@@ -1,3 +1,4 @@
import { select } from "../helpers";
import { forceAttrs, forceStringNoCtx, forceStringValue } from "../type-assert";
import type { NixValue } from "../types";
import { realisePath } from "./io";
@@ -20,14 +21,14 @@ export const hashString =
export const convertHash = (args: NixValue): string => {
const attrs = forceAttrs(args);
const hash = forceStringNoCtx(attrs.hash);
const hash = forceStringNoCtx(select(attrs, ["hash"]));
let hashAlgo: string | null = null;
if ("hashAlgo" in attrs) {
hashAlgo = forceStringNoCtx(attrs.hashAlgo);
if (attrs.has("hashAlgo")) {
hashAlgo = forceStringNoCtx(select(attrs, ["hashAlgo"]));
}
const toHashFormat = forceStringNoCtx(attrs.toHashFormat);
const toHashFormat = forceStringNoCtx(select(attrs, ["toHashFormat"]));
return Deno.core.ops.op_convert_hash({
hash,

View File

@@ -1,5 +1,5 @@
import { createThunk, force } from "../thunk";
import type { NixValue } from "../types";
import type { NixAttrs, NixValue } from "../types";
import * as arithmetic from "./arithmetic";
import * as attrs from "./attrs";
import * as conversion from "./conversion";
@@ -74,149 +74,151 @@ export const getPrimopMetadata = (func: unknown): PrimopMetadata | undefined =>
return undefined;
};
export const builtins: Record<string, NixValue> = {
add: mkPrimop(arithmetic.add, "add", 2),
sub: mkPrimop(arithmetic.sub, "sub", 2),
mul: mkPrimop(arithmetic.mul, "mul", 2),
div: mkPrimop(arithmetic.div, "div", 2),
bitAnd: mkPrimop(arithmetic.bitAnd, "bitAnd", 2),
bitOr: mkPrimop(arithmetic.bitOr, "bitOr", 2),
bitXor: mkPrimop(arithmetic.bitXor, "bitXor", 2),
lessThan: mkPrimop(arithmetic.lessThan, "lessThan", 2),
export const builtins: NixAttrs = new Map<string, NixValue>(
Object.entries({
add: mkPrimop(arithmetic.add, "add", 2),
sub: mkPrimop(arithmetic.sub, "sub", 2),
mul: mkPrimop(arithmetic.mul, "mul", 2),
div: mkPrimop(arithmetic.div, "div", 2),
bitAnd: mkPrimop(arithmetic.bitAnd, "bitAnd", 2),
bitOr: mkPrimop(arithmetic.bitOr, "bitOr", 2),
bitXor: mkPrimop(arithmetic.bitXor, "bitXor", 2),
lessThan: mkPrimop(arithmetic.lessThan, "lessThan", 2),
ceil: mkPrimop(math.ceil, "ceil", 1),
floor: mkPrimop(math.floor, "floor", 1),
ceil: mkPrimop(math.ceil, "ceil", 1),
floor: mkPrimop(math.floor, "floor", 1),
isAttrs: mkPrimop((e: NixValue) => typeCheck.isAttrs(force(e)), "isAttrs", 1),
isBool: mkPrimop((e: NixValue) => typeCheck.isBool(force(e)), "isBool", 1),
isFloat: mkPrimop((e: NixValue) => typeCheck.isFloat(force(e)), "isFloat", 1),
isFunction: mkPrimop((e: NixValue) => typeCheck.isFunction(force(e)), "isFunction", 1),
isInt: mkPrimop((e: NixValue) => typeCheck.isInt(force(e)), "isInt", 1),
isList: mkPrimop((e: NixValue) => typeCheck.isList(force(e)), "isList", 1),
isNull: mkPrimop((e: NixValue) => typeCheck.isNull(force(e)), "isNull", 1),
isPath: mkPrimop((e: NixValue) => typeCheck.isPath(force(e)), "isPath", 1),
isString: mkPrimop((e: NixValue) => typeCheck.isString(force(e)), "isString", 1),
typeOf: mkPrimop((e: NixValue) => typeCheck.typeOf(force(e)), "typeOf", 1),
isAttrs: mkPrimop((e: NixValue) => typeCheck.isAttrs(force(e)), "isAttrs", 1),
isBool: mkPrimop((e: NixValue) => typeCheck.isBool(force(e)), "isBool", 1),
isFloat: mkPrimop((e: NixValue) => typeCheck.isFloat(force(e)), "isFloat", 1),
isFunction: mkPrimop((e: NixValue) => typeCheck.isFunction(force(e)), "isFunction", 1),
isInt: mkPrimop((e: NixValue) => typeCheck.isInt(force(e)), "isInt", 1),
isList: mkPrimop((e: NixValue) => typeCheck.isList(force(e)), "isList", 1),
isNull: mkPrimop((e: NixValue) => typeCheck.isNull(force(e)), "isNull", 1),
isPath: mkPrimop((e: NixValue) => typeCheck.isPath(force(e)), "isPath", 1),
isString: mkPrimop((e: NixValue) => typeCheck.isString(force(e)), "isString", 1),
typeOf: mkPrimop((e: NixValue) => typeCheck.typeOf(force(e)), "typeOf", 1),
map: mkPrimop(list.map, "map", 2),
filter: mkPrimop(list.filter, "filter", 2),
length: mkPrimop(list.length, "length", 1),
head: mkPrimop(list.head, "head", 1),
tail: mkPrimop(list.tail, "tail", 1),
elem: mkPrimop(list.elem, "elem", 2),
elemAt: mkPrimop(list.elemAt, "elemAt", 2),
concatLists: mkPrimop(list.concatLists, "concatLists", 1),
concatMap: mkPrimop(list.concatMap, "concatMap", 2),
"foldl'": mkPrimop(list.foldlPrime, "foldl'", 3),
sort: mkPrimop(list.sort, "sort", 2),
partition: mkPrimop(list.partition, "partition", 2),
genList: mkPrimop(list.genList, "genList", 2),
all: mkPrimop(list.all, "all", 2),
any: mkPrimop(list.any, "any", 2),
map: mkPrimop(list.map, "map", 2),
filter: mkPrimop(list.filter, "filter", 2),
length: mkPrimop(list.length, "length", 1),
head: mkPrimop(list.head, "head", 1),
tail: mkPrimop(list.tail, "tail", 1),
elem: mkPrimop(list.elem, "elem", 2),
elemAt: mkPrimop(list.elemAt, "elemAt", 2),
concatLists: mkPrimop(list.concatLists, "concatLists", 1),
concatMap: mkPrimop(list.concatMap, "concatMap", 2),
"foldl'": mkPrimop(list.foldlPrime, "foldl'", 3),
sort: mkPrimop(list.sort, "sort", 2),
partition: mkPrimop(list.partition, "partition", 2),
genList: mkPrimop(list.genList, "genList", 2),
all: mkPrimop(list.all, "all", 2),
any: mkPrimop(list.any, "any", 2),
attrNames: mkPrimop(attrs.attrNames, "attrNames", 1),
attrValues: mkPrimop(attrs.attrValues, "attrValues", 1),
getAttr: mkPrimop(attrs.getAttr, "getAttr", 2),
hasAttr: mkPrimop(attrs.hasAttr, "hasAttr", 2),
mapAttrs: mkPrimop(attrs.mapAttrs, "mapAttrs", 2),
removeAttrs: mkPrimop(attrs.removeAttrs, "removeAttrs", 2),
listToAttrs: mkPrimop(attrs.listToAttrs, "listToAttrs", 1),
intersectAttrs: mkPrimop(attrs.intersectAttrs, "intersectAttrs", 2),
catAttrs: mkPrimop(attrs.catAttrs, "catAttrs", 2),
groupBy: mkPrimop(attrs.groupBy, "groupBy", 2),
zipAttrsWith: mkPrimop(attrs.zipAttrsWith, "zipAttrsWith", 2),
unsafeGetAttrPos: mkPrimop(attrs.unsafeGetAttrPos, "unsafeGetAttrPos", 2),
attrNames: mkPrimop(attrs.attrNames, "attrNames", 1),
attrValues: mkPrimop(attrs.attrValues, "attrValues", 1),
getAttr: mkPrimop(attrs.getAttr, "getAttr", 2),
hasAttr: mkPrimop(attrs.hasAttr, "hasAttr", 2),
mapAttrs: mkPrimop(attrs.mapAttrs, "mapAttrs", 2),
removeAttrs: mkPrimop(attrs.removeAttrs, "removeAttrs", 2),
listToAttrs: mkPrimop(attrs.listToAttrs, "listToAttrs", 1),
intersectAttrs: mkPrimop(attrs.intersectAttrs, "intersectAttrs", 2),
catAttrs: mkPrimop(attrs.catAttrs, "catAttrs", 2),
groupBy: mkPrimop(attrs.groupBy, "groupBy", 2),
zipAttrsWith: mkPrimop(attrs.zipAttrsWith, "zipAttrsWith", 2),
unsafeGetAttrPos: mkPrimop(attrs.unsafeGetAttrPos, "unsafeGetAttrPos", 2),
stringLength: mkPrimop(string.stringLength, "stringLength", 1),
substring: mkPrimop(string.substring, "substring", 3),
concatStringsSep: mkPrimop(string.concatStringsSep, "concatStringsSep", 2),
baseNameOf: mkPrimop(pathOps.baseNameOf, "baseNameOf", 1),
dirOf: mkPrimop(pathOps.dirOf, "dirOf", 1),
toPath: mkPrimop(pathOps.toPath, "toPath", 1),
match: mkPrimop(string.match, "match", 2),
split: mkPrimop(string.split, "split", 2),
stringLength: mkPrimop(string.stringLength, "stringLength", 1),
substring: mkPrimop(string.substring, "substring", 3),
concatStringsSep: mkPrimop(string.concatStringsSep, "concatStringsSep", 2),
baseNameOf: mkPrimop(pathOps.baseNameOf, "baseNameOf", 1),
dirOf: mkPrimop(pathOps.dirOf, "dirOf", 1),
toPath: mkPrimop(pathOps.toPath, "toPath", 1),
match: mkPrimop(string.match, "match", 2),
split: mkPrimop(string.split, "split", 2),
seq: mkPrimop(functional.seq, "seq", 2),
deepSeq: mkPrimop(functional.deepSeq, "deepSeq", 2),
abort: mkPrimop(functional.abort, "abort", 1),
throw: mkPrimop(functional.throwFunc, "throw", 1),
trace: mkPrimop(functional.trace, "trace", 2),
warn: mkPrimop(functional.warn, "warn", 2),
break: mkPrimop(functional.breakFunc, "break", 1),
seq: mkPrimop(functional.seq, "seq", 2),
deepSeq: mkPrimop(functional.deepSeq, "deepSeq", 2),
abort: mkPrimop(functional.abort, "abort", 1),
throw: mkPrimop(functional.throwFunc, "throw", 1),
trace: mkPrimop(functional.trace, "trace", 2),
warn: mkPrimop(functional.warn, "warn", 2),
break: mkPrimop(functional.breakFunc, "break", 1),
derivation: mkPrimop(derivation.derivationStub, "derivation", 1),
derivationStrict: mkPrimop(derivation.derivationStrict, "derivationStrict", 1),
derivation: mkPrimop(derivation.derivationStub, "derivation", 1),
derivationStrict: mkPrimop(derivation.derivationStrict, "derivationStrict", 1),
import: mkPrimop(io.importFunc, "import", 1),
scopedImport: mkPrimop(io.scopedImport, "scopedImport", 2),
storePath: mkPrimop(io.storePath, "storePath", 1),
fetchClosure: mkPrimop(io.fetchClosure, "fetchClosure", 1),
fetchMercurial: mkPrimop(io.fetchMercurial, "fetchMercurial", 1),
fetchGit: mkPrimop(io.fetchGit, "fetchGit", 1),
fetchTarball: mkPrimop(io.fetchTarball, "fetchTarball", 1),
fetchTree: mkPrimop(io.fetchTree, "fetchTree", 1),
fetchurl: mkPrimop(io.fetchurl, "fetchurl", 1),
readDir: mkPrimop(io.readDir, "readDir", 1),
readFile: mkPrimop(io.readFile, "readFile", 1),
readFileType: mkPrimop(io.readFileType, "readFileType", 1),
pathExists: mkPrimop(io.pathExists, "pathExists", 1),
path: mkPrimop(io.path, "path", 1),
toFile: mkPrimop(io.toFile, "toFile", 2),
filterSource: mkPrimop(io.filterSource, "filterSource", 2),
findFile: mkPrimop(io.findFile, "findFile", 2),
getEnv: mkPrimop(io.getEnv, "getEnv", 1),
import: mkPrimop(io.importFunc, "import", 1),
scopedImport: mkPrimop(io.scopedImport, "scopedImport", 2),
storePath: mkPrimop(io.storePath, "storePath", 1),
fetchClosure: mkPrimop(io.fetchClosure, "fetchClosure", 1),
fetchMercurial: mkPrimop(io.fetchMercurial, "fetchMercurial", 1),
fetchGit: mkPrimop(io.fetchGit, "fetchGit", 1),
fetchTarball: mkPrimop(io.fetchTarball, "fetchTarball", 1),
fetchTree: mkPrimop(io.fetchTree, "fetchTree", 1),
fetchurl: mkPrimop(io.fetchurl, "fetchurl", 1),
readDir: mkPrimop(io.readDir, "readDir", 1),
readFile: mkPrimop(io.readFile, "readFile", 1),
readFileType: mkPrimop(io.readFileType, "readFileType", 1),
pathExists: mkPrimop(io.pathExists, "pathExists", 1),
path: mkPrimop(io.path, "path", 1),
toFile: mkPrimop(io.toFile, "toFile", 2),
filterSource: mkPrimop(io.filterSource, "filterSource", 2),
findFile: mkPrimop(io.findFile, "findFile", 2),
getEnv: mkPrimop(io.getEnv, "getEnv", 1),
fromJSON: mkPrimop(conversion.fromJSON, "fromJSON", 1),
fromTOML: mkPrimop(conversion.fromTOML, "fromTOML", 1),
toJSON: mkPrimop(conversion.toJSON, "toJSON", 1),
toXML: mkPrimop(conversion.toXML, "toXML", 1),
toString: mkPrimop(conversion.toStringFunc, "toString", 1),
fromJSON: mkPrimop(conversion.fromJSON, "fromJSON", 1),
fromTOML: mkPrimop(conversion.fromTOML, "fromTOML", 1),
toJSON: mkPrimop(conversion.toJSON, "toJSON", 1),
toXML: mkPrimop(conversion.toXML, "toXML", 1),
toString: mkPrimop(conversion.toStringFunc, "toString", 1),
hashFile: mkPrimop(hash.hashFile, "hashFile", 2),
hashString: mkPrimop(hash.hashString, "hashString", 2),
convertHash: mkPrimop(hash.convertHash, "convertHash", 2),
hashFile: mkPrimop(hash.hashFile, "hashFile", 2),
hashString: mkPrimop(hash.hashString, "hashString", 2),
convertHash: mkPrimop(hash.convertHash, "convertHash", 2),
flakeRefToString: mkPrimop(flake.flakeRefToString, "flakeRefToString", 1),
getFlake: mkPrimop(flake.getFlake, "getFlake", 1),
parseFlakeName: mkPrimop(flake.parseFlakeName, "parseFlakeName", 1),
parseFlakeRef: mkPrimop(flake.parseFlakeRef, "parseFlakeRef", 1),
flakeRefToString: mkPrimop(flake.flakeRefToString, "flakeRefToString", 1),
getFlake: mkPrimop(flake.getFlake, "getFlake", 1),
parseFlakeName: mkPrimop(flake.parseFlakeName, "parseFlakeName", 1),
parseFlakeRef: mkPrimop(flake.parseFlakeRef, "parseFlakeRef", 1),
addErrorContext: mkPrimop(misc.addErrorContext, "addErrorContext", 1),
appendContext: mkPrimop(misc.appendContext, "appendContext", 1),
getContext: mkPrimop(misc.getContext, "getContext", 1),
hasContext: mkPrimop(misc.hasContext, "hasContext", 1),
unsafeDiscardOutputDependency: mkPrimop(
misc.unsafeDiscardOutputDependency,
"unsafeDiscardOutputDependency",
1,
),
unsafeDiscardStringContext: mkPrimop(misc.unsafeDiscardStringContext, "unsafeDiscardStringContext", 1),
addDrvOutputDependencies: mkPrimop(misc.addDrvOutputDependencies, "addDrvOutputDependencies", 2),
compareVersions: mkPrimop(misc.compareVersions, "compareVersions", 2),
functionArgs: mkPrimop(misc.functionArgs, "functionArgs", 1),
genericClosure: mkPrimop(misc.genericClosure, "genericClosure", 1),
outputOf: mkPrimop(misc.outputOf, "outputOf", 2),
parseDrvName: mkPrimop(misc.parseDrvName, "parseDrvName", 1),
placeholder: mkPrimop(misc.placeholder, "placeholder", 1),
replaceStrings: mkPrimop(misc.replaceStrings, "replaceStrings", 3),
splitVersion: mkPrimop(misc.splitVersion, "splitVersion", 1),
traceVerbose: mkPrimop(misc.traceVerbose, "traceVerbose", 2),
tryEval: mkPrimop(misc.tryEval, "tryEval", 1),
addErrorContext: mkPrimop(misc.addErrorContext, "addErrorContext", 1),
appendContext: mkPrimop(misc.appendContext, "appendContext", 1),
getContext: mkPrimop(misc.getContext, "getContext", 1),
hasContext: mkPrimop(misc.hasContext, "hasContext", 1),
unsafeDiscardOutputDependency: mkPrimop(
misc.unsafeDiscardOutputDependency,
"unsafeDiscardOutputDependency",
1,
),
unsafeDiscardStringContext: mkPrimop(misc.unsafeDiscardStringContext, "unsafeDiscardStringContext", 1),
addDrvOutputDependencies: mkPrimop(misc.addDrvOutputDependencies, "addDrvOutputDependencies", 2),
compareVersions: mkPrimop(misc.compareVersions, "compareVersions", 2),
functionArgs: mkPrimop(misc.functionArgs, "functionArgs", 1),
genericClosure: mkPrimop(misc.genericClosure, "genericClosure", 1),
outputOf: mkPrimop(misc.outputOf, "outputOf", 2),
parseDrvName: mkPrimop(misc.parseDrvName, "parseDrvName", 1),
placeholder: mkPrimop(misc.placeholder, "placeholder", 1),
replaceStrings: mkPrimop(misc.replaceStrings, "replaceStrings", 3),
splitVersion: mkPrimop(misc.splitVersion, "splitVersion", 1),
traceVerbose: mkPrimop(misc.traceVerbose, "traceVerbose", 2),
tryEval: mkPrimop(misc.tryEval, "tryEval", 1),
builtins: createThunk(() => builtins, "builtins"),
currentSystem: createThunk(() => {
return "x86_64-linux";
}, "currentSystem"),
currentTime: createThunk(() => Date.now(), "currentTime"),
builtins: createThunk(() => builtins, "builtins"),
currentSystem: createThunk(() => {
return "x86_64-linux";
}, "currentSystem"),
currentTime: createThunk(() => Date.now(), "currentTime"),
false: false,
true: true,
null: null,
false: false,
true: true,
null: null,
langVersion: 6,
nixPath: [],
nixVersion: "2.31.2",
storeDir: createThunk(() => {
throw new Error("stub storeDir evaluated");
langVersion: 6,
nixPath: [],
nixVersion: "2.31.2",
storeDir: createThunk(() => {
throw new Error("stub storeDir evaluated");
}),
}),
};
);

View File

@@ -1,3 +1,4 @@
import { select } from "../helpers";
import { getPathValue } from "../path";
import type { NixStringContext, StringWithContext } from "../string-context";
import { addOpaqueContext, decodeContextElem, mkStringWithContext } from "../string-context";
@@ -59,7 +60,7 @@ export const scopedImport =
(scope: NixValue) =>
(path: NixValue): NixValue => {
const scopeAttrs = forceAttrs(scope);
const scopeKeys = Object.keys(scopeAttrs);
const scopeKeys = Array.from(scopeAttrs.keys());
const pathStr = realisePath(path);
@@ -112,25 +113,23 @@ const normalizeUrlInput = (
return { url: forced };
}
const attrs = forceAttrs(args);
const url = forceStringValue(attrs.url);
const hash =
"sha256" in attrs
? forceStringValue(attrs.sha256)
: "hash" in attrs
? forceStringValue(attrs.hash)
: undefined;
const name = "name" in attrs ? forceStringValue(attrs.name) : undefined;
const executable = "executable" in attrs ? forceBool(attrs.executable) : false;
const url = forceStringValue(select(attrs, ["url"]));
const hash = attrs.has("sha256")
? forceStringValue(attrs.get("sha256") as NixValue)
: attrs.has("hash")
? forceStringValue(attrs.get("hash") as NixValue)
: undefined;
const name = attrs.has("name") ? forceStringValue(attrs.get("name") as NixValue) : undefined;
const executable = attrs.has("executable") ? forceBool(attrs.get("executable") as NixValue) : false;
return { url, hash, name, executable };
};
const normalizeTarballInput = (args: NixValue): { url: string; sha256?: string; name?: string } => {
const forced = force(args);
if (isAttrs(forced)) {
const url = resolvePseudoUrl(forceStringNoCtx(forced.url));
const sha256 = "sha256" in forced ? forceStringNoCtx(forced.sha256) : undefined;
const nameRaw = "name" in forced ? forceStringNoCtx(forced.name) : undefined;
// FIXME: extract baseNameOfRaw
const url = resolvePseudoUrl(forceStringNoCtx(select(forced, ["url"])));
const sha256 = forced.has("sha256") ? forceStringNoCtx(forced.get("sha256") as NixValue) : undefined;
const nameRaw = forced.has("name") ? forceStringNoCtx(forced.get("name") as NixValue) : undefined;
const name = nameRaw === "" ? (baseNameOf(nameRaw) as string) : nameRaw;
return { url, sha256, name };
} else {
@@ -175,25 +174,25 @@ export const fetchGit = (args: NixValue): NixAttrs => {
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);
return {
outPath: mkStringWithContext(result.out_path, outContext),
rev: result.rev,
shortRev: result.short_rev,
revCount: BigInt(result.rev_count),
lastModified: BigInt(result.last_modified),
lastModifiedDate: result.last_modified_date,
submodules: result.submodules,
narHash: result.nar_hash,
};
return new Map<string, NixValue>([
["outPath", mkStringWithContext(result.out_path, outContext)],
["rev", result.rev],
["shortRev", result.short_rev],
["revCount", BigInt(result.rev_count)],
["lastModified", BigInt(result.last_modified)],
["lastModifiedDate", result.last_modified_date],
["submodules", result.submodules],
["narHash", result.nar_hash],
]);
}
const attrs = forceAttrs(args);
const url = forceStringValue(attrs.url);
const gitRef = "ref" in attrs ? forceStringValue(attrs.ref) : null;
const rev = "rev" in attrs ? forceStringValue(attrs.rev) : null;
const shallow = "shallow" in attrs ? forceBool(attrs.shallow) : false;
const submodules = "submodules" in attrs ? forceBool(attrs.submodules) : false;
const allRefs = "allRefs" in attrs ? forceBool(attrs.allRefs) : false;
const name = "name" in attrs ? forceStringValue(attrs.name) : null;
const url = forceStringValue(select("attrs", ["url"]));
const gitRef = attrs.has("ref") ? forceStringValue(attrs.get("ref") as NixValue) : null;
const rev = attrs.has("rev") ? forceStringValue(attrs.get("rev") as NixValue) : null;
const shallow = attrs.has("shallow") ? forceBool(attrs.get("shallow") as NixValue) : false;
const submodules = attrs.has("submodules") ? forceBool(attrs.get("submodules") as NixValue) : false;
const allRefs = attrs.has("allRefs") ? forceBool(attrs.get("allRefs") as NixValue) : false;
const name = attrs.has("name") ? forceStringValue(attrs.get("name") as NixValue) : null;
const result: FetchGitResult = Deno.core.ops.op_fetch_git(
url,
@@ -207,16 +206,16 @@ export const fetchGit = (args: NixValue): NixAttrs => {
const outContext: NixStringContext = new Set();
addOpaqueContext(outContext, result.out_path);
return {
outPath: mkStringWithContext(result.out_path, outContext),
rev: result.rev,
shortRev: result.short_rev,
revCount: BigInt(result.rev_count),
lastModified: BigInt(result.last_modified),
lastModifiedDate: result.last_modified_date,
submodules: result.submodules,
narHash: result.nar_hash,
};
return new Map<string, NixValue>([
["outPath", mkStringWithContext(result.out_path, outContext)],
["rev", result.rev],
["shortRev", result.short_rev],
["revCount", BigInt(result.rev_count)],
["lastModified", BigInt(result.last_modified)],
["lastModifiedDate", result.last_modified_date],
["submodules", result.submodules],
["narHash", result.nar_hash],
]);
};
export const fetchMercurial = (_args: NixValue): NixAttrs => {
@@ -225,7 +224,7 @@ export const fetchMercurial = (_args: NixValue): NixAttrs => {
export const fetchTree = (args: NixValue): NixAttrs => {
const attrs = forceAttrs(args);
const type = "type" in attrs ? forceStringValue(attrs.type) : "auto";
const type = attrs.has("type") ? forceStringValue(attrs.get("type") as NixValue) : "auto";
switch (type) {
case "git":
@@ -234,12 +233,12 @@ export const fetchTree = (args: NixValue): NixAttrs => {
case "mercurial":
return fetchMercurial(args);
case "tarball":
return { outPath: fetchTarball(args) };
return new Map<string, NixValue>([["outPath", fetchTarball(args)]]);
case "file":
return { outPath: fetchurl(args) };
return new Map<string, NixValue>([["outPath", fetchurl(args)]]);
case "path": {
const path = forceStringValue(attrs.path);
return { outPath: path };
const path = forceStringValue(select(attrs, ["path"]));
return new Map<string, NixValue>([["outPath", path]]);
}
case "github":
case "gitlab":
@@ -251,11 +250,14 @@ export const fetchTree = (args: NixValue): NixAttrs => {
};
const fetchGitForge = (forge: string, attrs: NixAttrs): NixAttrs => {
const owner = forceStringValue(attrs.owner);
const repo = forceStringValue(attrs.repo);
const rev =
"rev" in attrs ? forceStringValue(attrs.rev) : "ref" in attrs ? forceStringValue(attrs.ref) : "HEAD";
const host = "host" in attrs ? forceStringValue(attrs.host) : undefined;
const owner = forceStringValue(select(forge, ["owner"]));
const repo = forceStringValue(select(forge, ["repo"]));
const rev = attrs.has("rev")
? forceStringValue(attrs.get("rev") as NixValue)
: attrs.has("ref")
? forceStringValue(attrs.get("ref") as NixValue)
: "HEAD";
const host = attrs.has("host") ? forceStringValue(attrs.get("host") as NixValue) : undefined;
let tarballUrl: string;
switch (forge) {
@@ -278,17 +280,17 @@ const fetchGitForge = (forge: string, attrs: NixAttrs): NixAttrs => {
throw new Error(`Unknown forge type: ${forge}`);
}
const outPath = fetchTarball({ url: tarballUrl, ...attrs });
const outPath = fetchTarball(new Map<string, NixValue>([["url", tarballUrl], ...attrs]));
return {
outPath,
rev,
shortRev: rev.substring(0, 7),
};
return new Map<string, NixValue>([
["outPath", outPath],
["rev", rev],
["shortRev", rev.substring(0, 7)],
]);
};
const autoDetectAndFetch = (attrs: NixAttrs): NixAttrs => {
const url = forceStringValue(attrs.url);
const url = forceStringValue(select(attrs, ["url"]));
if (url.endsWith(".git") || url.includes("github.com") || url.includes("gitlab.com")) {
return fetchGit(attrs);
}
@@ -298,17 +300,17 @@ const autoDetectAndFetch = (attrs: NixAttrs): NixAttrs => {
url.endsWith(".tar.bz2") ||
url.endsWith(".tgz")
) {
return { outPath: fetchTarball(attrs) };
return new Map<string, NixValue>([["outPath", fetchTarball(attrs)]]);
}
return { outPath: fetchurl(attrs) };
return new Map<string, NixValue>([["outPath", fetchurl(attrs)]]);
};
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 = {};
const result: NixAttrs = new Map();
for (const [name, type] of Object.entries(entries)) {
result[name] = type;
result.set(name, type);
}
return result;
};
@@ -348,11 +350,11 @@ export const pathExists = (path: NixValue): boolean => {
export const path = (args: NixValue): NixString => {
const attrs = forceAttrs(args);
if (!("path" in attrs)) {
if (!attrs.has("path")) {
throw new TypeError("builtins.path: 'path' attribute is required");
}
const pathValue = force(attrs.path);
const pathValue = force(attrs.get("path") as NixValue);
let pathStr: string;
if (isNixPath(pathValue)) {
@@ -361,14 +363,14 @@ export const path = (args: NixValue): NixString => {
pathStr = forceStringValue(pathValue);
}
const name = "name" in attrs ? forceStringValue(attrs.name) : null;
const recursive = "recursive" in attrs ? forceBool(attrs.recursive) : true;
const sha256 = "sha256" in attrs ? forceStringValue(attrs.sha256) : null;
const name = attrs.has("name") ? forceStringValue(attrs.get("name") as NixValue) : null;
const recursive = attrs.has("recursive") ? forceBool(attrs.get("recursive") as NixValue) : true;
const sha256 = attrs.has("sha256") ? forceStringValue(attrs.get("sha256") as NixValue) : null;
let storePath: string;
if ("filter" in attrs) {
const filterFn = forceFunction(attrs.filter);
if (attrs.has("filter")) {
const filterFn = forceFunction(attrs.get("filter") as NixValue);
const entries: [string, string][] = Deno.core.ops.op_walk_dir(pathStr);
@@ -445,9 +447,9 @@ export const findFile =
for (const item of forcedSearchPath) {
const attrs = forceAttrs(item);
const prefix = "prefix" in attrs ? forceStringNoCtx(attrs.prefix) : "";
const prefix = attrs.has("prefix") ? forceStringNoCtx(attrs.get("prefix") as NixValue) : "";
if (!("path" in attrs)) {
if (!attrs.has("path")) {
throw new Error("findFile: search path element is missing 'path' attribute");
}
@@ -457,7 +459,12 @@ export const findFile =
}
const context: NixStringContext = new Set();
const pathVal = coerceToString(attrs.path, StringCoercionMode.Interpolation, false, context);
const pathVal = coerceToString(
attrs.get("path") as NixValue,
StringCoercionMode.Interpolation,
false,
context,
);
if (context.size > 0) {
throw new Error("findFile: path with string context is not yet supported");

View File

@@ -95,18 +95,19 @@ export const partition =
(list: NixValue): NixAttrs => {
const forcedList = forceList(list);
const forcedPred = forceFunction(pred);
const attrs = {
right: [] as NixList,
wrong: [] as NixList,
};
const right: NixList = [];
const wrong: NixList = [];
for (const elem of forcedList) {
if (force(forcedPred(elem))) {
attrs.right.push(elem);
right.push(elem);
} else {
attrs.wrong.push(elem);
wrong.push(elem);
}
}
return attrs;
return new Map<string, NixValue>([
["right", right],
["wrong", wrong],
]);
};
export const genList =

View File

@@ -1,4 +1,5 @@
import { OrderedSet } from "js-sdsl";
import { select } from "../helpers";
import { compareValues } from "../operators";
import {
getStringContext,
@@ -15,7 +16,7 @@ import {
forceStringNoCtx,
forceStringValue,
} from "../type-assert";
import type { NixAttrs, NixBool, NixStrictValue, NixValue } from "../types";
import type { NixAttrs, NixStrictValue, NixValue } from "../types";
import { ATTR_POSITIONS, CatchableError } from "../types";
import * as context from "./context";
import { isBool, isFloat, isInt, isList, isString, typeOf } from "./type-check";
@@ -137,24 +138,20 @@ function componentsLt(c1: string, c2: string): boolean {
export const functionArgs = (f: NixValue): NixAttrs => {
const func = forceFunction(f);
if (func.args) {
const ret: NixAttrs = {};
const ret: NixAttrs = new Map();
for (const key of func.args.required) {
ret[key] = false;
ret.set(key, false);
}
for (const key of func.args.optional) {
ret[key] = true;
ret.set(key, true);
}
const positions = func.args.positions;
if (positions && Object.keys(positions).length > 0) {
Object.defineProperty(ret, ATTR_POSITIONS, {
value: positions,
enumerable: false,
writable: false,
});
ret[ATTR_POSITIONS] = positions;
}
return ret;
}
return {};
return new Map();
};
const checkComparable = (value: NixStrictValue): void => {
@@ -166,7 +163,8 @@ const checkComparable = (value: NixStrictValue): void => {
export const genericClosure = (args: NixValue): NixValue => {
const forcedArgs = forceAttrs(args);
const { startSet, operator } = forcedArgs;
const startSet = select(forcedArgs, ["startSet"]);
const operator = select(forcedArgs, ["operator"]);
const initialList = forceList(startSet);
const opFunction = forceFunction(operator);
@@ -177,7 +175,7 @@ export const genericClosure = (args: NixValue): NixValue => {
for (const item of initialList) {
const itemAttrs = forceAttrs(item);
const key = force(itemAttrs.key);
const key = force(select(itemAttrs, ["key"]));
checkComparable(key);
if (resultSet.find(key).equals(resultSet.end())) {
resultSet.insert(key);
@@ -193,7 +191,7 @@ export const genericClosure = (args: NixValue): NixValue => {
for (const newItem of newItems) {
const newItemAttrs = forceAttrs(newItem);
const key = force(newItemAttrs.key);
const key = force(select(newItemAttrs, ["key"]));
checkComparable(key);
if (resultSet.find(key).equals(resultSet.end())) {
resultSet.insert(key);
@@ -223,10 +221,10 @@ export const parseDrvName = (s: NixValue): NixAttrs => {
break;
}
}
return {
name,
version,
};
return new Map<string, NixValue>([
["name", name],
["version", version],
]);
};
export const placeholder = (output: NixValue): NixValue => {
@@ -322,21 +320,21 @@ export const splitVersion = (s: NixValue): NixValue => {
export const traceVerbose = (_e1: NixValue, e2: NixValue): NixStrictValue => {
// TODO: implement traceVerbose
return force(e2)
return force(e2);
};
export const tryEval = (e: NixValue): { success: NixBool; value: NixStrictValue } => {
export const tryEval = (e: NixValue): NixAttrs => {
try {
return {
success: true,
value: force(e),
};
return new Map<string, NixValue>([
["success", true],
["value", force(e)],
]);
} catch (err) {
if (err instanceof CatchableError) {
return {
success: false,
value: false,
};
return new Map<string, NixValue>([
["success", false],
["value", false],
]);
} else {
throw err;
}

View File

@@ -1,5 +1,4 @@
import {
HAS_CONTEXT,
isNixPath,
isStringWithContext,
type NixAttrs,
@@ -19,10 +18,7 @@ export const isNixString = (v: NixStrictValue): v is NixString => {
};
export const isAttrs = (e: NixStrictValue): e is NixAttrs => {
const val = e;
return (
typeof val === "object" && !Array.isArray(val) && val !== null && !(HAS_CONTEXT in val) && !isPath(val)
);
return e instanceof Map;
};
export const isBool = (e: NixStrictValue): e is NixBool => typeof e === "boolean";
@@ -60,7 +56,7 @@ export const typeOf = (e: NixStrictValue): NixType => {
if (isNixString(e)) return "string";
if (isNixPath(e)) return "path";
if (Array.isArray(e)) return "list";
if (typeof e === "object") return "set";
if (e instanceof Map) return "set";
if (typeof e === "function") return "lambda";
throw new TypeError(`Unknown Nix type: ${typeof e}`);

View File

@@ -3,7 +3,7 @@ import { isAttrs, typeOf } from "./builtins/type-check";
import { mkPath } from "./path";
import { isStringWithContext, mkStringWithContext, type NixStringContext } from "./string-context";
import { force } from "./thunk";
import { forceAttrs, forceBool, forceFunction, forceStringValue } from "./type-assert";
import { forceAttrs, forceBool, forceFunction, forceStringNoCtx, forceStringValue } from "./type-assert";
import type { NixAttrs, NixBool, NixPath, NixString, NixValue } from "./types";
import { CatchableError, isNixPath } from "./types";
@@ -169,18 +169,18 @@ function selectImpl(obj: NixValue, attrpath: NixValue[]): NixValue {
for (const attr of attrpath.slice(0, -1)) {
const key = forceStringValue(attr);
if (!(key in attrs)) {
if (!attrs.has(key)) {
throw new Error(`Attribute '${key}' not found`);
}
const cur = forceAttrs(attrs[forceStringValue(attr)]);
const cur = forceAttrs(attrs.get(key) as NixValue);
attrs = cur;
}
const last = forceStringValue(attrpath[attrpath.length - 1]);
if (!(last in attrs)) {
if (!attrs.has(last)) {
throw new Error(`Attribute '${last}' not found`);
}
return attrs[last];
return attrs.get(last) as NixValue;
}
export const selectWithDefault = (
@@ -218,10 +218,10 @@ function selectWithDefaultImpl(obj: NixValue, attrpath: NixValue[], defaultVal:
for (const attr of attrpath.slice(0, -1)) {
const key = forceStringValue(attr);
if (!(key in attrs)) {
if (!attrs.has(key)) {
return defaultVal;
}
const cur = force(attrs[key]);
const cur = force(attrs.get(key) as NixValue);
if (!isAttrs(cur)) {
return defaultVal;
}
@@ -229,8 +229,8 @@ function selectWithDefaultImpl(obj: NixValue, attrpath: NixValue[], defaultVal:
}
const last = forceStringValue(attrpath[attrpath.length - 1]);
if (last in attrs) {
return attrs[last];
if (attrs.has(last)) {
return attrs.get(last) as NixValue;
}
return defaultVal;
}
@@ -243,14 +243,18 @@ export const hasAttr = (obj: NixValue, attrpath: NixValue[]): NixBool => {
let attrs = forced;
for (const attr of attrpath.slice(0, -1)) {
const cur = force(attrs[forceStringValue(attr)]);
const key = forceStringNoCtx(attr);
if (!attrs.has(key)) {
return false;
}
const cur = force(attrs.get(key) as NixValue);
if (!isAttrs(cur)) {
return false;
}
attrs = cur;
}
return forceStringValue(attrpath[attrpath.length - 1]) in attrs;
return attrs.has(forceStringValue(attrpath[attrpath.length - 1]));
};
export const call = (func: NixValue, arg: NixValue, span?: string): NixValue => {
@@ -277,13 +281,8 @@ function callImpl(func: NixValue, arg: NixValue): NixValue {
forcedFunc.args?.check(arg);
return forcedFunc(arg);
}
if (
typeof forcedFunc === "object" &&
!Array.isArray(forcedFunc) &&
forcedFunc !== null &&
"__functor" in forcedFunc
) {
const functor = forceFunction(forcedFunc.__functor);
if (forcedFunc instanceof Map && forcedFunc.has("__functor")) {
const functor = forceFunction(forcedFunc.get("__functor") as NixValue);
return call(functor(forcedFunc), arg);
}
throw new Error(`attempt to call something which is not a function but ${typeOf(forcedFunc)}`);
@@ -307,7 +306,7 @@ export const ifFunc = (cond: NixValue, consq: NixValue, alter: NixValue) => {
};
export const mkPos = (span: string): NixAttrs => {
return Deno.core.ops.op_decode_span(span);
return new Map(Object.entries(Deno.core.ops.op_decode_span(span)));
};
interface WithScope {
@@ -319,8 +318,8 @@ export const lookupWith = (name: string, withScope: WithScope | null): NixValue
let current = withScope;
while (current !== null) {
const attrs = forceAttrs(current.env);
if (name in attrs) {
return attrs[name];
if (attrs.has(name)) {
return attrs.get(name) as NixValue;
}
current = current.last;
}

View File

@@ -16,8 +16,8 @@ import { isNixPath } from "./types";
const canCoerceToString = (v: NixValue): boolean => {
const forced = force(v);
if (isNixString(forced)) return true;
if (typeof forced === "object" && forced !== null && !Array.isArray(forced)) {
if ("outPath" in forced || "__toString" in forced) return true;
if (forced instanceof Map) {
if (forced.has("outPath") || forced.has("__toString")) return true;
}
return false;
};
@@ -219,27 +219,24 @@ export const op = {
const attrsA = av as NixAttrs;
const attrsB = bv as NixAttrs;
// Derivation comparison: compare outPaths only
// Safe to force 'type' because it's always a string literal, never a computed value
if ("type" in attrsA && "type" in attrsB) {
const typeValA = force(attrsA.type);
const typeValB = force(attrsB.type);
if (attrsA.has("type") && attrsB.has("type")) {
const typeValA = force(attrsA.get("type") as NixValue);
const typeValB = force(attrsB.get("type") as NixValue);
if (typeValA === "derivation" && typeValB === "derivation") {
if ("outPath" in attrsA && "outPath" in attrsB) {
return op.eq(attrsA.outPath, attrsB.outPath);
if (attrsA.has("outPath") && attrsB.has("outPath")) {
return op.eq(attrsA.get("outPath") as NixValue, attrsB.get("outPath") as NixValue);
}
}
}
// Otherwise, compare attributes one by one
const keysA = Object.keys(attrsA).sort();
const keysB = Object.keys(attrsB).sort();
const keysA = Array.from(attrsA.keys()).sort();
const keysB = Array.from(attrsB.keys()).sort();
if (keysA.length !== keysB.length) return false;
for (let i = 0; i < keysA.length; i++) {
if (keysA[i] !== keysB[i]) return false;
if (!op.eq(attrsA[keysA[i]], attrsB[keysB[i]])) return false;
if (!op.eq(attrsA.get(keysA[i]) as NixValue, attrsB.get(keysB[i]) as NixValue)) return false;
}
return true;
@@ -270,5 +267,13 @@ export const op = {
return forceList(a).concat(forceList(b));
},
update: (a: NixValue, b: NixValue): NixAttrs => ({ ...forceAttrs(a), ...forceAttrs(b) }),
update: (a: NixValue, b: NixValue): NixAttrs => {
const mapA = forceAttrs(a);
const mapB = forceAttrs(b);
const result: NixAttrs = new Map(mapA);
for (const [k, v] of mapB) {
result.set(k, v);
}
return result;
},
};

View File

@@ -14,12 +14,11 @@ const canonicalizePath = (path: string): string => {
const component = path.slice(i, j);
i = j;
if (component === ".") {
} else if (component === "..") {
if (component === "..") {
if (parts.length > 0) {
parts.pop();
}
} else {
} else if (component !== ".") {
parts.push(component);
}
}

View File

@@ -1,6 +1,6 @@
import { isAttrs, isList } from "./builtins/type-check";
import { HAS_CONTEXT } from "./string-context";
import type { NixStrictValue, NixThunkInterface, NixValue } from "./types";
import type { NixAttrs, NixStrictValue, NixThunkInterface, NixValue } from "./types";
import { IS_PATH } from "./types";
export const IS_THUNK = Symbol("is_thunk");
@@ -117,7 +117,7 @@ export const createThunk = (func: () => NixValue, label?: string): NixThunkInter
};
export const IS_CYCLE = Symbol("is_cycle");
export const CYCLE_MARKER = { [IS_CYCLE]: true };
export const CYCLE_MARKER = { [IS_CYCLE]: true as const };
/**
* Deeply force a value, handling cycles by returning a special marker.
@@ -150,10 +150,10 @@ export const forceDeep = (value: NixValue, seen: WeakSet<object> = new WeakSet()
return forced.map((item) => forceDeep(item, seen));
}
if (typeof forced === "object") {
const result: Record<string, NixValue> = {};
for (const [key, val] of Object.entries(forced)) {
result[key] = forceDeep(val, seen);
if (forced instanceof Map) {
const result: NixAttrs = new Map();
for (const [key, val] of forced) {
result.set(key, forceDeep(val, seen));
}
return result;
}
@@ -180,10 +180,10 @@ export const forceShallow = (value: NixValue): NixStrictValue => {
}
if (isAttrs(forced)) {
const result: Record<string, NixValue> = {};
for (const [key, val] of Object.entries(forced)) {
const forcedVal = force(val);
result[key] = forcedVal === forced ? CYCLE_MARKER : forcedVal;
const result: NixAttrs = new Map();
for (const [key, val] of forced) {
const forcedVal = force(val as NixValue);
result.set(key, forcedVal === forced ? CYCLE_MARKER : forcedVal);
}
return result;
}

View File

@@ -26,9 +26,9 @@ export const forceFunction = (value: NixValue): NixFunction => {
if (isFunction(forced)) {
return forced;
}
if (typeof forced === "object" && !Array.isArray(forced) && forced !== null && "__functor" in forced) {
if (forced instanceof Map && forced.has("__functor")) {
const functorSet = forced as NixAttrs;
const functor = forceFunction(functorSet.__functor);
const functor = forceFunction(functorSet.get("__functor") as NixValue);
return (arg: NixValue) => forceFunction(functor(functorSet))(arg);
}
throw new TypeError(`Expected function, got ${typeOf(forced)}`);

View File

@@ -1,5 +1,5 @@
import { HAS_CONTEXT, isStringWithContext, type StringWithContext } from "./string-context";
import { force, IS_THUNK } from "./thunk";
import { type CYCLE_MARKER, force, IS_THUNK } from "./thunk";
import { forceAttrs, forceStringNoCtx } from "./type-assert";
export { HAS_CONTEXT, isStringWithContext };
export type { StringWithContext };
@@ -22,9 +22,9 @@ export type NixBool = boolean;
export type NixString = string | StringWithContext;
export type NixNull = null;
export const ATTR_POSITIONS = Symbol("attrPositions");
export type NixList = NixValue[];
// FIXME: reject contextful string
export type NixAttrs = { [key: string]: NixValue };
export type NixAttrs = Map<string, NixValue> & { [ATTR_POSITIONS]?: Record<string, string> };
export type NixFunction = ((arg: NixValue) => NixValue) & { args?: NixArgs };
export class NixArgs {
required: string[];
@@ -43,13 +43,13 @@ export class NixArgs {
const attrs = forceAttrs(arg);
for (const key of this.required) {
if (!Object.hasOwn(attrs, key)) {
if (!attrs.has(key)) {
throw new Error(`Function called without required argument '${key}'`);
}
}
if (!this.ellipsis) {
for (const key in attrs) {
for (const key of attrs.keys()) {
if (!this.allowed.has(key)) {
throw new Error(`Function called with unexpected argument '${key}'`);
}
@@ -77,18 +77,18 @@ export const mkAttrs = (attrs: NixAttrs, keys: NixValue[], values: NixValue[]):
continue;
}
const str = forceStringNoCtx(key);
attrs[str] = values[i];
attrs.set(str, values[i]);
}
return attrs;
};
export const ATTR_POSITIONS = Symbol("attrPositions");
export const mkAttrsWithPos = (
attrs: NixAttrs,
obj: Record<string, NixValue>,
positions: Record<string, string>,
dyns?: { dynKeys: NixValue[]; dynVals: NixValue[]; dynSpans: string[] },
): NixAttrs => {
const attrs: NixAttrs = new Map(Object.entries(obj));
if (dyns) {
const len = dyns.dynKeys.length;
for (let i = 0; i < len; i++) {
@@ -97,17 +97,13 @@ export const mkAttrsWithPos = (
continue;
}
const str = forceStringNoCtx(key);
attrs[str] = dyns.dynVals[i];
attrs.set(str, dyns.dynVals[i]);
positions[str] = dyns.dynSpans[i];
}
}
if (Object.keys(positions).length > 0) {
Object.defineProperty(attrs, ATTR_POSITIONS, {
value: positions,
enumerable: false,
writable: false,
});
attrs[ATTR_POSITIONS] = positions;
}
return attrs;
@@ -120,7 +116,14 @@ export interface NixThunkInterface {
}
export type NixPrimitive = NixNull | NixBool | NixInt | NixFloat | NixString;
export type NixValue = NixPrimitive | NixPath | NixList | NixAttrs | NixFunction | NixThunkInterface;
export type NixValue =
| NixPrimitive
| NixPath
| NixList
| NixAttrs
| NixFunction
| NixThunkInterface
| typeof CYCLE_MARKER;
export type NixStrictValue = Exclude<NixValue, NixThunkInterface>;
export class CatchableError extends Error {}

View File

@@ -263,9 +263,9 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
}
&Ir::Builtin(Builtin { inner: name, .. }) => {
code!(buf, ctx;
"Nix.builtins["
"Nix.builtins.get("
ctx.get_sym(name)
"]"
")"
);
}
Ir::ConcatStrings(x) => x.compile(ctx, buf),
@@ -309,9 +309,9 @@ impl<Ctx: CodegenContext> Compile<Ctx> for Ir {
}
&Ir::ScopedImportBinding(ScopedImportBinding { inner: name, .. }) => {
code!(buf, ctx;
"__scope["
"__scope.get("
ctx.get_sym(name)
"]"
")"
);
}
Ir::WithExpr(x) => x.compile(ctx, buf),
@@ -632,7 +632,7 @@ impl<Ctx: CodegenContext> Compile<Ctx> for AttrSet {
"})"
);
} else {
code!(buf, ctx; "{}");
code!(buf, ctx; "new Map()");
}
}
}

View File

@@ -123,7 +123,7 @@ impl Context {
let code = self.ctx.compile(source, None)?;
self.runtime.eval(
format!(
"Nix.builtins.derivation = {};Nix.builtins.storeDir=\"{}\"",
"Nix.builtins.set('derivation',({}));Nix.builtins.set('storeDir','{}')",
code,
self.get_store_dir()
),

View File

@@ -364,6 +364,28 @@ fn to_value<'a>(
Value::Func
}
}
_ if val.is_map() => {
let val = val.try_cast::<v8::Map>().expect("infallible conversion");
let size = val.size() as u32;
let array = val.as_array(scope);
let attrs = (0..size)
.map(|i| {
let key = array.get_index(scope, i * 2).expect("infallible index operation");
let key = key.to_rust_string_lossy(scope);
let val = array.get_index(scope, i * 2 + 1).expect("infallible index operation");
let val = to_value(
val,
scope,
is_thunk_symbol,
primop_metadata_symbol,
has_context_symbol,
is_path_symbol,
is_cycle_symbol,
);
(Symbol::new(Cow::Owned(key)), val)
}).collect();
Value::AttrSet(AttrSet::new(attrs))
}
_ if val.is_object() => {
if is_thunk(val, scope, is_thunk_symbol) {
return Value::Thunk;

View File

@@ -745,10 +745,10 @@ impl<'a> deno_core::convert::ToV8<'a> for NixJsonValue {
scope: &mut v8::PinScope<'a, 'i>,
) -> std::result::Result<v8::Local<'a, v8::Value>, Self::Error> {
Ok(match self {
Self::Null => v8::null(scope).into() ,
Self::Bool(b) => v8::Boolean::new(scope, b).into() ,
Self::Int(i) => v8::BigInt::new_from_i64(scope, i).into() ,
Self::Float(f) => v8::Number::new(scope, f).into() ,
Self::Null => v8::null(scope).into(),
Self::Bool(b) => v8::Boolean::new(scope, b).into(),
Self::Int(i) => v8::BigInt::new_from_i64(scope, i).into(),
Self::Float(f) => v8::Number::new(scope, f).into(),
Self::Str(s) => v8::String::new(scope, &s)
.map(|s| s.into())
.ok_or("failed to create v8 string")?,
@@ -757,18 +757,18 @@ impl<'a> deno_core::convert::ToV8<'a> for NixJsonValue {
.into_iter()
.map(|v| v.to_v8(scope))
.collect::<std::result::Result<Vec<_>, _>>()?;
v8::Array::new_with_elements(scope, &elements).into()
v8::Array::new_with_elements(scope, &elements).into()
}
Self::Obj(entries) => {
let obj = v8::Object::new(scope);
let map = v8::Map::new(scope);
for (k, v) in entries {
let key: v8::Local<v8::Value> = v8::String::new(scope, &k)
.ok_or("failed to create v8 string")?
.into();
let val = v.to_v8(scope)?;
obj.set(scope, key, val);
map.set(scope, key, val);
}
obj.into()
map.into()
}
})
}
@@ -1227,19 +1227,19 @@ impl<'s> XmlCtx<'s> {
nix_obj: v8::Local<'s, v8::Object>,
) -> Result<Self> {
let get_fn = |scope: &v8::PinScope<'s, 'i>, name: &str| {
let key = v8::String::new(scope, name).ok_or("v8 string" )?;
let key = v8::String::new(scope, name).ok_or("v8 string")?;
let val = nix_obj
.get(scope, key.into())
.ok_or_else(|| format!("no {name}") )?;
.ok_or_else(|| format!("no {name}"))?;
v8::Local::<v8::Function>::try_from(val)
.map_err(|e| format!("{name} not function: {e}") )
.map_err(|e| format!("{name} not function: {e}"))
};
let get_sym = |scope: &v8::PinScope<'s, 'i>, name: &str| {
let key = v8::String::new(scope, name).ok_or("v8 string" )?;
let key = v8::String::new(scope, name).ok_or("v8 string")?;
let val = nix_obj
.get(scope, key.into())
.ok_or_else(|| format!("no {name}") )?;
v8::Local::<v8::Symbol>::try_from(val).map_err(|e| format!("{name} not symbol: {e}") )
.ok_or_else(|| format!("no {name}"))?;
v8::Local::<v8::Symbol>::try_from(val).map_err(|e| format!("{name} not symbol: {e}"))
};
Ok(Self {
force_fn: get_fn(scope, "force")?,
@@ -1378,12 +1378,12 @@ impl XmlWriter {
}
}
fn is_derivation<'s>(obj: v8::Local<'s, v8::Object>, scope: &mut v8::PinScope<'s, '_>) -> bool {
fn is_derivation<'s>(map: v8::Local<'s, v8::Map>, scope: &mut v8::PinScope<'s, '_>) -> bool {
let key = match v8::String::new(scope, "type") {
Some(k) => k,
None => return false,
};
match obj.get(scope, key.into()) {
match map.get(scope, key.into()) {
Some(v) if v.is_string() => v.to_rust_string_lossy(scope) == "derivation",
_ => false,
}
@@ -1414,7 +1414,7 @@ impl XmlWriter {
return Ok(());
}
if val.is_big_int() {
let bi = val.to_big_int(scope).ok_or("bigint" )?;
let bi = val.to_big_int(scope).ok_or("bigint")?;
let (i, _) = bi.i64_value();
self.indent(depth);
self.buf.push_str("<int value=\"");
@@ -1423,7 +1423,7 @@ impl XmlWriter {
return Ok(());
}
if val.is_number() {
let n = val.number_value(scope).ok_or("number" )?;
let n = val.number_value(scope).ok_or("number")?;
self.indent(depth);
self.buf.push_str("<float value=\"");
self.buf.push_str(&format!("{}", n));
@@ -1439,11 +1439,11 @@ impl XmlWriter {
return Ok(());
}
if val.is_array() {
let arr = v8::Local::<v8::Array>::try_from(val).map_err(|e| e.to_string() )?;
let arr = v8::Local::<v8::Array>::try_from(val).map_err(|e| e.to_string())?;
self.indent(depth);
self.buf.push_str("<list>\n");
for i in 0..arr.length() {
let elem = arr.get_index(scope, i).ok_or("array elem" )?;
let elem = arr.get_index(scope, i).ok_or("array elem")?;
self.write_value(elem, scope, ctx, depth + 1)?;
}
self.indent(depth);
@@ -1453,14 +1453,18 @@ impl XmlWriter {
if val.is_function() {
return self.write_function(val, scope, ctx, depth);
}
if val.is_map() {
let map = v8::Local::<v8::Map>::try_from(val).map_err(|e| e.to_string())?;
return self.write_attrs(map, scope, ctx, depth);
}
if val.is_object() {
let obj = val.to_object(scope).ok_or("to_object" )?;
let obj = val.to_object(scope).ok_or("to_object")?;
if Self::has_sym(obj, scope, ctx.has_context) {
let key = v8::String::new(scope, "value").ok_or("v8 str" )?;
let key = v8::String::new(scope, "value").ok_or("v8 str")?;
let s = obj
.get(scope, key.into())
.ok_or("value" )?
.ok_or("value")?
.to_rust_string_lossy(scope);
self.collect_context(obj, scope);
self.indent(depth);
@@ -1470,10 +1474,10 @@ impl XmlWriter {
return Ok(());
}
if Self::has_sym(obj, scope, ctx.is_path) {
let key = v8::String::new(scope, "value").ok_or("v8 str" )?;
let key = v8::String::new(scope, "value").ok_or("v8 str")?;
let s = obj
.get(scope, key.into())
.ok_or("value" )?
.ok_or("value")?
.to_rust_string_lossy(scope);
self.indent(depth);
self.buf.push_str("<path value=\"");
@@ -1486,8 +1490,6 @@ impl XmlWriter {
self.buf.push_str("<unevaluated />\n");
return Ok(());
}
return self.write_attrs(obj, scope, ctx, depth);
}
self.indent(depth);
@@ -1497,31 +1499,39 @@ impl XmlWriter {
fn write_attrs<'s>(
&mut self,
obj: v8::Local<'s, v8::Object>,
map: v8::Local<'s, v8::Map>,
scope: &mut v8::PinScope<'s, '_>,
ctx: &XmlCtx<'s>,
depth: usize,
) -> Result<()> {
if Self::is_derivation(obj, scope) {
if Self::is_derivation(map, scope) {
self.indent(depth);
self.buf.push_str("<derivation");
let drv_path_key = v8::String::new(scope, "drvPath").ok_or("v8 str" )?;
let drv_str = if let Some(drv_val) = obj.get(scope, drv_path_key.into()) {
let forced = self.force(drv_val, scope, ctx)?;
let s = self.extract_str(forced, scope, ctx);
if let Some(ref s) = s {
self.buf.push_str(" drvPath=\"");
self.escape_attr(s);
self.buf.push('"');
let drv_path_key: v8::Local<v8::Value> =
v8::String::new(scope, "drvPath").ok_or("v8 str")?.into();
let drv_str = if let Some(drv_val) = map.get(scope, drv_path_key) {
if drv_val.is_undefined() {
None
} else {
let forced = self.force(drv_val, scope, ctx)?;
let s = self.extract_str(forced, scope, ctx);
if let Some(ref s) = s {
self.buf.push_str(" drvPath=\"");
self.escape_attr(s);
self.buf.push('"');
}
s
}
s
} else {
None
};
let out_path_key = v8::String::new(scope, "outPath").ok_or("v8 str" )?;
if let Some(out_val) = obj.get(scope, out_path_key.into()) {
let out_path_key: v8::Local<v8::Value> =
v8::String::new(scope, "outPath").ok_or("v8 str")?.into();
if let Some(out_val) = map.get(scope, out_path_key)
&& !out_val.is_undefined()
{
let forced = self.force(out_val, scope, ctx)?;
if let Some(ref s) = self.extract_str(forced, scope, ctx) {
self.buf.push_str(" outPath=\"");
@@ -1542,7 +1552,7 @@ impl XmlWriter {
self.indent(depth + 1);
self.buf.push_str("<repeated />\n");
} else {
self.write_attrs_sorted(obj, scope, ctx, depth + 1)?;
self.write_attrs_sorted(map, scope, ctx, depth + 1)?;
}
self.indent(depth);
@@ -1550,7 +1560,7 @@ impl XmlWriter {
} else {
self.indent(depth);
self.buf.push_str("<attrs>\n");
self.write_attrs_sorted(obj, scope, ctx, depth + 1)?;
self.write_attrs_sorted(map, scope, ctx, depth + 1)?;
self.indent(depth);
self.buf.push_str("</attrs>\n");
}
@@ -1559,34 +1569,32 @@ impl XmlWriter {
fn write_attrs_sorted<'s>(
&mut self,
obj: v8::Local<'s, v8::Object>,
map: v8::Local<'s, v8::Map>,
scope: &mut v8::PinScope<'s, '_>,
ctx: &XmlCtx<'s>,
depth: usize,
) -> Result<()> {
let keys = obj
.get_own_property_names(scope, v8::GetPropertyNamesArgsBuilder::new().build())
.ok_or("property names" )?;
let arr = map.as_array(scope);
let len = arr.length();
let mut key_strings: Vec<String> = Vec::with_capacity(keys.length() as usize);
for i in 0..keys.length() {
let key = keys.get_index(scope, i).ok_or("key index" )?;
key_strings.push(key.to_rust_string_lossy(scope));
let mut entries: Vec<(String, v8::Local<'s, v8::Value>)> =
Vec::with_capacity((len / 2) as usize);
let mut i = 0;
while i < len {
let key = arr.get_index(scope, i).ok_or("map key")?;
let val = arr.get_index(scope, i + 1).ok_or("map value")?;
entries.push((key.to_rust_string_lossy(scope), val));
i += 2;
}
key_strings.sort();
for key_str in &key_strings {
let v8_key = v8::String::new(scope, key_str).ok_or("v8 str" )?;
let val = obj
.get(scope, v8_key.into())
.ok_or("attr value" )?;
entries.sort_by(|a, b| a.0.cmp(&b.0));
for (key_str, val) in &entries {
self.indent(depth);
self.buf.push_str("<attr name=\"");
self.escape_attr(key_str);
self.buf.push_str("\">\n");
self.write_value(val, scope, ctx, depth + 1)?;
self.write_value(*val, scope, ctx, depth + 1)?;
self.indent(depth);
self.buf.push_str("</attr>\n");
@@ -1601,7 +1609,7 @@ impl XmlWriter {
ctx: &XmlCtx<'s>,
depth: usize,
) -> Result<()> {
let obj = val.to_object(scope).ok_or("fn to_object" )?;
let obj = val.to_object(scope).ok_or("fn to_object")?;
if let Some(meta) = obj.get(scope, ctx.primop_meta.into())
&& meta.is_object()
@@ -1612,16 +1620,16 @@ impl XmlWriter {
return Ok(());
}
let args_key = v8::String::new(scope, "args").ok_or("v8 str" )?;
let args_key = v8::String::new(scope, "args").ok_or("v8 str")?;
let args_val = obj.get(scope, args_key.into());
match args_val {
Some(args) if args.is_object() && !args.is_null_or_undefined() => {
let args_obj = args.to_object(scope).ok_or("args to_object" )?;
let args_obj = args.to_object(scope).ok_or("args to_object")?;
let req_key = v8::String::new(scope, "required").ok_or("v8 str" )?;
let opt_key = v8::String::new(scope, "optional").ok_or("v8 str" )?;
let ellipsis_key = v8::String::new(scope, "ellipsis").ok_or("v8 str" )?;
let req_key = v8::String::new(scope, "required").ok_or("v8 str")?;
let opt_key = v8::String::new(scope, "optional").ok_or("v8 str")?;
let ellipsis_key = v8::String::new(scope, "ellipsis").ok_or("v8 str")?;
let mut all_formals: Vec<String> = Vec::new();
if let Some(req) = args_obj.get(scope, req_key.into())
@@ -1721,12 +1729,12 @@ impl<'a> FromV8<'a> for ToXmlResult {
value: v8::Local<'a, v8::Value>,
) -> std::result::Result<Self, Self::Error> {
let global = scope.get_current_context().global(scope);
let nix_key = v8::String::new(scope, "Nix").ok_or("v8 string" )?;
let nix_key = v8::String::new(scope, "Nix").ok_or("v8 string")?;
let nix_obj = global
.get(scope, nix_key.into())
.ok_or("no Nix global" )?
.ok_or("no Nix global")?
.to_object(scope)
.ok_or("Nix not object" )?;
.ok_or("Nix not object")?;
let ctx = XmlCtx::new(scope, nix_obj)?;