chore(runtime-ts): fix linter errors
This commit is contained in:
31
biome.json
31
biome.json
@@ -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": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
@@ -20,10 +20,37 @@
|
||||
"linter": {
|
||||
"rules": {
|
||||
"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": {
|
||||
"formatter": {
|
||||
"arrowParentheses": "always",
|
||||
|
||||
@@ -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 { coerceNumeric, forceInt, forceNumeric } from "../type-assert";
|
||||
import type { NixBool, NixInt, NixNumber, NixValue } from "../types";
|
||||
|
||||
export const add =
|
||||
(a: NixValue) =>
|
||||
(b: NixValue): bigint | number => {
|
||||
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 =
|
||||
(a: NixValue) =>
|
||||
(b: NixValue): bigint | number => {
|
||||
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 =
|
||||
(a: NixValue) =>
|
||||
(b: NixValue): bigint | number => {
|
||||
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 =
|
||||
@@ -36,10 +32,9 @@ export const div =
|
||||
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 =
|
||||
(a: NixValue) =>
|
||||
(b: NixValue): NixInt => {
|
||||
|
||||
@@ -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 { 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();
|
||||
|
||||
@@ -46,23 +42,23 @@ export const mapAttrs =
|
||||
export const removeAttrs =
|
||||
(attrs: NixValue) =>
|
||||
(list: NixValue): NixAttrs => {
|
||||
const new_attrs: NixAttrs = {};
|
||||
const forced_attrs = forceAttrs(attrs);
|
||||
const forced_list = forceList(list);
|
||||
const keys_to_remove = new Set(forced_list.map(forceStringValue));
|
||||
const newAttrs: NixAttrs = {};
|
||||
const forcedAttrs = forceAttrs(attrs);
|
||||
const forcedList = forceList(list);
|
||||
const keysToRemove = new Set(forcedList.map(forceStringValue));
|
||||
|
||||
for (const key in forced_attrs) {
|
||||
if (!keys_to_remove.has(key)) {
|
||||
new_attrs[key] = forced_attrs[key];
|
||||
for (const key in forcedAttrs) {
|
||||
if (!keysToRemove.has(key)) {
|
||||
newAttrs[key] = forcedAttrs[key];
|
||||
}
|
||||
}
|
||||
return new_attrs;
|
||||
return newAttrs;
|
||||
};
|
||||
|
||||
export const listToAttrs = (e: NixValue): NixAttrs => {
|
||||
const attrs: NixAttrs = {};
|
||||
const forced_e = [...forceList(e)].reverse();
|
||||
for (const obj of forced_e) {
|
||||
const forcedE = [...forceList(e)].reverse();
|
||||
for (const obj of forcedE) {
|
||||
const item = forceAttrs(obj);
|
||||
attrs[forceStringValue(item.name)] = item.value;
|
||||
}
|
||||
@@ -96,10 +92,10 @@ export const groupBy =
|
||||
(f: NixValue) =>
|
||||
(list: NixValue): NixAttrs => {
|
||||
const attrs: NixAttrs = {};
|
||||
const forced_f = forceFunction(f);
|
||||
const forced_list = forceList(list);
|
||||
for (const elem of forced_list) {
|
||||
const key = forceStringValue(forced_f(elem));
|
||||
const forcedF = forceFunction(f);
|
||||
const forcedList = forceList(list);
|
||||
for (const elem of forcedList) {
|
||||
const key = forceStringValue(forcedF(elem));
|
||||
if (!attrs[key]) attrs[key] = [];
|
||||
(attrs[key] as NixList).push(elem);
|
||||
}
|
||||
@@ -111,28 +107,22 @@ export const zipAttrsWith =
|
||||
(list: NixValue): NixValue => {
|
||||
const listForced = forceList(list);
|
||||
|
||||
// Map to collect all values for each attribute name
|
||||
const attrMap = new Map<string, NixValue[]>();
|
||||
|
||||
// Iterate through each attribute set in the list
|
||||
for (const item of listForced) {
|
||||
const attrs = forceAttrs(item);
|
||||
|
||||
// Collect all attribute names and their values
|
||||
for (const [key, value] of Object.entries(attrs)) {
|
||||
if (!attrMap.has(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> = {};
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -149,7 +139,9 @@ export const unsafeGetAttrPos =
|
||||
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)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
type NixStringContext,
|
||||
getStringValue,
|
||||
getStringContext,
|
||||
mkStringWithContext,
|
||||
decodeContextElem,
|
||||
getStringContext,
|
||||
getStringValue,
|
||||
mkStringWithContext,
|
||||
type NixStringContext,
|
||||
parseContextToInfoMap,
|
||||
} 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
|
||||
@@ -118,13 +118,13 @@ export const getContext = (value: NixValue): NixAttrs => {
|
||||
for (const [path, info] of infoMap) {
|
||||
const attrs: NixAttrs = {};
|
||||
if (info.path) {
|
||||
attrs["path"] = true;
|
||||
attrs.path = true;
|
||||
}
|
||||
if (info.allOutputs) {
|
||||
attrs["allOutputs"] = true;
|
||||
attrs.allOutputs = true;
|
||||
}
|
||||
if (info.outputs.length > 0) {
|
||||
attrs["outputs"] = info.outputs;
|
||||
attrs.outputs = info.outputs;
|
||||
}
|
||||
result[path] = attrs;
|
||||
}
|
||||
@@ -162,14 +162,14 @@ export const appendContext =
|
||||
const info = forceAttrs(infoVal);
|
||||
|
||||
if ("path" in info) {
|
||||
const pathVal = force(info["path"]);
|
||||
const pathVal = force(info.path);
|
||||
if (pathVal === true) {
|
||||
newContext.add(path);
|
||||
}
|
||||
}
|
||||
|
||||
if ("allOutputs" in info) {
|
||||
const allOutputs = force(info["allOutputs"]);
|
||||
const allOutputs = force(info.allOutputs);
|
||||
if (allOutputs === true) {
|
||||
if (!path.endsWith(".drv")) {
|
||||
throw new Error(
|
||||
@@ -181,7 +181,7 @@ export const appendContext =
|
||||
}
|
||||
|
||||
if ("outputs" in info) {
|
||||
const outputs = forceList(info["outputs"]);
|
||||
const outputs = forceList(info.outputs);
|
||||
if (outputs.length > 0 && !path.endsWith(".drv")) {
|
||||
throw new Error(
|
||||
`tried to add derivation output context of ${path}, which is not a derivation, to a string`,
|
||||
|
||||
@@ -2,12 +2,11 @@
|
||||
* Conversion and serialization builtin functions
|
||||
*/
|
||||
|
||||
import type { NixString, NixValue } from "../types";
|
||||
import { isStringWithContext, isNixPath } from "../types";
|
||||
import { force } from "../thunk";
|
||||
import { type NixStringContext, mkStringWithContext, addBuiltContext } from "../string-context";
|
||||
import { addBuiltContext, mkStringWithContext, type NixStringContext } from "../string-context";
|
||||
import { force, isThunk } from "../thunk";
|
||||
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";
|
||||
|
||||
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)}`);
|
||||
}
|
||||
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 => {
|
||||
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 => {
|
||||
@@ -33,7 +32,7 @@ export const toJSON = (e: NixValue): NixString => {
|
||||
return mkStringWithContext(string, context);
|
||||
};
|
||||
|
||||
export const toXML = (e: NixValue): never => {
|
||||
export const toXML = (_e: NixValue): never => {
|
||||
throw new Error("Not implemented: toXML");
|
||||
};
|
||||
|
||||
@@ -290,3 +289,82 @@ export const coerceToPath = (value: NixValue, outContext?: NixStringContext): st
|
||||
export const toStringFunc = (value: NixValue): NixString => {
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -1,40 +1,123 @@
|
||||
import type { NixValue, NixAttrs } from "../types";
|
||||
import { forceStringValue, forceList, forceStringNoCtx } from "../type-assert";
|
||||
import { force } from "../thunk";
|
||||
import {
|
||||
type DerivationData,
|
||||
type OutputInfo,
|
||||
generateAterm,
|
||||
generateAtermModulo,
|
||||
} from "../derivation-helpers";
|
||||
import { coerceToString, StringCoercionMode } from "./conversion";
|
||||
import {
|
||||
type NixStringContext,
|
||||
extractInputDrvsAndSrcs,
|
||||
isStringWithContext,
|
||||
mkStringWithContext,
|
||||
addDrvDeepContext,
|
||||
addBuiltContext,
|
||||
addDrvDeepContext,
|
||||
extractInputDrvsAndSrcs,
|
||||
mkStringWithContext,
|
||||
type NixStringContext,
|
||||
} from "../string-context";
|
||||
import { nixValueToJson } from "../conversion";
|
||||
import { isNixPath } from "../types";
|
||||
import { force } from "../thunk";
|
||||
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 forceAttrs = (value: NixValue): NixAttrs => {
|
||||
const forced = force(value);
|
||||
if (
|
||||
typeof forced !== "object" ||
|
||||
forced === null ||
|
||||
Array.isArray(forced) ||
|
||||
isStringWithContext(forced) ||
|
||||
isNixPath(forced)
|
||||
) {
|
||||
throw new TypeError(`Expected attribute set for derivation, got ${typeof forced}`);
|
||||
export interface OutputInfo {
|
||||
path: string;
|
||||
hashAlgo: string;
|
||||
hash: string;
|
||||
}
|
||||
return forced;
|
||||
|
||||
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}])`;
|
||||
};
|
||||
const validateName = (attrs: NixAttrs): string => {
|
||||
if (!("name" in attrs)) {
|
||||
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) => {
|
||||
if (output === "out") {
|
||||
return drvName
|
||||
}
|
||||
return `${drvName}-${output}`
|
||||
return drvName;
|
||||
}
|
||||
return `${drvName}-${output}`;
|
||||
};
|
||||
|
||||
const structuredAttrsExcludedKeys = new Set([
|
||||
"__structuredAttrs",
|
||||
@@ -124,9 +207,9 @@ const structuredAttrsExcludedKeys = new Set([
|
||||
|
||||
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 sortedObj: Record<string, any> = {};
|
||||
const sortedObj: Record<string, unknown> = {};
|
||||
for (const key of sortedKeys) {
|
||||
sortedObj[key] = obj[key];
|
||||
}
|
||||
@@ -143,7 +226,7 @@ const extractEnv = (
|
||||
const env = new Map<string, string>();
|
||||
|
||||
if (structuredAttrs) {
|
||||
const jsonAttrs: Record<string, any> = {};
|
||||
const jsonAttrs: Record<string, unknown> = {};
|
||||
for (const [key, value] of Object.entries(attrs)) {
|
||||
if (!structuredAttrsExcludedKeys.has(key)) {
|
||||
const forcedValue = force(value);
|
||||
@@ -419,6 +502,6 @@ export const derivationStrict = (args: NixValue): NixAttrs => {
|
||||
return result;
|
||||
};
|
||||
|
||||
export const derivation = (_: NixValue): NixAttrs => {
|
||||
throw new Error("unreachable: placeholder derivation implementation called")
|
||||
export const derivationStub = (_: NixValue): NixAttrs => {
|
||||
throw new Error("unreachable: stub derivation implementation called");
|
||||
};
|
||||
|
||||
17
nix-js/runtime-ts/src/builtins/flake.ts
Normal file
17
nix-js/runtime-ts/src/builtins/flake.ts
Normal 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");
|
||||
};
|
||||
@@ -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 { force } from "../thunk";
|
||||
import { CatchableError, type NixValue } from "../types";
|
||||
import { coerceToString, StringCoercionMode } from "./conversion";
|
||||
import { isAttrs } from "./type-check";
|
||||
|
||||
export const seq =
|
||||
|
||||
19
nix-js/runtime-ts/src/builtins/hash.ts
Normal file
19
nix-js/runtime-ts/src/builtins/hash.ts
Normal 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");
|
||||
};
|
||||
@@ -1,81 +1,50 @@
|
||||
/**
|
||||
* 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 { createThunk, force } from "../thunk";
|
||||
import type { NixValue } from "../types";
|
||||
import { createThunk, force, isThunk } from "../thunk";
|
||||
import { getTos } from "../helpers";
|
||||
import * as arithmetic from "./arithmetic";
|
||||
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");
|
||||
|
||||
/**
|
||||
* Metadata interface for primop functions
|
||||
*/
|
||||
export interface PrimopMetadata {
|
||||
/** The name of the primop (e.g., "add", "map") */
|
||||
name: string;
|
||||
/** Total arity of the function (number of arguments it expects) */
|
||||
arity: number;
|
||||
/** Number of arguments already applied (for partial applications) */
|
||||
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 = (
|
||||
func: (...args: NixValue[]) => NixValue,
|
||||
name: string,
|
||||
arity: number,
|
||||
applied: number = 0,
|
||||
): Function => {
|
||||
// Mark this function as a primop
|
||||
(func as any)[PRIMOP_METADATA] = {
|
||||
): ((...args: NixValue[]) => NixValue) => {
|
||||
(func as unknown as Record<symbol, unknown>)[PRIMOP_METADATA] = {
|
||||
name,
|
||||
arity,
|
||||
applied,
|
||||
} satisfies PrimopMetadata;
|
||||
|
||||
// If this is a curried function and not fully applied,
|
||||
// wrap it to mark the next layer too
|
||||
if (applied < arity - 1) {
|
||||
const wrappedFunc = ((...args: NixValue[]) => {
|
||||
const result = func(...args);
|
||||
// If result is a function, mark it as the next layer
|
||||
if (typeof result === "function") {
|
||||
return mkPrimop(result, name, arity, applied + args.length);
|
||||
}
|
||||
return result;
|
||||
}) as any;
|
||||
}) as (...args: NixValue[]) => NixValue;
|
||||
|
||||
// Copy the primop metadata to the wrapper
|
||||
wrappedFunc[PRIMOP_METADATA] = {
|
||||
(wrappedFunc as unknown as Record<symbol, unknown>)[PRIMOP_METADATA] = {
|
||||
name,
|
||||
arity,
|
||||
applied,
|
||||
@@ -87,12 +56,9 @@ export const mkPrimop = (
|
||||
return func;
|
||||
};
|
||||
|
||||
/**
|
||||
* Type guard to check if a value is a primop
|
||||
* @param value - Value to check
|
||||
* @returns true if value is marked as a primop
|
||||
*/
|
||||
export const is_primop = (value: unknown): value is Function & { [PRIMOP_METADATA]: PrimopMetadata } => {
|
||||
export const isPrimop = (
|
||||
value: unknown,
|
||||
): value is ((...args: never[]) => unknown) & { [PRIMOP_METADATA]: PrimopMetadata } => {
|
||||
return (
|
||||
typeof value === "function" &&
|
||||
PRIMOP_METADATA in value &&
|
||||
@@ -101,29 +67,14 @@ export const is_primop = (value: unknown): value is Function & { [PRIMOP_METADAT
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get primop metadata from a function
|
||||
* @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)) {
|
||||
export const getPrimopMetadata = (func: unknown): PrimopMetadata | undefined => {
|
||||
if (isPrimop(func)) {
|
||||
return func[PRIMOP_METADATA];
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* 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 = {
|
||||
export const builtins: Record<string, NixValue> = {
|
||||
add: mkPrimop(arithmetic.add, "add", 2),
|
||||
sub: mkPrimop(arithmetic.sub, "sub", 2),
|
||||
mul: mkPrimop(arithmetic.mul, "mul", 2),
|
||||
@@ -193,7 +144,7 @@ export const builtins: any = {
|
||||
warn: mkPrimop(functional.warn, "warn", 2),
|
||||
break: mkPrimop(functional.breakFunc, "break", 1),
|
||||
|
||||
derivation: undefined as any,
|
||||
derivation: mkPrimop(derivation.derivationStub, "derivation", 1),
|
||||
derivationStrict: mkPrimop(derivation.derivationStrict, "derivationStrict", 1),
|
||||
|
||||
import: mkPrimop(io.importFunc, "import", 1),
|
||||
@@ -221,13 +172,19 @@ export const builtins: any = {
|
||||
toXML: mkPrimop(conversion.toXML, "toXML", 1),
|
||||
toString: mkPrimop(conversion.toStringFunc, "toString", 1),
|
||||
|
||||
hashFile: mkPrimop(hash.hashFile, "hashFile", 2),
|
||||
hashString: mkPrimop(hash.hashString, "hashString", 2),
|
||||
convertHash: mkPrimop(hash.convertHash, "convertHash", 2),
|
||||
|
||||
flakeRefToString: mkPrimop(flake.flakeRefToString, "flakeRefToString", 1),
|
||||
getFlake: mkPrimop(flake.getFlake, "getFlake", 1),
|
||||
parseFlakeName: mkPrimop(flake.parseFlakeName, "parseFlakeName", 1),
|
||||
parseFlakeRef: mkPrimop(flake.parseFlakeRef, "parseFlakeRef", 1),
|
||||
|
||||
addErrorContext: mkPrimop(misc.addErrorContext, "addErrorContext", 1),
|
||||
appendContext: mkPrimop(misc.appendContext, "appendContext", 1),
|
||||
getContext: mkPrimop(misc.getContext, "getContext", 1),
|
||||
hasContext: mkPrimop(misc.hasContext, "hasContext", 1),
|
||||
hashFile: mkPrimop(misc.hashFile, "hashFile", 2),
|
||||
hashString: mkPrimop(misc.hashString, "hashString", 2),
|
||||
convertHash: mkPrimop(misc.convertHash, "convertHash", 2),
|
||||
unsafeDiscardOutputDependency: mkPrimop(
|
||||
misc.unsafeDiscardOutputDependency,
|
||||
"unsafeDiscardOutputDependency",
|
||||
@@ -236,14 +193,10 @@ export const builtins: any = {
|
||||
unsafeDiscardStringContext: mkPrimop(misc.unsafeDiscardStringContext, "unsafeDiscardStringContext", 1),
|
||||
addDrvOutputDependencies: mkPrimop(misc.addDrvOutputDependencies, "addDrvOutputDependencies", 2),
|
||||
compareVersions: mkPrimop(misc.compareVersions, "compareVersions", 2),
|
||||
flakeRefToString: mkPrimop(misc.flakeRefToString, "flakeRefToString", 1),
|
||||
functionArgs: mkPrimop(misc.functionArgs, "functionArgs", 1),
|
||||
genericClosure: mkPrimop(misc.genericClosure, "genericClosure", 1),
|
||||
getFlake: mkPrimop(misc.getFlake, "getFlake", 1),
|
||||
outputOf: mkPrimop(misc.outputOf, "outputOf", 2),
|
||||
parseDrvName: mkPrimop(misc.parseDrvName, "parseDrvName", 1),
|
||||
parseFlakeName: mkPrimop(misc.parseFlakeName, "parseFlakeName", 1),
|
||||
parseFlakeRef: mkPrimop(misc.parseFlakeRef, "parseFlakeRef", 1),
|
||||
placeholder: mkPrimop(misc.placeholder, "placeholder", 1),
|
||||
replaceStrings: mkPrimop(misc.replaceStrings, "replaceStrings", 3),
|
||||
splitVersion: mkPrimop(misc.splitVersion, "splitVersion", 1),
|
||||
@@ -263,10 +216,7 @@ export const builtins: any = {
|
||||
langVersion: 6,
|
||||
nixPath: [],
|
||||
nixVersion: "2.31.2",
|
||||
storeDir: "INVALID_PATH",
|
||||
|
||||
__traceCaller: (e: NixValue) => {
|
||||
console.log(`traceCaller: ${getTos()}`);
|
||||
return e;
|
||||
},
|
||||
storeDir: createThunk(() => {
|
||||
throw new Error("stub storeDir evaluated");
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
/**
|
||||
* I/O and filesystem builtin functions
|
||||
* Implemented via Rust ops exposed through deno_core
|
||||
*/
|
||||
|
||||
import { getPathValue } from "../path";
|
||||
import type { NixStringContext, StringWithContext } from "../string-context";
|
||||
import { addOpaqueContext, mkStringWithContext } from "../string-context";
|
||||
import { force } from "../thunk";
|
||||
import {
|
||||
forceAttrs,
|
||||
forceBool,
|
||||
@@ -11,15 +10,11 @@ import {
|
||||
forceStringNoCtx,
|
||||
forceStringValue,
|
||||
} from "../type-assert";
|
||||
import type { NixValue, NixAttrs, NixPath, NixString } from "../types";
|
||||
import { isNixPath, IS_PATH, CatchableError } from "../types";
|
||||
import { force } from "../thunk";
|
||||
import type { NixAttrs, NixPath, NixString, NixValue } from "../types";
|
||||
import { CatchableError, IS_PATH, isNixPath } from "../types";
|
||||
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 { isAttrs, isPath } from "./type-check";
|
||||
|
||||
const importCache = new Map<string, NixValue>();
|
||||
|
||||
@@ -84,7 +79,7 @@ export const storePath = (pathArg: NixValue): StringWithContext => {
|
||||
return mkStringWithContext(validatedPath, context);
|
||||
};
|
||||
|
||||
export const fetchClosure = (args: NixValue): never => {
|
||||
export const fetchClosure = (_args: NixValue): never => {
|
||||
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 nameRaw = "name" in forced ? forceStringNoCtx(forced.name) : undefined;
|
||||
// FIXME: extract baseNameOfRaw
|
||||
const name = nameRaw === "" ? baseNameOf(nameRaw) as string : nameRaw;
|
||||
const name = nameRaw === "" ? (baseNameOf(nameRaw) as string) : nameRaw;
|
||||
return { url, sha256, name };
|
||||
} else {
|
||||
return { url: forceStringNoCtx(forced) };
|
||||
@@ -145,11 +140,11 @@ const normalizeTarballInput = (args: NixValue): { url: string; sha256?: string;
|
||||
|
||||
const resolvePseudoUrl = (url: string) => {
|
||||
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 {
|
||||
return url
|
||||
}
|
||||
return url;
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchurl = (args: NixValue): NixString => {
|
||||
const { url, hash, name, executable } = normalizeUrlInput(args);
|
||||
@@ -224,7 +219,7 @@ export const fetchGit = (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 => {
|
||||
@@ -249,7 +244,6 @@ export const fetchTree = (args: NixValue): NixAttrs => {
|
||||
case "gitlab":
|
||||
case "sourcehut":
|
||||
return fetchGitForge(type, attrs);
|
||||
case "auto":
|
||||
default:
|
||||
return autoDetectAndFetch(attrs);
|
||||
}
|
||||
@@ -375,7 +369,7 @@ export const path = (args: NixValue): NixString => {
|
||||
|
||||
const includePaths: string[] = [];
|
||||
for (const [relPath, fileType] of entries) {
|
||||
const fullPath = pathStr + "/" + relPath;
|
||||
const fullPath = `${pathStr}/${relPath}`;
|
||||
const innerFn = forceFunction(filterFn(fullPath));
|
||||
const shouldInclude = force(innerFn(fileType));
|
||||
if (shouldInclude === true) {
|
||||
@@ -383,13 +377,7 @@ export const path = (args: NixValue): NixString => {
|
||||
}
|
||||
}
|
||||
|
||||
storePath = Deno.core.ops.op_add_filtered_path(
|
||||
pathStr,
|
||||
name,
|
||||
recursive,
|
||||
sha256,
|
||||
includePaths,
|
||||
);
|
||||
storePath = Deno.core.ops.op_add_filtered_path(pathStr, name, recursive, sha256, includePaths);
|
||||
} else {
|
||||
storePath = Deno.core.ops.op_add_path(pathStr, name, recursive, sha256);
|
||||
}
|
||||
@@ -421,11 +409,9 @@ export const toFile =
|
||||
return mkStringWithContext(storePath, new Set([storePath]));
|
||||
};
|
||||
|
||||
export const toPath = (name: NixValue, s: NixValue): never => {
|
||||
throw new Error("Not implemented: toPath");
|
||||
};
|
||||
|
||||
export const filterSource = (args: NixValue): never => {
|
||||
export const filterSource =
|
||||
(_filter: NixValue) =>
|
||||
(_path: NixValue): never => {
|
||||
throw new Error("Not implemented: filterSource");
|
||||
};
|
||||
|
||||
|
||||
@@ -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 { force } from "../thunk";
|
||||
import { forceBool, forceFunction, forceInt, forceList } from "../type-assert";
|
||||
import type { NixAttrs, NixList, NixValue } from "../types";
|
||||
|
||||
export const map =
|
||||
(f: NixValue) =>
|
||||
@@ -74,23 +69,23 @@ export const concatMap =
|
||||
};
|
||||
|
||||
export const foldlPrime =
|
||||
(op_fn: NixValue) =>
|
||||
(opFn: NixValue) =>
|
||||
(nul: NixValue) =>
|
||||
(list: NixValue): NixValue => {
|
||||
const forced_op = forceFunction(op_fn);
|
||||
const forcedOp = forceFunction(opFn);
|
||||
return forceList(list).reduce((acc: NixValue, cur: NixValue) => {
|
||||
return forceFunction(forced_op(acc))(cur);
|
||||
return forceFunction(forcedOp(acc))(cur);
|
||||
}, nul);
|
||||
};
|
||||
|
||||
export const sort =
|
||||
(cmp: NixValue) =>
|
||||
(list: NixValue): NixList => {
|
||||
const forced_list = [...forceList(list)];
|
||||
const forced_cmp = forceFunction(cmp);
|
||||
return forced_list.sort((a, b) => {
|
||||
if (force(forceFunction(forced_cmp(a))(b))) return -1;
|
||||
if (force(forceFunction(forced_cmp(b))(a))) return 1;
|
||||
const forcedList = [...forceList(list)];
|
||||
const forcedCmp = forceFunction(cmp);
|
||||
return forcedList.sort((a, b) => {
|
||||
if (force(forceFunction(forcedCmp(a))(b))) return -1;
|
||||
if (force(forceFunction(forcedCmp(b))(a))) return 1;
|
||||
return 0;
|
||||
});
|
||||
};
|
||||
@@ -98,14 +93,14 @@ export const sort =
|
||||
export const partition =
|
||||
(pred: NixValue) =>
|
||||
(list: NixValue): NixAttrs => {
|
||||
const forced_list = forceList(list);
|
||||
const forced_pred = forceFunction(pred);
|
||||
const forcedList = forceList(list);
|
||||
const forcedPred = forceFunction(pred);
|
||||
const attrs = {
|
||||
right: [] as NixList,
|
||||
wrong: [] as NixList,
|
||||
};
|
||||
for (const elem of forced_list) {
|
||||
if (force(forced_pred(elem))) {
|
||||
for (const elem of forcedList) {
|
||||
if (force(forcedPred(elem))) {
|
||||
attrs.right.push(elem);
|
||||
} else {
|
||||
attrs.wrong.push(elem);
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
/**
|
||||
* Math builtin functions
|
||||
*/
|
||||
|
||||
import type { NixValue } from "../types";
|
||||
import { forceNumeric } from "../type-assert";
|
||||
import type { NixValue } from "../types";
|
||||
|
||||
export const ceil = (x: NixValue): bigint => {
|
||||
const val = forceNumeric(x);
|
||||
|
||||
@@ -1,31 +1,27 @@
|
||||
/**
|
||||
* Miscellaneous builtin functions
|
||||
*/
|
||||
|
||||
import { force } from "../thunk";
|
||||
import { CatchableError, ATTR_POSITIONS } from "../types";
|
||||
import type { NixAttrs, NixBool, NixStrictValue, NixValue } from "../types";
|
||||
import { OrderedSet } from "js-sdsl";
|
||||
import { compareValues } from "../operators";
|
||||
import {
|
||||
getStringContext,
|
||||
getStringValue,
|
||||
mkStringWithContext,
|
||||
type NixStringContext,
|
||||
} from "../string-context";
|
||||
import { force } from "../thunk";
|
||||
import {
|
||||
forceList,
|
||||
forceAttrs,
|
||||
forceFunction,
|
||||
forceStringValue,
|
||||
forceList,
|
||||
forceString,
|
||||
forceStringNoCtx,
|
||||
forceStringValue,
|
||||
} from "../type-assert";
|
||||
import type { NixAttrs, NixBool, NixStrictValue, NixValue } from "../types";
|
||||
import { ATTR_POSITIONS, CatchableError } from "../types";
|
||||
import * as context from "./context";
|
||||
import { compareValues } from "../operators";
|
||||
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 =
|
||||
(e1: NixValue) =>
|
||||
(_e1: NixValue) =>
|
||||
(e2: NixValue): NixValue => {
|
||||
// FIXME:
|
||||
// console.log("[WARNING]: addErrorContext not implemented");
|
||||
@@ -38,23 +34,6 @@ export const getContext = context.getContext;
|
||||
|
||||
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 unsafeDiscardStringContext = context.unsafeDiscardStringContext;
|
||||
@@ -77,9 +56,9 @@ export const compareVersions =
|
||||
i1 = c1.nextIndex;
|
||||
i2 = c2.nextIndex;
|
||||
|
||||
if (componentsLT(c1.component, c2.component)) {
|
||||
if (componentsLt(c1.component, c2.component)) {
|
||||
return -1n;
|
||||
} else if (componentsLT(c2.component, c1.component)) {
|
||||
} else if (componentsLt(c2.component, c1.component)) {
|
||||
return 1n;
|
||||
}
|
||||
}
|
||||
@@ -121,7 +100,7 @@ function nextComponent(s: string, startIdx: number): ComponentResult {
|
||||
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 n2 = c2.match(/^[0-9]+$/) ? BigInt(c2) : null;
|
||||
|
||||
@@ -155,25 +134,17 @@ function componentsLT(c1: string, c2: string): boolean {
|
||||
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 => {
|
||||
const func = forceFunction(f);
|
||||
if (func.args) {
|
||||
const ret: NixAttrs = {};
|
||||
for (const key of func.args!.required) {
|
||||
for (const key of func.args.required) {
|
||||
ret[key] = false;
|
||||
}
|
||||
for (const key of func.args!.optional) {
|
||||
for (const key of func.args.optional) {
|
||||
ret[key] = true;
|
||||
}
|
||||
const positions = func.args!.positions;
|
||||
const positions = func.args.positions;
|
||||
if (positions && Object.keys(positions).length > 0) {
|
||||
Object.defineProperty(ret, ATTR_POSITIONS, {
|
||||
value: positions,
|
||||
@@ -235,13 +206,9 @@ export const genericClosure = (args: NixValue): NixValue => {
|
||||
return resultList;
|
||||
};
|
||||
|
||||
export const getFlake = (attrs: NixValue): never => {
|
||||
throw new Error("Not implemented: getFlake");
|
||||
};
|
||||
|
||||
export const outputOf =
|
||||
(drv: NixValue) =>
|
||||
(out: NixValue): never => {
|
||||
(_drv: NixValue) =>
|
||||
(_out: NixValue): never => {
|
||||
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 => {
|
||||
const outputStr = forceStringNoCtx(output);
|
||||
return Deno.core.ops.op_make_placeholder(outputStr);
|
||||
@@ -314,7 +273,7 @@ export const replaceStrings =
|
||||
resultContext.add(elem);
|
||||
}
|
||||
}
|
||||
const replacement = toCache.get(i)!;
|
||||
const replacement = toCache.get(i) as string;
|
||||
|
||||
result += replacement;
|
||||
|
||||
@@ -361,7 +320,7 @@ export const splitVersion = (s: NixValue): NixValue => {
|
||||
return components;
|
||||
};
|
||||
|
||||
export const traceVerbose = (e1: NixValue, e2: NixValue): never => {
|
||||
export const traceVerbose = (_e1: NixValue, _e2: NixValue): never => {
|
||||
throw new Error("Not implemented: traceVerbose");
|
||||
};
|
||||
|
||||
|
||||
@@ -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 { coerceToString, StringCoercionMode, coerceToPath } from "./conversion";
|
||||
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
|
||||
|
||||
@@ -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 {
|
||||
type NixStringContext,
|
||||
getStringValue,
|
||||
getStringContext,
|
||||
getStringValue,
|
||||
mkStringWithContext,
|
||||
type NixStringContext,
|
||||
} 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);
|
||||
|
||||
/**
|
||||
* 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 =
|
||||
(start: NixValue) =>
|
||||
(len: NixValue) =>
|
||||
@@ -55,13 +41,6 @@ export const substring =
|
||||
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 =
|
||||
(sep: NixValue) =>
|
||||
(list: NixValue): NixString => {
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
/**
|
||||
* Type checking builtin functions
|
||||
*/
|
||||
|
||||
import {
|
||||
HAS_CONTEXT,
|
||||
isNixPath,
|
||||
isStringWithContext,
|
||||
type NixPath,
|
||||
type NixAttrs,
|
||||
type NixBool,
|
||||
type NixFloat,
|
||||
@@ -14,14 +9,11 @@ import {
|
||||
type NixInt,
|
||||
type NixList,
|
||||
type NixNull,
|
||||
type NixString,
|
||||
type NixPath,
|
||||
type NixStrictValue,
|
||||
type NixString,
|
||||
} 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 => {
|
||||
return typeof v === "string" || isStringWithContext(v);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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}])`;
|
||||
};
|
||||
@@ -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 { type NixStringContext, mkStringWithContext, isStringWithContext } from "./string-context";
|
||||
import { force } from "./thunk";
|
||||
import { isAttrs, typeOf } from "./builtins/type-check";
|
||||
import { mkPath } from "./path";
|
||||
import { isStringWithContext, mkStringWithContext, type NixStringContext } from "./string-context";
|
||||
import { force } from "./thunk";
|
||||
import { forceAttrs, forceBool, forceFunction, forceStringValue } from "./type-assert";
|
||||
import type { NixAttrs, NixBool, NixPath, NixString, NixValue } from "./types";
|
||||
import { CatchableError, isNixPath } from "./types";
|
||||
|
||||
interface StackFrame {
|
||||
@@ -36,34 +32,17 @@ function enrichError(error: unknown): Error {
|
||||
return err;
|
||||
}
|
||||
|
||||
export const getTos = (): string => {
|
||||
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 => {
|
||||
const pushContext = (message: string, span: string): void => {
|
||||
if (callStack.length >= MAX_STACK_DEPTH) {
|
||||
callStack.shift();
|
||||
}
|
||||
callStack.push({ span, message });
|
||||
};
|
||||
|
||||
/**
|
||||
* Pop an error context from the stack
|
||||
*/
|
||||
export const popContext = (): void => {
|
||||
const popContext = (): void => {
|
||||
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 => {
|
||||
pushContext(message, span);
|
||||
try {
|
||||
@@ -149,13 +128,6 @@ export const concatStringsWithContext = (parts: NixValue[], forceString: boolean
|
||||
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 => {
|
||||
const forced = force(path);
|
||||
let pathStr: string;
|
||||
@@ -181,18 +153,18 @@ export const select = (obj: NixValue, attrpath: NixValue[], span?: string): NixV
|
||||
}
|
||||
callStack.push({ span, message });
|
||||
try {
|
||||
return select_impl(obj, attrpath);
|
||||
return selectImpl(obj, attrpath);
|
||||
} catch (error) {
|
||||
throw enrichError(error);
|
||||
} finally {
|
||||
callStack.pop();
|
||||
}
|
||||
} 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);
|
||||
|
||||
for (const attr of attrpath.slice(0, -1)) {
|
||||
@@ -214,7 +186,7 @@ function select_impl(obj: NixValue, attrpath: NixValue[]): NixValue {
|
||||
export const selectWithDefault = (
|
||||
obj: NixValue,
|
||||
attrpath: NixValue[],
|
||||
default_val: NixValue,
|
||||
defaultVal: NixValue,
|
||||
span?: string,
|
||||
): NixValue => {
|
||||
if (span) {
|
||||
@@ -227,18 +199,18 @@ export const selectWithDefault = (
|
||||
}
|
||||
callStack.push({ span, message });
|
||||
try {
|
||||
return selectWithDefault_impl(obj, attrpath, default_val);
|
||||
return selectWithDefaultImpl(obj, attrpath, defaultVal);
|
||||
} catch (error) {
|
||||
throw enrichError(error);
|
||||
} finally {
|
||||
callStack.pop();
|
||||
}
|
||||
} 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);
|
||||
if (!isAttrs(attrs)) {
|
||||
return defaultVal;
|
||||
@@ -281,49 +253,6 @@ export const hasAttr = (obj: NixValue, attrpath: NixValue[]): NixBool => {
|
||||
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 => {
|
||||
if (span) {
|
||||
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" });
|
||||
try {
|
||||
return call_impl(func, arg);
|
||||
return callImpl(func, arg);
|
||||
} catch (error) {
|
||||
throw enrichError(error);
|
||||
} finally {
|
||||
callStack.pop();
|
||||
}
|
||||
} 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);
|
||||
if (typeof forcedFunc === "function") {
|
||||
forcedFunc.args?.check(arg);
|
||||
|
||||
@@ -4,45 +4,39 @@
|
||||
* 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 { 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 { 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 { ATTR_POSITIONS, IS_PATH, mkAttrs, mkAttrsWithPos, mkFunction, type NixValue } from "./types";
|
||||
|
||||
export type NixRuntime = typeof Nix;
|
||||
|
||||
const replBindings: Record<string, NixValue> = {};
|
||||
|
||||
/**
|
||||
* The global Nix runtime object
|
||||
*/
|
||||
export const Nix = {
|
||||
createThunk,
|
||||
force,
|
||||
@@ -62,7 +56,6 @@ export const Nix = {
|
||||
select,
|
||||
selectWithDefault,
|
||||
lookupWith,
|
||||
validateParams,
|
||||
resolvePath,
|
||||
coerceToString,
|
||||
concatStringsWithContext,
|
||||
@@ -73,8 +66,6 @@ export const Nix = {
|
||||
mkPos,
|
||||
ATTR_POSITIONS,
|
||||
|
||||
pushContext,
|
||||
popContext,
|
||||
withContext,
|
||||
|
||||
op,
|
||||
|
||||
@@ -1,22 +1,17 @@
|
||||
/**
|
||||
* Nix operators module
|
||||
* Implements all binary and unary operators used by codegen
|
||||
*/
|
||||
|
||||
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 { coerceToString, StringCoercionMode } from "./builtins/conversion";
|
||||
import { isNixString, typeOf } from "./builtins/type-check";
|
||||
import { mkPath } from "./path";
|
||||
import {
|
||||
type NixStringContext,
|
||||
getStringValue,
|
||||
getStringContext,
|
||||
getStringValue,
|
||||
mergeContexts,
|
||||
mkStringWithContext,
|
||||
type NixStringContext,
|
||||
} from "./string-context";
|
||||
import { coerceToString, StringCoercionMode } from "./builtins/conversion";
|
||||
import { mkPath } from "./path";
|
||||
import { typeOf, isNixString } from "./builtins/type-check";
|
||||
import { force } from "./thunk";
|
||||
import { coerceNumeric, forceAttrs, forceBool, forceList, forceNumeric } from "./type-assert";
|
||||
import type { NixAttrs, NixList, NixPath, NixString, NixValue } from "./types";
|
||||
import { isNixPath } from "./types";
|
||||
|
||||
const canCoerceToString = (v: NixValue): boolean => {
|
||||
const forced = force(v);
|
||||
@@ -27,11 +22,6 @@ const canCoerceToString = (v: NixValue): boolean => {
|
||||
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 => {
|
||||
const av = force(a);
|
||||
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)}`);
|
||||
}
|
||||
|
||||
// Int and float comparison
|
||||
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") {
|
||||
const strA = getStringValue(av as NixString);
|
||||
const strB = getStringValue(bv as NixString);
|
||||
return strA < strB ? -1 : strA > strB ? 1 : 0;
|
||||
}
|
||||
|
||||
// Path comparison
|
||||
if (typeA === "path") {
|
||||
const aPath = av as NixPath;
|
||||
const bPath = bv as NixPath;
|
||||
return aPath.value < bPath.value ? -1 : aPath.value > bPath.value ? 1 : 0;
|
||||
}
|
||||
|
||||
// List comparison (lexicographic)
|
||||
if (typeA === "list") {
|
||||
const aList = av as NixList;
|
||||
const bList = bv as NixList;
|
||||
for (let i = 0; ; i++) {
|
||||
// Equal if same length, else aList > bList
|
||||
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) {
|
||||
return -1; // aList < bList
|
||||
} 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 = {
|
||||
add: (a: NixValue, b: NixValue): bigint | number | NixString | NixPath => {
|
||||
const av = force(a);
|
||||
@@ -109,15 +92,14 @@ export const op = {
|
||||
const strB = getStringValue(bv);
|
||||
const ctxB = getStringContext(bv);
|
||||
|
||||
// Lix constraint: cannot append string with store context to path
|
||||
if (ctxB.size > 0) {
|
||||
throw new TypeError("a string that refers to a store path cannot be appended to a path");
|
||||
}
|
||||
|
||||
// Concatenate paths
|
||||
return mkPath(av.value + strB);
|
||||
}
|
||||
|
||||
// FIXME: handle corepkgs
|
||||
// path + path: concatenate
|
||||
if (isNixPath(bv)) {
|
||||
return mkPath(av.value + bv.value);
|
||||
@@ -138,6 +120,7 @@ export const op = {
|
||||
|
||||
// String concatenation
|
||||
if (isNixString(av) && isNixString(bv)) {
|
||||
// Merge string context
|
||||
const strA = getStringValue(av);
|
||||
const strB = getStringValue(bv);
|
||||
const ctxA = getStringContext(av);
|
||||
@@ -162,19 +145,19 @@ export const op = {
|
||||
return mkStringWithContext(result, context);
|
||||
}
|
||||
|
||||
// Numeric addition
|
||||
// Perform numeric addition otherwise
|
||||
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 => {
|
||||
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 => {
|
||||
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 => {
|
||||
@@ -184,7 +167,7 @@ export const op = {
|
||||
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 => {
|
||||
@@ -202,7 +185,6 @@ export const op = {
|
||||
return av === Number(bv);
|
||||
}
|
||||
|
||||
// Get type names for comparison (skip if already handled above)
|
||||
const typeA = typeOf(av);
|
||||
const typeB = typeOf(bv);
|
||||
|
||||
@@ -223,10 +205,12 @@ export const op = {
|
||||
return (av as NixPath).value === (bv as NixPath).value;
|
||||
}
|
||||
|
||||
if (Array.isArray(av) && Array.isArray(bv)) {
|
||||
if (av.length !== bv.length) return false;
|
||||
for (let i = 0; i < av.length; i++) {
|
||||
if (!op.eq(av[i], bv[i])) return false;
|
||||
if (typeA === "list") {
|
||||
const aList = av as NixList;
|
||||
const bList = bv as NixList;
|
||||
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;
|
||||
}
|
||||
@@ -261,11 +245,7 @@ export const op = {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Functions are incomparable
|
||||
if (typeof av === "function") {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Other types are incomparable
|
||||
return false;
|
||||
},
|
||||
neq: (a: NixValue, b: NixValue): boolean => {
|
||||
@@ -284,10 +264,10 @@ export const op = {
|
||||
return compareValues(a, b) >= 0;
|
||||
},
|
||||
|
||||
bnot: (a: NixValue): boolean => !force(a),
|
||||
bnot: (a: NixValue): boolean => !forceBool(a),
|
||||
|
||||
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) }),
|
||||
|
||||
@@ -15,7 +15,6 @@ const canonicalizePath = (path: string): string => {
|
||||
i = j;
|
||||
|
||||
if (component === ".") {
|
||||
continue;
|
||||
} else if (component === "..") {
|
||||
if (parts.length > 0) {
|
||||
parts.pop();
|
||||
@@ -28,7 +27,7 @@ const canonicalizePath = (path: string): string => {
|
||||
if (parts.length === 0) {
|
||||
return "/";
|
||||
}
|
||||
return "/" + parts.join("/");
|
||||
return `/${parts.join("/")}`;
|
||||
};
|
||||
|
||||
export const mkPath = (value: string): NixPath => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { isThunk, IS_CYCLE } from "./thunk";
|
||||
import { getPrimopMetadata, isPrimop } from "./builtins/index";
|
||||
import { isStringWithContext } from "./string-context";
|
||||
import { IS_CYCLE, isThunk } from "./thunk";
|
||||
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 => {
|
||||
if (isThunk(value)) {
|
||||
@@ -29,8 +29,8 @@ export const printValue = (value: NixValue, seen: WeakSet<object> = new WeakSet(
|
||||
}
|
||||
|
||||
if (typeof value === "function") {
|
||||
if (is_primop(value)) {
|
||||
const meta = get_primop_metadata(value);
|
||||
if (isPrimop(value)) {
|
||||
const meta = getPrimopMetadata(value);
|
||||
if (meta && meta.applied > 0) {
|
||||
return "<PRIMOP-APP>";
|
||||
}
|
||||
@@ -40,7 +40,7 @@ export const printValue = (value: NixValue, seen: WeakSet<object> = new WeakSet(
|
||||
}
|
||||
|
||||
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»";
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ const printString = (s: string): string => {
|
||||
result += c;
|
||||
}
|
||||
}
|
||||
return result + '"';
|
||||
return `${result}"`;
|
||||
};
|
||||
|
||||
const SYMBOL_REGEX = /^[a-zA-Z_][a-zA-Z0-9_'-]*$/;
|
||||
|
||||
@@ -1,29 +1,4 @@
|
||||
/**
|
||||
* 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";
|
||||
import type { NixStrictValue } from "./types";
|
||||
|
||||
export const HAS_CONTEXT = Symbol("HAS_CONTEXT");
|
||||
|
||||
@@ -172,22 +147,6 @@ export const parseContextToInfoMap = (context: NixStringContext): Map<string, Pa
|
||||
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 = (
|
||||
context: NixStringContext,
|
||||
): { inputDrvs: Map<string, Set<string>>; inputSrcs: Set<string> } => {
|
||||
|
||||
@@ -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 { 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");
|
||||
|
||||
const forceStack: NixThunk[] = [];
|
||||
@@ -31,7 +22,7 @@ export const DEBUG_THUNKS = { enabled: true };
|
||||
* - Evaluated: func is undefined, result is defined
|
||||
*/
|
||||
export class NixThunk implements NixThunkInterface {
|
||||
[key: symbol]: any;
|
||||
[key: symbol]: unknown;
|
||||
readonly [IS_THUNK] = true as const;
|
||||
func: (() => NixValue) | 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 => {
|
||||
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 func = thunk.func!;
|
||||
const func = thunk.func as () => NixValue;
|
||||
thunk.func = undefined;
|
||||
|
||||
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 => {
|
||||
return new NixThunk(func, label);
|
||||
};
|
||||
|
||||
/**
|
||||
* Symbol to mark cyclic references detected during deep forcing
|
||||
*/
|
||||
export const IS_CYCLE = Symbol("is_cycle");
|
||||
|
||||
/**
|
||||
* Marker object for cyclic references
|
||||
*/
|
||||
export const CYCLE_MARKER = { [IS_CYCLE]: true };
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,27 +1,18 @@
|
||||
/**
|
||||
* Type assertion helpers for runtime type checking
|
||||
* These functions force evaluation and verify the type, throwing errors on mismatch
|
||||
*/
|
||||
|
||||
import { isAttrs, isFunction, typeOf } from "./builtins/type-check";
|
||||
import { force } from "./thunk";
|
||||
import type {
|
||||
NixValue,
|
||||
NixList,
|
||||
NixAttrs,
|
||||
NixFloat,
|
||||
NixFunction,
|
||||
NixInt,
|
||||
NixFloat,
|
||||
NixList,
|
||||
NixNumber,
|
||||
NixString,
|
||||
NixPath,
|
||||
NixString,
|
||||
NixValue,
|
||||
} from "./types";
|
||||
import { isStringWithContext, isNixPath } from "./types";
|
||||
import { force } from "./thunk";
|
||||
import { isAttrs, isFunction, typeOf } from "./builtins/type-check";
|
||||
import { isNixPath, isStringWithContext } from "./types";
|
||||
|
||||
/**
|
||||
* 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 => {
|
||||
const forced = force(value);
|
||||
if (!Array.isArray(forced)) {
|
||||
@@ -30,10 +21,6 @@ export const forceList = (value: NixValue): NixList => {
|
||||
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 => {
|
||||
const forced = force(value);
|
||||
if (isFunction(forced)) {
|
||||
@@ -47,10 +34,6 @@ export const forceFunction = (value: NixValue): NixFunction => {
|
||||
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 => {
|
||||
const forced = force(value);
|
||||
if (!isAttrs(forced)) {
|
||||
@@ -59,10 +42,6 @@ export const forceAttrs = (value: NixValue): NixAttrs => {
|
||||
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 => {
|
||||
const forced = force(value);
|
||||
if (typeof forced === "string") {
|
||||
@@ -74,10 +53,6 @@ export const forceStringValue = (value: NixValue): string => {
|
||||
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 => {
|
||||
const forced = force(value);
|
||||
if (typeof forced === "string") {
|
||||
@@ -100,10 +75,6 @@ export const forceStringNoCtx = (value: NixValue): string => {
|
||||
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 => {
|
||||
const forced = force(value);
|
||||
if (typeof forced !== "boolean") {
|
||||
@@ -112,10 +83,6 @@ export const forceBool = (value: NixValue): boolean => {
|
||||
return forced;
|
||||
};
|
||||
|
||||
/**
|
||||
* Force a value and extract int value
|
||||
* @throws TypeError if value is not an int
|
||||
*/
|
||||
export const forceInt = (value: NixValue): NixInt => {
|
||||
const forced = force(value);
|
||||
if (typeof forced === "bigint") {
|
||||
@@ -124,10 +91,6 @@ export const forceInt = (value: NixValue): NixInt => {
|
||||
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 => {
|
||||
const forced = force(value);
|
||||
if (typeof forced === "number") {
|
||||
@@ -136,10 +99,6 @@ export const forceFloat = (value: NixValue): NixFloat => {
|
||||
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 => {
|
||||
const forced = force(value);
|
||||
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)}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* 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] => {
|
||||
const aIsInt = typeof a === "bigint";
|
||||
const bIsInt = typeof b === "bigint";
|
||||
|
||||
// If either is float, convert both to float
|
||||
if (!aIsInt || !bIsInt) {
|
||||
return [Number(a), Number(b)];
|
||||
}
|
||||
|
||||
// Both are integers
|
||||
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 => {
|
||||
const forced = force(value);
|
||||
if (isNixPath(forced)) {
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
/**
|
||||
* Core TypeScript type definitions for nix-js runtime
|
||||
*/
|
||||
|
||||
import { HAS_CONTEXT, isStringWithContext, type StringWithContext } from "./string-context";
|
||||
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 { isString, typeOf } from "./builtins/type-check";
|
||||
export { HAS_CONTEXT, isStringWithContext };
|
||||
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;
|
||||
};
|
||||
|
||||
// Nix primitive types
|
||||
export type NixInt = bigint;
|
||||
export type NixFloat = number;
|
||||
export type NixNumber = NixInt | NixFloat;
|
||||
@@ -29,7 +22,6 @@ export type NixBool = boolean;
|
||||
export type NixString = string | StringWithContext;
|
||||
export type NixNull = null;
|
||||
|
||||
// Nix composite types
|
||||
export type NixList = NixValue[];
|
||||
// FIXME: reject contextful string
|
||||
export type NixAttrs = { [key: string]: NixValue };
|
||||
@@ -72,7 +64,7 @@ export const mkFunction = (
|
||||
positions: Record<string, string>,
|
||||
ellipsis: boolean,
|
||||
): NixFunction => {
|
||||
const func = f as NixFunction;
|
||||
const func: NixFunction = f;
|
||||
func.args = new NixArgs(required, optional, positions, ellipsis);
|
||||
return func;
|
||||
};
|
||||
@@ -90,7 +82,7 @@ export const mkAttrs = (attrs: NixAttrs, keys: NixValue[], values: NixValue[]):
|
||||
return attrs;
|
||||
};
|
||||
|
||||
const ATTR_POSITIONS = Symbol("attrPositions");
|
||||
export const ATTR_POSITIONS = Symbol("attrPositions");
|
||||
|
||||
export const mkAttrsWithPos = (
|
||||
attrs: NixAttrs,
|
||||
@@ -121,46 +113,14 @@ export const mkAttrsWithPos = (
|
||||
return attrs;
|
||||
};
|
||||
|
||||
export { ATTR_POSITIONS };
|
||||
|
||||
/**
|
||||
* Interface for lazy thunk values
|
||||
* Thunks delay evaluation until forced
|
||||
*/
|
||||
export interface NixThunkInterface {
|
||||
readonly [IS_THUNK]: true;
|
||||
func: (() => NixValue) | undefined;
|
||||
result: NixStrictValue | undefined;
|
||||
}
|
||||
|
||||
// Union of all Nix primitive types
|
||||
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 NixStrictValue = Exclude<NixValue, NixThunkInterface>;
|
||||
|
||||
/**
|
||||
* CatchableError: Error type thrown by `builtins.throw`
|
||||
* This can be caught by `builtins.tryEval`
|
||||
*/
|
||||
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;
|
||||
|
||||
26
nix-js/runtime-ts/src/types/global.d.ts
vendored
26
nix-js/runtime-ts/src/types/global.d.ts
vendored
@@ -1,5 +1,5 @@
|
||||
import type { NixRuntime } from "..";
|
||||
import type { FetchTarballResult, FetchUrlResult, FetchGitResult, FetchHgResult } from "../builtins/io";
|
||||
import type { FetchTarballResult, FetchUrlResult, FetchGitResult } from "../builtins/io";
|
||||
|
||||
declare global {
|
||||
var Nix: NixRuntime;
|
||||
@@ -20,17 +20,17 @@ declare global {
|
||||
line: number | null;
|
||||
column: number | null;
|
||||
};
|
||||
function op_make_store_path(ty: string, hash_hex: string, name: string): string;
|
||||
function op_parse_hash(hash_str: string, algo: string | null): { hex: string; algo: string };
|
||||
function op_make_store_path(ty: string, hashHex: string, name: string): string;
|
||||
function op_parse_hash(hashStr: string, algo: string | null): { hex: string; algo: string };
|
||||
function op_make_fixed_output_path(
|
||||
hash_algo: string,
|
||||
hashAlgo: string,
|
||||
hash: string,
|
||||
hash_mode: string,
|
||||
hashMode: string,
|
||||
name: string,
|
||||
): string;
|
||||
function op_fetch_url(
|
||||
url: string,
|
||||
expected_hash: string | null,
|
||||
expectedHash: string | null,
|
||||
name: string | null,
|
||||
executable: boolean,
|
||||
): FetchUrlResult;
|
||||
@@ -45,7 +45,7 @@ declare global {
|
||||
rev: string | null,
|
||||
shallow: boolean,
|
||||
submodules: boolean,
|
||||
all_refs: boolean,
|
||||
allRefs: boolean,
|
||||
name: string | null,
|
||||
): FetchGitResult;
|
||||
function op_add_path(
|
||||
@@ -56,9 +56,9 @@ declare global {
|
||||
): string;
|
||||
function op_store_path(path: 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_read_derivation_outputs(drv_path: string): string[];
|
||||
function op_compute_fs_closure(drv_path: string): {
|
||||
function op_write_derivation(drvName: string, aterm: string, references: string[]): string;
|
||||
function op_read_derivation_outputs(drvPath: string): string[];
|
||||
function op_compute_fs_closure(drvPath: string): {
|
||||
input_drvs: [string, string[]][];
|
||||
input_srcs: string[];
|
||||
};
|
||||
@@ -70,12 +70,12 @@ declare global {
|
||||
name: string | null,
|
||||
recursive: boolean,
|
||||
sha256: string | null,
|
||||
include_paths: string[],
|
||||
includePaths: string[],
|
||||
): string;
|
||||
function op_match(regex: string, text: string): (string | null)[] | null;
|
||||
function op_split(regex: string, text: string): (string | (string | null)[])[];
|
||||
function op_from_json(json: string): any;
|
||||
function op_from_toml(toml: string): any;
|
||||
function op_from_json(json: string): unknown;
|
||||
function op_from_toml(toml: string): unknown;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user