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": {
|
"vcs": {
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"clientKind": "git",
|
"clientKind": "git",
|
||||||
@@ -20,10 +20,37 @@
|
|||||||
"linter": {
|
"linter": {
|
||||||
"rules": {
|
"rules": {
|
||||||
"style": {
|
"style": {
|
||||||
"useNamingConvention": "warn"
|
"useNamingConvention": {
|
||||||
|
"level": "warn",
|
||||||
|
"options": {
|
||||||
|
"strictCase": false,
|
||||||
|
"conventions": [
|
||||||
|
{
|
||||||
|
"selector": { "kind": "objectLiteralProperty" },
|
||||||
|
"formats": ["camelCase", "PascalCase", "CONSTANT_CASE"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selector": { "kind": "typeProperty" },
|
||||||
|
"formats": ["camelCase", "snake_case"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"includes": ["**/global.d.ts"],
|
||||||
|
"linter": {
|
||||||
|
"rules": {
|
||||||
|
"style": {
|
||||||
|
"useNamingConvention": "off"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
"javascript": {
|
"javascript": {
|
||||||
"formatter": {
|
"formatter": {
|
||||||
"arrowParentheses": "always",
|
"arrowParentheses": "always",
|
||||||
|
|||||||
@@ -1,30 +1,26 @@
|
|||||||
/**
|
|
||||||
* Arithmetic builtin functions
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { NixBool, NixInt, NixNumber, NixValue } from "../types";
|
|
||||||
import { forceNumeric, coerceNumeric, forceInt } from "../type-assert";
|
|
||||||
import { op } from "../operators";
|
import { op } from "../operators";
|
||||||
|
import { coerceNumeric, forceInt, forceNumeric } from "../type-assert";
|
||||||
|
import type { NixBool, NixInt, NixNumber, NixValue } from "../types";
|
||||||
|
|
||||||
export const add =
|
export const add =
|
||||||
(a: NixValue) =>
|
(a: NixValue) =>
|
||||||
(b: NixValue): bigint | number => {
|
(b: NixValue): bigint | number => {
|
||||||
const [av, bv] = coerceNumeric(forceNumeric(a), forceNumeric(b));
|
const [av, bv] = coerceNumeric(forceNumeric(a), forceNumeric(b));
|
||||||
return (av as any) + (bv as any);
|
return (av as never) + (bv as never);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sub =
|
export const sub =
|
||||||
(a: NixValue) =>
|
(a: NixValue) =>
|
||||||
(b: NixValue): bigint | number => {
|
(b: NixValue): bigint | number => {
|
||||||
const [av, bv] = coerceNumeric(forceNumeric(a), forceNumeric(b));
|
const [av, bv] = coerceNumeric(forceNumeric(a), forceNumeric(b));
|
||||||
return (av as any) - (bv as any);
|
return (av as never) - (bv as never);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mul =
|
export const mul =
|
||||||
(a: NixValue) =>
|
(a: NixValue) =>
|
||||||
(b: NixValue): bigint | number => {
|
(b: NixValue): bigint | number => {
|
||||||
const [av, bv] = coerceNumeric(forceNumeric(a), forceNumeric(b));
|
const [av, bv] = coerceNumeric(forceNumeric(a), forceNumeric(b));
|
||||||
return (av as any) * (bv as any);
|
return (av as never) * (bv as never);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const div =
|
export const div =
|
||||||
@@ -36,10 +32,9 @@ export const div =
|
|||||||
throw new RangeError("Division by zero");
|
throw new RangeError("Division by zero");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (av as any) / (bv as any);
|
return (av as never) / (bv as never);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Bitwise operations - only for integers
|
|
||||||
export const bitAnd =
|
export const bitAnd =
|
||||||
(a: NixValue) =>
|
(a: NixValue) =>
|
||||||
(b: NixValue): NixInt => {
|
(b: NixValue): NixInt => {
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
/**
|
|
||||||
* Attribute set operation builtin functions
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { NixValue, NixAttrs, NixList } from "../types";
|
|
||||||
import { forceAttrs, forceStringValue, forceFunction, forceList } from "../type-assert";
|
|
||||||
import { createThunk } from "../thunk";
|
import { createThunk } from "../thunk";
|
||||||
|
import { forceAttrs, forceFunction, forceList, forceStringValue } from "../type-assert";
|
||||||
|
import type { NixAttrs, NixList, NixValue } from "../types";
|
||||||
|
|
||||||
export const attrNames = (set: NixValue): string[] => Object.keys(forceAttrs(set)).sort();
|
export const attrNames = (set: NixValue): string[] => Object.keys(forceAttrs(set)).sort();
|
||||||
|
|
||||||
@@ -46,23 +42,23 @@ export const mapAttrs =
|
|||||||
export const removeAttrs =
|
export const removeAttrs =
|
||||||
(attrs: NixValue) =>
|
(attrs: NixValue) =>
|
||||||
(list: NixValue): NixAttrs => {
|
(list: NixValue): NixAttrs => {
|
||||||
const new_attrs: NixAttrs = {};
|
const newAttrs: NixAttrs = {};
|
||||||
const forced_attrs = forceAttrs(attrs);
|
const forcedAttrs = forceAttrs(attrs);
|
||||||
const forced_list = forceList(list);
|
const forcedList = forceList(list);
|
||||||
const keys_to_remove = new Set(forced_list.map(forceStringValue));
|
const keysToRemove = new Set(forcedList.map(forceStringValue));
|
||||||
|
|
||||||
for (const key in forced_attrs) {
|
for (const key in forcedAttrs) {
|
||||||
if (!keys_to_remove.has(key)) {
|
if (!keysToRemove.has(key)) {
|
||||||
new_attrs[key] = forced_attrs[key];
|
newAttrs[key] = forcedAttrs[key];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new_attrs;
|
return newAttrs;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const listToAttrs = (e: NixValue): NixAttrs => {
|
export const listToAttrs = (e: NixValue): NixAttrs => {
|
||||||
const attrs: NixAttrs = {};
|
const attrs: NixAttrs = {};
|
||||||
const forced_e = [...forceList(e)].reverse();
|
const forcedE = [...forceList(e)].reverse();
|
||||||
for (const obj of forced_e) {
|
for (const obj of forcedE) {
|
||||||
const item = forceAttrs(obj);
|
const item = forceAttrs(obj);
|
||||||
attrs[forceStringValue(item.name)] = item.value;
|
attrs[forceStringValue(item.name)] = item.value;
|
||||||
}
|
}
|
||||||
@@ -96,10 +92,10 @@ export const groupBy =
|
|||||||
(f: NixValue) =>
|
(f: NixValue) =>
|
||||||
(list: NixValue): NixAttrs => {
|
(list: NixValue): NixAttrs => {
|
||||||
const attrs: NixAttrs = {};
|
const attrs: NixAttrs = {};
|
||||||
const forced_f = forceFunction(f);
|
const forcedF = forceFunction(f);
|
||||||
const forced_list = forceList(list);
|
const forcedList = forceList(list);
|
||||||
for (const elem of forced_list) {
|
for (const elem of forcedList) {
|
||||||
const key = forceStringValue(forced_f(elem));
|
const key = forceStringValue(forcedF(elem));
|
||||||
if (!attrs[key]) attrs[key] = [];
|
if (!attrs[key]) attrs[key] = [];
|
||||||
(attrs[key] as NixList).push(elem);
|
(attrs[key] as NixList).push(elem);
|
||||||
}
|
}
|
||||||
@@ -111,28 +107,22 @@ export const zipAttrsWith =
|
|||||||
(list: NixValue): NixValue => {
|
(list: NixValue): NixValue => {
|
||||||
const listForced = forceList(list);
|
const listForced = forceList(list);
|
||||||
|
|
||||||
// Map to collect all values for each attribute name
|
|
||||||
const attrMap = new Map<string, NixValue[]>();
|
const attrMap = new Map<string, NixValue[]>();
|
||||||
|
|
||||||
// Iterate through each attribute set in the list
|
|
||||||
for (const item of listForced) {
|
for (const item of listForced) {
|
||||||
const attrs = forceAttrs(item);
|
const attrs = forceAttrs(item);
|
||||||
|
|
||||||
// Collect all attribute names and their values
|
|
||||||
for (const [key, value] of Object.entries(attrs)) {
|
for (const [key, value] of Object.entries(attrs)) {
|
||||||
if (!attrMap.has(key)) {
|
if (!attrMap.has(key)) {
|
||||||
attrMap.set(key, []);
|
attrMap.set(key, []);
|
||||||
}
|
}
|
||||||
attrMap.get(key)!.push(value);
|
(attrMap.get(key) as NixValue[]).push(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the result attribute set
|
|
||||||
const result: Record<string, NixValue> = {};
|
const result: Record<string, NixValue> = {};
|
||||||
|
|
||||||
for (const [name, values] of attrMap.entries()) {
|
for (const [name, values] of attrMap.entries()) {
|
||||||
// Apply f to name and values list
|
|
||||||
// f is curried: f name values
|
|
||||||
result[name] = createThunk(() => forceFunction(forceFunction(f)(name))(values));
|
result[name] = createThunk(() => forceFunction(forceFunction(f)(name))(values));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,7 +139,9 @@ export const unsafeGetAttrPos =
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const positions = (attrs as any)[Nix.ATTR_POSITIONS];
|
const positions = (attrs as NixAttrs & Record<symbol, unknown>)[Nix.ATTR_POSITIONS] as
|
||||||
|
| Record<string, string>
|
||||||
|
| undefined;
|
||||||
if (!positions || !(name in positions)) {
|
if (!positions || !(name in positions)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import type { NixValue, NixAttrs, NixString } from "../types";
|
|
||||||
import { isStringWithContext } from "../types";
|
|
||||||
import { forceString, forceAttrs, forceList, forceStringValue } from "../type-assert";
|
|
||||||
import { force } from "../thunk";
|
|
||||||
import {
|
import {
|
||||||
type NixStringContext,
|
|
||||||
getStringValue,
|
|
||||||
getStringContext,
|
|
||||||
mkStringWithContext,
|
|
||||||
decodeContextElem,
|
decodeContextElem,
|
||||||
|
getStringContext,
|
||||||
|
getStringValue,
|
||||||
|
mkStringWithContext,
|
||||||
|
type NixStringContext,
|
||||||
parseContextToInfoMap,
|
parseContextToInfoMap,
|
||||||
} from "../string-context";
|
} from "../string-context";
|
||||||
|
import { force } from "../thunk";
|
||||||
|
import { forceAttrs, forceList, forceString, forceStringValue } from "../type-assert";
|
||||||
|
import type { NixAttrs, NixString, NixValue } from "../types";
|
||||||
|
import { isStringWithContext } from "../types";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* builtins.hasContext - Check if string has context
|
* builtins.hasContext - Check if string has context
|
||||||
@@ -118,13 +118,13 @@ export const getContext = (value: NixValue): NixAttrs => {
|
|||||||
for (const [path, info] of infoMap) {
|
for (const [path, info] of infoMap) {
|
||||||
const attrs: NixAttrs = {};
|
const attrs: NixAttrs = {};
|
||||||
if (info.path) {
|
if (info.path) {
|
||||||
attrs["path"] = true;
|
attrs.path = true;
|
||||||
}
|
}
|
||||||
if (info.allOutputs) {
|
if (info.allOutputs) {
|
||||||
attrs["allOutputs"] = true;
|
attrs.allOutputs = true;
|
||||||
}
|
}
|
||||||
if (info.outputs.length > 0) {
|
if (info.outputs.length > 0) {
|
||||||
attrs["outputs"] = info.outputs;
|
attrs.outputs = info.outputs;
|
||||||
}
|
}
|
||||||
result[path] = attrs;
|
result[path] = attrs;
|
||||||
}
|
}
|
||||||
@@ -162,14 +162,14 @@ export const appendContext =
|
|||||||
const info = forceAttrs(infoVal);
|
const info = forceAttrs(infoVal);
|
||||||
|
|
||||||
if ("path" in info) {
|
if ("path" in info) {
|
||||||
const pathVal = force(info["path"]);
|
const pathVal = force(info.path);
|
||||||
if (pathVal === true) {
|
if (pathVal === true) {
|
||||||
newContext.add(path);
|
newContext.add(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("allOutputs" in info) {
|
if ("allOutputs" in info) {
|
||||||
const allOutputs = force(info["allOutputs"]);
|
const allOutputs = force(info.allOutputs);
|
||||||
if (allOutputs === true) {
|
if (allOutputs === true) {
|
||||||
if (!path.endsWith(".drv")) {
|
if (!path.endsWith(".drv")) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -181,7 +181,7 @@ export const appendContext =
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ("outputs" in info) {
|
if ("outputs" in info) {
|
||||||
const outputs = forceList(info["outputs"]);
|
const outputs = forceList(info.outputs);
|
||||||
if (outputs.length > 0 && !path.endsWith(".drv")) {
|
if (outputs.length > 0 && !path.endsWith(".drv")) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`tried to add derivation output context of ${path}, which is not a derivation, to a string`,
|
`tried to add derivation output context of ${path}, which is not a derivation, to a string`,
|
||||||
|
|||||||
@@ -2,12 +2,11 @@
|
|||||||
* Conversion and serialization builtin functions
|
* Conversion and serialization builtin functions
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { NixString, NixValue } from "../types";
|
import { addBuiltContext, mkStringWithContext, type NixStringContext } from "../string-context";
|
||||||
import { isStringWithContext, isNixPath } from "../types";
|
import { force, isThunk } from "../thunk";
|
||||||
import { force } from "../thunk";
|
|
||||||
import { type NixStringContext, mkStringWithContext, addBuiltContext } from "../string-context";
|
|
||||||
import { forceFunction, forceStringNoCtx } from "../type-assert";
|
import { forceFunction, forceStringNoCtx } from "../type-assert";
|
||||||
import { nixValueToJson } from "../conversion";
|
import type { NixString, NixValue } from "../types";
|
||||||
|
import { HAS_CONTEXT, IS_PATH, isNixPath, isStringWithContext } from "../types";
|
||||||
import { isAttrs, isPath, typeOf } from "./type-check";
|
import { isAttrs, isPath, typeOf } from "./type-check";
|
||||||
|
|
||||||
export const fromJSON = (e: NixValue): NixValue => {
|
export const fromJSON = (e: NixValue): NixValue => {
|
||||||
@@ -16,12 +15,12 @@ export const fromJSON = (e: NixValue): NixValue => {
|
|||||||
throw new TypeError(`builtins.fromJSON: expected a string, got ${typeOf(str)}`);
|
throw new TypeError(`builtins.fromJSON: expected a string, got ${typeOf(str)}`);
|
||||||
}
|
}
|
||||||
const jsonStr = isStringWithContext(str) ? str.value : str;
|
const jsonStr = isStringWithContext(str) ? str.value : str;
|
||||||
return Deno.core.ops.op_from_json(jsonStr);
|
return Deno.core.ops.op_from_json(jsonStr) as NixValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fromTOML = (e: NixValue): NixValue => {
|
export const fromTOML = (e: NixValue): NixValue => {
|
||||||
const toml = forceStringNoCtx(e);
|
const toml = forceStringNoCtx(e);
|
||||||
return Deno.core.ops.op_from_toml(toml);
|
return Deno.core.ops.op_from_toml(toml) as NixValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toJSON = (e: NixValue): NixString => {
|
export const toJSON = (e: NixValue): NixString => {
|
||||||
@@ -33,7 +32,7 @@ export const toJSON = (e: NixValue): NixString => {
|
|||||||
return mkStringWithContext(string, context);
|
return mkStringWithContext(string, context);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toXML = (e: NixValue): never => {
|
export const toXML = (_e: NixValue): never => {
|
||||||
throw new Error("Not implemented: toXML");
|
throw new Error("Not implemented: toXML");
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -290,3 +289,82 @@ export const coerceToPath = (value: NixValue, outContext?: NixStringContext): st
|
|||||||
export const toStringFunc = (value: NixValue): NixString => {
|
export const toStringFunc = (value: NixValue): NixString => {
|
||||||
return coerceToStringWithContext(value, StringCoercionMode.ToString, false);
|
return coerceToStringWithContext(value, StringCoercionMode.ToString, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const nixValueToJson = (
|
||||||
|
value: NixValue,
|
||||||
|
strict: boolean,
|
||||||
|
outContext: NixStringContext,
|
||||||
|
copyToStore: boolean,
|
||||||
|
seen: Set<NixValue> = new Set(),
|
||||||
|
): unknown => {
|
||||||
|
const v = strict ? force(value) : value;
|
||||||
|
|
||||||
|
if (isThunk(v) || typeof v === "function")
|
||||||
|
throw new Error(`cannot convert ${isThunk(v) ? "thunk" : "lambda"} to JSON`);
|
||||||
|
if (v === null) return null;
|
||||||
|
if (typeof v === "bigint") {
|
||||||
|
const num = Number(v);
|
||||||
|
if (v > Number.MAX_SAFE_INTEGER || v < Number.MIN_SAFE_INTEGER) {
|
||||||
|
console.warn(`integer ${v} exceeds safe range, precision may be lost`);
|
||||||
|
}
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
if (typeof v === "number") return v;
|
||||||
|
if (typeof v === "boolean") return v;
|
||||||
|
if (typeof v === "string") return v;
|
||||||
|
if (typeof v === "object" && HAS_CONTEXT in v) {
|
||||||
|
for (const elem of v.context) {
|
||||||
|
outContext.add(elem);
|
||||||
|
}
|
||||||
|
return v.value;
|
||||||
|
}
|
||||||
|
if (typeof v === "object" && IS_PATH in v) {
|
||||||
|
if (copyToStore) {
|
||||||
|
const storePath = Deno.core.ops.op_copy_path_to_store(v.value);
|
||||||
|
outContext.add(storePath);
|
||||||
|
return storePath;
|
||||||
|
} else {
|
||||||
|
return v.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: is this check necessary?
|
||||||
|
// if (seen.has(v)) {
|
||||||
|
// throw new Error("cycle detected in toJSON");
|
||||||
|
// } else {
|
||||||
|
// seen.add(v)
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (Array.isArray(v)) {
|
||||||
|
return v.map((item) => nixValueToJson(item, strict, outContext, copyToStore, seen));
|
||||||
|
}
|
||||||
|
|
||||||
|
// NixAttrs
|
||||||
|
if ("__toString" in v && typeof force(v.__toString) === "function") {
|
||||||
|
const toStringMethod = force(v.__toString) as (self: typeof v) => NixValue;
|
||||||
|
const result = force(toStringMethod(v));
|
||||||
|
if (typeof result === "string") {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (isStringWithContext(result)) {
|
||||||
|
if (outContext) {
|
||||||
|
for (const elem of result.context) {
|
||||||
|
outContext.add(elem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.value;
|
||||||
|
}
|
||||||
|
return nixValueToJson(result, strict, outContext, copyToStore, seen);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("outPath" in v) {
|
||||||
|
return nixValueToJson(v.outPath, strict, outContext, copyToStore, seen);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: Record<string, unknown> = {};
|
||||||
|
const keys = Object.keys(v).sort();
|
||||||
|
for (const key of keys) {
|
||||||
|
result[key] = nixValueToJson(v[key], strict, outContext, copyToStore, seen);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,40 +1,123 @@
|
|||||||
import type { NixValue, NixAttrs } from "../types";
|
|
||||||
import { forceStringValue, forceList, forceStringNoCtx } from "../type-assert";
|
|
||||||
import { force } from "../thunk";
|
|
||||||
import {
|
import {
|
||||||
type DerivationData,
|
|
||||||
type OutputInfo,
|
|
||||||
generateAterm,
|
|
||||||
generateAtermModulo,
|
|
||||||
} from "../derivation-helpers";
|
|
||||||
import { coerceToString, StringCoercionMode } from "./conversion";
|
|
||||||
import {
|
|
||||||
type NixStringContext,
|
|
||||||
extractInputDrvsAndSrcs,
|
|
||||||
isStringWithContext,
|
|
||||||
mkStringWithContext,
|
|
||||||
addDrvDeepContext,
|
|
||||||
addBuiltContext,
|
addBuiltContext,
|
||||||
|
addDrvDeepContext,
|
||||||
|
extractInputDrvsAndSrcs,
|
||||||
|
mkStringWithContext,
|
||||||
|
type NixStringContext,
|
||||||
} from "../string-context";
|
} from "../string-context";
|
||||||
import { nixValueToJson } from "../conversion";
|
import { force } from "../thunk";
|
||||||
import { isNixPath } from "../types";
|
import { forceAttrs, forceList, forceStringNoCtx, forceStringValue } from "../type-assert";
|
||||||
|
import type { NixAttrs, NixValue } from "../types";
|
||||||
|
import { coerceToString, nixValueToJson, StringCoercionMode } from "./conversion";
|
||||||
|
|
||||||
const drvHashCache = new Map<string, string>();
|
const drvHashCache = new Map<string, string>();
|
||||||
|
|
||||||
const forceAttrs = (value: NixValue): NixAttrs => {
|
export interface OutputInfo {
|
||||||
const forced = force(value);
|
path: string;
|
||||||
if (
|
hashAlgo: string;
|
||||||
typeof forced !== "object" ||
|
hash: string;
|
||||||
forced === null ||
|
}
|
||||||
Array.isArray(forced) ||
|
|
||||||
isStringWithContext(forced) ||
|
export interface DerivationData {
|
||||||
isNixPath(forced)
|
name: string;
|
||||||
) {
|
outputs: Map<string, OutputInfo>;
|
||||||
throw new TypeError(`Expected attribute set for derivation, got ${typeof forced}`);
|
inputDrvs: Map<string, Set<string>>;
|
||||||
|
inputSrcs: Set<string>;
|
||||||
|
platform: string;
|
||||||
|
builder: string;
|
||||||
|
args: string[];
|
||||||
|
env: Map<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const escapeString = (s: string): string => {
|
||||||
|
let result = "";
|
||||||
|
for (const char of s) {
|
||||||
|
switch (char) {
|
||||||
|
case '"':
|
||||||
|
result += '\\"';
|
||||||
|
break;
|
||||||
|
case "\\":
|
||||||
|
result += "\\\\";
|
||||||
|
break;
|
||||||
|
case "\n":
|
||||||
|
result += "\\n";
|
||||||
|
break;
|
||||||
|
case "\r":
|
||||||
|
result += "\\r";
|
||||||
|
break;
|
||||||
|
case "\t":
|
||||||
|
result += "\\t";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
result += char;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return forced;
|
return `"${result}"`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const quoteString = (s: string): string => `"${s}"`;
|
||||||
|
|
||||||
|
const cmpByKey = <T>(a: [string, T], b: [string, T]): number => (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
|
||||||
|
|
||||||
|
export const generateAterm = (drv: DerivationData): string => {
|
||||||
|
const outputEntries: string[] = [];
|
||||||
|
const sortedOutputs = Array.from(drv.outputs.entries()).sort(cmpByKey);
|
||||||
|
for (const [name, info] of sortedOutputs) {
|
||||||
|
outputEntries.push(
|
||||||
|
`(${quoteString(name)},${quoteString(info.path)},${quoteString(info.hashAlgo)},${quoteString(info.hash)})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const outputs = outputEntries.join(",");
|
||||||
|
|
||||||
|
const inputDrvEntries: string[] = [];
|
||||||
|
const sortedInputDrvs = Array.from(drv.inputDrvs.entries()).sort(cmpByKey);
|
||||||
|
for (const [drvPath, outputs] of sortedInputDrvs) {
|
||||||
|
const sortedOuts = Array.from(outputs).sort();
|
||||||
|
const outList = `[${sortedOuts.map(quoteString).join(",")}]`;
|
||||||
|
inputDrvEntries.push(`(${quoteString(drvPath)},${outList})`);
|
||||||
|
}
|
||||||
|
const inputDrvs = inputDrvEntries.join(",");
|
||||||
|
|
||||||
|
const sortedInputSrcs = Array.from(drv.inputSrcs).sort();
|
||||||
|
const inputSrcs = sortedInputSrcs.map(quoteString).join(",");
|
||||||
|
|
||||||
|
const args = drv.args.map(escapeString).join(",");
|
||||||
|
const envs = Array.from(drv.env.entries())
|
||||||
|
.sort(cmpByKey)
|
||||||
|
.map(([k, v]) => `(${escapeString(k)},${escapeString(v)})`);
|
||||||
|
|
||||||
|
return `Derive([${outputs}],[${inputDrvs}],[${inputSrcs}],${quoteString(drv.platform)},${escapeString(drv.builder)},[${args}],[${envs}])`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const generateAtermModulo = (drv: DerivationData, inputDrvHashes: Map<string, string>): string => {
|
||||||
|
const outputEntries: string[] = [];
|
||||||
|
const sortedOutputs = Array.from(drv.outputs.entries()).sort(cmpByKey);
|
||||||
|
for (const [name, info] of sortedOutputs) {
|
||||||
|
outputEntries.push(
|
||||||
|
`(${quoteString(name)},${quoteString(info.path)},${quoteString(info.hashAlgo)},${quoteString(info.hash)})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const outputs = outputEntries.join(",");
|
||||||
|
|
||||||
|
const inputDrvEntries: string[] = [];
|
||||||
|
const sortedInputDrvHashes = Array.from(inputDrvHashes.entries()).sort(cmpByKey);
|
||||||
|
for (const [drvHash, outputs] of sortedInputDrvHashes) {
|
||||||
|
const sortedOuts = outputs.split(",").sort();
|
||||||
|
const outList = `[${sortedOuts.map(quoteString).join(",")}]`;
|
||||||
|
inputDrvEntries.push(`(${quoteString(drvHash)},${outList})`);
|
||||||
|
}
|
||||||
|
const inputDrvs = inputDrvEntries.join(",");
|
||||||
|
|
||||||
|
const sortedInputSrcs = Array.from(drv.inputSrcs).sort();
|
||||||
|
const inputSrcs = sortedInputSrcs.map(quoteString).join(",");
|
||||||
|
|
||||||
|
const args = drv.args.map(escapeString).join(",");
|
||||||
|
const envs = Array.from(drv.env.entries())
|
||||||
|
.sort(cmpByKey)
|
||||||
|
.map(([k, v]) => `(${escapeString(k)},${escapeString(v)})`);
|
||||||
|
|
||||||
|
return `Derive([${outputs}],[${inputDrvs}],[${inputSrcs}],${quoteString(drv.platform)},${escapeString(drv.builder)},[${args}],[${envs}])`;
|
||||||
|
};
|
||||||
const validateName = (attrs: NixAttrs): string => {
|
const validateName = (attrs: NixAttrs): string => {
|
||||||
if (!("name" in attrs)) {
|
if (!("name" in attrs)) {
|
||||||
throw new Error("derivation: missing required attribute 'name'");
|
throw new Error("derivation: missing required attribute 'name'");
|
||||||
@@ -109,10 +192,10 @@ const extractArgs = (attrs: NixAttrs, outContext: NixStringContext): string[] =>
|
|||||||
|
|
||||||
const outputPathName = (drvName: string, output: string) => {
|
const outputPathName = (drvName: string, output: string) => {
|
||||||
if (output === "out") {
|
if (output === "out") {
|
||||||
return drvName
|
return drvName;
|
||||||
}
|
}
|
||||||
return `${drvName}-${output}`
|
return `${drvName}-${output}`;
|
||||||
}
|
};
|
||||||
|
|
||||||
const structuredAttrsExcludedKeys = new Set([
|
const structuredAttrsExcludedKeys = new Set([
|
||||||
"__structuredAttrs",
|
"__structuredAttrs",
|
||||||
@@ -124,9 +207,9 @@ const structuredAttrsExcludedKeys = new Set([
|
|||||||
|
|
||||||
const specialAttrs = new Set(["args", "__ignoreNulls", "__contentAddressed", "__impure"]);
|
const specialAttrs = new Set(["args", "__ignoreNulls", "__contentAddressed", "__impure"]);
|
||||||
|
|
||||||
const sortedJsonStringify = (obj: Record<string, any>): string => {
|
const sortedJsonStringify = (obj: Record<string, unknown>): string => {
|
||||||
const sortedKeys = Object.keys(obj).sort();
|
const sortedKeys = Object.keys(obj).sort();
|
||||||
const sortedObj: Record<string, any> = {};
|
const sortedObj: Record<string, unknown> = {};
|
||||||
for (const key of sortedKeys) {
|
for (const key of sortedKeys) {
|
||||||
sortedObj[key] = obj[key];
|
sortedObj[key] = obj[key];
|
||||||
}
|
}
|
||||||
@@ -143,7 +226,7 @@ const extractEnv = (
|
|||||||
const env = new Map<string, string>();
|
const env = new Map<string, string>();
|
||||||
|
|
||||||
if (structuredAttrs) {
|
if (structuredAttrs) {
|
||||||
const jsonAttrs: Record<string, any> = {};
|
const jsonAttrs: Record<string, unknown> = {};
|
||||||
for (const [key, value] of Object.entries(attrs)) {
|
for (const [key, value] of Object.entries(attrs)) {
|
||||||
if (!structuredAttrsExcludedKeys.has(key)) {
|
if (!structuredAttrsExcludedKeys.has(key)) {
|
||||||
const forcedValue = force(value);
|
const forcedValue = force(value);
|
||||||
@@ -419,6 +502,6 @@ export const derivationStrict = (args: NixValue): NixAttrs => {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const derivation = (_: NixValue): NixAttrs => {
|
export const derivationStub = (_: NixValue): NixAttrs => {
|
||||||
throw new Error("unreachable: placeholder derivation implementation called")
|
throw new Error("unreachable: stub derivation implementation called");
|
||||||
};
|
};
|
||||||
|
|||||||
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 { printValue } from "../print";
|
||||||
|
import { force } from "../thunk";
|
||||||
|
import { CatchableError, type NixValue } from "../types";
|
||||||
|
import { coerceToString, StringCoercionMode } from "./conversion";
|
||||||
import { isAttrs } from "./type-check";
|
import { isAttrs } from "./type-check";
|
||||||
|
|
||||||
export const seq =
|
export const seq =
|
||||||
|
|||||||
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 @@
|
|||||||
/**
|
import { createThunk, force } from "../thunk";
|
||||||
* Main builtins export
|
|
||||||
* Combines all builtin function categories into the global `builtins` object
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Import all builtin categories
|
|
||||||
import * as arithmetic from "./arithmetic";
|
|
||||||
import * as math from "./math";
|
|
||||||
import * as typeCheck from "./type-check";
|
|
||||||
import * as list from "./list";
|
|
||||||
import * as attrs from "./attrs";
|
|
||||||
import * as string from "./string";
|
|
||||||
import * as pathOps from "./path";
|
|
||||||
import * as functional from "./functional";
|
|
||||||
import * as io from "./io";
|
|
||||||
import * as conversion from "./conversion";
|
|
||||||
import * as misc from "./misc";
|
|
||||||
import * as derivation from "./derivation";
|
|
||||||
|
|
||||||
import type { NixValue } from "../types";
|
import type { NixValue } from "../types";
|
||||||
import { createThunk, force, isThunk } from "../thunk";
|
import * as arithmetic from "./arithmetic";
|
||||||
import { getTos } from "../helpers";
|
import * as attrs from "./attrs";
|
||||||
|
import * as conversion from "./conversion";
|
||||||
|
import * as derivation from "./derivation";
|
||||||
|
import * as flake from "./flake";
|
||||||
|
import * as functional from "./functional";
|
||||||
|
import * as hash from "./hash";
|
||||||
|
import * as io from "./io";
|
||||||
|
import * as list from "./list";
|
||||||
|
import * as math from "./math";
|
||||||
|
import * as misc from "./misc";
|
||||||
|
import * as pathOps from "./path";
|
||||||
|
import * as string from "./string";
|
||||||
|
import * as typeCheck from "./type-check";
|
||||||
|
|
||||||
/**
|
|
||||||
* Symbol used to mark functions as primops (primitive operations)
|
|
||||||
* This is similar to IS_THUNK but for builtin functions
|
|
||||||
*/
|
|
||||||
export const PRIMOP_METADATA = Symbol("primop_metadata");
|
export const PRIMOP_METADATA = Symbol("primop_metadata");
|
||||||
|
|
||||||
/**
|
|
||||||
* Metadata interface for primop functions
|
|
||||||
*/
|
|
||||||
export interface PrimopMetadata {
|
export interface PrimopMetadata {
|
||||||
/** The name of the primop (e.g., "add", "map") */
|
|
||||||
name: string;
|
name: string;
|
||||||
/** Total arity of the function (number of arguments it expects) */
|
|
||||||
arity: number;
|
arity: number;
|
||||||
/** Number of arguments already applied (for partial applications) */
|
|
||||||
applied: number;
|
applied: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Mark a function as a primop with metadata
|
|
||||||
* For curried functions, this recursively marks each layer
|
|
||||||
*
|
|
||||||
* @param func - The function to mark
|
|
||||||
* @param name - Name of the primop
|
|
||||||
* @param arity - Total number of arguments expected
|
|
||||||
* @param applied - Number of arguments already applied (default: 0)
|
|
||||||
* @returns The marked function
|
|
||||||
*/
|
|
||||||
export const mkPrimop = (
|
export const mkPrimop = (
|
||||||
func: (...args: NixValue[]) => NixValue,
|
func: (...args: NixValue[]) => NixValue,
|
||||||
name: string,
|
name: string,
|
||||||
arity: number,
|
arity: number,
|
||||||
applied: number = 0,
|
applied: number = 0,
|
||||||
): Function => {
|
): ((...args: NixValue[]) => NixValue) => {
|
||||||
// Mark this function as a primop
|
(func as unknown as Record<symbol, unknown>)[PRIMOP_METADATA] = {
|
||||||
(func as any)[PRIMOP_METADATA] = {
|
|
||||||
name,
|
name,
|
||||||
arity,
|
arity,
|
||||||
applied,
|
applied,
|
||||||
} satisfies PrimopMetadata;
|
} satisfies PrimopMetadata;
|
||||||
|
|
||||||
// If this is a curried function and not fully applied,
|
|
||||||
// wrap it to mark the next layer too
|
|
||||||
if (applied < arity - 1) {
|
if (applied < arity - 1) {
|
||||||
const wrappedFunc = ((...args: NixValue[]) => {
|
const wrappedFunc = ((...args: NixValue[]) => {
|
||||||
const result = func(...args);
|
const result = func(...args);
|
||||||
// If result is a function, mark it as the next layer
|
|
||||||
if (typeof result === "function") {
|
if (typeof result === "function") {
|
||||||
return mkPrimop(result, name, arity, applied + args.length);
|
return mkPrimop(result, name, arity, applied + args.length);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}) as any;
|
}) as (...args: NixValue[]) => NixValue;
|
||||||
|
|
||||||
// Copy the primop metadata to the wrapper
|
(wrappedFunc as unknown as Record<symbol, unknown>)[PRIMOP_METADATA] = {
|
||||||
wrappedFunc[PRIMOP_METADATA] = {
|
|
||||||
name,
|
name,
|
||||||
arity,
|
arity,
|
||||||
applied,
|
applied,
|
||||||
@@ -87,12 +56,9 @@ export const mkPrimop = (
|
|||||||
return func;
|
return func;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
export const isPrimop = (
|
||||||
* Type guard to check if a value is a primop
|
value: unknown,
|
||||||
* @param value - Value to check
|
): value is ((...args: never[]) => unknown) & { [PRIMOP_METADATA]: PrimopMetadata } => {
|
||||||
* @returns true if value is marked as a primop
|
|
||||||
*/
|
|
||||||
export const is_primop = (value: unknown): value is Function & { [PRIMOP_METADATA]: PrimopMetadata } => {
|
|
||||||
return (
|
return (
|
||||||
typeof value === "function" &&
|
typeof value === "function" &&
|
||||||
PRIMOP_METADATA in value &&
|
PRIMOP_METADATA in value &&
|
||||||
@@ -101,29 +67,14 @@ export const is_primop = (value: unknown): value is Function & { [PRIMOP_METADAT
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
export const getPrimopMetadata = (func: unknown): PrimopMetadata | undefined => {
|
||||||
* Get primop metadata from a function
|
if (isPrimop(func)) {
|
||||||
* @param func - Function to get metadata from
|
|
||||||
* @returns Metadata if function is a primop, undefined otherwise
|
|
||||||
*/
|
|
||||||
export const get_primop_metadata = (func: unknown): PrimopMetadata | undefined => {
|
|
||||||
if (is_primop(func)) {
|
|
||||||
return func[PRIMOP_METADATA];
|
return func[PRIMOP_METADATA];
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
export const builtins: Record<string, NixValue> = {
|
||||||
* The global builtins object
|
|
||||||
* Contains 80+ Nix builtin functions plus metadata
|
|
||||||
*
|
|
||||||
* All functions are curried for Nix semantics:
|
|
||||||
* - Single argument functions: (a) => result
|
|
||||||
* - Multi-argument functions: (a) => (b) => result
|
|
||||||
*
|
|
||||||
* All primop functions are marked with PRIMOP_METADATA symbol for runtime introspection
|
|
||||||
*/
|
|
||||||
export const builtins: any = {
|
|
||||||
add: mkPrimop(arithmetic.add, "add", 2),
|
add: mkPrimop(arithmetic.add, "add", 2),
|
||||||
sub: mkPrimop(arithmetic.sub, "sub", 2),
|
sub: mkPrimop(arithmetic.sub, "sub", 2),
|
||||||
mul: mkPrimop(arithmetic.mul, "mul", 2),
|
mul: mkPrimop(arithmetic.mul, "mul", 2),
|
||||||
@@ -193,7 +144,7 @@ export const builtins: any = {
|
|||||||
warn: mkPrimop(functional.warn, "warn", 2),
|
warn: mkPrimop(functional.warn, "warn", 2),
|
||||||
break: mkPrimop(functional.breakFunc, "break", 1),
|
break: mkPrimop(functional.breakFunc, "break", 1),
|
||||||
|
|
||||||
derivation: undefined as any,
|
derivation: mkPrimop(derivation.derivationStub, "derivation", 1),
|
||||||
derivationStrict: mkPrimop(derivation.derivationStrict, "derivationStrict", 1),
|
derivationStrict: mkPrimop(derivation.derivationStrict, "derivationStrict", 1),
|
||||||
|
|
||||||
import: mkPrimop(io.importFunc, "import", 1),
|
import: mkPrimop(io.importFunc, "import", 1),
|
||||||
@@ -221,13 +172,19 @@ export const builtins: any = {
|
|||||||
toXML: mkPrimop(conversion.toXML, "toXML", 1),
|
toXML: mkPrimop(conversion.toXML, "toXML", 1),
|
||||||
toString: mkPrimop(conversion.toStringFunc, "toString", 1),
|
toString: mkPrimop(conversion.toStringFunc, "toString", 1),
|
||||||
|
|
||||||
|
hashFile: mkPrimop(hash.hashFile, "hashFile", 2),
|
||||||
|
hashString: mkPrimop(hash.hashString, "hashString", 2),
|
||||||
|
convertHash: mkPrimop(hash.convertHash, "convertHash", 2),
|
||||||
|
|
||||||
|
flakeRefToString: mkPrimop(flake.flakeRefToString, "flakeRefToString", 1),
|
||||||
|
getFlake: mkPrimop(flake.getFlake, "getFlake", 1),
|
||||||
|
parseFlakeName: mkPrimop(flake.parseFlakeName, "parseFlakeName", 1),
|
||||||
|
parseFlakeRef: mkPrimop(flake.parseFlakeRef, "parseFlakeRef", 1),
|
||||||
|
|
||||||
addErrorContext: mkPrimop(misc.addErrorContext, "addErrorContext", 1),
|
addErrorContext: mkPrimop(misc.addErrorContext, "addErrorContext", 1),
|
||||||
appendContext: mkPrimop(misc.appendContext, "appendContext", 1),
|
appendContext: mkPrimop(misc.appendContext, "appendContext", 1),
|
||||||
getContext: mkPrimop(misc.getContext, "getContext", 1),
|
getContext: mkPrimop(misc.getContext, "getContext", 1),
|
||||||
hasContext: mkPrimop(misc.hasContext, "hasContext", 1),
|
hasContext: mkPrimop(misc.hasContext, "hasContext", 1),
|
||||||
hashFile: mkPrimop(misc.hashFile, "hashFile", 2),
|
|
||||||
hashString: mkPrimop(misc.hashString, "hashString", 2),
|
|
||||||
convertHash: mkPrimop(misc.convertHash, "convertHash", 2),
|
|
||||||
unsafeDiscardOutputDependency: mkPrimop(
|
unsafeDiscardOutputDependency: mkPrimop(
|
||||||
misc.unsafeDiscardOutputDependency,
|
misc.unsafeDiscardOutputDependency,
|
||||||
"unsafeDiscardOutputDependency",
|
"unsafeDiscardOutputDependency",
|
||||||
@@ -236,14 +193,10 @@ export const builtins: any = {
|
|||||||
unsafeDiscardStringContext: mkPrimop(misc.unsafeDiscardStringContext, "unsafeDiscardStringContext", 1),
|
unsafeDiscardStringContext: mkPrimop(misc.unsafeDiscardStringContext, "unsafeDiscardStringContext", 1),
|
||||||
addDrvOutputDependencies: mkPrimop(misc.addDrvOutputDependencies, "addDrvOutputDependencies", 2),
|
addDrvOutputDependencies: mkPrimop(misc.addDrvOutputDependencies, "addDrvOutputDependencies", 2),
|
||||||
compareVersions: mkPrimop(misc.compareVersions, "compareVersions", 2),
|
compareVersions: mkPrimop(misc.compareVersions, "compareVersions", 2),
|
||||||
flakeRefToString: mkPrimop(misc.flakeRefToString, "flakeRefToString", 1),
|
|
||||||
functionArgs: mkPrimop(misc.functionArgs, "functionArgs", 1),
|
functionArgs: mkPrimop(misc.functionArgs, "functionArgs", 1),
|
||||||
genericClosure: mkPrimop(misc.genericClosure, "genericClosure", 1),
|
genericClosure: mkPrimop(misc.genericClosure, "genericClosure", 1),
|
||||||
getFlake: mkPrimop(misc.getFlake, "getFlake", 1),
|
|
||||||
outputOf: mkPrimop(misc.outputOf, "outputOf", 2),
|
outputOf: mkPrimop(misc.outputOf, "outputOf", 2),
|
||||||
parseDrvName: mkPrimop(misc.parseDrvName, "parseDrvName", 1),
|
parseDrvName: mkPrimop(misc.parseDrvName, "parseDrvName", 1),
|
||||||
parseFlakeName: mkPrimop(misc.parseFlakeName, "parseFlakeName", 1),
|
|
||||||
parseFlakeRef: mkPrimop(misc.parseFlakeRef, "parseFlakeRef", 1),
|
|
||||||
placeholder: mkPrimop(misc.placeholder, "placeholder", 1),
|
placeholder: mkPrimop(misc.placeholder, "placeholder", 1),
|
||||||
replaceStrings: mkPrimop(misc.replaceStrings, "replaceStrings", 3),
|
replaceStrings: mkPrimop(misc.replaceStrings, "replaceStrings", 3),
|
||||||
splitVersion: mkPrimop(misc.splitVersion, "splitVersion", 1),
|
splitVersion: mkPrimop(misc.splitVersion, "splitVersion", 1),
|
||||||
@@ -263,10 +216,7 @@ export const builtins: any = {
|
|||||||
langVersion: 6,
|
langVersion: 6,
|
||||||
nixPath: [],
|
nixPath: [],
|
||||||
nixVersion: "2.31.2",
|
nixVersion: "2.31.2",
|
||||||
storeDir: "INVALID_PATH",
|
storeDir: createThunk(() => {
|
||||||
|
throw new Error("stub storeDir evaluated");
|
||||||
__traceCaller: (e: NixValue) => {
|
}),
|
||||||
console.log(`traceCaller: ${getTos()}`);
|
|
||||||
return e;
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
/**
|
import { getPathValue } from "../path";
|
||||||
* I/O and filesystem builtin functions
|
import type { NixStringContext, StringWithContext } from "../string-context";
|
||||||
* Implemented via Rust ops exposed through deno_core
|
import { addOpaqueContext, mkStringWithContext } from "../string-context";
|
||||||
*/
|
import { force } from "../thunk";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
forceAttrs,
|
forceAttrs,
|
||||||
forceBool,
|
forceBool,
|
||||||
@@ -11,15 +10,11 @@ import {
|
|||||||
forceStringNoCtx,
|
forceStringNoCtx,
|
||||||
forceStringValue,
|
forceStringValue,
|
||||||
} from "../type-assert";
|
} from "../type-assert";
|
||||||
import type { NixValue, NixAttrs, NixPath, NixString } from "../types";
|
import type { NixAttrs, NixPath, NixString, NixValue } from "../types";
|
||||||
import { isNixPath, IS_PATH, CatchableError } from "../types";
|
import { CatchableError, IS_PATH, isNixPath } from "../types";
|
||||||
import { force } from "../thunk";
|
|
||||||
import { coerceToPath, coerceToString, StringCoercionMode } from "./conversion";
|
import { coerceToPath, coerceToString, StringCoercionMode } from "./conversion";
|
||||||
import { getPathValue } from "../path";
|
|
||||||
import type { NixStringContext, StringWithContext } from "../string-context";
|
|
||||||
import { mkStringWithContext, addOpaqueContext } from "../string-context";
|
|
||||||
import { isAttrs, isPath } from "./type-check";
|
|
||||||
import { baseNameOf } from "./path";
|
import { baseNameOf } from "./path";
|
||||||
|
import { isAttrs, isPath } from "./type-check";
|
||||||
|
|
||||||
const importCache = new Map<string, NixValue>();
|
const importCache = new Map<string, NixValue>();
|
||||||
|
|
||||||
@@ -84,7 +79,7 @@ export const storePath = (pathArg: NixValue): StringWithContext => {
|
|||||||
return mkStringWithContext(validatedPath, context);
|
return mkStringWithContext(validatedPath, context);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchClosure = (args: NixValue): never => {
|
export const fetchClosure = (_args: NixValue): never => {
|
||||||
throw new Error("Not implemented: fetchClosure");
|
throw new Error("Not implemented: fetchClosure");
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -136,7 +131,7 @@ const normalizeTarballInput = (args: NixValue): { url: string; sha256?: string;
|
|||||||
const sha256 = "sha256" in forced ? forceStringNoCtx(forced.sha256) : undefined;
|
const sha256 = "sha256" in forced ? forceStringNoCtx(forced.sha256) : undefined;
|
||||||
const nameRaw = "name" in forced ? forceStringNoCtx(forced.name) : undefined;
|
const nameRaw = "name" in forced ? forceStringNoCtx(forced.name) : undefined;
|
||||||
// FIXME: extract baseNameOfRaw
|
// FIXME: extract baseNameOfRaw
|
||||||
const name = nameRaw === "" ? baseNameOf(nameRaw) as string : nameRaw;
|
const name = nameRaw === "" ? (baseNameOf(nameRaw) as string) : nameRaw;
|
||||||
return { url, sha256, name };
|
return { url, sha256, name };
|
||||||
} else {
|
} else {
|
||||||
return { url: forceStringNoCtx(forced) };
|
return { url: forceStringNoCtx(forced) };
|
||||||
@@ -145,11 +140,11 @@ const normalizeTarballInput = (args: NixValue): { url: string; sha256?: string;
|
|||||||
|
|
||||||
const resolvePseudoUrl = (url: string) => {
|
const resolvePseudoUrl = (url: string) => {
|
||||||
if (url.startsWith("channel:")) {
|
if (url.startsWith("channel:")) {
|
||||||
return `https://channels.nixos.org/${url.substring(8)}/nixexprs.tar.xz`
|
return `https://channels.nixos.org/${url.substring(8)}/nixexprs.tar.xz`;
|
||||||
} else {
|
} else {
|
||||||
return url
|
return url;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export const fetchurl = (args: NixValue): NixString => {
|
export const fetchurl = (args: NixValue): NixString => {
|
||||||
const { url, hash, name, executable } = normalizeUrlInput(args);
|
const { url, hash, name, executable } = normalizeUrlInput(args);
|
||||||
@@ -224,7 +219,7 @@ export const fetchGit = (args: NixValue): NixAttrs => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const fetchMercurial = (_args: NixValue): NixAttrs => {
|
export const fetchMercurial = (_args: NixValue): NixAttrs => {
|
||||||
throw new Error("Not implemented: fetchMercurial")
|
throw new Error("Not implemented: fetchMercurial");
|
||||||
};
|
};
|
||||||
|
|
||||||
export const fetchTree = (args: NixValue): NixAttrs => {
|
export const fetchTree = (args: NixValue): NixAttrs => {
|
||||||
@@ -249,7 +244,6 @@ export const fetchTree = (args: NixValue): NixAttrs => {
|
|||||||
case "gitlab":
|
case "gitlab":
|
||||||
case "sourcehut":
|
case "sourcehut":
|
||||||
return fetchGitForge(type, attrs);
|
return fetchGitForge(type, attrs);
|
||||||
case "auto":
|
|
||||||
default:
|
default:
|
||||||
return autoDetectAndFetch(attrs);
|
return autoDetectAndFetch(attrs);
|
||||||
}
|
}
|
||||||
@@ -375,7 +369,7 @@ export const path = (args: NixValue): NixString => {
|
|||||||
|
|
||||||
const includePaths: string[] = [];
|
const includePaths: string[] = [];
|
||||||
for (const [relPath, fileType] of entries) {
|
for (const [relPath, fileType] of entries) {
|
||||||
const fullPath = pathStr + "/" + relPath;
|
const fullPath = `${pathStr}/${relPath}`;
|
||||||
const innerFn = forceFunction(filterFn(fullPath));
|
const innerFn = forceFunction(filterFn(fullPath));
|
||||||
const shouldInclude = force(innerFn(fileType));
|
const shouldInclude = force(innerFn(fileType));
|
||||||
if (shouldInclude === true) {
|
if (shouldInclude === true) {
|
||||||
@@ -383,13 +377,7 @@ export const path = (args: NixValue): NixString => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
storePath = Deno.core.ops.op_add_filtered_path(
|
storePath = Deno.core.ops.op_add_filtered_path(pathStr, name, recursive, sha256, includePaths);
|
||||||
pathStr,
|
|
||||||
name,
|
|
||||||
recursive,
|
|
||||||
sha256,
|
|
||||||
includePaths,
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
storePath = Deno.core.ops.op_add_path(pathStr, name, recursive, sha256);
|
storePath = Deno.core.ops.op_add_path(pathStr, name, recursive, sha256);
|
||||||
}
|
}
|
||||||
@@ -421,13 +409,11 @@ export const toFile =
|
|||||||
return mkStringWithContext(storePath, new Set([storePath]));
|
return mkStringWithContext(storePath, new Set([storePath]));
|
||||||
};
|
};
|
||||||
|
|
||||||
export const toPath = (name: NixValue, s: NixValue): never => {
|
export const filterSource =
|
||||||
throw new Error("Not implemented: toPath");
|
(_filter: NixValue) =>
|
||||||
};
|
(_path: NixValue): never => {
|
||||||
|
throw new Error("Not implemented: filterSource");
|
||||||
export const filterSource = (args: NixValue): never => {
|
};
|
||||||
throw new Error("Not implemented: filterSource");
|
|
||||||
};
|
|
||||||
|
|
||||||
const suffixIfPotentialMatch = (prefix: string, path: string): string | null => {
|
const suffixIfPotentialMatch = (prefix: string, path: string): string | null => {
|
||||||
const n = prefix.length;
|
const n = prefix.length;
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
/**
|
|
||||||
* List operation builtin functions
|
|
||||||
* All functions are properly curried
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { NixValue, NixList, NixAttrs } from "../types";
|
|
||||||
import { force } from "../thunk";
|
|
||||||
import { forceList, forceFunction, forceInt, forceBool } from "../type-assert";
|
|
||||||
import { op } from "../operators";
|
import { op } from "../operators";
|
||||||
|
import { force } from "../thunk";
|
||||||
|
import { forceBool, forceFunction, forceInt, forceList } from "../type-assert";
|
||||||
|
import type { NixAttrs, NixList, NixValue } from "../types";
|
||||||
|
|
||||||
export const map =
|
export const map =
|
||||||
(f: NixValue) =>
|
(f: NixValue) =>
|
||||||
@@ -74,23 +69,23 @@ export const concatMap =
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const foldlPrime =
|
export const foldlPrime =
|
||||||
(op_fn: NixValue) =>
|
(opFn: NixValue) =>
|
||||||
(nul: NixValue) =>
|
(nul: NixValue) =>
|
||||||
(list: NixValue): NixValue => {
|
(list: NixValue): NixValue => {
|
||||||
const forced_op = forceFunction(op_fn);
|
const forcedOp = forceFunction(opFn);
|
||||||
return forceList(list).reduce((acc: NixValue, cur: NixValue) => {
|
return forceList(list).reduce((acc: NixValue, cur: NixValue) => {
|
||||||
return forceFunction(forced_op(acc))(cur);
|
return forceFunction(forcedOp(acc))(cur);
|
||||||
}, nul);
|
}, nul);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sort =
|
export const sort =
|
||||||
(cmp: NixValue) =>
|
(cmp: NixValue) =>
|
||||||
(list: NixValue): NixList => {
|
(list: NixValue): NixList => {
|
||||||
const forced_list = [...forceList(list)];
|
const forcedList = [...forceList(list)];
|
||||||
const forced_cmp = forceFunction(cmp);
|
const forcedCmp = forceFunction(cmp);
|
||||||
return forced_list.sort((a, b) => {
|
return forcedList.sort((a, b) => {
|
||||||
if (force(forceFunction(forced_cmp(a))(b))) return -1;
|
if (force(forceFunction(forcedCmp(a))(b))) return -1;
|
||||||
if (force(forceFunction(forced_cmp(b))(a))) return 1;
|
if (force(forceFunction(forcedCmp(b))(a))) return 1;
|
||||||
return 0;
|
return 0;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -98,14 +93,14 @@ export const sort =
|
|||||||
export const partition =
|
export const partition =
|
||||||
(pred: NixValue) =>
|
(pred: NixValue) =>
|
||||||
(list: NixValue): NixAttrs => {
|
(list: NixValue): NixAttrs => {
|
||||||
const forced_list = forceList(list);
|
const forcedList = forceList(list);
|
||||||
const forced_pred = forceFunction(pred);
|
const forcedPred = forceFunction(pred);
|
||||||
const attrs = {
|
const attrs = {
|
||||||
right: [] as NixList,
|
right: [] as NixList,
|
||||||
wrong: [] as NixList,
|
wrong: [] as NixList,
|
||||||
};
|
};
|
||||||
for (const elem of forced_list) {
|
for (const elem of forcedList) {
|
||||||
if (force(forced_pred(elem))) {
|
if (force(forcedPred(elem))) {
|
||||||
attrs.right.push(elem);
|
attrs.right.push(elem);
|
||||||
} else {
|
} else {
|
||||||
attrs.wrong.push(elem);
|
attrs.wrong.push(elem);
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
/**
|
|
||||||
* Math builtin functions
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { NixValue } from "../types";
|
|
||||||
import { forceNumeric } from "../type-assert";
|
import { forceNumeric } from "../type-assert";
|
||||||
|
import type { NixValue } from "../types";
|
||||||
|
|
||||||
export const ceil = (x: NixValue): bigint => {
|
export const ceil = (x: NixValue): bigint => {
|
||||||
const val = forceNumeric(x);
|
const val = forceNumeric(x);
|
||||||
|
|||||||
@@ -1,31 +1,27 @@
|
|||||||
/**
|
import { OrderedSet } from "js-sdsl";
|
||||||
* Miscellaneous builtin functions
|
import { compareValues } from "../operators";
|
||||||
*/
|
import {
|
||||||
|
getStringContext,
|
||||||
import { force } from "../thunk";
|
getStringValue,
|
||||||
import { CatchableError, ATTR_POSITIONS } from "../types";
|
mkStringWithContext,
|
||||||
import type { NixAttrs, NixBool, NixStrictValue, NixValue } from "../types";
|
type NixStringContext,
|
||||||
|
} from "../string-context";
|
||||||
|
import { force } from "../thunk";
|
||||||
import {
|
import {
|
||||||
forceList,
|
|
||||||
forceAttrs,
|
forceAttrs,
|
||||||
forceFunction,
|
forceFunction,
|
||||||
forceStringValue,
|
forceList,
|
||||||
forceString,
|
forceString,
|
||||||
forceStringNoCtx,
|
forceStringNoCtx,
|
||||||
|
forceStringValue,
|
||||||
} from "../type-assert";
|
} from "../type-assert";
|
||||||
|
import type { NixAttrs, NixBool, NixStrictValue, NixValue } from "../types";
|
||||||
|
import { ATTR_POSITIONS, CatchableError } from "../types";
|
||||||
import * as context from "./context";
|
import * as context from "./context";
|
||||||
import { compareValues } from "../operators";
|
|
||||||
import { isBool, isFloat, isInt, isList, isString, typeOf } from "./type-check";
|
import { isBool, isFloat, isInt, isList, isString, typeOf } from "./type-check";
|
||||||
import { OrderedSet } from "js-sdsl";
|
|
||||||
import {
|
|
||||||
type NixStringContext,
|
|
||||||
getStringValue,
|
|
||||||
getStringContext,
|
|
||||||
mkStringWithContext,
|
|
||||||
} from "../string-context";
|
|
||||||
|
|
||||||
export const addErrorContext =
|
export const addErrorContext =
|
||||||
(e1: NixValue) =>
|
(_e1: NixValue) =>
|
||||||
(e2: NixValue): NixValue => {
|
(e2: NixValue): NixValue => {
|
||||||
// FIXME:
|
// FIXME:
|
||||||
// console.log("[WARNING]: addErrorContext not implemented");
|
// console.log("[WARNING]: addErrorContext not implemented");
|
||||||
@@ -38,23 +34,6 @@ export const getContext = context.getContext;
|
|||||||
|
|
||||||
export const hasContext = context.hasContext;
|
export const hasContext = context.hasContext;
|
||||||
|
|
||||||
export const hashFile =
|
|
||||||
(type: NixValue) =>
|
|
||||||
(p: NixValue): never => {
|
|
||||||
const ty = forceStringNoCtx(type);
|
|
||||||
throw new Error("Not implemented: hashFile");
|
|
||||||
};
|
|
||||||
|
|
||||||
export const hashString =
|
|
||||||
(type: NixValue) =>
|
|
||||||
(p: NixValue): never => {
|
|
||||||
throw new Error("Not implemented: hashString");
|
|
||||||
};
|
|
||||||
|
|
||||||
export const convertHash = (args: NixValue): never => {
|
|
||||||
throw new Error("Not implemented: convertHash");
|
|
||||||
};
|
|
||||||
|
|
||||||
export const unsafeDiscardOutputDependency = context.unsafeDiscardOutputDependency;
|
export const unsafeDiscardOutputDependency = context.unsafeDiscardOutputDependency;
|
||||||
|
|
||||||
export const unsafeDiscardStringContext = context.unsafeDiscardStringContext;
|
export const unsafeDiscardStringContext = context.unsafeDiscardStringContext;
|
||||||
@@ -77,9 +56,9 @@ export const compareVersions =
|
|||||||
i1 = c1.nextIndex;
|
i1 = c1.nextIndex;
|
||||||
i2 = c2.nextIndex;
|
i2 = c2.nextIndex;
|
||||||
|
|
||||||
if (componentsLT(c1.component, c2.component)) {
|
if (componentsLt(c1.component, c2.component)) {
|
||||||
return -1n;
|
return -1n;
|
||||||
} else if (componentsLT(c2.component, c1.component)) {
|
} else if (componentsLt(c2.component, c1.component)) {
|
||||||
return 1n;
|
return 1n;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,7 +100,7 @@ function nextComponent(s: string, startIdx: number): ComponentResult {
|
|||||||
return { component: s.substring(start, p), nextIndex: p };
|
return { component: s.substring(start, p), nextIndex: p };
|
||||||
}
|
}
|
||||||
|
|
||||||
function componentsLT(c1: string, c2: string): boolean {
|
function componentsLt(c1: string, c2: string): boolean {
|
||||||
const n1 = c1.match(/^[0-9]+$/) ? BigInt(c1) : null;
|
const n1 = c1.match(/^[0-9]+$/) ? BigInt(c1) : null;
|
||||||
const n2 = c2.match(/^[0-9]+$/) ? BigInt(c2) : null;
|
const n2 = c2.match(/^[0-9]+$/) ? BigInt(c2) : null;
|
||||||
|
|
||||||
@@ -155,25 +134,17 @@ function componentsLT(c1: string, c2: string): boolean {
|
|||||||
return c1 < c2;
|
return c1 < c2;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const dirOf = (s: NixValue): never => {
|
|
||||||
throw new Error("Not implemented: dirOf");
|
|
||||||
};
|
|
||||||
|
|
||||||
export const flakeRefToString = (attrs: NixValue): never => {
|
|
||||||
throw new Error("Not implemented: flakeRefToString");
|
|
||||||
};
|
|
||||||
|
|
||||||
export const functionArgs = (f: NixValue): NixAttrs => {
|
export const functionArgs = (f: NixValue): NixAttrs => {
|
||||||
const func = forceFunction(f);
|
const func = forceFunction(f);
|
||||||
if (func.args) {
|
if (func.args) {
|
||||||
const ret: NixAttrs = {};
|
const ret: NixAttrs = {};
|
||||||
for (const key of func.args!.required) {
|
for (const key of func.args.required) {
|
||||||
ret[key] = false;
|
ret[key] = false;
|
||||||
}
|
}
|
||||||
for (const key of func.args!.optional) {
|
for (const key of func.args.optional) {
|
||||||
ret[key] = true;
|
ret[key] = true;
|
||||||
}
|
}
|
||||||
const positions = func.args!.positions;
|
const positions = func.args.positions;
|
||||||
if (positions && Object.keys(positions).length > 0) {
|
if (positions && Object.keys(positions).length > 0) {
|
||||||
Object.defineProperty(ret, ATTR_POSITIONS, {
|
Object.defineProperty(ret, ATTR_POSITIONS, {
|
||||||
value: positions,
|
value: positions,
|
||||||
@@ -235,13 +206,9 @@ export const genericClosure = (args: NixValue): NixValue => {
|
|||||||
return resultList;
|
return resultList;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getFlake = (attrs: NixValue): never => {
|
|
||||||
throw new Error("Not implemented: getFlake");
|
|
||||||
};
|
|
||||||
|
|
||||||
export const outputOf =
|
export const outputOf =
|
||||||
(drv: NixValue) =>
|
(_drv: NixValue) =>
|
||||||
(out: NixValue): never => {
|
(_out: NixValue): never => {
|
||||||
throw new Error("Not implemented: outputOf");
|
throw new Error("Not implemented: outputOf");
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -262,14 +229,6 @@ export const parseDrvName = (s: NixValue): NixAttrs => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const parseFlakeName = (s: NixValue): never => {
|
|
||||||
throw new Error("Not implemented: parseFlakeName");
|
|
||||||
};
|
|
||||||
|
|
||||||
export const parseFlakeRef = (s: NixValue): never => {
|
|
||||||
throw new Error("Not implemented: parseFlakeRef");
|
|
||||||
};
|
|
||||||
|
|
||||||
export const placeholder = (output: NixValue): NixValue => {
|
export const placeholder = (output: NixValue): NixValue => {
|
||||||
const outputStr = forceStringNoCtx(output);
|
const outputStr = forceStringNoCtx(output);
|
||||||
return Deno.core.ops.op_make_placeholder(outputStr);
|
return Deno.core.ops.op_make_placeholder(outputStr);
|
||||||
@@ -314,7 +273,7 @@ export const replaceStrings =
|
|||||||
resultContext.add(elem);
|
resultContext.add(elem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const replacement = toCache.get(i)!;
|
const replacement = toCache.get(i) as string;
|
||||||
|
|
||||||
result += replacement;
|
result += replacement;
|
||||||
|
|
||||||
@@ -361,7 +320,7 @@ export const splitVersion = (s: NixValue): NixValue => {
|
|||||||
return components;
|
return components;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const traceVerbose = (e1: NixValue, e2: NixValue): never => {
|
export const traceVerbose = (_e1: NixValue, _e2: NixValue): never => {
|
||||||
throw new Error("Not implemented: traceVerbose");
|
throw new Error("Not implemented: traceVerbose");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
/**
|
|
||||||
* Path-related builtin functions
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { NixValue, NixString, NixPath } from "../types";
|
|
||||||
import { isNixPath, isStringWithContext } from "../types";
|
|
||||||
import { force } from "../thunk";
|
|
||||||
import { mkPath } from "../path";
|
import { mkPath } from "../path";
|
||||||
import { coerceToString, StringCoercionMode, coerceToPath } from "./conversion";
|
|
||||||
import { mkStringWithContext, type NixStringContext } from "../string-context";
|
import { mkStringWithContext, type NixStringContext } from "../string-context";
|
||||||
|
import { force } from "../thunk";
|
||||||
|
import type { NixPath, NixString, NixValue } from "../types";
|
||||||
|
import { isNixPath, isStringWithContext } from "../types";
|
||||||
|
import { coerceToPath, coerceToString, StringCoercionMode } from "./conversion";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* builtins.baseNameOf
|
* builtins.baseNameOf
|
||||||
|
|||||||
@@ -1,29 +1,15 @@
|
|||||||
/**
|
|
||||||
* String operation builtin functions
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { NixInt, NixValue, NixString } from "../types";
|
|
||||||
import { forceStringValue, forceList, forceInt, forceString } from "../type-assert";
|
|
||||||
import { coerceToString, StringCoercionMode } from "./conversion";
|
|
||||||
import {
|
import {
|
||||||
type NixStringContext,
|
|
||||||
getStringValue,
|
|
||||||
getStringContext,
|
getStringContext,
|
||||||
|
getStringValue,
|
||||||
mkStringWithContext,
|
mkStringWithContext,
|
||||||
|
type NixStringContext,
|
||||||
} from "../string-context";
|
} from "../string-context";
|
||||||
|
import { forceInt, forceList, forceString, forceStringValue } from "../type-assert";
|
||||||
|
import type { NixInt, NixString, NixValue } from "../types";
|
||||||
|
import { coerceToString, StringCoercionMode } from "./conversion";
|
||||||
|
|
||||||
export const stringLength = (e: NixValue): NixInt => BigInt(forceStringValue(e).length);
|
export const stringLength = (e: NixValue): NixInt => BigInt(forceStringValue(e).length);
|
||||||
|
|
||||||
/**
|
|
||||||
* builtins.substring - Extract substring while preserving string context
|
|
||||||
*
|
|
||||||
* IMPORTANT: String context must be preserved from the source string.
|
|
||||||
* This matches Lix behavior where substring operations maintain references
|
|
||||||
* to store paths and derivations.
|
|
||||||
*
|
|
||||||
* Special case: substring 0 0 str can be used idiomatically to capture
|
|
||||||
* string context efficiently without copying the string value.
|
|
||||||
*/
|
|
||||||
export const substring =
|
export const substring =
|
||||||
(start: NixValue) =>
|
(start: NixValue) =>
|
||||||
(len: NixValue) =>
|
(len: NixValue) =>
|
||||||
@@ -55,13 +41,6 @@ export const substring =
|
|||||||
return mkStringWithContext(result, context);
|
return mkStringWithContext(result, context);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* builtins.concatStringsSep - Concatenate strings with separator, merging contexts
|
|
||||||
*
|
|
||||||
* IMPORTANT: String context must be collected from both the separator and all
|
|
||||||
* list elements, then merged into the result. This ensures that store path
|
|
||||||
* references are preserved when building paths like "/nix/store/xxx/bin:/nix/store/yyy/bin".
|
|
||||||
*/
|
|
||||||
export const concatStringsSep =
|
export const concatStringsSep =
|
||||||
(sep: NixValue) =>
|
(sep: NixValue) =>
|
||||||
(list: NixValue): NixString => {
|
(list: NixValue): NixString => {
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
/**
|
|
||||||
* Type checking builtin functions
|
|
||||||
*/
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
HAS_CONTEXT,
|
HAS_CONTEXT,
|
||||||
isNixPath,
|
isNixPath,
|
||||||
isStringWithContext,
|
isStringWithContext,
|
||||||
type NixPath,
|
|
||||||
type NixAttrs,
|
type NixAttrs,
|
||||||
type NixBool,
|
type NixBool,
|
||||||
type NixFloat,
|
type NixFloat,
|
||||||
@@ -14,14 +9,11 @@ import {
|
|||||||
type NixInt,
|
type NixInt,
|
||||||
type NixList,
|
type NixList,
|
||||||
type NixNull,
|
type NixNull,
|
||||||
type NixString,
|
type NixPath,
|
||||||
type NixStrictValue,
|
type NixStrictValue,
|
||||||
|
type NixString,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if a value is a Nix string (plain string or StringWithContext)
|
|
||||||
* This works on already-forced values (NixStrictValue).
|
|
||||||
*/
|
|
||||||
export const isNixString = (v: NixStrictValue): v is NixString => {
|
export const isNixString = (v: NixStrictValue): v is NixString => {
|
||||||
return typeof v === "string" || isStringWithContext(v);
|
return typeof v === "string" || isStringWithContext(v);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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 { coerceToString, StringCoercionMode } from "./builtins/conversion";
|
||||||
import { type NixStringContext, mkStringWithContext, isStringWithContext } from "./string-context";
|
import { isAttrs, typeOf } from "./builtins/type-check";
|
||||||
import { force } from "./thunk";
|
|
||||||
import { mkPath } from "./path";
|
import { mkPath } from "./path";
|
||||||
|
import { isStringWithContext, mkStringWithContext, type NixStringContext } from "./string-context";
|
||||||
|
import { force } from "./thunk";
|
||||||
|
import { forceAttrs, forceBool, forceFunction, forceStringValue } from "./type-assert";
|
||||||
|
import type { NixAttrs, NixBool, NixPath, NixString, NixValue } from "./types";
|
||||||
import { CatchableError, isNixPath } from "./types";
|
import { CatchableError, isNixPath } from "./types";
|
||||||
|
|
||||||
interface StackFrame {
|
interface StackFrame {
|
||||||
@@ -36,34 +32,17 @@ function enrichError(error: unknown): Error {
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getTos = (): string => {
|
const pushContext = (message: string, span: string): void => {
|
||||||
const tos = callStack[callStack.length - 2];
|
|
||||||
const { file, line, column } = Deno.core.ops.op_decode_span(tos.span);
|
|
||||||
return `${tos.message} at ${file}:${line}:${column}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Push an error context onto the stack
|
|
||||||
* Used for tracking evaluation context (e.g., "while evaluating the condition")
|
|
||||||
*/
|
|
||||||
export const pushContext = (message: string, span: string): void => {
|
|
||||||
if (callStack.length >= MAX_STACK_DEPTH) {
|
if (callStack.length >= MAX_STACK_DEPTH) {
|
||||||
callStack.shift();
|
callStack.shift();
|
||||||
}
|
}
|
||||||
callStack.push({ span, message });
|
callStack.push({ span, message });
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
const popContext = (): void => {
|
||||||
* Pop an error context from the stack
|
|
||||||
*/
|
|
||||||
export const popContext = (): void => {
|
|
||||||
callStack.pop();
|
callStack.pop();
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute a function with error context tracking
|
|
||||||
* Automatically pushes context before execution and pops after
|
|
||||||
*/
|
|
||||||
export const withContext = <T>(message: string, span: string, fn: () => T): T => {
|
export const withContext = <T>(message: string, span: string, fn: () => T): T => {
|
||||||
pushContext(message, span);
|
pushContext(message, span);
|
||||||
try {
|
try {
|
||||||
@@ -149,13 +128,6 @@ export const concatStringsWithContext = (parts: NixValue[], forceString: boolean
|
|||||||
return mkStringWithContext(value, context);
|
return mkStringWithContext(value, context);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve a path (handles both absolute and relative paths)
|
|
||||||
* For relative paths, resolves against current import stack
|
|
||||||
*
|
|
||||||
* @param path - Path string (may be relative or absolute)
|
|
||||||
* @returns NixPath object with absolute path
|
|
||||||
*/
|
|
||||||
export const resolvePath = (currentDir: string, path: NixValue): NixPath => {
|
export const resolvePath = (currentDir: string, path: NixValue): NixPath => {
|
||||||
const forced = force(path);
|
const forced = force(path);
|
||||||
let pathStr: string;
|
let pathStr: string;
|
||||||
@@ -181,18 +153,18 @@ export const select = (obj: NixValue, attrpath: NixValue[], span?: string): NixV
|
|||||||
}
|
}
|
||||||
callStack.push({ span, message });
|
callStack.push({ span, message });
|
||||||
try {
|
try {
|
||||||
return select_impl(obj, attrpath);
|
return selectImpl(obj, attrpath);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw enrichError(error);
|
throw enrichError(error);
|
||||||
} finally {
|
} finally {
|
||||||
callStack.pop();
|
callStack.pop();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return select_impl(obj, attrpath);
|
return selectImpl(obj, attrpath);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function select_impl(obj: NixValue, attrpath: NixValue[]): NixValue {
|
function selectImpl(obj: NixValue, attrpath: NixValue[]): NixValue {
|
||||||
let attrs = forceAttrs(obj);
|
let attrs = forceAttrs(obj);
|
||||||
|
|
||||||
for (const attr of attrpath.slice(0, -1)) {
|
for (const attr of attrpath.slice(0, -1)) {
|
||||||
@@ -214,7 +186,7 @@ function select_impl(obj: NixValue, attrpath: NixValue[]): NixValue {
|
|||||||
export const selectWithDefault = (
|
export const selectWithDefault = (
|
||||||
obj: NixValue,
|
obj: NixValue,
|
||||||
attrpath: NixValue[],
|
attrpath: NixValue[],
|
||||||
default_val: NixValue,
|
defaultVal: NixValue,
|
||||||
span?: string,
|
span?: string,
|
||||||
): NixValue => {
|
): NixValue => {
|
||||||
if (span) {
|
if (span) {
|
||||||
@@ -227,18 +199,18 @@ export const selectWithDefault = (
|
|||||||
}
|
}
|
||||||
callStack.push({ span, message });
|
callStack.push({ span, message });
|
||||||
try {
|
try {
|
||||||
return selectWithDefault_impl(obj, attrpath, default_val);
|
return selectWithDefaultImpl(obj, attrpath, defaultVal);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw enrichError(error);
|
throw enrichError(error);
|
||||||
} finally {
|
} finally {
|
||||||
callStack.pop();
|
callStack.pop();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return selectWithDefault_impl(obj, attrpath, default_val);
|
return selectWithDefaultImpl(obj, attrpath, defaultVal);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function selectWithDefault_impl(obj: NixValue, attrpath: NixValue[], defaultVal: NixValue): NixValue {
|
function selectWithDefaultImpl(obj: NixValue, attrpath: NixValue[], defaultVal: NixValue): NixValue {
|
||||||
let attrs = force(obj);
|
let attrs = force(obj);
|
||||||
if (!isAttrs(attrs)) {
|
if (!isAttrs(attrs)) {
|
||||||
return defaultVal;
|
return defaultVal;
|
||||||
@@ -281,49 +253,6 @@ export const hasAttr = (obj: NixValue, attrpath: NixValue[]): NixBool => {
|
|||||||
return forceStringValue(attrpath[attrpath.length - 1]) in attrs;
|
return forceStringValue(attrpath[attrpath.length - 1]) in attrs;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate function parameters
|
|
||||||
* Used for pattern matching in function parameters
|
|
||||||
*
|
|
||||||
* Example: { a, b ? 1, ... }: ...
|
|
||||||
* - required: ["a"]
|
|
||||||
* - allowed: ["a", "b"] (or null if ellipsis "..." present)
|
|
||||||
*
|
|
||||||
* @param arg - Argument object to validate
|
|
||||||
* @param required - Array of required parameter names (or null)
|
|
||||||
* @param allowed - Array of allowed parameter names (or null for ellipsis)
|
|
||||||
* @returns The forced argument object
|
|
||||||
* @throws Error if required param missing or unexpected param present
|
|
||||||
*/
|
|
||||||
export const validateParams = (
|
|
||||||
arg: NixValue,
|
|
||||||
required: string[] | null,
|
|
||||||
allowed: string[] | null,
|
|
||||||
): NixAttrs => {
|
|
||||||
const forced_arg = forceAttrs(arg);
|
|
||||||
|
|
||||||
// Check required parameters
|
|
||||||
if (required) {
|
|
||||||
for (const key of required) {
|
|
||||||
if (!Object.hasOwn(forced_arg, key)) {
|
|
||||||
throw new Error(`Function called without required argument '${key}'`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check allowed parameters (if not using ellipsis)
|
|
||||||
if (allowed) {
|
|
||||||
const allowed_set = new Set(allowed);
|
|
||||||
for (const key in forced_arg) {
|
|
||||||
if (!allowed_set.has(key)) {
|
|
||||||
throw new Error(`Function called with unexpected argument '${key}'`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return forced_arg;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const call = (func: NixValue, arg: NixValue, span?: string): NixValue => {
|
export const call = (func: NixValue, arg: NixValue, span?: string): NixValue => {
|
||||||
if (span) {
|
if (span) {
|
||||||
if (callStack.length >= MAX_STACK_DEPTH) {
|
if (callStack.length >= MAX_STACK_DEPTH) {
|
||||||
@@ -331,18 +260,18 @@ export const call = (func: NixValue, arg: NixValue, span?: string): NixValue =>
|
|||||||
}
|
}
|
||||||
callStack.push({ span, message: "from call site" });
|
callStack.push({ span, message: "from call site" });
|
||||||
try {
|
try {
|
||||||
return call_impl(func, arg);
|
return callImpl(func, arg);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw enrichError(error);
|
throw enrichError(error);
|
||||||
} finally {
|
} finally {
|
||||||
callStack.pop();
|
callStack.pop();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return call_impl(func, arg);
|
return callImpl(func, arg);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function call_impl(func: NixValue, arg: NixValue): NixValue {
|
function callImpl(func: NixValue, arg: NixValue): NixValue {
|
||||||
const forcedFunc = force(func);
|
const forcedFunc = force(func);
|
||||||
if (typeof forcedFunc === "function") {
|
if (typeof forcedFunc === "function") {
|
||||||
forcedFunc.args?.check(arg);
|
forcedFunc.args?.check(arg);
|
||||||
|
|||||||
@@ -4,45 +4,39 @@
|
|||||||
* All functionality is exported via the global `Nix` object
|
* All functionality is exported via the global `Nix` object
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
|
||||||
createThunk,
|
|
||||||
force,
|
|
||||||
isThunk,
|
|
||||||
IS_THUNK,
|
|
||||||
DEBUG_THUNKS,
|
|
||||||
forceDeep,
|
|
||||||
IS_CYCLE,
|
|
||||||
forceShallow,
|
|
||||||
} from "./thunk";
|
|
||||||
import {
|
|
||||||
select,
|
|
||||||
selectWithDefault,
|
|
||||||
validateParams,
|
|
||||||
resolvePath,
|
|
||||||
hasAttr,
|
|
||||||
concatStringsWithContext,
|
|
||||||
call,
|
|
||||||
assert,
|
|
||||||
pushContext,
|
|
||||||
popContext,
|
|
||||||
withContext,
|
|
||||||
mkPos,
|
|
||||||
lookupWith,
|
|
||||||
} from "./helpers";
|
|
||||||
import { op } from "./operators";
|
|
||||||
import { builtins, PRIMOP_METADATA } from "./builtins";
|
import { builtins, PRIMOP_METADATA } from "./builtins";
|
||||||
import { coerceToString, StringCoercionMode } from "./builtins/conversion";
|
import { coerceToString, StringCoercionMode } from "./builtins/conversion";
|
||||||
|
import {
|
||||||
|
assert,
|
||||||
|
call,
|
||||||
|
concatStringsWithContext,
|
||||||
|
hasAttr,
|
||||||
|
lookupWith,
|
||||||
|
mkPos,
|
||||||
|
resolvePath,
|
||||||
|
select,
|
||||||
|
selectWithDefault,
|
||||||
|
withContext,
|
||||||
|
} from "./helpers";
|
||||||
|
import { op } from "./operators";
|
||||||
import { HAS_CONTEXT } from "./string-context";
|
import { HAS_CONTEXT } from "./string-context";
|
||||||
import { IS_PATH, mkAttrs, mkFunction, mkAttrsWithPos, ATTR_POSITIONS, NixValue } from "./types";
|
import {
|
||||||
|
createThunk,
|
||||||
|
DEBUG_THUNKS,
|
||||||
|
force,
|
||||||
|
forceDeep,
|
||||||
|
forceShallow,
|
||||||
|
IS_CYCLE,
|
||||||
|
IS_THUNK,
|
||||||
|
isThunk,
|
||||||
|
} from "./thunk";
|
||||||
import { forceBool } from "./type-assert";
|
import { forceBool } from "./type-assert";
|
||||||
|
import { ATTR_POSITIONS, IS_PATH, mkAttrs, mkAttrsWithPos, mkFunction, type NixValue } from "./types";
|
||||||
|
|
||||||
export type NixRuntime = typeof Nix;
|
export type NixRuntime = typeof Nix;
|
||||||
|
|
||||||
const replBindings: Record<string, NixValue> = {};
|
const replBindings: Record<string, NixValue> = {};
|
||||||
|
|
||||||
/**
|
|
||||||
* The global Nix runtime object
|
|
||||||
*/
|
|
||||||
export const Nix = {
|
export const Nix = {
|
||||||
createThunk,
|
createThunk,
|
||||||
force,
|
force,
|
||||||
@@ -62,7 +56,6 @@ export const Nix = {
|
|||||||
select,
|
select,
|
||||||
selectWithDefault,
|
selectWithDefault,
|
||||||
lookupWith,
|
lookupWith,
|
||||||
validateParams,
|
|
||||||
resolvePath,
|
resolvePath,
|
||||||
coerceToString,
|
coerceToString,
|
||||||
concatStringsWithContext,
|
concatStringsWithContext,
|
||||||
@@ -73,8 +66,6 @@ export const Nix = {
|
|||||||
mkPos,
|
mkPos,
|
||||||
ATTR_POSITIONS,
|
ATTR_POSITIONS,
|
||||||
|
|
||||||
pushContext,
|
|
||||||
popContext,
|
|
||||||
withContext,
|
withContext,
|
||||||
|
|
||||||
op,
|
op,
|
||||||
|
|||||||
@@ -1,22 +1,17 @@
|
|||||||
/**
|
import { coerceToString, StringCoercionMode } from "./builtins/conversion";
|
||||||
* Nix operators module
|
import { isNixString, typeOf } from "./builtins/type-check";
|
||||||
* Implements all binary and unary operators used by codegen
|
import { mkPath } from "./path";
|
||||||
*/
|
|
||||||
|
|
||||||
import type { NixValue, NixList, NixAttrs, NixString, NixPath } from "./types";
|
|
||||||
import { isNixPath } from "./types";
|
|
||||||
import { force } from "./thunk";
|
|
||||||
import { forceNumeric, forceList, forceAttrs, coerceNumeric } from "./type-assert";
|
|
||||||
import {
|
import {
|
||||||
type NixStringContext,
|
|
||||||
getStringValue,
|
|
||||||
getStringContext,
|
getStringContext,
|
||||||
|
getStringValue,
|
||||||
mergeContexts,
|
mergeContexts,
|
||||||
mkStringWithContext,
|
mkStringWithContext,
|
||||||
|
type NixStringContext,
|
||||||
} from "./string-context";
|
} from "./string-context";
|
||||||
import { coerceToString, StringCoercionMode } from "./builtins/conversion";
|
import { force } from "./thunk";
|
||||||
import { mkPath } from "./path";
|
import { coerceNumeric, forceAttrs, forceBool, forceList, forceNumeric } from "./type-assert";
|
||||||
import { typeOf, isNixString } from "./builtins/type-check";
|
import type { NixAttrs, NixList, NixPath, NixString, NixValue } from "./types";
|
||||||
|
import { isNixPath } from "./types";
|
||||||
|
|
||||||
const canCoerceToString = (v: NixValue): boolean => {
|
const canCoerceToString = (v: NixValue): boolean => {
|
||||||
const forced = force(v);
|
const forced = force(v);
|
||||||
@@ -27,11 +22,6 @@ const canCoerceToString = (v: NixValue): boolean => {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare two values, similar to Nix's CompareValues.
|
|
||||||
* Returns: -1 if a < b, 0 if a == b, 1 if a > b
|
|
||||||
* Throws TypeError for incomparable types.
|
|
||||||
*/
|
|
||||||
export const compareValues = (a: NixValue, b: NixValue): -1 | 0 | 1 => {
|
export const compareValues = (a: NixValue, b: NixValue): -1 | 0 | 1 => {
|
||||||
const av = force(a);
|
const av = force(a);
|
||||||
const bv = force(b);
|
const bv = force(b);
|
||||||
@@ -54,32 +44,29 @@ export const compareValues = (a: NixValue, b: NixValue): -1 | 0 | 1 => {
|
|||||||
throw new TypeError(`cannot compare ${typeOf(av)} with ${typeOf(bv)}`);
|
throw new TypeError(`cannot compare ${typeOf(av)} with ${typeOf(bv)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Int and float comparison
|
|
||||||
if (typeA === "int" || typeA === "float") {
|
if (typeA === "int" || typeA === "float") {
|
||||||
return av! < bv! ? -1 : av === bv ? 0 : 1;
|
return (av as never) < (bv as never) ? -1 : av === bv ? 0 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// String comparison (handles both plain strings and StringWithContext)
|
|
||||||
if (typeA === "string") {
|
if (typeA === "string") {
|
||||||
const strA = getStringValue(av as NixString);
|
const strA = getStringValue(av as NixString);
|
||||||
const strB = getStringValue(bv as NixString);
|
const strB = getStringValue(bv as NixString);
|
||||||
return strA < strB ? -1 : strA > strB ? 1 : 0;
|
return strA < strB ? -1 : strA > strB ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Path comparison
|
|
||||||
if (typeA === "path") {
|
if (typeA === "path") {
|
||||||
const aPath = av as NixPath;
|
const aPath = av as NixPath;
|
||||||
const bPath = bv as NixPath;
|
const bPath = bv as NixPath;
|
||||||
return aPath.value < bPath.value ? -1 : aPath.value > bPath.value ? 1 : 0;
|
return aPath.value < bPath.value ? -1 : aPath.value > bPath.value ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// List comparison (lexicographic)
|
|
||||||
if (typeA === "list") {
|
if (typeA === "list") {
|
||||||
const aList = av as NixList;
|
const aList = av as NixList;
|
||||||
const bList = bv as NixList;
|
const bList = bv as NixList;
|
||||||
for (let i = 0; ; i++) {
|
for (let i = 0; ; i++) {
|
||||||
|
// Equal if same length, else aList > bList
|
||||||
if (i === bList.length) {
|
if (i === bList.length) {
|
||||||
return i === aList.length ? 0 : 1; // Equal if same length, else aList > bList
|
return i === aList.length ? 0 : 1;
|
||||||
} else if (i === aList.length) {
|
} else if (i === aList.length) {
|
||||||
return -1; // aList < bList
|
return -1; // aList < bList
|
||||||
} else if (!op.eq(aList[i], bList[i])) {
|
} else if (!op.eq(aList[i], bList[i])) {
|
||||||
@@ -94,10 +81,6 @@ export const compareValues = (a: NixValue, b: NixValue): -1 | 0 | 1 => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Operator object exported as Nix.op
|
|
||||||
* All operators referenced by codegen (e.g., Nix.op.add, Nix.op.eq)
|
|
||||||
*/
|
|
||||||
export const op = {
|
export const op = {
|
||||||
add: (a: NixValue, b: NixValue): bigint | number | NixString | NixPath => {
|
add: (a: NixValue, b: NixValue): bigint | number | NixString | NixPath => {
|
||||||
const av = force(a);
|
const av = force(a);
|
||||||
@@ -109,15 +92,14 @@ export const op = {
|
|||||||
const strB = getStringValue(bv);
|
const strB = getStringValue(bv);
|
||||||
const ctxB = getStringContext(bv);
|
const ctxB = getStringContext(bv);
|
||||||
|
|
||||||
// Lix constraint: cannot append string with store context to path
|
|
||||||
if (ctxB.size > 0) {
|
if (ctxB.size > 0) {
|
||||||
throw new TypeError("a string that refers to a store path cannot be appended to a path");
|
throw new TypeError("a string that refers to a store path cannot be appended to a path");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Concatenate paths
|
|
||||||
return mkPath(av.value + strB);
|
return mkPath(av.value + strB);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: handle corepkgs
|
||||||
// path + path: concatenate
|
// path + path: concatenate
|
||||||
if (isNixPath(bv)) {
|
if (isNixPath(bv)) {
|
||||||
return mkPath(av.value + bv.value);
|
return mkPath(av.value + bv.value);
|
||||||
@@ -138,6 +120,7 @@ export const op = {
|
|||||||
|
|
||||||
// String concatenation
|
// String concatenation
|
||||||
if (isNixString(av) && isNixString(bv)) {
|
if (isNixString(av) && isNixString(bv)) {
|
||||||
|
// Merge string context
|
||||||
const strA = getStringValue(av);
|
const strA = getStringValue(av);
|
||||||
const strB = getStringValue(bv);
|
const strB = getStringValue(bv);
|
||||||
const ctxA = getStringContext(av);
|
const ctxA = getStringContext(av);
|
||||||
@@ -162,19 +145,19 @@ export const op = {
|
|||||||
return mkStringWithContext(result, context);
|
return mkStringWithContext(result, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Numeric addition
|
// Perform numeric addition otherwise
|
||||||
const [numA, numB] = coerceNumeric(forceNumeric(a), forceNumeric(b));
|
const [numA, numB] = coerceNumeric(forceNumeric(a), forceNumeric(b));
|
||||||
return (numA as any) + (numB as any);
|
return (numA as never) + (numB as never);
|
||||||
},
|
},
|
||||||
|
|
||||||
sub: (a: NixValue, b: NixValue): bigint | number => {
|
sub: (a: NixValue, b: NixValue): bigint | number => {
|
||||||
const [av, bv] = coerceNumeric(forceNumeric(a), forceNumeric(b));
|
const [av, bv] = coerceNumeric(forceNumeric(a), forceNumeric(b));
|
||||||
return (av as any) - (bv as any);
|
return (av as never) - (bv as never);
|
||||||
},
|
},
|
||||||
|
|
||||||
mul: (a: NixValue, b: NixValue): bigint | number => {
|
mul: (a: NixValue, b: NixValue): bigint | number => {
|
||||||
const [av, bv] = coerceNumeric(forceNumeric(a), forceNumeric(b));
|
const [av, bv] = coerceNumeric(forceNumeric(a), forceNumeric(b));
|
||||||
return (av as any) * (bv as any);
|
return (av as never) * (bv as never);
|
||||||
},
|
},
|
||||||
|
|
||||||
div: (a: NixValue, b: NixValue): bigint | number => {
|
div: (a: NixValue, b: NixValue): bigint | number => {
|
||||||
@@ -184,7 +167,7 @@ export const op = {
|
|||||||
throw new RangeError("Division by zero");
|
throw new RangeError("Division by zero");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (av as any) / (bv as any);
|
return (av as never) / (bv as never);
|
||||||
},
|
},
|
||||||
|
|
||||||
eq: (a: NixValue, b: NixValue): boolean => {
|
eq: (a: NixValue, b: NixValue): boolean => {
|
||||||
@@ -202,7 +185,6 @@ export const op = {
|
|||||||
return av === Number(bv);
|
return av === Number(bv);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get type names for comparison (skip if already handled above)
|
|
||||||
const typeA = typeOf(av);
|
const typeA = typeOf(av);
|
||||||
const typeB = typeOf(bv);
|
const typeB = typeOf(bv);
|
||||||
|
|
||||||
@@ -223,10 +205,12 @@ export const op = {
|
|||||||
return (av as NixPath).value === (bv as NixPath).value;
|
return (av as NixPath).value === (bv as NixPath).value;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Array.isArray(av) && Array.isArray(bv)) {
|
if (typeA === "list") {
|
||||||
if (av.length !== bv.length) return false;
|
const aList = av as NixList;
|
||||||
for (let i = 0; i < av.length; i++) {
|
const bList = bv as NixList;
|
||||||
if (!op.eq(av[i], bv[i])) return false;
|
if (aList.length !== bList.length) return false;
|
||||||
|
for (let i = 0; i < aList.length; i++) {
|
||||||
|
if (!op.eq(aList[i], bList[i])) return false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -261,11 +245,7 @@ export const op = {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Functions are incomparable
|
// Other types are incomparable
|
||||||
if (typeof av === "function") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
neq: (a: NixValue, b: NixValue): boolean => {
|
neq: (a: NixValue, b: NixValue): boolean => {
|
||||||
@@ -284,10 +264,10 @@ export const op = {
|
|||||||
return compareValues(a, b) >= 0;
|
return compareValues(a, b) >= 0;
|
||||||
},
|
},
|
||||||
|
|
||||||
bnot: (a: NixValue): boolean => !force(a),
|
bnot: (a: NixValue): boolean => !forceBool(a),
|
||||||
|
|
||||||
concat: (a: NixValue, b: NixValue): NixList => {
|
concat: (a: NixValue, b: NixValue): NixList => {
|
||||||
return Array.prototype.concat.call(forceList(a), forceList(b));
|
return forceList(a).concat(forceList(b));
|
||||||
},
|
},
|
||||||
|
|
||||||
update: (a: NixValue, b: NixValue): NixAttrs => ({ ...forceAttrs(a), ...forceAttrs(b) }),
|
update: (a: NixValue, b: NixValue): NixAttrs => ({ ...forceAttrs(a), ...forceAttrs(b) }),
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ const canonicalizePath = (path: string): string => {
|
|||||||
i = j;
|
i = j;
|
||||||
|
|
||||||
if (component === ".") {
|
if (component === ".") {
|
||||||
continue;
|
|
||||||
} else if (component === "..") {
|
} else if (component === "..") {
|
||||||
if (parts.length > 0) {
|
if (parts.length > 0) {
|
||||||
parts.pop();
|
parts.pop();
|
||||||
@@ -28,7 +27,7 @@ const canonicalizePath = (path: string): string => {
|
|||||||
if (parts.length === 0) {
|
if (parts.length === 0) {
|
||||||
return "/";
|
return "/";
|
||||||
}
|
}
|
||||||
return "/" + parts.join("/");
|
return `/${parts.join("/")}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const mkPath = (value: string): NixPath => {
|
export const mkPath = (value: string): NixPath => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { isThunk, IS_CYCLE } from "./thunk";
|
import { getPrimopMetadata, isPrimop } from "./builtins/index";
|
||||||
import { isStringWithContext } from "./string-context";
|
import { isStringWithContext } from "./string-context";
|
||||||
|
import { IS_CYCLE, isThunk } from "./thunk";
|
||||||
import { isNixPath, type NixValue } from "./types";
|
import { isNixPath, type NixValue } from "./types";
|
||||||
import { is_primop, get_primop_metadata } from "./builtins/index";
|
|
||||||
|
|
||||||
export const printValue = (value: NixValue, seen: WeakSet<object> = new WeakSet()): string => {
|
export const printValue = (value: NixValue, seen: WeakSet<object> = new WeakSet()): string => {
|
||||||
if (isThunk(value)) {
|
if (isThunk(value)) {
|
||||||
@@ -29,8 +29,8 @@ export const printValue = (value: NixValue, seen: WeakSet<object> = new WeakSet(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof value === "function") {
|
if (typeof value === "function") {
|
||||||
if (is_primop(value)) {
|
if (isPrimop(value)) {
|
||||||
const meta = get_primop_metadata(value);
|
const meta = getPrimopMetadata(value);
|
||||||
if (meta && meta.applied > 0) {
|
if (meta && meta.applied > 0) {
|
||||||
return "<PRIMOP-APP>";
|
return "<PRIMOP-APP>";
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@ export const printValue = (value: NixValue, seen: WeakSet<object> = new WeakSet(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof value === "object") {
|
if (typeof value === "object") {
|
||||||
if (IS_CYCLE in value && (value as any)[IS_CYCLE] === true) {
|
if (IS_CYCLE in value && (value as Record<symbol, unknown>)[IS_CYCLE] === true) {
|
||||||
return "«repeated»";
|
return "«repeated»";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ const printString = (s: string): string => {
|
|||||||
result += c;
|
result += c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result + '"';
|
return `${result}"`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SYMBOL_REGEX = /^[a-zA-Z_][a-zA-Z0-9_'-]*$/;
|
const SYMBOL_REGEX = /^[a-zA-Z_][a-zA-Z0-9_'-]*$/;
|
||||||
|
|||||||
@@ -1,29 +1,4 @@
|
|||||||
/**
|
import type { NixStrictValue } from "./types";
|
||||||
* String Context System for Nix
|
|
||||||
*
|
|
||||||
* String context tracks references to store paths and derivations within strings.
|
|
||||||
* This is critical for Nix's dependency tracking - when a string containing a
|
|
||||||
* store path is used in a derivation, that store path becomes a build dependency.
|
|
||||||
*
|
|
||||||
* Context Elements (encoded as strings):
|
|
||||||
* - Opaque: Plain store path reference
|
|
||||||
* Format: "/nix/store/..."
|
|
||||||
* Example: "/nix/store/abc123-hello"
|
|
||||||
*
|
|
||||||
* - DrvDeep: Derivation with all outputs
|
|
||||||
* Format: "=/nix/store/...drv"
|
|
||||||
* Example: "=/nix/store/xyz789-hello.drv"
|
|
||||||
* Meaning: All outputs of this derivation and its closure
|
|
||||||
*
|
|
||||||
* - Built: Specific derivation output
|
|
||||||
* Format: "!<output>!/nix/store/...drv"
|
|
||||||
* Example: "!out!/nix/store/xyz789-hello.drv"
|
|
||||||
* Meaning: Specific output (e.g., "out", "dev", "lib") of this derivation
|
|
||||||
*
|
|
||||||
* This implementation matches Lix's NixStringContext system.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { NixStrictValue } from "./types";
|
|
||||||
|
|
||||||
export const HAS_CONTEXT = Symbol("HAS_CONTEXT");
|
export const HAS_CONTEXT = Symbol("HAS_CONTEXT");
|
||||||
|
|
||||||
@@ -172,22 +147,6 @@ export const parseContextToInfoMap = (context: NixStringContext): Map<string, Pa
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract input derivations and source paths from context
|
|
||||||
*
|
|
||||||
* IMPORTANT: Used by derivation builder to determine build dependencies.
|
|
||||||
*
|
|
||||||
* Returns:
|
|
||||||
* - inputDrvs: Map of derivation paths to their required output names
|
|
||||||
* - inputSrcs: Set of plain store paths (opaque) and drvDeep references
|
|
||||||
*
|
|
||||||
* Context type handling:
|
|
||||||
* - Opaque: Added to inputSrcs
|
|
||||||
* - DrvDeep: Computes FS closure (like Nix's computeFSClosure) - adds all paths
|
|
||||||
* in the dependency graph to inputSrcs, and all derivations with their
|
|
||||||
* outputs to inputDrvs
|
|
||||||
* - Built: Added to inputDrvs with specific output name
|
|
||||||
*/
|
|
||||||
export const extractInputDrvsAndSrcs = (
|
export const extractInputDrvsAndSrcs = (
|
||||||
context: NixStringContext,
|
context: NixStringContext,
|
||||||
): { inputDrvs: Map<string, Set<string>>; inputSrcs: Set<string> } => {
|
): { inputDrvs: Map<string, Set<string>>; inputSrcs: Set<string> } => {
|
||||||
|
|||||||
@@ -1,17 +1,8 @@
|
|||||||
/**
|
|
||||||
* Lazy evaluation system for nix-js
|
|
||||||
* Implements thunks for lazy evaluation of Nix expressions
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { NixValue, NixThunkInterface, NixStrictValue } from "./types";
|
|
||||||
import { HAS_CONTEXT } from "./string-context";
|
|
||||||
import { IS_PATH } from "./types";
|
|
||||||
import { isAttrs, isList } from "./builtins/type-check";
|
import { isAttrs, isList } from "./builtins/type-check";
|
||||||
|
import { HAS_CONTEXT } from "./string-context";
|
||||||
|
import type { NixStrictValue, NixThunkInterface, NixValue } from "./types";
|
||||||
|
import { IS_PATH } from "./types";
|
||||||
|
|
||||||
/**
|
|
||||||
* Symbol used to mark objects as thunks
|
|
||||||
* This is exported to Rust via Nix.IS_THUNK
|
|
||||||
*/
|
|
||||||
export const IS_THUNK = Symbol("is_thunk");
|
export const IS_THUNK = Symbol("is_thunk");
|
||||||
|
|
||||||
const forceStack: NixThunk[] = [];
|
const forceStack: NixThunk[] = [];
|
||||||
@@ -31,7 +22,7 @@ export const DEBUG_THUNKS = { enabled: true };
|
|||||||
* - Evaluated: func is undefined, result is defined
|
* - Evaluated: func is undefined, result is defined
|
||||||
*/
|
*/
|
||||||
export class NixThunk implements NixThunkInterface {
|
export class NixThunk implements NixThunkInterface {
|
||||||
[key: symbol]: any;
|
[key: symbol]: unknown;
|
||||||
readonly [IS_THUNK] = true as const;
|
readonly [IS_THUNK] = true as const;
|
||||||
func: (() => NixValue) | undefined;
|
func: (() => NixValue) | undefined;
|
||||||
result: NixStrictValue | undefined;
|
result: NixStrictValue | undefined;
|
||||||
@@ -51,11 +42,6 @@ export class NixThunk implements NixThunkInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Type guard to check if a value is a thunk
|
|
||||||
* @param value - Value to check
|
|
||||||
* @returns true if value is a NixThunk
|
|
||||||
*/
|
|
||||||
export const isThunk = (value: NixValue): value is NixThunkInterface => {
|
export const isThunk = (value: NixValue): value is NixThunkInterface => {
|
||||||
return value !== null && typeof value === "object" && IS_THUNK in value && value[IS_THUNK] === true;
|
return value !== null && typeof value === "object" && IS_THUNK in value && value[IS_THUNK] === true;
|
||||||
};
|
};
|
||||||
@@ -96,7 +82,7 @@ export const force = (value: NixValue): NixStrictValue => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const thunk = value as NixThunk;
|
const thunk = value as NixThunk;
|
||||||
const func = thunk.func!;
|
const func = thunk.func as () => NixValue;
|
||||||
thunk.func = undefined;
|
thunk.func = undefined;
|
||||||
|
|
||||||
if (DEBUG_THUNKS.enabled) {
|
if (DEBUG_THUNKS.enabled) {
|
||||||
@@ -126,24 +112,11 @@ export const force = (value: NixValue): NixStrictValue => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new thunk from a function
|
|
||||||
* @param func - Function that produces a value when called
|
|
||||||
* @param label - Optional label for debugging
|
|
||||||
* @returns A new NixThunk wrapping the function
|
|
||||||
*/
|
|
||||||
export const createThunk = (func: () => NixValue, label?: string): NixThunkInterface => {
|
export const createThunk = (func: () => NixValue, label?: string): NixThunkInterface => {
|
||||||
return new NixThunk(func, label);
|
return new NixThunk(func, label);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Symbol to mark cyclic references detected during deep forcing
|
|
||||||
*/
|
|
||||||
export const IS_CYCLE = Symbol("is_cycle");
|
export const IS_CYCLE = Symbol("is_cycle");
|
||||||
|
|
||||||
/**
|
|
||||||
* Marker object for cyclic references
|
|
||||||
*/
|
|
||||||
export const CYCLE_MARKER = { [IS_CYCLE]: true };
|
export const CYCLE_MARKER = { [IS_CYCLE]: true };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,27 +1,18 @@
|
|||||||
/**
|
import { isAttrs, isFunction, typeOf } from "./builtins/type-check";
|
||||||
* Type assertion helpers for runtime type checking
|
import { force } from "./thunk";
|
||||||
* These functions force evaluation and verify the type, throwing errors on mismatch
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
NixValue,
|
|
||||||
NixList,
|
|
||||||
NixAttrs,
|
NixAttrs,
|
||||||
|
NixFloat,
|
||||||
NixFunction,
|
NixFunction,
|
||||||
NixInt,
|
NixInt,
|
||||||
NixFloat,
|
NixList,
|
||||||
NixNumber,
|
NixNumber,
|
||||||
NixString,
|
|
||||||
NixPath,
|
NixPath,
|
||||||
|
NixString,
|
||||||
|
NixValue,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { isStringWithContext, isNixPath } from "./types";
|
import { isNixPath, isStringWithContext } from "./types";
|
||||||
import { force } from "./thunk";
|
|
||||||
import { isAttrs, isFunction, typeOf } from "./builtins/type-check";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Force a value and assert it's a list
|
|
||||||
* @throws TypeError if value is not a list after forcing
|
|
||||||
*/
|
|
||||||
export const forceList = (value: NixValue): NixList => {
|
export const forceList = (value: NixValue): NixList => {
|
||||||
const forced = force(value);
|
const forced = force(value);
|
||||||
if (!Array.isArray(forced)) {
|
if (!Array.isArray(forced)) {
|
||||||
@@ -30,10 +21,6 @@ export const forceList = (value: NixValue): NixList => {
|
|||||||
return forced;
|
return forced;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Force a value and assert it's a function or functor
|
|
||||||
* @throws TypeError if value is not a function or functor after forcing
|
|
||||||
*/
|
|
||||||
export const forceFunction = (value: NixValue): NixFunction => {
|
export const forceFunction = (value: NixValue): NixFunction => {
|
||||||
const forced = force(value);
|
const forced = force(value);
|
||||||
if (isFunction(forced)) {
|
if (isFunction(forced)) {
|
||||||
@@ -47,10 +34,6 @@ export const forceFunction = (value: NixValue): NixFunction => {
|
|||||||
throw new TypeError(`Expected function, got ${typeOf(forced)}`);
|
throw new TypeError(`Expected function, got ${typeOf(forced)}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Force a value and assert it's an attribute set
|
|
||||||
* @throws TypeError if value is not an attribute set after forcing
|
|
||||||
*/
|
|
||||||
export const forceAttrs = (value: NixValue): NixAttrs => {
|
export const forceAttrs = (value: NixValue): NixAttrs => {
|
||||||
const forced = force(value);
|
const forced = force(value);
|
||||||
if (!isAttrs(forced)) {
|
if (!isAttrs(forced)) {
|
||||||
@@ -59,10 +42,6 @@ export const forceAttrs = (value: NixValue): NixAttrs => {
|
|||||||
return forced;
|
return forced;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Force a value and assert it's a string (plain or with context)
|
|
||||||
* @throws TypeError if value is not a string after forcing
|
|
||||||
*/
|
|
||||||
export const forceStringValue = (value: NixValue): string => {
|
export const forceStringValue = (value: NixValue): string => {
|
||||||
const forced = force(value);
|
const forced = force(value);
|
||||||
if (typeof forced === "string") {
|
if (typeof forced === "string") {
|
||||||
@@ -74,10 +53,6 @@ export const forceStringValue = (value: NixValue): string => {
|
|||||||
throw new TypeError(`Expected string, got ${typeOf(forced)}`);
|
throw new TypeError(`Expected string, got ${typeOf(forced)}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Force a value and assert it's a string, returning NixString (preserving context)
|
|
||||||
* @throws TypeError if value is not a string after forcing
|
|
||||||
*/
|
|
||||||
export const forceString = (value: NixValue): NixString => {
|
export const forceString = (value: NixValue): NixString => {
|
||||||
const forced = force(value);
|
const forced = force(value);
|
||||||
if (typeof forced === "string") {
|
if (typeof forced === "string") {
|
||||||
@@ -100,10 +75,6 @@ export const forceStringNoCtx = (value: NixValue): string => {
|
|||||||
throw new TypeError(`Expected string, got ${typeOf(forced)}`);
|
throw new TypeError(`Expected string, got ${typeOf(forced)}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Force a value and assert it's a boolean
|
|
||||||
* @throws TypeError if value is not a boolean after forcing
|
|
||||||
*/
|
|
||||||
export const forceBool = (value: NixValue): boolean => {
|
export const forceBool = (value: NixValue): boolean => {
|
||||||
const forced = force(value);
|
const forced = force(value);
|
||||||
if (typeof forced !== "boolean") {
|
if (typeof forced !== "boolean") {
|
||||||
@@ -112,10 +83,6 @@ export const forceBool = (value: NixValue): boolean => {
|
|||||||
return forced;
|
return forced;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Force a value and extract int value
|
|
||||||
* @throws TypeError if value is not an int
|
|
||||||
*/
|
|
||||||
export const forceInt = (value: NixValue): NixInt => {
|
export const forceInt = (value: NixValue): NixInt => {
|
||||||
const forced = force(value);
|
const forced = force(value);
|
||||||
if (typeof forced === "bigint") {
|
if (typeof forced === "bigint") {
|
||||||
@@ -124,10 +91,6 @@ export const forceInt = (value: NixValue): NixInt => {
|
|||||||
throw new TypeError(`Expected int, got ${typeOf(forced)}`);
|
throw new TypeError(`Expected int, got ${typeOf(forced)}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Force a value and extract float value
|
|
||||||
* @throws TypeError if value is not a float
|
|
||||||
*/
|
|
||||||
export const forceFloat = (value: NixValue): NixFloat => {
|
export const forceFloat = (value: NixValue): NixFloat => {
|
||||||
const forced = force(value);
|
const forced = force(value);
|
||||||
if (typeof forced === "number") {
|
if (typeof forced === "number") {
|
||||||
@@ -136,10 +99,6 @@ export const forceFloat = (value: NixValue): NixFloat => {
|
|||||||
throw new TypeError(`Expected float, got ${typeOf(forced)}`);
|
throw new TypeError(`Expected float, got ${typeOf(forced)}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Force a value and extract numeric value (int or float)
|
|
||||||
* @throws TypeError if value is not a numeric type
|
|
||||||
*/
|
|
||||||
export const forceNumeric = (value: NixValue): NixNumber => {
|
export const forceNumeric = (value: NixValue): NixNumber => {
|
||||||
const forced = force(value);
|
const forced = force(value);
|
||||||
if (typeof forced === "bigint" || typeof forced === "number") {
|
if (typeof forced === "bigint" || typeof forced === "number") {
|
||||||
@@ -148,28 +107,17 @@ export const forceNumeric = (value: NixValue): NixNumber => {
|
|||||||
throw new TypeError(`Expected numeric type, got ${typeOf(forced)}`);
|
throw new TypeError(`Expected numeric type, got ${typeOf(forced)}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Coerce two numeric values to a common type for arithmetic
|
|
||||||
* Rule: If either is float, convert both to float; otherwise keep as bigint
|
|
||||||
* @returns [a, b] tuple of coerced values
|
|
||||||
*/
|
|
||||||
export const coerceNumeric = (a: NixNumber, b: NixNumber): [NixFloat, NixFloat] | [NixInt, NixInt] => {
|
export const coerceNumeric = (a: NixNumber, b: NixNumber): [NixFloat, NixFloat] | [NixInt, NixInt] => {
|
||||||
const aIsInt = typeof a === "bigint";
|
const aIsInt = typeof a === "bigint";
|
||||||
const bIsInt = typeof b === "bigint";
|
const bIsInt = typeof b === "bigint";
|
||||||
|
|
||||||
// If either is float, convert both to float
|
|
||||||
if (!aIsInt || !bIsInt) {
|
if (!aIsInt || !bIsInt) {
|
||||||
return [Number(a), Number(b)];
|
return [Number(a), Number(b)];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Both are integers
|
|
||||||
return [a, b];
|
return [a, b];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Force a value and assert it's a path
|
|
||||||
* @throws TypeError if value is not a path after forcing
|
|
||||||
*/
|
|
||||||
export const forceNixPath = (value: NixValue): NixPath => {
|
export const forceNixPath = (value: NixValue): NixPath => {
|
||||||
const forced = force(value);
|
const forced = force(value);
|
||||||
if (isNixPath(forced)) {
|
if (isNixPath(forced)) {
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
/**
|
import { HAS_CONTEXT, isStringWithContext, type StringWithContext } from "./string-context";
|
||||||
* Core TypeScript type definitions for nix-js runtime
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { force, IS_THUNK } from "./thunk";
|
import { force, IS_THUNK } from "./thunk";
|
||||||
import { type StringWithContext, HAS_CONTEXT, isStringWithContext, getStringContext } from "./string-context";
|
|
||||||
import { op } from "./operators";
|
|
||||||
import { forceAttrs, forceStringNoCtx } from "./type-assert";
|
import { forceAttrs, forceStringNoCtx } from "./type-assert";
|
||||||
import { isString, typeOf } from "./builtins/type-check";
|
|
||||||
export { HAS_CONTEXT, isStringWithContext };
|
export { HAS_CONTEXT, isStringWithContext };
|
||||||
export type { StringWithContext };
|
export type { StringWithContext };
|
||||||
|
|
||||||
@@ -21,7 +15,6 @@ export const isNixPath = (v: NixStrictValue): v is NixPath => {
|
|||||||
return typeof v === "object" && v !== null && IS_PATH in v;
|
return typeof v === "object" && v !== null && IS_PATH in v;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Nix primitive types
|
|
||||||
export type NixInt = bigint;
|
export type NixInt = bigint;
|
||||||
export type NixFloat = number;
|
export type NixFloat = number;
|
||||||
export type NixNumber = NixInt | NixFloat;
|
export type NixNumber = NixInt | NixFloat;
|
||||||
@@ -29,7 +22,6 @@ export type NixBool = boolean;
|
|||||||
export type NixString = string | StringWithContext;
|
export type NixString = string | StringWithContext;
|
||||||
export type NixNull = null;
|
export type NixNull = null;
|
||||||
|
|
||||||
// Nix composite types
|
|
||||||
export type NixList = NixValue[];
|
export type NixList = NixValue[];
|
||||||
// FIXME: reject contextful string
|
// FIXME: reject contextful string
|
||||||
export type NixAttrs = { [key: string]: NixValue };
|
export type NixAttrs = { [key: string]: NixValue };
|
||||||
@@ -72,7 +64,7 @@ export const mkFunction = (
|
|||||||
positions: Record<string, string>,
|
positions: Record<string, string>,
|
||||||
ellipsis: boolean,
|
ellipsis: boolean,
|
||||||
): NixFunction => {
|
): NixFunction => {
|
||||||
const func = f as NixFunction;
|
const func: NixFunction = f;
|
||||||
func.args = new NixArgs(required, optional, positions, ellipsis);
|
func.args = new NixArgs(required, optional, positions, ellipsis);
|
||||||
return func;
|
return func;
|
||||||
};
|
};
|
||||||
@@ -90,7 +82,7 @@ export const mkAttrs = (attrs: NixAttrs, keys: NixValue[], values: NixValue[]):
|
|||||||
return attrs;
|
return attrs;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ATTR_POSITIONS = Symbol("attrPositions");
|
export const ATTR_POSITIONS = Symbol("attrPositions");
|
||||||
|
|
||||||
export const mkAttrsWithPos = (
|
export const mkAttrsWithPos = (
|
||||||
attrs: NixAttrs,
|
attrs: NixAttrs,
|
||||||
@@ -121,46 +113,14 @@ export const mkAttrsWithPos = (
|
|||||||
return attrs;
|
return attrs;
|
||||||
};
|
};
|
||||||
|
|
||||||
export { ATTR_POSITIONS };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for lazy thunk values
|
|
||||||
* Thunks delay evaluation until forced
|
|
||||||
*/
|
|
||||||
export interface NixThunkInterface {
|
export interface NixThunkInterface {
|
||||||
readonly [IS_THUNK]: true;
|
readonly [IS_THUNK]: true;
|
||||||
func: (() => NixValue) | undefined;
|
func: (() => NixValue) | undefined;
|
||||||
result: NixStrictValue | undefined;
|
result: NixStrictValue | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Union of all Nix primitive types
|
|
||||||
export type NixPrimitive = NixNull | NixBool | NixInt | NixFloat | NixString;
|
export type NixPrimitive = NixNull | NixBool | NixInt | NixFloat | NixString;
|
||||||
|
|
||||||
/**
|
|
||||||
* NixValue: Union type representing any possible Nix value
|
|
||||||
* This is the core type used throughout the runtime
|
|
||||||
*/
|
|
||||||
export type NixValue = NixPrimitive | NixPath | NixList | NixAttrs | NixFunction | NixThunkInterface;
|
export type NixValue = NixPrimitive | NixPath | NixList | NixAttrs | NixFunction | NixThunkInterface;
|
||||||
|
|
||||||
export type NixStrictValue = Exclude<NixValue, NixThunkInterface>;
|
export type NixStrictValue = Exclude<NixValue, NixThunkInterface>;
|
||||||
|
|
||||||
/**
|
|
||||||
* CatchableError: Error type thrown by `builtins.throw`
|
|
||||||
* This can be caught by `builtins.tryEval`
|
|
||||||
*/
|
|
||||||
export class CatchableError extends Error {}
|
export class CatchableError extends Error {}
|
||||||
|
|
||||||
// Operator function signatures
|
|
||||||
export type BinaryOp<T = NixValue, U = NixValue, R = NixValue> = (a: T, b: U) => R;
|
|
||||||
export type UnaryOp<T = NixValue, R = NixValue> = (a: T) => R;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Curried function types - All Nix builtins must be curried!
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
* - add: Curried2<number, number, number> = (a) => (b) => a + b
|
|
||||||
* - map: Curried2<NixFunction, NixList, NixList> = (f) => (list) => list.map(f)
|
|
||||||
*/
|
|
||||||
export type Curried2<A, B, R> = (a: A) => (b: B) => R;
|
|
||||||
export type Curried3<A, B, C, R> = (a: A) => (b: B) => (c: C) => R;
|
|
||||||
export type Curried4<A, B, C, D, R> = (a: A) => (b: B) => (c: C) => (d: D) => R;
|
|
||||||
|
|||||||
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 { NixRuntime } from "..";
|
||||||
import type { FetchTarballResult, FetchUrlResult, FetchGitResult, FetchHgResult } from "../builtins/io";
|
import type { FetchTarballResult, FetchUrlResult, FetchGitResult } from "../builtins/io";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
var Nix: NixRuntime;
|
var Nix: NixRuntime;
|
||||||
@@ -20,17 +20,17 @@ declare global {
|
|||||||
line: number | null;
|
line: number | null;
|
||||||
column: number | null;
|
column: number | null;
|
||||||
};
|
};
|
||||||
function op_make_store_path(ty: string, hash_hex: string, name: string): string;
|
function op_make_store_path(ty: string, hashHex: string, name: string): string;
|
||||||
function op_parse_hash(hash_str: string, algo: string | null): { hex: string; algo: string };
|
function op_parse_hash(hashStr: string, algo: string | null): { hex: string; algo: string };
|
||||||
function op_make_fixed_output_path(
|
function op_make_fixed_output_path(
|
||||||
hash_algo: string,
|
hashAlgo: string,
|
||||||
hash: string,
|
hash: string,
|
||||||
hash_mode: string,
|
hashMode: string,
|
||||||
name: string,
|
name: string,
|
||||||
): string;
|
): string;
|
||||||
function op_fetch_url(
|
function op_fetch_url(
|
||||||
url: string,
|
url: string,
|
||||||
expected_hash: string | null,
|
expectedHash: string | null,
|
||||||
name: string | null,
|
name: string | null,
|
||||||
executable: boolean,
|
executable: boolean,
|
||||||
): FetchUrlResult;
|
): FetchUrlResult;
|
||||||
@@ -45,7 +45,7 @@ declare global {
|
|||||||
rev: string | null,
|
rev: string | null,
|
||||||
shallow: boolean,
|
shallow: boolean,
|
||||||
submodules: boolean,
|
submodules: boolean,
|
||||||
all_refs: boolean,
|
allRefs: boolean,
|
||||||
name: string | null,
|
name: string | null,
|
||||||
): FetchGitResult;
|
): FetchGitResult;
|
||||||
function op_add_path(
|
function op_add_path(
|
||||||
@@ -56,9 +56,9 @@ declare global {
|
|||||||
): string;
|
): string;
|
||||||
function op_store_path(path: string): string;
|
function op_store_path(path: string): string;
|
||||||
function op_to_file(name: string, contents: string, references: string[]): string;
|
function op_to_file(name: string, contents: string, references: string[]): string;
|
||||||
function op_write_derivation(drv_name: string, aterm: string, references: string[]): string;
|
function op_write_derivation(drvName: string, aterm: string, references: string[]): string;
|
||||||
function op_read_derivation_outputs(drv_path: string): string[];
|
function op_read_derivation_outputs(drvPath: string): string[];
|
||||||
function op_compute_fs_closure(drv_path: string): {
|
function op_compute_fs_closure(drvPath: string): {
|
||||||
input_drvs: [string, string[]][];
|
input_drvs: [string, string[]][];
|
||||||
input_srcs: string[];
|
input_srcs: string[];
|
||||||
};
|
};
|
||||||
@@ -70,12 +70,12 @@ declare global {
|
|||||||
name: string | null,
|
name: string | null,
|
||||||
recursive: boolean,
|
recursive: boolean,
|
||||||
sha256: string | null,
|
sha256: string | null,
|
||||||
include_paths: string[],
|
includePaths: string[],
|
||||||
): string;
|
): string;
|
||||||
function op_match(regex: string, text: string): (string | null)[] | null;
|
function op_match(regex: string, text: string): (string | null)[] | null;
|
||||||
function op_split(regex: string, text: string): (string | (string | null)[])[];
|
function op_split(regex: string, text: string): (string | (string | null)[])[];
|
||||||
function op_from_json(json: string): any;
|
function op_from_json(json: string): unknown;
|
||||||
function op_from_toml(toml: string): any;
|
function op_from_toml(toml: string): unknown;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user