chore(runtime-ts): fix linter errors

This commit is contained in:
2026-02-15 10:46:22 +08:00
parent cf4dd6c379
commit 2f2c690023
29 changed files with 527 additions and 917 deletions

View File

@@ -1,5 +1,5 @@
{ {
"$schema": "https://biomejs.dev/schemas/2.3.9/schema.json", "$schema": "https://biomejs.dev/schemas/2.3.14/schema.json",
"vcs": { "vcs": {
"enabled": true, "enabled": true,
"clientKind": "git", "clientKind": "git",
@@ -20,10 +20,37 @@
"linter": { "linter": {
"rules": { "rules": {
"style": { "style": {
"useNamingConvention": "warn" "useNamingConvention": {
"level": "warn",
"options": {
"strictCase": false,
"conventions": [
{
"selector": { "kind": "objectLiteralProperty" },
"formats": ["camelCase", "PascalCase", "CONSTANT_CASE"]
},
{
"selector": { "kind": "typeProperty" },
"formats": ["camelCase", "snake_case"]
}
]
}
}
} }
} }
}, },
"overrides": [
{
"includes": ["**/global.d.ts"],
"linter": {
"rules": {
"style": {
"useNamingConvention": "off"
}
}
}
}
],
"javascript": { "javascript": {
"formatter": { "formatter": {
"arrowParentheses": "always", "arrowParentheses": "always",

View File

@@ -1,30 +1,26 @@
/**
* Arithmetic builtin functions
*/
import type { NixBool, NixInt, NixNumber, NixValue } from "../types";
import { forceNumeric, coerceNumeric, forceInt } from "../type-assert";
import { op } from "../operators"; import { op } from "../operators";
import { coerceNumeric, forceInt, forceNumeric } from "../type-assert";
import type { NixBool, NixInt, NixNumber, NixValue } from "../types";
export const add = export const add =
(a: NixValue) => (a: NixValue) =>
(b: NixValue): bigint | number => { (b: NixValue): bigint | number => {
const [av, bv] = coerceNumeric(forceNumeric(a), forceNumeric(b)); const [av, bv] = coerceNumeric(forceNumeric(a), forceNumeric(b));
return (av as any) + (bv as any); return (av as never) + (bv as never);
}; };
export const sub = export const sub =
(a: NixValue) => (a: NixValue) =>
(b: NixValue): bigint | number => { (b: NixValue): bigint | number => {
const [av, bv] = coerceNumeric(forceNumeric(a), forceNumeric(b)); const [av, bv] = coerceNumeric(forceNumeric(a), forceNumeric(b));
return (av as any) - (bv as any); return (av as never) - (bv as never);
}; };
export const mul = export const mul =
(a: NixValue) => (a: NixValue) =>
(b: NixValue): bigint | number => { (b: NixValue): bigint | number => {
const [av, bv] = coerceNumeric(forceNumeric(a), forceNumeric(b)); const [av, bv] = coerceNumeric(forceNumeric(a), forceNumeric(b));
return (av as any) * (bv as any); return (av as never) * (bv as never);
}; };
export const div = export const div =
@@ -36,10 +32,9 @@ export const div =
throw new RangeError("Division by zero"); throw new RangeError("Division by zero");
} }
return (av as any) / (bv as any); return (av as never) / (bv as never);
}; };
// Bitwise operations - only for integers
export const bitAnd = export const bitAnd =
(a: NixValue) => (a: NixValue) =>
(b: NixValue): NixInt => { (b: NixValue): NixInt => {

View File

@@ -1,10 +1,6 @@
/**
* Attribute set operation builtin functions
*/
import type { NixValue, NixAttrs, NixList } from "../types";
import { forceAttrs, forceStringValue, forceFunction, forceList } from "../type-assert";
import { createThunk } from "../thunk"; import { createThunk } from "../thunk";
import { forceAttrs, forceFunction, forceList, forceStringValue } from "../type-assert";
import type { NixAttrs, NixList, NixValue } from "../types";
export const attrNames = (set: NixValue): string[] => Object.keys(forceAttrs(set)).sort(); export const attrNames = (set: NixValue): string[] => Object.keys(forceAttrs(set)).sort();
@@ -46,23 +42,23 @@ export const mapAttrs =
export const removeAttrs = export const removeAttrs =
(attrs: NixValue) => (attrs: NixValue) =>
(list: NixValue): NixAttrs => { (list: NixValue): NixAttrs => {
const new_attrs: NixAttrs = {}; const newAttrs: NixAttrs = {};
const forced_attrs = forceAttrs(attrs); const forcedAttrs = forceAttrs(attrs);
const forced_list = forceList(list); const forcedList = forceList(list);
const keys_to_remove = new Set(forced_list.map(forceStringValue)); const keysToRemove = new Set(forcedList.map(forceStringValue));
for (const key in forced_attrs) { for (const key in forcedAttrs) {
if (!keys_to_remove.has(key)) { if (!keysToRemove.has(key)) {
new_attrs[key] = forced_attrs[key]; newAttrs[key] = forcedAttrs[key];
} }
} }
return new_attrs; return newAttrs;
}; };
export const listToAttrs = (e: NixValue): NixAttrs => { export const listToAttrs = (e: NixValue): NixAttrs => {
const attrs: NixAttrs = {}; const attrs: NixAttrs = {};
const forced_e = [...forceList(e)].reverse(); const forcedE = [...forceList(e)].reverse();
for (const obj of forced_e) { for (const obj of forcedE) {
const item = forceAttrs(obj); const item = forceAttrs(obj);
attrs[forceStringValue(item.name)] = item.value; attrs[forceStringValue(item.name)] = item.value;
} }
@@ -96,10 +92,10 @@ export const groupBy =
(f: NixValue) => (f: NixValue) =>
(list: NixValue): NixAttrs => { (list: NixValue): NixAttrs => {
const attrs: NixAttrs = {}; const attrs: NixAttrs = {};
const forced_f = forceFunction(f); const forcedF = forceFunction(f);
const forced_list = forceList(list); const forcedList = forceList(list);
for (const elem of forced_list) { for (const elem of forcedList) {
const key = forceStringValue(forced_f(elem)); const key = forceStringValue(forcedF(elem));
if (!attrs[key]) attrs[key] = []; if (!attrs[key]) attrs[key] = [];
(attrs[key] as NixList).push(elem); (attrs[key] as NixList).push(elem);
} }
@@ -111,28 +107,22 @@ export const zipAttrsWith =
(list: NixValue): NixValue => { (list: NixValue): NixValue => {
const listForced = forceList(list); const listForced = forceList(list);
// Map to collect all values for each attribute name
const attrMap = new Map<string, NixValue[]>(); const attrMap = new Map<string, NixValue[]>();
// Iterate through each attribute set in the list
for (const item of listForced) { for (const item of listForced) {
const attrs = forceAttrs(item); const attrs = forceAttrs(item);
// Collect all attribute names and their values
for (const [key, value] of Object.entries(attrs)) { for (const [key, value] of Object.entries(attrs)) {
if (!attrMap.has(key)) { if (!attrMap.has(key)) {
attrMap.set(key, []); attrMap.set(key, []);
} }
attrMap.get(key)!.push(value); (attrMap.get(key) as NixValue[]).push(value);
} }
} }
// Build the result attribute set
const result: Record<string, NixValue> = {}; const result: Record<string, NixValue> = {};
for (const [name, values] of attrMap.entries()) { for (const [name, values] of attrMap.entries()) {
// Apply f to name and values list
// f is curried: f name values
result[name] = createThunk(() => forceFunction(forceFunction(f)(name))(values)); result[name] = createThunk(() => forceFunction(forceFunction(f)(name))(values));
} }
@@ -149,7 +139,9 @@ export const unsafeGetAttrPos =
return null; return null;
} }
const positions = (attrs as any)[Nix.ATTR_POSITIONS]; const positions = (attrs as NixAttrs & Record<symbol, unknown>)[Nix.ATTR_POSITIONS] as
| Record<string, string>
| undefined;
if (!positions || !(name in positions)) { if (!positions || !(name in positions)) {
return null; return null;
} }

View File

@@ -1,15 +1,15 @@
import type { NixValue, NixAttrs, NixString } from "../types";
import { isStringWithContext } from "../types";
import { forceString, forceAttrs, forceList, forceStringValue } from "../type-assert";
import { force } from "../thunk";
import { import {
type NixStringContext,
getStringValue,
getStringContext,
mkStringWithContext,
decodeContextElem, decodeContextElem,
getStringContext,
getStringValue,
mkStringWithContext,
type NixStringContext,
parseContextToInfoMap, parseContextToInfoMap,
} from "../string-context"; } from "../string-context";
import { force } from "../thunk";
import { forceAttrs, forceList, forceString, forceStringValue } from "../type-assert";
import type { NixAttrs, NixString, NixValue } from "../types";
import { isStringWithContext } from "../types";
/** /**
* builtins.hasContext - Check if string has context * builtins.hasContext - Check if string has context
@@ -118,13 +118,13 @@ export const getContext = (value: NixValue): NixAttrs => {
for (const [path, info] of infoMap) { for (const [path, info] of infoMap) {
const attrs: NixAttrs = {}; const attrs: NixAttrs = {};
if (info.path) { if (info.path) {
attrs["path"] = true; attrs.path = true;
} }
if (info.allOutputs) { if (info.allOutputs) {
attrs["allOutputs"] = true; attrs.allOutputs = true;
} }
if (info.outputs.length > 0) { if (info.outputs.length > 0) {
attrs["outputs"] = info.outputs; attrs.outputs = info.outputs;
} }
result[path] = attrs; result[path] = attrs;
} }
@@ -162,14 +162,14 @@ export const appendContext =
const info = forceAttrs(infoVal); const info = forceAttrs(infoVal);
if ("path" in info) { if ("path" in info) {
const pathVal = force(info["path"]); const pathVal = force(info.path);
if (pathVal === true) { if (pathVal === true) {
newContext.add(path); newContext.add(path);
} }
} }
if ("allOutputs" in info) { if ("allOutputs" in info) {
const allOutputs = force(info["allOutputs"]); const allOutputs = force(info.allOutputs);
if (allOutputs === true) { if (allOutputs === true) {
if (!path.endsWith(".drv")) { if (!path.endsWith(".drv")) {
throw new Error( throw new Error(
@@ -181,7 +181,7 @@ export const appendContext =
} }
if ("outputs" in info) { if ("outputs" in info) {
const outputs = forceList(info["outputs"]); const outputs = forceList(info.outputs);
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

@@ -2,12 +2,11 @@
* Conversion and serialization builtin functions * Conversion and serialization builtin functions
*/ */
import type { NixString, NixValue } from "../types"; import { addBuiltContext, mkStringWithContext, type NixStringContext } from "../string-context";
import { isStringWithContext, isNixPath } from "../types"; import { force, isThunk } from "../thunk";
import { force } from "../thunk";
import { type NixStringContext, mkStringWithContext, addBuiltContext } from "../string-context";
import { forceFunction, forceStringNoCtx } from "../type-assert"; import { forceFunction, forceStringNoCtx } from "../type-assert";
import { nixValueToJson } from "../conversion"; import type { NixString, NixValue } from "../types";
import { HAS_CONTEXT, IS_PATH, isNixPath, isStringWithContext } from "../types";
import { isAttrs, isPath, typeOf } from "./type-check"; import { isAttrs, isPath, typeOf } from "./type-check";
export const fromJSON = (e: NixValue): NixValue => { export const fromJSON = (e: NixValue): NixValue => {
@@ -16,12 +15,12 @@ export const fromJSON = (e: NixValue): NixValue => {
throw new TypeError(`builtins.fromJSON: expected a string, got ${typeOf(str)}`); throw new TypeError(`builtins.fromJSON: expected a string, got ${typeOf(str)}`);
} }
const jsonStr = isStringWithContext(str) ? str.value : str; const jsonStr = isStringWithContext(str) ? str.value : str;
return Deno.core.ops.op_from_json(jsonStr); return Deno.core.ops.op_from_json(jsonStr) as NixValue;
}; };
export const fromTOML = (e: NixValue): NixValue => { export const fromTOML = (e: NixValue): NixValue => {
const toml = forceStringNoCtx(e); const toml = forceStringNoCtx(e);
return Deno.core.ops.op_from_toml(toml); return Deno.core.ops.op_from_toml(toml) as NixValue;
}; };
export const toJSON = (e: NixValue): NixString => { export const toJSON = (e: NixValue): NixString => {
@@ -33,7 +32,7 @@ export const toJSON = (e: NixValue): NixString => {
return mkStringWithContext(string, context); return mkStringWithContext(string, context);
}; };
export const toXML = (e: NixValue): never => { export const toXML = (_e: NixValue): never => {
throw new Error("Not implemented: toXML"); throw new Error("Not implemented: toXML");
}; };
@@ -290,3 +289,82 @@ export const coerceToPath = (value: NixValue, outContext?: NixStringContext): st
export const toStringFunc = (value: NixValue): NixString => { export const toStringFunc = (value: NixValue): NixString => {
return coerceToStringWithContext(value, StringCoercionMode.ToString, false); return coerceToStringWithContext(value, StringCoercionMode.ToString, false);
}; };
export const nixValueToJson = (
value: NixValue,
strict: boolean,
outContext: NixStringContext,
copyToStore: boolean,
seen: Set<NixValue> = new Set(),
): unknown => {
const v = strict ? force(value) : value;
if (isThunk(v) || typeof v === "function")
throw new Error(`cannot convert ${isThunk(v) ? "thunk" : "lambda"} to JSON`);
if (v === null) return null;
if (typeof v === "bigint") {
const num = Number(v);
if (v > Number.MAX_SAFE_INTEGER || v < Number.MIN_SAFE_INTEGER) {
console.warn(`integer ${v} exceeds safe range, precision may be lost`);
}
return num;
}
if (typeof v === "number") return v;
if (typeof v === "boolean") return v;
if (typeof v === "string") return v;
if (typeof v === "object" && HAS_CONTEXT in v) {
for (const elem of v.context) {
outContext.add(elem);
}
return v.value;
}
if (typeof v === "object" && IS_PATH in v) {
if (copyToStore) {
const storePath = Deno.core.ops.op_copy_path_to_store(v.value);
outContext.add(storePath);
return storePath;
} else {
return v.value;
}
}
// FIXME: is this check necessary?
// if (seen.has(v)) {
// throw new Error("cycle detected in toJSON");
// } else {
// seen.add(v)
// }
if (Array.isArray(v)) {
return v.map((item) => nixValueToJson(item, strict, outContext, copyToStore, seen));
}
// NixAttrs
if ("__toString" in v && typeof force(v.__toString) === "function") {
const toStringMethod = force(v.__toString) as (self: typeof v) => NixValue;
const result = force(toStringMethod(v));
if (typeof result === "string") {
return result;
}
if (isStringWithContext(result)) {
if (outContext) {
for (const elem of result.context) {
outContext.add(elem);
}
}
return result.value;
}
return nixValueToJson(result, strict, outContext, copyToStore, seen);
}
if ("outPath" in v) {
return nixValueToJson(v.outPath, strict, outContext, copyToStore, seen);
}
const result: Record<string, unknown> = {};
const keys = Object.keys(v).sort();
for (const key of keys) {
result[key] = nixValueToJson(v[key], strict, outContext, copyToStore, seen);
}
return result;
};

View File

@@ -1,40 +1,123 @@
import type { NixValue, NixAttrs } from "../types";
import { forceStringValue, forceList, forceStringNoCtx } from "../type-assert";
import { force } from "../thunk";
import { import {
type DerivationData,
type OutputInfo,
generateAterm,
generateAtermModulo,
} from "../derivation-helpers";
import { coerceToString, StringCoercionMode } from "./conversion";
import {
type NixStringContext,
extractInputDrvsAndSrcs,
isStringWithContext,
mkStringWithContext,
addDrvDeepContext,
addBuiltContext, addBuiltContext,
addDrvDeepContext,
extractInputDrvsAndSrcs,
mkStringWithContext,
type NixStringContext,
} from "../string-context"; } from "../string-context";
import { nixValueToJson } from "../conversion"; import { force } from "../thunk";
import { isNixPath } from "../types"; import { forceAttrs, forceList, forceStringNoCtx, forceStringValue } from "../type-assert";
import type { NixAttrs, NixValue } from "../types";
import { coerceToString, nixValueToJson, StringCoercionMode } from "./conversion";
const drvHashCache = new Map<string, string>(); const drvHashCache = new Map<string, string>();
const forceAttrs = (value: NixValue): NixAttrs => { export interface OutputInfo {
const forced = force(value); path: string;
if ( hashAlgo: string;
typeof forced !== "object" || hash: string;
forced === null || }
Array.isArray(forced) ||
isStringWithContext(forced) || export interface DerivationData {
isNixPath(forced) name: string;
) { outputs: Map<string, OutputInfo>;
throw new TypeError(`Expected attribute set for derivation, got ${typeof forced}`); inputDrvs: Map<string, Set<string>>;
inputSrcs: Set<string>;
platform: string;
builder: string;
args: string[];
env: Map<string, string>;
}
export const escapeString = (s: string): string => {
let result = "";
for (const char of s) {
switch (char) {
case '"':
result += '\\"';
break;
case "\\":
result += "\\\\";
break;
case "\n":
result += "\\n";
break;
case "\r":
result += "\\r";
break;
case "\t":
result += "\\t";
break;
default:
result += char;
}
} }
return forced; return `"${result}"`;
}; };
const quoteString = (s: string): string => `"${s}"`;
const cmpByKey = <T>(a: [string, T], b: [string, T]): number => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
export const generateAterm = (drv: DerivationData): string => {
const outputEntries: string[] = [];
const sortedOutputs = Array.from(drv.outputs.entries()).sort(cmpByKey);
for (const [name, info] of sortedOutputs) {
outputEntries.push(
`(${quoteString(name)},${quoteString(info.path)},${quoteString(info.hashAlgo)},${quoteString(info.hash)})`,
);
}
const outputs = outputEntries.join(",");
const inputDrvEntries: string[] = [];
const sortedInputDrvs = Array.from(drv.inputDrvs.entries()).sort(cmpByKey);
for (const [drvPath, outputs] of sortedInputDrvs) {
const sortedOuts = Array.from(outputs).sort();
const outList = `[${sortedOuts.map(quoteString).join(",")}]`;
inputDrvEntries.push(`(${quoteString(drvPath)},${outList})`);
}
const inputDrvs = inputDrvEntries.join(",");
const sortedInputSrcs = Array.from(drv.inputSrcs).sort();
const inputSrcs = sortedInputSrcs.map(quoteString).join(",");
const args = drv.args.map(escapeString).join(",");
const envs = Array.from(drv.env.entries())
.sort(cmpByKey)
.map(([k, v]) => `(${escapeString(k)},${escapeString(v)})`);
return `Derive([${outputs}],[${inputDrvs}],[${inputSrcs}],${quoteString(drv.platform)},${escapeString(drv.builder)},[${args}],[${envs}])`;
};
export const generateAtermModulo = (drv: DerivationData, inputDrvHashes: Map<string, string>): string => {
const outputEntries: string[] = [];
const sortedOutputs = Array.from(drv.outputs.entries()).sort(cmpByKey);
for (const [name, info] of sortedOutputs) {
outputEntries.push(
`(${quoteString(name)},${quoteString(info.path)},${quoteString(info.hashAlgo)},${quoteString(info.hash)})`,
);
}
const outputs = outputEntries.join(",");
const inputDrvEntries: string[] = [];
const sortedInputDrvHashes = Array.from(inputDrvHashes.entries()).sort(cmpByKey);
for (const [drvHash, outputs] of sortedInputDrvHashes) {
const sortedOuts = outputs.split(",").sort();
const outList = `[${sortedOuts.map(quoteString).join(",")}]`;
inputDrvEntries.push(`(${quoteString(drvHash)},${outList})`);
}
const inputDrvs = inputDrvEntries.join(",");
const sortedInputSrcs = Array.from(drv.inputSrcs).sort();
const inputSrcs = sortedInputSrcs.map(quoteString).join(",");
const args = drv.args.map(escapeString).join(",");
const envs = Array.from(drv.env.entries())
.sort(cmpByKey)
.map(([k, v]) => `(${escapeString(k)},${escapeString(v)})`);
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 (!("name" in attrs)) {
throw new Error("derivation: missing required attribute 'name'"); throw new Error("derivation: missing required attribute 'name'");
@@ -109,10 +192,10 @@ const extractArgs = (attrs: NixAttrs, outContext: NixStringContext): string[] =>
const outputPathName = (drvName: string, output: string) => { const outputPathName = (drvName: string, output: string) => {
if (output === "out") { if (output === "out") {
return drvName return drvName;
} }
return `${drvName}-${output}` return `${drvName}-${output}`;
} };
const structuredAttrsExcludedKeys = new Set([ const structuredAttrsExcludedKeys = new Set([
"__structuredAttrs", "__structuredAttrs",
@@ -124,9 +207,9 @@ const structuredAttrsExcludedKeys = new Set([
const specialAttrs = new Set(["args", "__ignoreNulls", "__contentAddressed", "__impure"]); const specialAttrs = new Set(["args", "__ignoreNulls", "__contentAddressed", "__impure"]);
const sortedJsonStringify = (obj: Record<string, any>): string => { const sortedJsonStringify = (obj: Record<string, unknown>): string => {
const sortedKeys = Object.keys(obj).sort(); const sortedKeys = Object.keys(obj).sort();
const sortedObj: Record<string, any> = {}; const sortedObj: Record<string, unknown> = {};
for (const key of sortedKeys) { for (const key of sortedKeys) {
sortedObj[key] = obj[key]; sortedObj[key] = obj[key];
} }
@@ -143,7 +226,7 @@ const extractEnv = (
const env = new Map<string, string>(); const env = new Map<string, string>();
if (structuredAttrs) { if (structuredAttrs) {
const jsonAttrs: Record<string, any> = {}; const jsonAttrs: Record<string, unknown> = {};
for (const [key, value] of Object.entries(attrs)) { for (const [key, value] of Object.entries(attrs)) {
if (!structuredAttrsExcludedKeys.has(key)) { if (!structuredAttrsExcludedKeys.has(key)) {
const forcedValue = force(value); const forcedValue = force(value);
@@ -419,6 +502,6 @@ export const derivationStrict = (args: NixValue): NixAttrs => {
return result; return result;
}; };
export const derivation = (_: NixValue): NixAttrs => { export const derivationStub = (_: NixValue): NixAttrs => {
throw new Error("unreachable: placeholder derivation implementation called") throw new Error("unreachable: stub derivation implementation called");
}; };

View File

@@ -0,0 +1,17 @@
import type { NixValue } from "../types";
export const getFlake = (_attrs: NixValue): never => {
throw new Error("Not implemented: getFlake");
};
export const parseFlakeName = (_s: NixValue): never => {
throw new Error("Not implemented: parseFlakeName");
};
export const parseFlakeRef = (_s: NixValue): never => {
throw new Error("Not implemented: parseFlakeRef");
};
export const flakeRefToString = (_attrs: NixValue): never => {
throw new Error("Not implemented: flakeRefToString");
};

View File

@@ -1,11 +1,7 @@
/**
* Functional programming builtin functions
*/
import { CatchableError, type NixValue } from "../types";
import { force } from "../thunk";
import { coerceToString, StringCoercionMode } from "./conversion";
import { printValue } from "../print"; import { printValue } from "../print";
import { force } from "../thunk";
import { CatchableError, type NixValue } from "../types";
import { coerceToString, StringCoercionMode } from "./conversion";
import { isAttrs } from "./type-check"; import { isAttrs } from "./type-check";
export const seq = export const seq =

View File

@@ -0,0 +1,19 @@
import { forceStringNoCtx } from "../type-assert";
import type { NixValue } from "../types";
export const hashFile =
(type: NixValue) =>
(_p: NixValue): never => {
const _ty = forceStringNoCtx(type);
throw new Error("Not implemented: hashFile");
};
export const hashString =
(_type: NixValue) =>
(_p: NixValue): never => {
throw new Error("Not implemented: hashString");
};
export const convertHash = (_args: NixValue): never => {
throw new Error("Not implemented: convertHash");
};

View File

@@ -1,81 +1,50 @@
/** import { createThunk, force } from "../thunk";
* Main builtins export
* Combines all builtin function categories into the global `builtins` object
*/
// Import all builtin categories
import * as arithmetic from "./arithmetic";
import * as math from "./math";
import * as typeCheck from "./type-check";
import * as list from "./list";
import * as attrs from "./attrs";
import * as string from "./string";
import * as pathOps from "./path";
import * as functional from "./functional";
import * as io from "./io";
import * as conversion from "./conversion";
import * as misc from "./misc";
import * as derivation from "./derivation";
import type { NixValue } from "../types"; import type { NixValue } from "../types";
import { createThunk, force, isThunk } from "../thunk"; import * as arithmetic from "./arithmetic";
import { getTos } from "../helpers"; import * as attrs from "./attrs";
import * as conversion from "./conversion";
import * as derivation from "./derivation";
import * as flake from "./flake";
import * as functional from "./functional";
import * as hash from "./hash";
import * as io from "./io";
import * as list from "./list";
import * as math from "./math";
import * as misc from "./misc";
import * as pathOps from "./path";
import * as string from "./string";
import * as typeCheck from "./type-check";
/**
* Symbol used to mark functions as primops (primitive operations)
* This is similar to IS_THUNK but for builtin functions
*/
export const PRIMOP_METADATA = Symbol("primop_metadata"); export const PRIMOP_METADATA = Symbol("primop_metadata");
/**
* Metadata interface for primop functions
*/
export interface PrimopMetadata { export interface PrimopMetadata {
/** The name of the primop (e.g., "add", "map") */
name: string; name: string;
/** Total arity of the function (number of arguments it expects) */
arity: number; arity: number;
/** Number of arguments already applied (for partial applications) */
applied: number; applied: number;
} }
/**
* Mark a function as a primop with metadata
* For curried functions, this recursively marks each layer
*
* @param func - The function to mark
* @param name - Name of the primop
* @param arity - Total number of arguments expected
* @param applied - Number of arguments already applied (default: 0)
* @returns The marked function
*/
export const mkPrimop = ( export const mkPrimop = (
func: (...args: NixValue[]) => NixValue, func: (...args: NixValue[]) => NixValue,
name: string, name: string,
arity: number, arity: number,
applied: number = 0, applied: number = 0,
): Function => { ): ((...args: NixValue[]) => NixValue) => {
// Mark this function as a primop (func as unknown as Record<symbol, unknown>)[PRIMOP_METADATA] = {
(func as any)[PRIMOP_METADATA] = {
name, name,
arity, arity,
applied, applied,
} satisfies PrimopMetadata; } satisfies PrimopMetadata;
// If this is a curried function and not fully applied,
// wrap it to mark the next layer too
if (applied < arity - 1) { if (applied < arity - 1) {
const wrappedFunc = ((...args: NixValue[]) => { const wrappedFunc = ((...args: NixValue[]) => {
const result = func(...args); const result = func(...args);
// If result is a function, mark it as the next layer
if (typeof result === "function") { if (typeof result === "function") {
return mkPrimop(result, name, arity, applied + args.length); return mkPrimop(result, name, arity, applied + args.length);
} }
return result; return result;
}) as any; }) as (...args: NixValue[]) => NixValue;
// Copy the primop metadata to the wrapper (wrappedFunc as unknown as Record<symbol, unknown>)[PRIMOP_METADATA] = {
wrappedFunc[PRIMOP_METADATA] = {
name, name,
arity, arity,
applied, applied,
@@ -87,12 +56,9 @@ export const mkPrimop = (
return func; return func;
}; };
/** export const isPrimop = (
* Type guard to check if a value is a primop value: unknown,
* @param value - Value to check ): value is ((...args: never[]) => unknown) & { [PRIMOP_METADATA]: PrimopMetadata } => {
* @returns true if value is marked as a primop
*/
export const is_primop = (value: unknown): value is Function & { [PRIMOP_METADATA]: PrimopMetadata } => {
return ( return (
typeof value === "function" && typeof value === "function" &&
PRIMOP_METADATA in value && PRIMOP_METADATA in value &&
@@ -101,29 +67,14 @@ export const is_primop = (value: unknown): value is Function & { [PRIMOP_METADAT
); );
}; };
/** export const getPrimopMetadata = (func: unknown): PrimopMetadata | undefined => {
* Get primop metadata from a function if (isPrimop(func)) {
* @param func - Function to get metadata from
* @returns Metadata if function is a primop, undefined otherwise
*/
export const get_primop_metadata = (func: unknown): PrimopMetadata | undefined => {
if (is_primop(func)) {
return func[PRIMOP_METADATA]; return func[PRIMOP_METADATA];
} }
return undefined; return undefined;
}; };
/** export const builtins: Record<string, NixValue> = {
* The global builtins object
* Contains 80+ Nix builtin functions plus metadata
*
* All functions are curried for Nix semantics:
* - Single argument functions: (a) => result
* - Multi-argument functions: (a) => (b) => result
*
* All primop functions are marked with PRIMOP_METADATA symbol for runtime introspection
*/
export const builtins: any = {
add: mkPrimop(arithmetic.add, "add", 2), add: mkPrimop(arithmetic.add, "add", 2),
sub: mkPrimop(arithmetic.sub, "sub", 2), sub: mkPrimop(arithmetic.sub, "sub", 2),
mul: mkPrimop(arithmetic.mul, "mul", 2), mul: mkPrimop(arithmetic.mul, "mul", 2),
@@ -193,7 +144,7 @@ export const builtins: any = {
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: undefined as any, 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),
@@ -221,13 +172,19 @@ export const builtins: any = {
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),
hashString: mkPrimop(hash.hashString, "hashString", 2),
convertHash: mkPrimop(hash.convertHash, "convertHash", 2),
flakeRefToString: mkPrimop(flake.flakeRefToString, "flakeRefToString", 1),
getFlake: mkPrimop(flake.getFlake, "getFlake", 1),
parseFlakeName: mkPrimop(flake.parseFlakeName, "parseFlakeName", 1),
parseFlakeRef: mkPrimop(flake.parseFlakeRef, "parseFlakeRef", 1),
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),
hashFile: mkPrimop(misc.hashFile, "hashFile", 2),
hashString: mkPrimop(misc.hashString, "hashString", 2),
convertHash: mkPrimop(misc.convertHash, "convertHash", 2),
unsafeDiscardOutputDependency: mkPrimop( unsafeDiscardOutputDependency: mkPrimop(
misc.unsafeDiscardOutputDependency, misc.unsafeDiscardOutputDependency,
"unsafeDiscardOutputDependency", "unsafeDiscardOutputDependency",
@@ -236,14 +193,10 @@ export const builtins: any = {
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),
flakeRefToString: mkPrimop(misc.flakeRefToString, "flakeRefToString", 1),
functionArgs: mkPrimop(misc.functionArgs, "functionArgs", 1), functionArgs: mkPrimop(misc.functionArgs, "functionArgs", 1),
genericClosure: mkPrimop(misc.genericClosure, "genericClosure", 1), genericClosure: mkPrimop(misc.genericClosure, "genericClosure", 1),
getFlake: mkPrimop(misc.getFlake, "getFlake", 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),
parseFlakeName: mkPrimop(misc.parseFlakeName, "parseFlakeName", 1),
parseFlakeRef: mkPrimop(misc.parseFlakeRef, "parseFlakeRef", 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),
@@ -263,10 +216,7 @@ export const builtins: any = {
langVersion: 6, langVersion: 6,
nixPath: [], nixPath: [],
nixVersion: "2.31.2", nixVersion: "2.31.2",
storeDir: "INVALID_PATH", storeDir: createThunk(() => {
throw new Error("stub storeDir evaluated");
__traceCaller: (e: NixValue) => { }),
console.log(`traceCaller: ${getTos()}`);
return e;
},
}; };

View File

@@ -1,8 +1,7 @@
/** import { getPathValue } from "../path";
* I/O and filesystem builtin functions import type { NixStringContext, StringWithContext } from "../string-context";
* Implemented via Rust ops exposed through deno_core import { addOpaqueContext, mkStringWithContext } from "../string-context";
*/ import { force } from "../thunk";
import { import {
forceAttrs, forceAttrs,
forceBool, forceBool,
@@ -11,15 +10,11 @@ import {
forceStringNoCtx, forceStringNoCtx,
forceStringValue, forceStringValue,
} from "../type-assert"; } from "../type-assert";
import type { NixValue, NixAttrs, NixPath, NixString } from "../types"; import type { NixAttrs, NixPath, NixString, NixValue } from "../types";
import { isNixPath, IS_PATH, CatchableError } from "../types"; import { CatchableError, IS_PATH, isNixPath } from "../types";
import { force } from "../thunk";
import { coerceToPath, coerceToString, StringCoercionMode } from "./conversion"; import { coerceToPath, coerceToString, StringCoercionMode } from "./conversion";
import { getPathValue } from "../path";
import type { NixStringContext, StringWithContext } from "../string-context";
import { mkStringWithContext, addOpaqueContext } from "../string-context";
import { isAttrs, isPath } from "./type-check";
import { baseNameOf } from "./path"; import { baseNameOf } from "./path";
import { isAttrs, isPath } from "./type-check";
const importCache = new Map<string, NixValue>(); const importCache = new Map<string, NixValue>();
@@ -84,7 +79,7 @@ export const storePath = (pathArg: NixValue): StringWithContext => {
return mkStringWithContext(validatedPath, context); return mkStringWithContext(validatedPath, context);
}; };
export const fetchClosure = (args: NixValue): never => { export const fetchClosure = (_args: NixValue): never => {
throw new Error("Not implemented: fetchClosure"); throw new Error("Not implemented: fetchClosure");
}; };
@@ -136,7 +131,7 @@ const normalizeTarballInput = (args: NixValue): { url: string; sha256?: string;
const sha256 = "sha256" in forced ? forceStringNoCtx(forced.sha256) : undefined; const sha256 = "sha256" in forced ? forceStringNoCtx(forced.sha256) : undefined;
const nameRaw = "name" in forced ? forceStringNoCtx(forced.name) : undefined; const nameRaw = "name" in forced ? forceStringNoCtx(forced.name) : undefined;
// FIXME: extract baseNameOfRaw // 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 {
return { url: forceStringNoCtx(forced) }; return { url: forceStringNoCtx(forced) };
@@ -145,11 +140,11 @@ const normalizeTarballInput = (args: NixValue): { url: string; sha256?: string;
const resolvePseudoUrl = (url: string) => { const resolvePseudoUrl = (url: string) => {
if (url.startsWith("channel:")) { if (url.startsWith("channel:")) {
return `https://channels.nixos.org/${url.substring(8)}/nixexprs.tar.xz` return `https://channels.nixos.org/${url.substring(8)}/nixexprs.tar.xz`;
} else { } else {
return url return url;
} }
} };
export const fetchurl = (args: NixValue): NixString => { export const fetchurl = (args: NixValue): NixString => {
const { url, hash, name, executable } = normalizeUrlInput(args); const { url, hash, name, executable } = normalizeUrlInput(args);
@@ -224,7 +219,7 @@ export const fetchGit = (args: NixValue): NixAttrs => {
}; };
export const fetchMercurial = (_args: NixValue): NixAttrs => { export const fetchMercurial = (_args: NixValue): NixAttrs => {
throw new Error("Not implemented: fetchMercurial") throw new Error("Not implemented: fetchMercurial");
}; };
export const fetchTree = (args: NixValue): NixAttrs => { export const fetchTree = (args: NixValue): NixAttrs => {
@@ -249,7 +244,6 @@ export const fetchTree = (args: NixValue): NixAttrs => {
case "gitlab": case "gitlab":
case "sourcehut": case "sourcehut":
return fetchGitForge(type, attrs); return fetchGitForge(type, attrs);
case "auto":
default: default:
return autoDetectAndFetch(attrs); return autoDetectAndFetch(attrs);
} }
@@ -375,7 +369,7 @@ export const path = (args: NixValue): NixString => {
const includePaths: string[] = []; const includePaths: string[] = [];
for (const [relPath, fileType] of entries) { for (const [relPath, fileType] of entries) {
const fullPath = pathStr + "/" + relPath; const fullPath = `${pathStr}/${relPath}`;
const innerFn = forceFunction(filterFn(fullPath)); const innerFn = forceFunction(filterFn(fullPath));
const shouldInclude = force(innerFn(fileType)); const shouldInclude = force(innerFn(fileType));
if (shouldInclude === true) { if (shouldInclude === true) {
@@ -383,13 +377,7 @@ export const path = (args: NixValue): NixString => {
} }
} }
storePath = Deno.core.ops.op_add_filtered_path( storePath = Deno.core.ops.op_add_filtered_path(pathStr, name, recursive, sha256, includePaths);
pathStr,
name,
recursive,
sha256,
includePaths,
);
} else { } else {
storePath = Deno.core.ops.op_add_path(pathStr, name, recursive, sha256); storePath = Deno.core.ops.op_add_path(pathStr, name, recursive, sha256);
} }
@@ -421,13 +409,11 @@ export const toFile =
return mkStringWithContext(storePath, new Set([storePath])); return mkStringWithContext(storePath, new Set([storePath]));
}; };
export const toPath = (name: NixValue, s: NixValue): never => { export const filterSource =
throw new Error("Not implemented: toPath"); (_filter: NixValue) =>
}; (_path: NixValue): never => {
throw new Error("Not implemented: filterSource");
export const filterSource = (args: NixValue): never => { };
throw new Error("Not implemented: filterSource");
};
const suffixIfPotentialMatch = (prefix: string, path: string): string | null => { const suffixIfPotentialMatch = (prefix: string, path: string): string | null => {
const n = prefix.length; const n = prefix.length;

View File

@@ -1,12 +1,7 @@
/**
* List operation builtin functions
* All functions are properly curried
*/
import type { NixValue, NixList, NixAttrs } from "../types";
import { force } from "../thunk";
import { forceList, forceFunction, forceInt, forceBool } from "../type-assert";
import { op } from "../operators"; import { op } from "../operators";
import { force } from "../thunk";
import { forceBool, forceFunction, forceInt, forceList } from "../type-assert";
import type { NixAttrs, NixList, NixValue } from "../types";
export const map = export const map =
(f: NixValue) => (f: NixValue) =>
@@ -74,23 +69,23 @@ export const concatMap =
}; };
export const foldlPrime = export const foldlPrime =
(op_fn: NixValue) => (opFn: NixValue) =>
(nul: NixValue) => (nul: NixValue) =>
(list: NixValue): NixValue => { (list: NixValue): NixValue => {
const forced_op = forceFunction(op_fn); const forcedOp = forceFunction(opFn);
return forceList(list).reduce((acc: NixValue, cur: NixValue) => { return forceList(list).reduce((acc: NixValue, cur: NixValue) => {
return forceFunction(forced_op(acc))(cur); return forceFunction(forcedOp(acc))(cur);
}, nul); }, nul);
}; };
export const sort = export const sort =
(cmp: NixValue) => (cmp: NixValue) =>
(list: NixValue): NixList => { (list: NixValue): NixList => {
const forced_list = [...forceList(list)]; const forcedList = [...forceList(list)];
const forced_cmp = forceFunction(cmp); const forcedCmp = forceFunction(cmp);
return forced_list.sort((a, b) => { return forcedList.sort((a, b) => {
if (force(forceFunction(forced_cmp(a))(b))) return -1; if (force(forceFunction(forcedCmp(a))(b))) return -1;
if (force(forceFunction(forced_cmp(b))(a))) return 1; if (force(forceFunction(forcedCmp(b))(a))) return 1;
return 0; return 0;
}); });
}; };
@@ -98,14 +93,14 @@ export const sort =
export const partition = export const partition =
(pred: NixValue) => (pred: NixValue) =>
(list: NixValue): NixAttrs => { (list: NixValue): NixAttrs => {
const forced_list = forceList(list); const forcedList = forceList(list);
const forced_pred = forceFunction(pred); const forcedPred = forceFunction(pred);
const attrs = { const attrs = {
right: [] as NixList, right: [] as NixList,
wrong: [] as NixList, wrong: [] as NixList,
}; };
for (const elem of forced_list) { for (const elem of forcedList) {
if (force(forced_pred(elem))) { if (force(forcedPred(elem))) {
attrs.right.push(elem); attrs.right.push(elem);
} else { } else {
attrs.wrong.push(elem); attrs.wrong.push(elem);

View File

@@ -1,9 +1,5 @@
/**
* Math builtin functions
*/
import type { NixValue } from "../types";
import { forceNumeric } from "../type-assert"; import { forceNumeric } from "../type-assert";
import type { NixValue } from "../types";
export const ceil = (x: NixValue): bigint => { export const ceil = (x: NixValue): bigint => {
const val = forceNumeric(x); const val = forceNumeric(x);

View File

@@ -1,31 +1,27 @@
/** import { OrderedSet } from "js-sdsl";
* Miscellaneous builtin functions import { compareValues } from "../operators";
*/ import {
getStringContext,
import { force } from "../thunk"; getStringValue,
import { CatchableError, ATTR_POSITIONS } from "../types"; mkStringWithContext,
import type { NixAttrs, NixBool, NixStrictValue, NixValue } from "../types"; type NixStringContext,
} from "../string-context";
import { force } from "../thunk";
import { import {
forceList,
forceAttrs, forceAttrs,
forceFunction, forceFunction,
forceStringValue, forceList,
forceString, forceString,
forceStringNoCtx, forceStringNoCtx,
forceStringValue,
} from "../type-assert"; } from "../type-assert";
import type { NixAttrs, NixBool, NixStrictValue, NixValue } from "../types";
import { ATTR_POSITIONS, CatchableError } from "../types";
import * as context from "./context"; import * as context from "./context";
import { compareValues } from "../operators";
import { isBool, isFloat, isInt, isList, isString, typeOf } from "./type-check"; import { isBool, isFloat, isInt, isList, isString, typeOf } from "./type-check";
import { OrderedSet } from "js-sdsl";
import {
type NixStringContext,
getStringValue,
getStringContext,
mkStringWithContext,
} from "../string-context";
export const addErrorContext = export const addErrorContext =
(e1: NixValue) => (_e1: NixValue) =>
(e2: NixValue): NixValue => { (e2: NixValue): NixValue => {
// FIXME: // FIXME:
// console.log("[WARNING]: addErrorContext not implemented"); // console.log("[WARNING]: addErrorContext not implemented");
@@ -38,23 +34,6 @@ export const getContext = context.getContext;
export const hasContext = context.hasContext; export const hasContext = context.hasContext;
export const hashFile =
(type: NixValue) =>
(p: NixValue): never => {
const ty = forceStringNoCtx(type);
throw new Error("Not implemented: hashFile");
};
export const hashString =
(type: NixValue) =>
(p: NixValue): never => {
throw new Error("Not implemented: hashString");
};
export const convertHash = (args: NixValue): never => {
throw new Error("Not implemented: convertHash");
};
export const unsafeDiscardOutputDependency = context.unsafeDiscardOutputDependency; export const unsafeDiscardOutputDependency = context.unsafeDiscardOutputDependency;
export const unsafeDiscardStringContext = context.unsafeDiscardStringContext; export const unsafeDiscardStringContext = context.unsafeDiscardStringContext;
@@ -77,9 +56,9 @@ export const compareVersions =
i1 = c1.nextIndex; i1 = c1.nextIndex;
i2 = c2.nextIndex; i2 = c2.nextIndex;
if (componentsLT(c1.component, c2.component)) { if (componentsLt(c1.component, c2.component)) {
return -1n; return -1n;
} else if (componentsLT(c2.component, c1.component)) { } else if (componentsLt(c2.component, c1.component)) {
return 1n; return 1n;
} }
} }
@@ -121,7 +100,7 @@ function nextComponent(s: string, startIdx: number): ComponentResult {
return { component: s.substring(start, p), nextIndex: p }; return { component: s.substring(start, p), nextIndex: p };
} }
function componentsLT(c1: string, c2: string): boolean { function componentsLt(c1: string, c2: string): boolean {
const n1 = c1.match(/^[0-9]+$/) ? BigInt(c1) : null; const n1 = c1.match(/^[0-9]+$/) ? BigInt(c1) : null;
const n2 = c2.match(/^[0-9]+$/) ? BigInt(c2) : null; const n2 = c2.match(/^[0-9]+$/) ? BigInt(c2) : null;
@@ -155,25 +134,17 @@ function componentsLT(c1: string, c2: string): boolean {
return c1 < c2; return c1 < c2;
} }
export const dirOf = (s: NixValue): never => {
throw new Error("Not implemented: dirOf");
};
export const flakeRefToString = (attrs: NixValue): never => {
throw new Error("Not implemented: flakeRefToString");
};
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 = {};
for (const key of func.args!.required) { for (const key of func.args.required) {
ret[key] = false; ret[key] = false;
} }
for (const key of func.args!.optional) { for (const key of func.args.optional) {
ret[key] = true; ret[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, { Object.defineProperty(ret, ATTR_POSITIONS, {
value: positions, value: positions,
@@ -235,13 +206,9 @@ export const genericClosure = (args: NixValue): NixValue => {
return resultList; return resultList;
}; };
export const getFlake = (attrs: NixValue): never => {
throw new Error("Not implemented: getFlake");
};
export const outputOf = export const outputOf =
(drv: NixValue) => (_drv: NixValue) =>
(out: NixValue): never => { (_out: NixValue): never => {
throw new Error("Not implemented: outputOf"); throw new Error("Not implemented: outputOf");
}; };
@@ -262,14 +229,6 @@ export const parseDrvName = (s: NixValue): NixAttrs => {
}; };
}; };
export const parseFlakeName = (s: NixValue): never => {
throw new Error("Not implemented: parseFlakeName");
};
export const parseFlakeRef = (s: NixValue): never => {
throw new Error("Not implemented: parseFlakeRef");
};
export const placeholder = (output: NixValue): NixValue => { export const placeholder = (output: NixValue): NixValue => {
const outputStr = forceStringNoCtx(output); const outputStr = forceStringNoCtx(output);
return Deno.core.ops.op_make_placeholder(outputStr); return Deno.core.ops.op_make_placeholder(outputStr);
@@ -314,7 +273,7 @@ export const replaceStrings =
resultContext.add(elem); resultContext.add(elem);
} }
} }
const replacement = toCache.get(i)!; const replacement = toCache.get(i) as string;
result += replacement; result += replacement;
@@ -361,7 +320,7 @@ export const splitVersion = (s: NixValue): NixValue => {
return components; return components;
}; };
export const traceVerbose = (e1: NixValue, e2: NixValue): never => { export const traceVerbose = (_e1: NixValue, _e2: NixValue): never => {
throw new Error("Not implemented: traceVerbose"); throw new Error("Not implemented: traceVerbose");
}; };

View File

@@ -1,13 +1,9 @@
/**
* Path-related builtin functions
*/
import type { NixValue, NixString, NixPath } from "../types";
import { isNixPath, isStringWithContext } from "../types";
import { force } from "../thunk";
import { mkPath } from "../path"; import { mkPath } from "../path";
import { coerceToString, StringCoercionMode, coerceToPath } from "./conversion";
import { mkStringWithContext, type NixStringContext } from "../string-context"; import { mkStringWithContext, type NixStringContext } from "../string-context";
import { force } from "../thunk";
import type { NixPath, NixString, NixValue } from "../types";
import { isNixPath, isStringWithContext } from "../types";
import { coerceToPath, coerceToString, StringCoercionMode } from "./conversion";
/** /**
* builtins.baseNameOf * builtins.baseNameOf

View File

@@ -1,29 +1,15 @@
/**
* String operation builtin functions
*/
import type { NixInt, NixValue, NixString } from "../types";
import { forceStringValue, forceList, forceInt, forceString } from "../type-assert";
import { coerceToString, StringCoercionMode } from "./conversion";
import { import {
type NixStringContext,
getStringValue,
getStringContext, getStringContext,
getStringValue,
mkStringWithContext, mkStringWithContext,
type NixStringContext,
} from "../string-context"; } from "../string-context";
import { forceInt, forceList, forceString, forceStringValue } from "../type-assert";
import type { NixInt, NixString, NixValue } from "../types";
import { coerceToString, StringCoercionMode } from "./conversion";
export const stringLength = (e: NixValue): NixInt => BigInt(forceStringValue(e).length); export const stringLength = (e: NixValue): NixInt => BigInt(forceStringValue(e).length);
/**
* builtins.substring - Extract substring while preserving string context
*
* IMPORTANT: String context must be preserved from the source string.
* This matches Lix behavior where substring operations maintain references
* to store paths and derivations.
*
* Special case: substring 0 0 str can be used idiomatically to capture
* string context efficiently without copying the string value.
*/
export const substring = export const substring =
(start: NixValue) => (start: NixValue) =>
(len: NixValue) => (len: NixValue) =>
@@ -55,13 +41,6 @@ export const substring =
return mkStringWithContext(result, context); return mkStringWithContext(result, context);
}; };
/**
* builtins.concatStringsSep - Concatenate strings with separator, merging contexts
*
* IMPORTANT: String context must be collected from both the separator and all
* list elements, then merged into the result. This ensures that store path
* references are preserved when building paths like "/nix/store/xxx/bin:/nix/store/yyy/bin".
*/
export const concatStringsSep = export const concatStringsSep =
(sep: NixValue) => (sep: NixValue) =>
(list: NixValue): NixString => { (list: NixValue): NixString => {

View File

@@ -1,12 +1,7 @@
/**
* Type checking builtin functions
*/
import { import {
HAS_CONTEXT, HAS_CONTEXT,
isNixPath, isNixPath,
isStringWithContext, isStringWithContext,
type NixPath,
type NixAttrs, type NixAttrs,
type NixBool, type NixBool,
type NixFloat, type NixFloat,
@@ -14,14 +9,11 @@ import {
type NixInt, type NixInt,
type NixList, type NixList,
type NixNull, type NixNull,
type NixString, type NixPath,
type NixStrictValue, type NixStrictValue,
type NixString,
} from "../types"; } from "../types";
/**
* Check if a value is a Nix string (plain string or StringWithContext)
* This works on already-forced values (NixStrictValue).
*/
export const isNixString = (v: NixStrictValue): v is NixString => { export const isNixString = (v: NixStrictValue): v is NixString => {
return typeof v === "string" || isStringWithContext(v); return typeof v === "string" || isStringWithContext(v);
}; };

View File

@@ -1,83 +0,0 @@
import { HAS_CONTEXT, NixStringContext } from "./string-context";
import { force, isThunk } from "./thunk";
import type { NixValue } from "./types";
import { isStringWithContext, IS_PATH } from "./types";
export const nixValueToJson = (
value: NixValue,
strict: boolean,
outContext: NixStringContext,
copyToStore: boolean,
seen: Set<NixValue> = new Set(),
): any => {
const v = strict ? force(value) : value;
if (isThunk(v) || typeof v === "function")
throw new Error(`cannot convert ${isThunk(v) ? "thunk" : "lambda"} to JSON`);
if (v === null) return null;
if (typeof v === "bigint") {
const num = Number(v);
if (v > Number.MAX_SAFE_INTEGER || v < Number.MIN_SAFE_INTEGER) {
console.warn(`integer ${v} exceeds safe range, precision may be lost`);
}
return num;
}
if (typeof v === "number") return v;
if (typeof v === "boolean") return v;
if (typeof v === "string") return v;
if (typeof v === "object" && HAS_CONTEXT in v) {
for (const elem of v.context) {
outContext.add(elem);
}
return v.value;
}
if (typeof v === "object" && IS_PATH in v) {
if (copyToStore) {
const storePath = Deno.core.ops.op_copy_path_to_store(v.value);
outContext.add(storePath);
return storePath;
} else {
return v.value;
}
}
// FIXME: is this check necessary?
// if (seen.has(v)) {
// throw new Error("cycle detected in toJSON");
// } else {
// seen.add(v)
// }
if (Array.isArray(v)) {
return v.map((item) => nixValueToJson(item, strict, outContext, copyToStore, seen));
}
// NixAttrs
if ("__toString" in v && typeof force(v.__toString) === "function") {
const toStringMethod = force(v.__toString) as (self: typeof v) => NixValue;
const result = force(toStringMethod(v));
if (typeof result === "string") {
return result;
}
if (isStringWithContext(result)) {
if (outContext) {
for (const elem of result.context) {
outContext.add(elem);
}
}
return result.value;
}
return nixValueToJson(result, strict, outContext, copyToStore, seen);
}
if ("outPath" in v) {
return nixValueToJson(v.outPath, strict, outContext, copyToStore,seen);
}
const result: Record<string, any> = {};
const keys = Object.keys(v).sort();
for (const key of keys) {
result[key] = nixValueToJson(v[key], strict, outContext, copyToStore, seen);
}
return result;
};

View File

@@ -1,106 +0,0 @@
export interface OutputInfo {
path: string;
hashAlgo: string;
hash: string;
}
export interface DerivationData {
name: string;
outputs: Map<string, OutputInfo>;
inputDrvs: Map<string, Set<string>>;
inputSrcs: Set<string>;
platform: string;
builder: string;
args: string[];
env: Map<string, string>;
}
export const escapeString = (s: string): string => {
let result = "";
for (const char of s) {
switch (char) {
case '"':
result += '\\"';
break;
case "\\":
result += "\\\\";
break;
case "\n":
result += "\\n";
break;
case "\r":
result += "\\r";
break;
case "\t":
result += "\\t";
break;
default:
result += char;
}
}
return `"${result}"`;
};
const quoteString = (s: string): string => `"${s}"`;
const cmpByKey = <T>(a: [string, T], b: [string, T]): number => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
export const generateAterm = (drv: DerivationData): string => {
const outputEntries: string[] = [];
const sortedOutputs = Array.from(drv.outputs.entries()).sort(cmpByKey);
for (const [name, info] of sortedOutputs) {
outputEntries.push(
`(${quoteString(name)},${quoteString(info.path)},${quoteString(info.hashAlgo)},${quoteString(info.hash)})`,
);
}
const outputs = outputEntries.join(",");
const inputDrvEntries: string[] = [];
const sortedInputDrvs = Array.from(drv.inputDrvs.entries()).sort(cmpByKey);
for (const [drvPath, outputs] of sortedInputDrvs) {
const sortedOuts = Array.from(outputs).sort();
const outList = `[${sortedOuts.map(quoteString).join(",")}]`;
inputDrvEntries.push(`(${quoteString(drvPath)},${outList})`);
}
const inputDrvs = inputDrvEntries.join(",");
const sortedInputSrcs = Array.from(drv.inputSrcs).sort();
const inputSrcs = sortedInputSrcs.map(quoteString).join(",");
const args = drv.args.map(escapeString).join(",");
const envs = Array.from(drv.env.entries())
.sort(cmpByKey)
.map(([k, v]) => `(${escapeString(k)},${escapeString(v)})`);
return `Derive([${outputs}],[${inputDrvs}],[${inputSrcs}],${quoteString(drv.platform)},${escapeString(drv.builder)},[${args}],[${envs}])`;
};
export const generateAtermModulo = (drv: DerivationData, inputDrvHashes: Map<string, string>): string => {
const outputEntries: string[] = [];
const sortedOutputs = Array.from(drv.outputs.entries()).sort(cmpByKey);
for (const [name, info] of sortedOutputs) {
outputEntries.push(
`(${quoteString(name)},${quoteString(info.path)},${quoteString(info.hashAlgo)},${quoteString(info.hash)})`,
);
}
const outputs = outputEntries.join(",");
const inputDrvEntries: string[] = [];
const sortedInputDrvHashes = Array.from(inputDrvHashes.entries()).sort(cmpByKey);
for (const [drvHash, outputs] of sortedInputDrvHashes) {
const sortedOuts = outputs.split(",").sort();
const outList = `[${sortedOuts.map(quoteString).join(",")}]`;
inputDrvEntries.push(`(${quoteString(drvHash)},${outList})`);
}
const inputDrvs = inputDrvEntries.join(",");
const sortedInputSrcs = Array.from(drv.inputSrcs).sort();
const inputSrcs = sortedInputSrcs.map(quoteString).join(",");
const args = drv.args.map(escapeString).join(",");
const envs = Array.from(drv.env.entries())
.sort(cmpByKey)
.map(([k, v]) => `(${escapeString(k)},${escapeString(v)})`);
return `Derive([${outputs}],[${inputDrvs}],[${inputSrcs}],${quoteString(drv.platform)},${escapeString(drv.builder)},[${args}],[${envs}])`;
};

View File

@@ -1,14 +1,10 @@
/**
* Helper functions for nix-js runtime
*/
import type { NixValue, NixAttrs, NixBool, NixString, NixPath } from "./types";
import { forceAttrs, forceBool, forceFunction, forceStringValue } from "./type-assert";
import { isAttrs, typeOf } from "./builtins/type-check";
import { coerceToString, StringCoercionMode } from "./builtins/conversion"; import { coerceToString, StringCoercionMode } from "./builtins/conversion";
import { type NixStringContext, mkStringWithContext, isStringWithContext } from "./string-context"; import { isAttrs, typeOf } from "./builtins/type-check";
import { force } from "./thunk";
import { mkPath } from "./path"; import { mkPath } from "./path";
import { isStringWithContext, mkStringWithContext, type NixStringContext } from "./string-context";
import { force } from "./thunk";
import { forceAttrs, forceBool, forceFunction, forceStringValue } from "./type-assert";
import type { NixAttrs, NixBool, NixPath, NixString, NixValue } from "./types";
import { CatchableError, isNixPath } from "./types"; import { CatchableError, isNixPath } from "./types";
interface StackFrame { interface StackFrame {
@@ -36,34 +32,17 @@ function enrichError(error: unknown): Error {
return err; return err;
} }
export const getTos = (): string => { const pushContext = (message: string, span: string): void => {
const tos = callStack[callStack.length - 2];
const { file, line, column } = Deno.core.ops.op_decode_span(tos.span);
return `${tos.message} at ${file}:${line}:${column}`;
};
/**
* Push an error context onto the stack
* Used for tracking evaluation context (e.g., "while evaluating the condition")
*/
export const pushContext = (message: string, span: string): void => {
if (callStack.length >= MAX_STACK_DEPTH) { if (callStack.length >= MAX_STACK_DEPTH) {
callStack.shift(); callStack.shift();
} }
callStack.push({ span, message }); callStack.push({ span, message });
}; };
/** const popContext = (): void => {
* Pop an error context from the stack
*/
export const popContext = (): void => {
callStack.pop(); callStack.pop();
}; };
/**
* Execute a function with error context tracking
* Automatically pushes context before execution and pops after
*/
export const withContext = <T>(message: string, span: string, fn: () => T): T => { export const withContext = <T>(message: string, span: string, fn: () => T): T => {
pushContext(message, span); pushContext(message, span);
try { try {
@@ -149,13 +128,6 @@ export const concatStringsWithContext = (parts: NixValue[], forceString: boolean
return mkStringWithContext(value, context); return mkStringWithContext(value, context);
}; };
/**
* Resolve a path (handles both absolute and relative paths)
* For relative paths, resolves against current import stack
*
* @param path - Path string (may be relative or absolute)
* @returns NixPath object with absolute path
*/
export const resolvePath = (currentDir: string, path: NixValue): NixPath => { export const resolvePath = (currentDir: string, path: NixValue): NixPath => {
const forced = force(path); const forced = force(path);
let pathStr: string; let pathStr: string;
@@ -181,18 +153,18 @@ export const select = (obj: NixValue, attrpath: NixValue[], span?: string): NixV
} }
callStack.push({ span, message }); callStack.push({ span, message });
try { try {
return select_impl(obj, attrpath); return selectImpl(obj, attrpath);
} catch (error) { } catch (error) {
throw enrichError(error); throw enrichError(error);
} finally { } finally {
callStack.pop(); callStack.pop();
} }
} else { } else {
return select_impl(obj, attrpath); return selectImpl(obj, attrpath);
} }
}; };
function select_impl(obj: NixValue, attrpath: NixValue[]): NixValue { function selectImpl(obj: NixValue, attrpath: NixValue[]): NixValue {
let attrs = forceAttrs(obj); let attrs = forceAttrs(obj);
for (const attr of attrpath.slice(0, -1)) { for (const attr of attrpath.slice(0, -1)) {
@@ -214,7 +186,7 @@ function select_impl(obj: NixValue, attrpath: NixValue[]): NixValue {
export const selectWithDefault = ( export const selectWithDefault = (
obj: NixValue, obj: NixValue,
attrpath: NixValue[], attrpath: NixValue[],
default_val: NixValue, defaultVal: NixValue,
span?: string, span?: string,
): NixValue => { ): NixValue => {
if (span) { if (span) {
@@ -227,18 +199,18 @@ export const selectWithDefault = (
} }
callStack.push({ span, message }); callStack.push({ span, message });
try { try {
return selectWithDefault_impl(obj, attrpath, default_val); return selectWithDefaultImpl(obj, attrpath, defaultVal);
} catch (error) { } catch (error) {
throw enrichError(error); throw enrichError(error);
} finally { } finally {
callStack.pop(); callStack.pop();
} }
} else { } else {
return selectWithDefault_impl(obj, attrpath, default_val); return selectWithDefaultImpl(obj, attrpath, defaultVal);
} }
}; };
function selectWithDefault_impl(obj: NixValue, attrpath: NixValue[], defaultVal: NixValue): NixValue { function selectWithDefaultImpl(obj: NixValue, attrpath: NixValue[], defaultVal: NixValue): NixValue {
let attrs = force(obj); let attrs = force(obj);
if (!isAttrs(attrs)) { if (!isAttrs(attrs)) {
return defaultVal; return defaultVal;
@@ -281,49 +253,6 @@ export const hasAttr = (obj: NixValue, attrpath: NixValue[]): NixBool => {
return forceStringValue(attrpath[attrpath.length - 1]) in attrs; return forceStringValue(attrpath[attrpath.length - 1]) in attrs;
}; };
/**
* Validate function parameters
* Used for pattern matching in function parameters
*
* Example: { a, b ? 1, ... }: ...
* - required: ["a"]
* - allowed: ["a", "b"] (or null if ellipsis "..." present)
*
* @param arg - Argument object to validate
* @param required - Array of required parameter names (or null)
* @param allowed - Array of allowed parameter names (or null for ellipsis)
* @returns The forced argument object
* @throws Error if required param missing or unexpected param present
*/
export const validateParams = (
arg: NixValue,
required: string[] | null,
allowed: string[] | null,
): NixAttrs => {
const forced_arg = forceAttrs(arg);
// Check required parameters
if (required) {
for (const key of required) {
if (!Object.hasOwn(forced_arg, key)) {
throw new Error(`Function called without required argument '${key}'`);
}
}
}
// Check allowed parameters (if not using ellipsis)
if (allowed) {
const allowed_set = new Set(allowed);
for (const key in forced_arg) {
if (!allowed_set.has(key)) {
throw new Error(`Function called with unexpected argument '${key}'`);
}
}
}
return forced_arg;
};
export const call = (func: NixValue, arg: NixValue, span?: string): NixValue => { export const call = (func: NixValue, arg: NixValue, span?: string): NixValue => {
if (span) { if (span) {
if (callStack.length >= MAX_STACK_DEPTH) { if (callStack.length >= MAX_STACK_DEPTH) {
@@ -331,18 +260,18 @@ export const call = (func: NixValue, arg: NixValue, span?: string): NixValue =>
} }
callStack.push({ span, message: "from call site" }); callStack.push({ span, message: "from call site" });
try { try {
return call_impl(func, arg); return callImpl(func, arg);
} catch (error) { } catch (error) {
throw enrichError(error); throw enrichError(error);
} finally { } finally {
callStack.pop(); callStack.pop();
} }
} else { } else {
return call_impl(func, arg); return callImpl(func, arg);
} }
}; };
function call_impl(func: NixValue, arg: NixValue): NixValue { function callImpl(func: NixValue, arg: NixValue): NixValue {
const forcedFunc = force(func); const forcedFunc = force(func);
if (typeof forcedFunc === "function") { if (typeof forcedFunc === "function") {
forcedFunc.args?.check(arg); forcedFunc.args?.check(arg);

View File

@@ -4,45 +4,39 @@
* All functionality is exported via the global `Nix` object * All functionality is exported via the global `Nix` object
*/ */
import {
createThunk,
force,
isThunk,
IS_THUNK,
DEBUG_THUNKS,
forceDeep,
IS_CYCLE,
forceShallow,
} from "./thunk";
import {
select,
selectWithDefault,
validateParams,
resolvePath,
hasAttr,
concatStringsWithContext,
call,
assert,
pushContext,
popContext,
withContext,
mkPos,
lookupWith,
} from "./helpers";
import { op } from "./operators";
import { builtins, PRIMOP_METADATA } from "./builtins"; import { builtins, PRIMOP_METADATA } from "./builtins";
import { coerceToString, StringCoercionMode } from "./builtins/conversion"; import { coerceToString, StringCoercionMode } from "./builtins/conversion";
import {
assert,
call,
concatStringsWithContext,
hasAttr,
lookupWith,
mkPos,
resolvePath,
select,
selectWithDefault,
withContext,
} from "./helpers";
import { op } from "./operators";
import { HAS_CONTEXT } from "./string-context"; import { HAS_CONTEXT } from "./string-context";
import { IS_PATH, mkAttrs, mkFunction, mkAttrsWithPos, ATTR_POSITIONS, NixValue } from "./types"; import {
createThunk,
DEBUG_THUNKS,
force,
forceDeep,
forceShallow,
IS_CYCLE,
IS_THUNK,
isThunk,
} from "./thunk";
import { forceBool } from "./type-assert"; import { forceBool } from "./type-assert";
import { ATTR_POSITIONS, IS_PATH, mkAttrs, mkAttrsWithPos, mkFunction, type NixValue } from "./types";
export type NixRuntime = typeof Nix; export type NixRuntime = typeof Nix;
const replBindings: Record<string, NixValue> = {}; const replBindings: Record<string, NixValue> = {};
/**
* The global Nix runtime object
*/
export const Nix = { export const Nix = {
createThunk, createThunk,
force, force,
@@ -62,7 +56,6 @@ export const Nix = {
select, select,
selectWithDefault, selectWithDefault,
lookupWith, lookupWith,
validateParams,
resolvePath, resolvePath,
coerceToString, coerceToString,
concatStringsWithContext, concatStringsWithContext,
@@ -73,8 +66,6 @@ export const Nix = {
mkPos, mkPos,
ATTR_POSITIONS, ATTR_POSITIONS,
pushContext,
popContext,
withContext, withContext,
op, op,

View File

@@ -1,22 +1,17 @@
/** import { coerceToString, StringCoercionMode } from "./builtins/conversion";
* Nix operators module import { isNixString, typeOf } from "./builtins/type-check";
* Implements all binary and unary operators used by codegen import { mkPath } from "./path";
*/
import type { NixValue, NixList, NixAttrs, NixString, NixPath } from "./types";
import { isNixPath } from "./types";
import { force } from "./thunk";
import { forceNumeric, forceList, forceAttrs, coerceNumeric } from "./type-assert";
import { import {
type NixStringContext,
getStringValue,
getStringContext, getStringContext,
getStringValue,
mergeContexts, mergeContexts,
mkStringWithContext, mkStringWithContext,
type NixStringContext,
} from "./string-context"; } from "./string-context";
import { coerceToString, StringCoercionMode } from "./builtins/conversion"; import { force } from "./thunk";
import { mkPath } from "./path"; import { coerceNumeric, forceAttrs, forceBool, forceList, forceNumeric } from "./type-assert";
import { typeOf, isNixString } from "./builtins/type-check"; import type { NixAttrs, NixList, NixPath, NixString, NixValue } from "./types";
import { isNixPath } from "./types";
const canCoerceToString = (v: NixValue): boolean => { const canCoerceToString = (v: NixValue): boolean => {
const forced = force(v); const forced = force(v);
@@ -27,11 +22,6 @@ const canCoerceToString = (v: NixValue): boolean => {
return false; return false;
}; };
/**
* Compare two values, similar to Nix's CompareValues.
* Returns: -1 if a < b, 0 if a == b, 1 if a > b
* Throws TypeError for incomparable types.
*/
export const compareValues = (a: NixValue, b: NixValue): -1 | 0 | 1 => { export const compareValues = (a: NixValue, b: NixValue): -1 | 0 | 1 => {
const av = force(a); const av = force(a);
const bv = force(b); const bv = force(b);
@@ -54,32 +44,29 @@ export const compareValues = (a: NixValue, b: NixValue): -1 | 0 | 1 => {
throw new TypeError(`cannot compare ${typeOf(av)} with ${typeOf(bv)}`); throw new TypeError(`cannot compare ${typeOf(av)} with ${typeOf(bv)}`);
} }
// Int and float comparison
if (typeA === "int" || typeA === "float") { if (typeA === "int" || typeA === "float") {
return av! < bv! ? -1 : av === bv ? 0 : 1; return (av as never) < (bv as never) ? -1 : av === bv ? 0 : 1;
} }
// String comparison (handles both plain strings and StringWithContext)
if (typeA === "string") { if (typeA === "string") {
const strA = getStringValue(av as NixString); const strA = getStringValue(av as NixString);
const strB = getStringValue(bv as NixString); const strB = getStringValue(bv as NixString);
return strA < strB ? -1 : strA > strB ? 1 : 0; return strA < strB ? -1 : strA > strB ? 1 : 0;
} }
// Path comparison
if (typeA === "path") { if (typeA === "path") {
const aPath = av as NixPath; const aPath = av as NixPath;
const bPath = bv as NixPath; const bPath = bv as NixPath;
return aPath.value < bPath.value ? -1 : aPath.value > bPath.value ? 1 : 0; return aPath.value < bPath.value ? -1 : aPath.value > bPath.value ? 1 : 0;
} }
// List comparison (lexicographic)
if (typeA === "list") { if (typeA === "list") {
const aList = av as NixList; const aList = av as NixList;
const bList = bv as NixList; const bList = bv as NixList;
for (let i = 0; ; i++) { for (let i = 0; ; i++) {
// Equal if same length, else aList > bList
if (i === bList.length) { if (i === bList.length) {
return i === aList.length ? 0 : 1; // Equal if same length, else aList > bList return i === aList.length ? 0 : 1;
} else if (i === aList.length) { } else if (i === aList.length) {
return -1; // aList < bList return -1; // aList < bList
} else if (!op.eq(aList[i], bList[i])) { } else if (!op.eq(aList[i], bList[i])) {
@@ -94,10 +81,6 @@ export const compareValues = (a: NixValue, b: NixValue): -1 | 0 | 1 => {
); );
}; };
/**
* Operator object exported as Nix.op
* All operators referenced by codegen (e.g., Nix.op.add, Nix.op.eq)
*/
export const op = { export const op = {
add: (a: NixValue, b: NixValue): bigint | number | NixString | NixPath => { add: (a: NixValue, b: NixValue): bigint | number | NixString | NixPath => {
const av = force(a); const av = force(a);
@@ -109,15 +92,14 @@ export const op = {
const strB = getStringValue(bv); const strB = getStringValue(bv);
const ctxB = getStringContext(bv); const ctxB = getStringContext(bv);
// Lix constraint: cannot append string with store context to path
if (ctxB.size > 0) { if (ctxB.size > 0) {
throw new TypeError("a string that refers to a store path cannot be appended to a path"); throw new TypeError("a string that refers to a store path cannot be appended to a path");
} }
// Concatenate paths
return mkPath(av.value + strB); return mkPath(av.value + strB);
} }
// FIXME: handle corepkgs
// path + path: concatenate // path + path: concatenate
if (isNixPath(bv)) { if (isNixPath(bv)) {
return mkPath(av.value + bv.value); return mkPath(av.value + bv.value);
@@ -138,6 +120,7 @@ export const op = {
// String concatenation // String concatenation
if (isNixString(av) && isNixString(bv)) { if (isNixString(av) && isNixString(bv)) {
// Merge string context
const strA = getStringValue(av); const strA = getStringValue(av);
const strB = getStringValue(bv); const strB = getStringValue(bv);
const ctxA = getStringContext(av); const ctxA = getStringContext(av);
@@ -162,19 +145,19 @@ export const op = {
return mkStringWithContext(result, context); return mkStringWithContext(result, context);
} }
// Numeric addition // Perform numeric addition otherwise
const [numA, numB] = coerceNumeric(forceNumeric(a), forceNumeric(b)); const [numA, numB] = coerceNumeric(forceNumeric(a), forceNumeric(b));
return (numA as any) + (numB as any); return (numA as never) + (numB as never);
}, },
sub: (a: NixValue, b: NixValue): bigint | number => { sub: (a: NixValue, b: NixValue): bigint | number => {
const [av, bv] = coerceNumeric(forceNumeric(a), forceNumeric(b)); const [av, bv] = coerceNumeric(forceNumeric(a), forceNumeric(b));
return (av as any) - (bv as any); return (av as never) - (bv as never);
}, },
mul: (a: NixValue, b: NixValue): bigint | number => { mul: (a: NixValue, b: NixValue): bigint | number => {
const [av, bv] = coerceNumeric(forceNumeric(a), forceNumeric(b)); const [av, bv] = coerceNumeric(forceNumeric(a), forceNumeric(b));
return (av as any) * (bv as any); return (av as never) * (bv as never);
}, },
div: (a: NixValue, b: NixValue): bigint | number => { div: (a: NixValue, b: NixValue): bigint | number => {
@@ -184,7 +167,7 @@ export const op = {
throw new RangeError("Division by zero"); throw new RangeError("Division by zero");
} }
return (av as any) / (bv as any); return (av as never) / (bv as never);
}, },
eq: (a: NixValue, b: NixValue): boolean => { eq: (a: NixValue, b: NixValue): boolean => {
@@ -202,7 +185,6 @@ export const op = {
return av === Number(bv); return av === Number(bv);
} }
// Get type names for comparison (skip if already handled above)
const typeA = typeOf(av); const typeA = typeOf(av);
const typeB = typeOf(bv); const typeB = typeOf(bv);
@@ -223,10 +205,12 @@ export const op = {
return (av as NixPath).value === (bv as NixPath).value; return (av as NixPath).value === (bv as NixPath).value;
} }
if (Array.isArray(av) && Array.isArray(bv)) { if (typeA === "list") {
if (av.length !== bv.length) return false; const aList = av as NixList;
for (let i = 0; i < av.length; i++) { const bList = bv as NixList;
if (!op.eq(av[i], bv[i])) return false; if (aList.length !== bList.length) return false;
for (let i = 0; i < aList.length; i++) {
if (!op.eq(aList[i], bList[i])) return false;
} }
return true; return true;
} }
@@ -261,11 +245,7 @@ export const op = {
return true; return true;
} }
// Functions are incomparable // Other types are incomparable
if (typeof av === "function") {
return false;
}
return false; return false;
}, },
neq: (a: NixValue, b: NixValue): boolean => { neq: (a: NixValue, b: NixValue): boolean => {
@@ -284,10 +264,10 @@ export const op = {
return compareValues(a, b) >= 0; return compareValues(a, b) >= 0;
}, },
bnot: (a: NixValue): boolean => !force(a), bnot: (a: NixValue): boolean => !forceBool(a),
concat: (a: NixValue, b: NixValue): NixList => { concat: (a: NixValue, b: NixValue): NixList => {
return Array.prototype.concat.call(forceList(a), forceList(b)); return forceList(a).concat(forceList(b));
}, },
update: (a: NixValue, b: NixValue): NixAttrs => ({ ...forceAttrs(a), ...forceAttrs(b) }), update: (a: NixValue, b: NixValue): NixAttrs => ({ ...forceAttrs(a), ...forceAttrs(b) }),

View File

@@ -15,7 +15,6 @@ const canonicalizePath = (path: string): string => {
i = j; i = j;
if (component === ".") { if (component === ".") {
continue;
} else if (component === "..") { } else if (component === "..") {
if (parts.length > 0) { if (parts.length > 0) {
parts.pop(); parts.pop();
@@ -28,7 +27,7 @@ const canonicalizePath = (path: string): string => {
if (parts.length === 0) { if (parts.length === 0) {
return "/"; return "/";
} }
return "/" + parts.join("/"); return `/${parts.join("/")}`;
}; };
export const mkPath = (value: string): NixPath => { export const mkPath = (value: string): NixPath => {

View File

@@ -1,7 +1,7 @@
import { isThunk, IS_CYCLE } from "./thunk"; import { getPrimopMetadata, isPrimop } from "./builtins/index";
import { isStringWithContext } from "./string-context"; import { isStringWithContext } from "./string-context";
import { IS_CYCLE, isThunk } from "./thunk";
import { isNixPath, type NixValue } from "./types"; import { isNixPath, type NixValue } from "./types";
import { is_primop, get_primop_metadata } from "./builtins/index";
export const printValue = (value: NixValue, seen: WeakSet<object> = new WeakSet()): string => { export const printValue = (value: NixValue, seen: WeakSet<object> = new WeakSet()): string => {
if (isThunk(value)) { if (isThunk(value)) {
@@ -29,8 +29,8 @@ export const printValue = (value: NixValue, seen: WeakSet<object> = new WeakSet(
} }
if (typeof value === "function") { if (typeof value === "function") {
if (is_primop(value)) { if (isPrimop(value)) {
const meta = get_primop_metadata(value); const meta = getPrimopMetadata(value);
if (meta && meta.applied > 0) { if (meta && meta.applied > 0) {
return "<PRIMOP-APP>"; return "<PRIMOP-APP>";
} }
@@ -40,7 +40,7 @@ export const printValue = (value: NixValue, seen: WeakSet<object> = new WeakSet(
} }
if (typeof value === "object") { if (typeof value === "object") {
if (IS_CYCLE in value && (value as any)[IS_CYCLE] === true) { if (IS_CYCLE in value && (value as Record<symbol, unknown>)[IS_CYCLE] === true) {
return "«repeated»"; return "«repeated»";
} }
@@ -94,7 +94,7 @@ const printString = (s: string): string => {
result += c; result += c;
} }
} }
return result + '"'; return `${result}"`;
}; };
const SYMBOL_REGEX = /^[a-zA-Z_][a-zA-Z0-9_'-]*$/; const SYMBOL_REGEX = /^[a-zA-Z_][a-zA-Z0-9_'-]*$/;

View File

@@ -1,29 +1,4 @@
/** import type { NixStrictValue } from "./types";
* String Context System for Nix
*
* String context tracks references to store paths and derivations within strings.
* This is critical for Nix's dependency tracking - when a string containing a
* store path is used in a derivation, that store path becomes a build dependency.
*
* Context Elements (encoded as strings):
* - Opaque: Plain store path reference
* Format: "/nix/store/..."
* Example: "/nix/store/abc123-hello"
*
* - DrvDeep: Derivation with all outputs
* Format: "=/nix/store/...drv"
* Example: "=/nix/store/xyz789-hello.drv"
* Meaning: All outputs of this derivation and its closure
*
* - Built: Specific derivation output
* Format: "!<output>!/nix/store/...drv"
* Example: "!out!/nix/store/xyz789-hello.drv"
* Meaning: Specific output (e.g., "out", "dev", "lib") of this derivation
*
* This implementation matches Lix's NixStringContext system.
*/
import { NixStrictValue } from "./types";
export const HAS_CONTEXT = Symbol("HAS_CONTEXT"); export const HAS_CONTEXT = Symbol("HAS_CONTEXT");
@@ -172,22 +147,6 @@ export const parseContextToInfoMap = (context: NixStringContext): Map<string, Pa
return result; return result;
}; };
/**
* Extract input derivations and source paths from context
*
* IMPORTANT: Used by derivation builder to determine build dependencies.
*
* Returns:
* - inputDrvs: Map of derivation paths to their required output names
* - inputSrcs: Set of plain store paths (opaque) and drvDeep references
*
* Context type handling:
* - Opaque: Added to inputSrcs
* - DrvDeep: Computes FS closure (like Nix's computeFSClosure) - adds all paths
* in the dependency graph to inputSrcs, and all derivations with their
* outputs to inputDrvs
* - Built: Added to inputDrvs with specific output name
*/
export const extractInputDrvsAndSrcs = ( export const extractInputDrvsAndSrcs = (
context: NixStringContext, context: NixStringContext,
): { inputDrvs: Map<string, Set<string>>; inputSrcs: Set<string> } => { ): { inputDrvs: Map<string, Set<string>>; inputSrcs: Set<string> } => {

View File

@@ -1,17 +1,8 @@
/**
* Lazy evaluation system for nix-js
* Implements thunks for lazy evaluation of Nix expressions
*/
import type { NixValue, NixThunkInterface, NixStrictValue } from "./types";
import { HAS_CONTEXT } from "./string-context";
import { IS_PATH } from "./types";
import { isAttrs, isList } from "./builtins/type-check"; import { isAttrs, isList } from "./builtins/type-check";
import { HAS_CONTEXT } from "./string-context";
import type { NixStrictValue, NixThunkInterface, NixValue } from "./types";
import { IS_PATH } from "./types";
/**
* Symbol used to mark objects as thunks
* This is exported to Rust via Nix.IS_THUNK
*/
export const IS_THUNK = Symbol("is_thunk"); export const IS_THUNK = Symbol("is_thunk");
const forceStack: NixThunk[] = []; const forceStack: NixThunk[] = [];
@@ -31,7 +22,7 @@ export const DEBUG_THUNKS = { enabled: true };
* - Evaluated: func is undefined, result is defined * - Evaluated: func is undefined, result is defined
*/ */
export class NixThunk implements NixThunkInterface { export class NixThunk implements NixThunkInterface {
[key: symbol]: any; [key: symbol]: unknown;
readonly [IS_THUNK] = true as const; readonly [IS_THUNK] = true as const;
func: (() => NixValue) | undefined; func: (() => NixValue) | undefined;
result: NixStrictValue | undefined; result: NixStrictValue | undefined;
@@ -51,11 +42,6 @@ export class NixThunk implements NixThunkInterface {
} }
} }
/**
* Type guard to check if a value is a thunk
* @param value - Value to check
* @returns true if value is a NixThunk
*/
export const isThunk = (value: NixValue): value is NixThunkInterface => { export const isThunk = (value: NixValue): value is NixThunkInterface => {
return value !== null && typeof value === "object" && IS_THUNK in value && value[IS_THUNK] === true; return value !== null && typeof value === "object" && IS_THUNK in value && value[IS_THUNK] === true;
}; };
@@ -96,7 +82,7 @@ export const force = (value: NixValue): NixStrictValue => {
} }
const thunk = value as NixThunk; const thunk = value as NixThunk;
const func = thunk.func!; const func = thunk.func as () => NixValue;
thunk.func = undefined; thunk.func = undefined;
if (DEBUG_THUNKS.enabled) { if (DEBUG_THUNKS.enabled) {
@@ -126,24 +112,11 @@ export const force = (value: NixValue): NixStrictValue => {
} }
}; };
/**
* Create a new thunk from a function
* @param func - Function that produces a value when called
* @param label - Optional label for debugging
* @returns A new NixThunk wrapping the function
*/
export const createThunk = (func: () => NixValue, label?: string): NixThunkInterface => { export const createThunk = (func: () => NixValue, label?: string): NixThunkInterface => {
return new NixThunk(func, label); return new NixThunk(func, label);
}; };
/**
* Symbol to mark cyclic references detected during deep forcing
*/
export const IS_CYCLE = Symbol("is_cycle"); export const IS_CYCLE = Symbol("is_cycle");
/**
* Marker object for cyclic references
*/
export const CYCLE_MARKER = { [IS_CYCLE]: true }; export const CYCLE_MARKER = { [IS_CYCLE]: true };
/** /**

View File

@@ -1,27 +1,18 @@
/** import { isAttrs, isFunction, typeOf } from "./builtins/type-check";
* Type assertion helpers for runtime type checking import { force } from "./thunk";
* These functions force evaluation and verify the type, throwing errors on mismatch
*/
import type { import type {
NixValue,
NixList,
NixAttrs, NixAttrs,
NixFloat,
NixFunction, NixFunction,
NixInt, NixInt,
NixFloat, NixList,
NixNumber, NixNumber,
NixString,
NixPath, NixPath,
NixString,
NixValue,
} from "./types"; } from "./types";
import { isStringWithContext, isNixPath } from "./types"; import { isNixPath, isStringWithContext } from "./types";
import { force } from "./thunk";
import { isAttrs, isFunction, typeOf } from "./builtins/type-check";
/**
* Force a value and assert it's a list
* @throws TypeError if value is not a list after forcing
*/
export const forceList = (value: NixValue): NixList => { export const forceList = (value: NixValue): NixList => {
const forced = force(value); const forced = force(value);
if (!Array.isArray(forced)) { if (!Array.isArray(forced)) {
@@ -30,10 +21,6 @@ export const forceList = (value: NixValue): NixList => {
return forced; return forced;
}; };
/**
* Force a value and assert it's a function or functor
* @throws TypeError if value is not a function or functor after forcing
*/
export const forceFunction = (value: NixValue): NixFunction => { export const forceFunction = (value: NixValue): NixFunction => {
const forced = force(value); const forced = force(value);
if (isFunction(forced)) { if (isFunction(forced)) {
@@ -47,10 +34,6 @@ export const forceFunction = (value: NixValue): NixFunction => {
throw new TypeError(`Expected function, got ${typeOf(forced)}`); throw new TypeError(`Expected function, got ${typeOf(forced)}`);
}; };
/**
* Force a value and assert it's an attribute set
* @throws TypeError if value is not an attribute set after forcing
*/
export const forceAttrs = (value: NixValue): NixAttrs => { export const forceAttrs = (value: NixValue): NixAttrs => {
const forced = force(value); const forced = force(value);
if (!isAttrs(forced)) { if (!isAttrs(forced)) {
@@ -59,10 +42,6 @@ export const forceAttrs = (value: NixValue): NixAttrs => {
return forced; return forced;
}; };
/**
* Force a value and assert it's a string (plain or with context)
* @throws TypeError if value is not a string after forcing
*/
export const forceStringValue = (value: NixValue): string => { export const forceStringValue = (value: NixValue): string => {
const forced = force(value); const forced = force(value);
if (typeof forced === "string") { if (typeof forced === "string") {
@@ -74,10 +53,6 @@ export const forceStringValue = (value: NixValue): string => {
throw new TypeError(`Expected string, got ${typeOf(forced)}`); throw new TypeError(`Expected string, got ${typeOf(forced)}`);
}; };
/**
* Force a value and assert it's a string, returning NixString (preserving context)
* @throws TypeError if value is not a string after forcing
*/
export const forceString = (value: NixValue): NixString => { export const forceString = (value: NixValue): NixString => {
const forced = force(value); const forced = force(value);
if (typeof forced === "string") { if (typeof forced === "string") {
@@ -100,10 +75,6 @@ export const forceStringNoCtx = (value: NixValue): string => {
throw new TypeError(`Expected string, got ${typeOf(forced)}`); throw new TypeError(`Expected string, got ${typeOf(forced)}`);
}; };
/**
* Force a value and assert it's a boolean
* @throws TypeError if value is not a boolean after forcing
*/
export const forceBool = (value: NixValue): boolean => { export const forceBool = (value: NixValue): boolean => {
const forced = force(value); const forced = force(value);
if (typeof forced !== "boolean") { if (typeof forced !== "boolean") {
@@ -112,10 +83,6 @@ export const forceBool = (value: NixValue): boolean => {
return forced; return forced;
}; };
/**
* Force a value and extract int value
* @throws TypeError if value is not an int
*/
export const forceInt = (value: NixValue): NixInt => { export const forceInt = (value: NixValue): NixInt => {
const forced = force(value); const forced = force(value);
if (typeof forced === "bigint") { if (typeof forced === "bigint") {
@@ -124,10 +91,6 @@ export const forceInt = (value: NixValue): NixInt => {
throw new TypeError(`Expected int, got ${typeOf(forced)}`); throw new TypeError(`Expected int, got ${typeOf(forced)}`);
}; };
/**
* Force a value and extract float value
* @throws TypeError if value is not a float
*/
export const forceFloat = (value: NixValue): NixFloat => { export const forceFloat = (value: NixValue): NixFloat => {
const forced = force(value); const forced = force(value);
if (typeof forced === "number") { if (typeof forced === "number") {
@@ -136,10 +99,6 @@ export const forceFloat = (value: NixValue): NixFloat => {
throw new TypeError(`Expected float, got ${typeOf(forced)}`); throw new TypeError(`Expected float, got ${typeOf(forced)}`);
}; };
/**
* Force a value and extract numeric value (int or float)
* @throws TypeError if value is not a numeric type
*/
export const forceNumeric = (value: NixValue): NixNumber => { export const forceNumeric = (value: NixValue): NixNumber => {
const forced = force(value); const forced = force(value);
if (typeof forced === "bigint" || typeof forced === "number") { if (typeof forced === "bigint" || typeof forced === "number") {
@@ -148,28 +107,17 @@ export const forceNumeric = (value: NixValue): NixNumber => {
throw new TypeError(`Expected numeric type, got ${typeOf(forced)}`); throw new TypeError(`Expected numeric type, got ${typeOf(forced)}`);
}; };
/**
* Coerce two numeric values to a common type for arithmetic
* Rule: If either is float, convert both to float; otherwise keep as bigint
* @returns [a, b] tuple of coerced values
*/
export const coerceNumeric = (a: NixNumber, b: NixNumber): [NixFloat, NixFloat] | [NixInt, NixInt] => { export const coerceNumeric = (a: NixNumber, b: NixNumber): [NixFloat, NixFloat] | [NixInt, NixInt] => {
const aIsInt = typeof a === "bigint"; const aIsInt = typeof a === "bigint";
const bIsInt = typeof b === "bigint"; const bIsInt = typeof b === "bigint";
// If either is float, convert both to float
if (!aIsInt || !bIsInt) { if (!aIsInt || !bIsInt) {
return [Number(a), Number(b)]; return [Number(a), Number(b)];
} }
// Both are integers
return [a, b]; return [a, b];
}; };
/**
* Force a value and assert it's a path
* @throws TypeError if value is not a path after forcing
*/
export const forceNixPath = (value: NixValue): NixPath => { export const forceNixPath = (value: NixValue): NixPath => {
const forced = force(value); const forced = force(value);
if (isNixPath(forced)) { if (isNixPath(forced)) {

View File

@@ -1,12 +1,6 @@
/** import { HAS_CONTEXT, isStringWithContext, type StringWithContext } from "./string-context";
* Core TypeScript type definitions for nix-js runtime
*/
import { force, IS_THUNK } from "./thunk"; import { force, IS_THUNK } from "./thunk";
import { type StringWithContext, HAS_CONTEXT, isStringWithContext, getStringContext } from "./string-context";
import { op } from "./operators";
import { forceAttrs, forceStringNoCtx } from "./type-assert"; import { forceAttrs, forceStringNoCtx } from "./type-assert";
import { isString, typeOf } from "./builtins/type-check";
export { HAS_CONTEXT, isStringWithContext }; export { HAS_CONTEXT, isStringWithContext };
export type { StringWithContext }; export type { StringWithContext };
@@ -21,7 +15,6 @@ export const isNixPath = (v: NixStrictValue): v is NixPath => {
return typeof v === "object" && v !== null && IS_PATH in v; return typeof v === "object" && v !== null && IS_PATH in v;
}; };
// Nix primitive types
export type NixInt = bigint; export type NixInt = bigint;
export type NixFloat = number; export type NixFloat = number;
export type NixNumber = NixInt | NixFloat; export type NixNumber = NixInt | NixFloat;
@@ -29,7 +22,6 @@ export type NixBool = boolean;
export type NixString = string | StringWithContext; export type NixString = string | StringWithContext;
export type NixNull = null; export type NixNull = null;
// Nix composite types
export type NixList = NixValue[]; export type NixList = NixValue[];
// FIXME: reject contextful string // FIXME: reject contextful string
export type NixAttrs = { [key: string]: NixValue }; export type NixAttrs = { [key: string]: NixValue };
@@ -72,7 +64,7 @@ export const mkFunction = (
positions: Record<string, string>, positions: Record<string, string>,
ellipsis: boolean, ellipsis: boolean,
): NixFunction => { ): NixFunction => {
const func = f as NixFunction; const func: NixFunction = f;
func.args = new NixArgs(required, optional, positions, ellipsis); func.args = new NixArgs(required, optional, positions, ellipsis);
return func; return func;
}; };
@@ -90,7 +82,7 @@ export const mkAttrs = (attrs: NixAttrs, keys: NixValue[], values: NixValue[]):
return attrs; return attrs;
}; };
const ATTR_POSITIONS = Symbol("attrPositions"); export const ATTR_POSITIONS = Symbol("attrPositions");
export const mkAttrsWithPos = ( export const mkAttrsWithPos = (
attrs: NixAttrs, attrs: NixAttrs,
@@ -121,46 +113,14 @@ export const mkAttrsWithPos = (
return attrs; return attrs;
}; };
export { ATTR_POSITIONS };
/**
* Interface for lazy thunk values
* Thunks delay evaluation until forced
*/
export interface NixThunkInterface { export interface NixThunkInterface {
readonly [IS_THUNK]: true; readonly [IS_THUNK]: true;
func: (() => NixValue) | undefined; func: (() => NixValue) | undefined;
result: NixStrictValue | undefined; result: NixStrictValue | undefined;
} }
// Union of all Nix primitive types
export type NixPrimitive = NixNull | NixBool | NixInt | NixFloat | NixString; export type NixPrimitive = NixNull | NixBool | NixInt | NixFloat | NixString;
/**
* NixValue: Union type representing any possible Nix value
* This is the core type used throughout the runtime
*/
export type NixValue = NixPrimitive | NixPath | NixList | NixAttrs | NixFunction | NixThunkInterface; export type NixValue = NixPrimitive | NixPath | NixList | NixAttrs | NixFunction | NixThunkInterface;
export type NixStrictValue = Exclude<NixValue, NixThunkInterface>; export type NixStrictValue = Exclude<NixValue, NixThunkInterface>;
/**
* CatchableError: Error type thrown by `builtins.throw`
* This can be caught by `builtins.tryEval`
*/
export class CatchableError extends Error {} export class CatchableError extends Error {}
// Operator function signatures
export type BinaryOp<T = NixValue, U = NixValue, R = NixValue> = (a: T, b: U) => R;
export type UnaryOp<T = NixValue, R = NixValue> = (a: T) => R;
/**
* Curried function types - All Nix builtins must be curried!
*
* Examples:
* - add: Curried2<number, number, number> = (a) => (b) => a + b
* - map: Curried2<NixFunction, NixList, NixList> = (f) => (list) => list.map(f)
*/
export type Curried2<A, B, R> = (a: A) => (b: B) => R;
export type Curried3<A, B, C, R> = (a: A) => (b: B) => (c: C) => R;
export type Curried4<A, B, C, D, R> = (a: A) => (b: B) => (c: C) => (d: D) => R;

View File

@@ -1,5 +1,5 @@
import type { NixRuntime } from ".."; import type { NixRuntime } from "..";
import type { FetchTarballResult, FetchUrlResult, FetchGitResult, FetchHgResult } from "../builtins/io"; import type { FetchTarballResult, FetchUrlResult, FetchGitResult } from "../builtins/io";
declare global { declare global {
var Nix: NixRuntime; var Nix: NixRuntime;
@@ -20,17 +20,17 @@ declare global {
line: number | null; line: number | null;
column: number | null; column: number | null;
}; };
function op_make_store_path(ty: string, hash_hex: string, name: string): string; function op_make_store_path(ty: string, hashHex: string, name: string): string;
function op_parse_hash(hash_str: string, algo: string | null): { hex: string; algo: string }; function op_parse_hash(hashStr: string, algo: string | null): { hex: string; algo: string };
function op_make_fixed_output_path( function op_make_fixed_output_path(
hash_algo: string, hashAlgo: string,
hash: string, hash: string,
hash_mode: string, hashMode: string,
name: string, name: string,
): string; ): string;
function op_fetch_url( function op_fetch_url(
url: string, url: string,
expected_hash: string | null, expectedHash: string | null,
name: string | null, name: string | null,
executable: boolean, executable: boolean,
): FetchUrlResult; ): FetchUrlResult;
@@ -45,7 +45,7 @@ declare global {
rev: string | null, rev: string | null,
shallow: boolean, shallow: boolean,
submodules: boolean, submodules: boolean,
all_refs: boolean, allRefs: boolean,
name: string | null, name: string | null,
): FetchGitResult; ): FetchGitResult;
function op_add_path( function op_add_path(
@@ -56,9 +56,9 @@ declare global {
): string; ): string;
function op_store_path(path: string): string; function op_store_path(path: string): string;
function op_to_file(name: string, contents: string, references: string[]): string; function op_to_file(name: string, contents: string, references: string[]): string;
function op_write_derivation(drv_name: string, aterm: string, references: string[]): string; function op_write_derivation(drvName: string, aterm: string, references: string[]): string;
function op_read_derivation_outputs(drv_path: string): string[]; function op_read_derivation_outputs(drvPath: string): string[];
function op_compute_fs_closure(drv_path: string): { function op_compute_fs_closure(drvPath: string): {
input_drvs: [string, string[]][]; input_drvs: [string, string[]][];
input_srcs: string[]; input_srcs: string[];
}; };
@@ -70,12 +70,12 @@ declare global {
name: string | null, name: string | null,
recursive: boolean, recursive: boolean,
sha256: string | null, sha256: string | null,
include_paths: string[], includePaths: string[],
): string; ): string;
function op_match(regex: string, text: string): (string | null)[] | null; function op_match(regex: string, text: string): (string | null)[] | null;
function op_split(regex: string, text: string): (string | (string | null)[])[]; function op_split(regex: string, text: string): (string | (string | null)[])[];
function op_from_json(json: string): any; function op_from_json(json: string): unknown;
function op_from_toml(toml: string): any; function op_from_toml(toml: string): unknown;
} }
} }
} }