Compare commits

...

4 Commits

Author SHA1 Message Date
182470d92d optimize: use Map to represent NixAttrs 2026-02-17 10:31:50 +08:00
62e48cabd6 chore: add eslint 2026-02-16 23:33:13 +08:00
37e395c0e3 optimize: builtins.intersectAttrs 2026-02-16 23:07:52 +08:00
16a8480d29 feat(cli): support eval file 2026-02-16 21:52:08 +08:00
28 changed files with 2109 additions and 588 deletions

View File

@@ -3,6 +3,15 @@ vim.lsp.config("biome", {
on_dir(vim.fn.getcwd()) on_dir(vim.fn.getcwd())
end end
}) })
vim.lsp.config("eslint", {
settings = {
eslint = {
options = {
configFile = "./nix-js/runtime-ts/eslint.config.mts"
}
}
}
})
vim.lsp.config("rust_analyzer", { vim.lsp.config("rust_analyzer", {
settings = { settings = {
["rust-analyzer"] = { ["rust-analyzer"] = {

View File

@@ -4,7 +4,7 @@
[no-exit-message] [no-exit-message]
@eval expr: @eval expr:
cargo run -- eval '{{expr}}' cargo run -- eval --expr '{{expr}}'
[no-exit-message] [no-exit-message]
@replr: @replr:
@@ -12,7 +12,7 @@
[no-exit-message] [no-exit-message]
@evalr expr: @evalr expr:
cargo run --release -- eval '{{expr}}' cargo run --release -- eval --expr '{{expr}}'
[no-exit-message] [no-exit-message]
@repli: @repli:
@@ -20,4 +20,4 @@
[no-exit-message] [no-exit-message]
@evali expr: @evali expr:
cargo run --release --features inspector -- --inspect-brk 127.0.0.1:9229 eval '{{expr}}' cargo run --release --features inspector -- --inspect-brk 127.0.0.1:9229 eval --expr '{{expr}}'

View File

@@ -0,0 +1,20 @@
import js from "@eslint/js";
import { defineConfig } from "eslint/config";
import globals from "globals";
import tseslint from "typescript-eslint";
export default defineConfig([
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ["**/*.{js,mjs,cjs,ts,mts,cts}"],
languageOptions: { globals: globals.browser },
rules: {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": ["error", { varsIgnorePattern: "^_", argsIgnorePattern: "^_" }],
},
},
{
ignores: ["dist/**/*"],
},
]);

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"check": "tsc --noEmit && npx eslint && biome check",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"build": "node build.mjs", "build": "node build.mjs",
"dev": "npm run typecheck && npm run build" "dev": "npm run typecheck && npm run build"
@@ -12,6 +13,9 @@
"typescript": "^5.7.2" "typescript": "^5.7.2"
}, },
"dependencies": { "dependencies": {
"js-sdsl": "^4.4.2" "eslint": "^9.39.2",
"globals": "^17.3.0",
"js-sdsl": "^4.4.2",
"typescript-eslint": "^8.55.0"
} }
} }

View File

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

View File

@@ -113,20 +113,20 @@ export const getContext = (value: NixValue): NixAttrs => {
const context = getStringContext(s); const context = getStringContext(s);
const infoMap = parseContextToInfoMap(context); const infoMap = parseContextToInfoMap(context);
const result: NixAttrs = {}; const result: NixAttrs = new Map();
for (const [path, info] of infoMap) { for (const [path, info] of infoMap) {
const attrs: NixAttrs = {}; const attrs: NixAttrs = new Map();
if (info.path) { if (info.path) {
attrs.path = true; attrs.set("path", true);
} }
if (info.allOutputs) { if (info.allOutputs) {
attrs.allOutputs = true; attrs.set("allOutputs", true);
} }
if (info.outputs.length > 0) { if (info.outputs.length > 0) {
attrs.outputs = info.outputs; attrs.set("outputs", info.outputs);
} }
result[path] = attrs; result.set(path, attrs);
} }
return result; return result;
@@ -154,22 +154,22 @@ export const appendContext =
const ctxAttrs = forceAttrs(ctxValue); const ctxAttrs = forceAttrs(ctxValue);
const newContext: NixStringContext = new Set(existingContext); 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/")) { if (!path.startsWith("/nix/store/")) {
throw new Error(`context key '${path}' is not a store path`); 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) { if (info.has("path")) {
const pathVal = force(info.path); const pathVal = force(info.get("path") as NixValue);
if (pathVal === true) { if (pathVal === true) {
newContext.add(path); newContext.add(path);
} }
} }
if ("allOutputs" in info) { if (info.has("allOutputs")) {
const allOutputs = force(info.allOutputs); const allOutputs = force(info.get("allOutputs") as NixValue);
if (allOutputs === true) { if (allOutputs === true) {
if (!path.endsWith(".drv")) { if (!path.endsWith(".drv")) {
throw new Error( throw new Error(
@@ -180,8 +180,8 @@ export const appendContext =
} }
} }
if ("outputs" in info) { if (info.has("outputs")) {
const outputs = forceList(info.outputs); const outputs = forceList(info.get("outputs") as NixValue);
if (outputs.length > 0 && !path.endsWith(".drv")) { if (outputs.length > 0 && !path.endsWith(".drv")) {
throw new Error( throw new Error(
`tried to add derivation output context of ${path}, which is not a derivation, to a string`, `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)) { if (typeof v === "object" && v !== null && !Array.isArray(v)) {
// First, try the __toString method if present if (v instanceof Map) {
// This allows custom types to define their own string representation if (v.has("__toString")) {
if ("__toString" in v) { const toStringMethod = forceFunction(v.get("__toString") as NixValue);
// Force the method in case it's a thunk const result = force(toStringMethod(v));
const toStringMethod = forceFunction(v.__toString); return coerceToString(result, mode, copyToStore, outContext);
const result = force(toStringMethod(v)); }
// Recursively coerceToString
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) {
// If no __toString, try outPath (used for derivations and store paths) const drvPathValue = force(v.get("drvPath") as NixValue);
// This allows derivation objects like { outPath = "/nix/store/..."; } to be coerced const drvPathStr = isStringWithContext(drvPathValue)
if ("outPath" in v) { ? drvPathValue.value
// Recursively coerce the outPath value : typeof drvPathValue === "string"
const outPath = coerceToString(v.outPath, mode, copyToStore, outContext); ? drvPathValue
if ("type" in v && v.type === "derivation" && "drvPath" in v && outContext) { : null;
const drvPathValue = force(v.drvPath); if (drvPathStr) {
const drvPathStr = isStringWithContext(drvPathValue) const outputName = v.has("outputName") ? String(force(v.get("outputName") as NixValue)) : "out";
? drvPathValue.value addBuiltContext(outContext, drvPathStr, outputName);
: typeof drvPathValue === "string" }
? drvPathValue }
: null; return outPath;
if (drvPathStr) {
const outputName = "outputName" in v ? String(force(v.outputName)) : "out";
addBuiltContext(outContext, drvPathStr, outputName);
}
} }
return outPath;
} }
// Attribute sets without __toString or outPath cannot be coerced
throw new TypeError(`cannot coerce ${typeOf(v)} to a string`); 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)) { if (isPath(forced)) {
return forced.value; return forced.value;
} }
if (isAttrs(forced) && Object.hasOwn(forced, "__toString")) { if (isAttrs(forced) && forced.has("__toString")) {
const toStringFunc = forceFunction(forced.__toString); const toStringFunc = forceFunction(forced.get("__toString") as NixValue);
return coerceToPath(toStringFunc(forced), outContext); return coerceToPath(toStringFunc(forced), outContext);
} }
@@ -339,30 +333,33 @@ export const nixValueToJson = (
return v.map((item) => nixValueToJson(item, strict, outContext, copyToStore, seen)); return v.map((item) => nixValueToJson(item, strict, outContext, copyToStore, seen));
} }
// NixAttrs if (v instanceof Map) {
if ("__toString" in v && typeof force(v.__toString) === "function") { if (v.has("__toString") && typeof force(v.get("__toString") as NixValue) === "function") {
const toStringMethod = force(v.__toString) as (self: typeof v) => NixValue; const toStringMethod = force(v.get("__toString") as NixValue) as (self: typeof v) => NixValue;
const result = force(toStringMethod(v)); const result = force(toStringMethod(v));
if (typeof result === "string") { if (typeof result === "string") {
return result; return result;
}
if (isStringWithContext(result)) {
for (const elem of result.context) {
outContext.add(elem);
} }
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) { throw new Error(`cannot convert ${typeof v} to JSON`);
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;
}; };

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,4 @@
import { import {
HAS_CONTEXT,
isNixPath, isNixPath,
isStringWithContext, isStringWithContext,
type NixAttrs, type NixAttrs,
@@ -19,10 +18,7 @@ export const isNixString = (v: NixStrictValue): v is NixString => {
}; };
export const isAttrs = (e: NixStrictValue): e is NixAttrs => { export const isAttrs = (e: NixStrictValue): e is NixAttrs => {
const val = e; return e instanceof Map;
return (
typeof val === "object" && !Array.isArray(val) && val !== null && !(HAS_CONTEXT in val) && !isPath(val)
);
}; };
export const isBool = (e: NixStrictValue): e is NixBool => typeof e === "boolean"; 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 (isNixString(e)) return "string";
if (isNixPath(e)) return "path"; if (isNixPath(e)) return "path";
if (Array.isArray(e)) return "list"; if (Array.isArray(e)) return "list";
if (typeof e === "object") return "set"; if (e instanceof Map) return "set";
if (typeof e === "function") return "lambda"; if (typeof e === "function") return "lambda";
throw new TypeError(`Unknown Nix type: ${typeof e}`); 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 { mkPath } from "./path";
import { isStringWithContext, mkStringWithContext, type NixStringContext } from "./string-context"; import { isStringWithContext, mkStringWithContext, type NixStringContext } from "./string-context";
import { force } from "./thunk"; 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 type { NixAttrs, NixBool, NixPath, NixString, NixValue } from "./types";
import { CatchableError, isNixPath } 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)) { for (const attr of attrpath.slice(0, -1)) {
const key = forceStringValue(attr); const key = forceStringValue(attr);
if (!(key in attrs)) { if (!attrs.has(key)) {
throw new Error(`Attribute '${key}' not found`); throw new Error(`Attribute '${key}' not found`);
} }
const cur = forceAttrs(attrs[forceStringValue(attr)]); const cur = forceAttrs(attrs.get(key) as NixValue);
attrs = cur; attrs = cur;
} }
const last = forceStringValue(attrpath[attrpath.length - 1]); const last = forceStringValue(attrpath[attrpath.length - 1]);
if (!(last in attrs)) { if (!attrs.has(last)) {
throw new Error(`Attribute '${last}' not found`); throw new Error(`Attribute '${last}' not found`);
} }
return attrs[last]; return attrs.get(last) as NixValue;
} }
export const selectWithDefault = ( export const selectWithDefault = (
@@ -218,10 +218,10 @@ function selectWithDefaultImpl(obj: NixValue, attrpath: NixValue[], defaultVal:
for (const attr of attrpath.slice(0, -1)) { for (const attr of attrpath.slice(0, -1)) {
const key = forceStringValue(attr); const key = forceStringValue(attr);
if (!(key in attrs)) { if (!attrs.has(key)) {
return defaultVal; return defaultVal;
} }
const cur = force(attrs[key]); const cur = force(attrs.get(key) as NixValue);
if (!isAttrs(cur)) { if (!isAttrs(cur)) {
return defaultVal; return defaultVal;
} }
@@ -229,8 +229,8 @@ function selectWithDefaultImpl(obj: NixValue, attrpath: NixValue[], defaultVal:
} }
const last = forceStringValue(attrpath[attrpath.length - 1]); const last = forceStringValue(attrpath[attrpath.length - 1]);
if (last in attrs) { if (attrs.has(last)) {
return attrs[last]; return attrs.get(last) as NixValue;
} }
return defaultVal; return defaultVal;
} }
@@ -243,14 +243,18 @@ export const hasAttr = (obj: NixValue, attrpath: NixValue[]): NixBool => {
let attrs = forced; let attrs = forced;
for (const attr of attrpath.slice(0, -1)) { 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)) { if (!isAttrs(cur)) {
return false; return false;
} }
attrs = cur; 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 => { 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); forcedFunc.args?.check(arg);
return forcedFunc(arg); return forcedFunc(arg);
} }
if ( if (forcedFunc instanceof Map && forcedFunc.has("__functor")) {
typeof forcedFunc === "object" && const functor = forceFunction(forcedFunc.get("__functor") as NixValue);
!Array.isArray(forcedFunc) &&
forcedFunc !== null &&
"__functor" in forcedFunc
) {
const functor = forceFunction(forcedFunc.__functor);
return call(functor(forcedFunc), arg); return call(functor(forcedFunc), 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(forcedFunc)}`);
@@ -307,7 +306,7 @@ export const ifFunc = (cond: NixValue, consq: NixValue, alter: NixValue) => {
}; };
export const mkPos = (span: string): NixAttrs => { 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 { interface WithScope {
@@ -319,8 +318,8 @@ export const lookupWith = (name: string, withScope: WithScope | null): NixValue
let current = withScope; let current = withScope;
while (current !== null) { while (current !== null) {
const attrs = forceAttrs(current.env); const attrs = forceAttrs(current.env);
if (name in attrs) { if (attrs.has(name)) {
return attrs[name]; return attrs.get(name) as NixValue;
} }
current = current.last; current = current.last;
} }

View File

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

View File

@@ -1,6 +1,6 @@
import { isAttrs, isList } from "./builtins/type-check"; import { isAttrs, isList } from "./builtins/type-check";
import { HAS_CONTEXT } from "./string-context"; 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"; import { IS_PATH } from "./types";
export const IS_THUNK = Symbol("is_thunk"); 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 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. * 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)); return forced.map((item) => forceDeep(item, seen));
} }
if (typeof forced === "object") { if (forced instanceof Map) {
const result: Record<string, NixValue> = {}; const result: NixAttrs = new Map();
for (const [key, val] of Object.entries(forced)) { for (const [key, val] of forced) {
result[key] = forceDeep(val, seen); result.set(key, forceDeep(val, seen));
} }
return result; return result;
} }
@@ -180,10 +180,10 @@ export const forceShallow = (value: NixValue): NixStrictValue => {
} }
if (isAttrs(forced)) { if (isAttrs(forced)) {
const result: Record<string, NixValue> = {}; const result: NixAttrs = new Map();
for (const [key, val] of Object.entries(forced)) { for (const [key, val] of forced) {
const forcedVal = force(val); const forcedVal = force(val as NixValue);
result[key] = forcedVal === forced ? CYCLE_MARKER : forcedVal; result.set(key, forcedVal === forced ? CYCLE_MARKER : forcedVal);
} }
return result; return result;
} }

View File

@@ -26,9 +26,9 @@ export const forceFunction = (value: NixValue): NixFunction => {
if (isFunction(forced)) { if (isFunction(forced)) {
return 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 functorSet = forced as NixAttrs;
const functor = forceFunction(functorSet.__functor); const functor = forceFunction(functorSet.get("__functor") as NixValue);
return (arg: NixValue) => forceFunction(functor(functorSet))(arg); return (arg: NixValue) => forceFunction(functor(functorSet))(arg);
} }
throw new TypeError(`Expected function, got ${typeOf(forced)}`); throw new TypeError(`Expected function, got ${typeOf(forced)}`);

View File

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

View File

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

View File

@@ -12,6 +12,8 @@ use crate::ir::{
Arg, ArgId, Bool, Builtin, ExprId, Ir, Null, ReplBinding, ScopedImportBinding, SymId, Thunk, Arg, ArgId, Bool, Builtin, ExprId, Ir, Null, ReplBinding, ScopedImportBinding, SymId, Thunk,
ToIr as _, WithLookup, ToIr as _, WithLookup,
}; };
#[cfg(feature = "inspector")]
use crate::runtime::inspector::InspectorServer;
use crate::runtime::{Runtime, RuntimeContext}; use crate::runtime::{Runtime, RuntimeContext};
use crate::store::{DaemonStore, Store, StoreConfig}; use crate::store::{DaemonStore, Store, StoreConfig};
use crate::value::{Symbol, Value}; use crate::value::{Symbol, Value};
@@ -48,7 +50,7 @@ pub struct Context {
ctx: Ctx, ctx: Ctx,
runtime: Runtime<Ctx>, runtime: Runtime<Ctx>,
#[cfg(feature = "inspector")] #[cfg(feature = "inspector")]
_inspector_server: Option<crate::runtime::inspector::InspectorServer>, _inspector_server: Option<InspectorServer>,
} }
macro_rules! eval { macro_rules! eval {
@@ -121,7 +123,7 @@ impl Context {
let code = self.ctx.compile(source, None)?; let code = self.ctx.compile(source, None)?;
self.runtime.eval( self.runtime.eval(
format!( format!(
"Nix.builtins.derivation = {};Nix.builtins.storeDir=\"{}\"", "Nix.builtins.set('derivation',({}));Nix.builtins.set('storeDir','{}')",
code, code,
self.get_store_dir() self.get_store_dir()
), ),

View File

@@ -1,7 +1,8 @@
use std::path::PathBuf;
use std::process::exit; use std::process::exit;
use anyhow::Result; use anyhow::Result;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand, Args};
use hashbrown::HashSet; use hashbrown::HashSet;
use nix_js::context::Context; use nix_js::context::Context;
use nix_js::error::Source; use nix_js::error::Source;
@@ -25,10 +26,22 @@ struct Cli {
#[derive(Subcommand)] #[derive(Subcommand)]
enum Command { enum Command {
Eval { expr: String }, Eval {
#[clap(flatten)]
source: ExprSource
},
Repl, Repl,
} }
#[derive(Args)]
#[group(required = true, multiple = false)]
struct ExprSource {
#[clap(short, long)]
expr: Option<String>,
#[clap(short, long)]
file: Option<PathBuf>
}
fn create_context(#[cfg(feature = "inspector")] cli: &Cli) -> Result<Context> { fn create_context(#[cfg(feature = "inspector")] cli: &Cli) -> Result<Context> {
#[cfg(feature = "inspector")] #[cfg(feature = "inspector")]
{ {
@@ -50,9 +63,15 @@ fn create_context(#[cfg(feature = "inspector")] cli: &Cli) -> Result<Context> {
Ok(Context::new()?) Ok(Context::new()?)
} }
fn run_eval(context: &mut Context, expr: String) -> Result<()> { fn run_eval(context: &mut Context, src: ExprSource) -> Result<()> {
let src = Source::new_eval(expr)?; let src = if let Some(expr) = src.expr {
match context.eval(src) { Source::new_eval(expr)?
} else if let Some(file) = src.file {
Source::new_file(file)?
} else {
unreachable!()
};
match context.eval_shallow(src) {
Ok(value) => { Ok(value) => {
println!("{value}"); println!("{value}");
} }
@@ -131,7 +150,9 @@ fn main() -> Result<()> {
)?; )?;
match cli.command { match cli.command {
Command::Eval { expr } => run_eval(&mut context, expr), Command::Eval { source } => {
run_eval(&mut context, source)
}
Command::Repl => run_repl(&mut context), Command::Repl => run_repl(&mut context),
} }
} }

View File

@@ -364,6 +364,28 @@ fn to_value<'a>(
Value::Func 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 val.is_object() => {
if is_thunk(val, scope, is_thunk_symbol) { if is_thunk(val, scope, is_thunk_symbol) {
return Value::Thunk; return Value::Thunk;

View File

@@ -248,9 +248,9 @@ async fn server(
.boxed_local(); .boxed_local();
let json_version_response = json!({ let json_version_response = json!({
"Browser": name, "Browser": name,
"Protocol-Version": "1.3", "Protocol-Version": "1.3",
"V8-Version": deno_core::v8::VERSION_STRING, "V8-Version": deno_core::v8::VERSION_STRING,
}); });
// Create the server manually so it can use the Local Executor // Create the server manually so it can use the Local Executor
@@ -269,19 +269,20 @@ async fn server(
let mut accept = pin!(listener.accept()); let mut accept = pin!(listener.accept());
let stream = tokio::select! { let stream = tokio::select! {
accept_result = &mut accept => { accept_result =
match accept_result { &mut accept => {
Ok((s, _)) => s, match accept_result {
Err(err) => { Ok((s, _)) => s,
eprintln!("Failed to accept inspector connection: {:?}", err); Err(err) => {
continue; eprintln!("Failed to accept inspector connection: {:?}", err);
} continue;
} }
}, }
},
_ = &mut shutdown_rx => { _ = &mut shutdown_rx => {
break; break;
} }
}; };
let io = TokioIo::new(stream); let io = TokioIo::new(stream);
@@ -332,15 +333,15 @@ async fn server(
let mut shutdown_rx = pin!(shutdown_server_rx.recv()); let mut shutdown_rx = pin!(shutdown_server_rx.recv());
tokio::select! { tokio::select! {
result = conn.as_mut() => { result = conn.as_mut() => {
if let Err(err) = result { if let Err(err) = result {
eprintln!("Failed to serve connection: {:?}", err); eprintln!("Failed to serve connection: {:?}", err);
}
},
_ = &mut shutdown_rx => {
conn.as_mut().graceful_shutdown();
let _ = conn.await;
} }
},
_ = &mut shutdown_rx => {
conn.as_mut().graceful_shutdown();
let _ = conn.await;
}
} }
}); });
} }
@@ -348,9 +349,9 @@ async fn server(
.boxed_local(); .boxed_local();
tokio::select! { tokio::select! {
_ = register_inspector_handler => {}, _ = register_inspector_handler => {},
_ = deregister_inspector_handler => unreachable!(), _ = deregister_inspector_handler => unreachable!(),
_ = server_handler => {}, _ = server_handler => {},
} }
} }
@@ -392,31 +393,31 @@ async fn pump_websocket_messages(
) { ) {
'pump: loop { 'pump: loop {
tokio::select! { tokio::select! {
Some(msg) = outbound_rx.next() => { Some(msg) = outbound_rx.next() => {
let msg = Frame::text(msg.content.into_bytes().into()); let msg = Frame::text(msg.content.into_bytes().into());
let _ = websocket.write_frame(msg).await; let _ = websocket.write_frame(msg).await;
} }
Ok(msg) = websocket.read_frame() => { Ok(msg) = websocket.read_frame() => {
match msg.opcode { match msg.opcode {
OpCode::Text => { OpCode::Text => {
if let Ok(s) = String::from_utf8(msg.payload.to_vec()) { if let Ok(s) = String::from_utf8(msg.payload.to_vec()) {
let _ = inbound_tx.unbounded_send(s); let _ = inbound_tx.unbounded_send(s);
} }
} }
OpCode::Close => { OpCode::Close => {
// Users don't care if there was an error coming from debugger, // Users don't care if there was an error coming from debugger,
// just about the fact that debugger did disconnect. // just about the fact that debugger did disconnect.
eprintln!("Debugger session ended"); eprintln!("Debugger session ended");
break 'pump; break 'pump;
} }
_ => { _ => {
// Ignore other messages. // Ignore other messages.
} }
}
}
else => {
break 'pump;
} }
}
else => {
break 'pump;
}
} }
} }
} }
@@ -456,14 +457,14 @@ impl InspectorInfo {
let host_listen = format!("{}", self.host); let host_listen = format!("{}", self.host);
let host = host.as_ref().unwrap_or(&host_listen); let host = host.as_ref().unwrap_or(&host_listen);
json!({ json!({
"description": "nix-js", "description": "nix-js",
"devtoolsFrontendUrl": self.get_frontend_url(host), "devtoolsFrontendUrl": self.get_frontend_url(host),
"faviconUrl": "https://deno.land/favicon.ico", "faviconUrl": "https://deno.land/favicon.ico",
"id": self.uuid.to_string(), "id": self.uuid.to_string(),
"title": self.get_title(), "title": self.get_title(),
"type": "node", "type": "node",
"url": self.url.to_string(), "url": self.url.to_string(),
"webSocketDebuggerUrl": self.get_websocket_debugger_url(host), "webSocketDebuggerUrl": self.get_websocket_debugger_url(host),
}) })
} }

View File

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

View File

@@ -540,6 +540,7 @@ impl NixDaemonClient {
} }
/// Query information about a store path /// Query information about a store path
#[allow(dead_code)]
pub async fn query_path_info(&mut self, path: &str) -> IoResult<Option<UnkeyedValidPathInfo>> { pub async fn query_path_info(&mut self, path: &str) -> IoResult<Option<UnkeyedValidPathInfo>> {
let store_path = StorePath::<String>::from_absolute_path(path.as_bytes()) let store_path = StorePath::<String>::from_absolute_path(path.as_bytes())
.map_err(|e| IoError::new(IoErrorKind::InvalidInput, e.to_string()))?; .map_err(|e| IoError::new(IoErrorKind::InvalidInput, e.to_string()))?;
@@ -613,6 +614,7 @@ impl NixDaemonClient {
} }
/// Query which paths are valid /// Query which paths are valid
#[allow(dead_code)]
pub async fn query_valid_paths(&mut self, paths: Vec<String>) -> IoResult<Vec<String>> { pub async fn query_valid_paths(&mut self, paths: Vec<String>) -> IoResult<Vec<String>> {
let store_paths: IoResult<Vec<StorePath<String>>> = paths let store_paths: IoResult<Vec<StorePath<String>>> = paths
.iter() .iter()
@@ -717,6 +719,7 @@ impl NixDaemonConnection {
} }
/// Query information about a store path /// Query information about a store path
#[allow(dead_code)]
pub async fn query_path_info(&self, path: &str) -> IoResult<Option<UnkeyedValidPathInfo>> { pub async fn query_path_info(&self, path: &str) -> IoResult<Option<UnkeyedValidPathInfo>> {
let mut client = self.client.lock().await; let mut client = self.client.lock().await;
client.query_path_info(path).await client.query_path_info(path).await
@@ -729,6 +732,7 @@ impl NixDaemonConnection {
} }
/// Query which paths are valid /// Query which paths are valid
#[allow(dead_code)]
pub async fn query_valid_paths(&self, paths: Vec<String>) -> IoResult<Vec<String>> { pub async fn query_valid_paths(&self, paths: Vec<String>) -> IoResult<Vec<String>> {
let mut client = self.client.lock().await; let mut client = self.client.lock().await;
client.query_valid_paths(paths).await client.query_valid_paths(paths).await