345 lines
8.8 KiB
TypeScript
345 lines
8.8 KiB
TypeScript
import { OrderedSet } from "js-sdsl";
|
|
import { select } from "../helpers";
|
|
import { compareValues } from "../operators";
|
|
import {
|
|
getStringContext,
|
|
getStringValue,
|
|
mkStringWithContext,
|
|
type NixStringContext,
|
|
} from "../string-context";
|
|
import { force } from "../thunk";
|
|
import {
|
|
forceAttrs,
|
|
forceFunction,
|
|
forceList,
|
|
forceString,
|
|
forceStringNoCtx,
|
|
forceStringValue,
|
|
} from "../type-assert";
|
|
import type { NixAttrs, NixStrictValue, NixValue } from "../types";
|
|
import { ATTR_POSITIONS, CatchableError } from "../types";
|
|
import * as context from "./context";
|
|
import { isBool, isFloat, isInt, isList, isString, typeOf } from "./type-check";
|
|
|
|
export const addErrorContext =
|
|
(_e1: NixValue) =>
|
|
(e2: NixValue): NixValue => {
|
|
// FIXME:
|
|
// console.log("[WARNING]: addErrorContext not implemented");
|
|
return e2;
|
|
};
|
|
|
|
export const appendContext = context.appendContext;
|
|
|
|
export const getContext = context.getContext;
|
|
|
|
export const hasContext = context.hasContext;
|
|
|
|
export const unsafeDiscardOutputDependency = context.unsafeDiscardOutputDependency;
|
|
|
|
export const unsafeDiscardStringContext = context.unsafeDiscardStringContext;
|
|
|
|
export const addDrvOutputDependencies = context.addDrvOutputDependencies;
|
|
|
|
export const compareVersions =
|
|
(s1: NixValue) =>
|
|
(s2: NixValue): NixValue => {
|
|
const str1 = forceStringValue(s1);
|
|
const str2 = forceStringValue(s2);
|
|
|
|
let i1 = 0;
|
|
let i2 = 0;
|
|
|
|
while (i1 < str1.length || i2 < str2.length) {
|
|
const c1 = nextComponent(str1, i1);
|
|
const c2 = nextComponent(str2, i2);
|
|
|
|
i1 = c1.nextIndex;
|
|
i2 = c2.nextIndex;
|
|
|
|
if (componentsLt(c1.component, c2.component)) {
|
|
return -1n;
|
|
} else if (componentsLt(c2.component, c1.component)) {
|
|
return 1n;
|
|
}
|
|
}
|
|
|
|
return 0n;
|
|
};
|
|
|
|
interface ComponentResult {
|
|
component: string;
|
|
nextIndex: number;
|
|
}
|
|
|
|
function nextComponent(s: string, startIdx: number): ComponentResult {
|
|
let p = startIdx;
|
|
|
|
// Skip any dots and dashes (component separators)
|
|
while (p < s.length && (s[p] === "." || s[p] === "-")) {
|
|
p++;
|
|
}
|
|
|
|
if (p >= s.length) {
|
|
return { component: "", nextIndex: p };
|
|
}
|
|
|
|
const start = p;
|
|
|
|
// If the first character is a digit, consume the longest sequence of digits
|
|
if (s[p] >= "0" && s[p] <= "9") {
|
|
while (p < s.length && s[p] >= "0" && s[p] <= "9") {
|
|
p++;
|
|
}
|
|
} else {
|
|
// Otherwise, consume the longest sequence of non-digit, non-separator characters
|
|
while (p < s.length && !(s[p] >= "0" && s[p] <= "9") && s[p] !== "." && s[p] !== "-") {
|
|
p++;
|
|
}
|
|
}
|
|
|
|
return { component: s.substring(start, p), nextIndex: p };
|
|
}
|
|
|
|
function componentsLt(c1: string, c2: string): boolean {
|
|
const n1 = c1.match(/^[0-9]+$/) ? BigInt(c1) : null;
|
|
const n2 = c2.match(/^[0-9]+$/) ? BigInt(c2) : null;
|
|
|
|
// Both are numbers: compare numerically
|
|
if (n1 !== null && n2 !== null) {
|
|
return n1 < n2;
|
|
}
|
|
|
|
// Empty string < number
|
|
if (c1 === "" && n2 !== null) {
|
|
return true;
|
|
}
|
|
|
|
// Special case: "pre" comes before everything except another "pre"
|
|
if (c1 === "pre" && c2 !== "pre") {
|
|
return true;
|
|
}
|
|
if (c2 === "pre") {
|
|
return false;
|
|
}
|
|
|
|
// Assume that `2.3a' < `2.3.1'
|
|
if (n2 !== null) {
|
|
return true;
|
|
}
|
|
if (n1 !== null) {
|
|
return false;
|
|
}
|
|
|
|
// Both are strings: compare lexicographically
|
|
return c1 < c2;
|
|
}
|
|
|
|
export const functionArgs = (f: NixValue): NixAttrs => {
|
|
const func = forceFunction(f);
|
|
if (func.args) {
|
|
const ret: NixAttrs = new Map();
|
|
for (const key of func.args.required) {
|
|
ret.set(key, false);
|
|
}
|
|
for (const key of func.args.optional) {
|
|
ret.set(key, true);
|
|
}
|
|
const positions = func.args.positions;
|
|
if (positions) {
|
|
ret[ATTR_POSITIONS] = positions;
|
|
}
|
|
return ret;
|
|
}
|
|
return new Map();
|
|
};
|
|
|
|
const checkComparable = (value: NixStrictValue): void => {
|
|
if (isString(value) || isInt(value) || isFloat(value) || isBool(value) || isList(value)) {
|
|
return;
|
|
}
|
|
throw new Error(`Unsupported key type for genericClosure: ${typeOf(value)}`);
|
|
};
|
|
|
|
export const genericClosure = (args: NixValue): NixValue => {
|
|
const forcedArgs = forceAttrs(args);
|
|
const startSet = select(forcedArgs, ["startSet"]);
|
|
const operator = select(forcedArgs, ["operator"]);
|
|
|
|
const initialList = forceList(startSet);
|
|
const opFunction = forceFunction(operator);
|
|
|
|
const resultSet = new OrderedSet<NixStrictValue>(undefined, compareValues);
|
|
const resultList: NixStrictValue[] = [];
|
|
const queue: NixStrictValue[] = [];
|
|
|
|
for (const item of initialList) {
|
|
const itemAttrs = forceAttrs(item);
|
|
const key = force(select(itemAttrs, ["key"]));
|
|
checkComparable(key);
|
|
if (resultSet.find(key).equals(resultSet.end())) {
|
|
resultSet.insert(key);
|
|
resultList.push(itemAttrs);
|
|
queue.push(itemAttrs);
|
|
}
|
|
}
|
|
|
|
let head = 0;
|
|
while (head < queue.length) {
|
|
const currentItem = queue[head++];
|
|
const newItems = forceList(opFunction(currentItem));
|
|
|
|
for (const newItem of newItems) {
|
|
const newItemAttrs = forceAttrs(newItem);
|
|
const key = force(select(newItemAttrs, ["key"]));
|
|
checkComparable(key);
|
|
if (resultSet.find(key).equals(resultSet.end())) {
|
|
resultSet.insert(key);
|
|
resultList.push(newItemAttrs);
|
|
queue.push(newItemAttrs);
|
|
}
|
|
}
|
|
}
|
|
|
|
return resultList;
|
|
};
|
|
|
|
export const outputOf =
|
|
(_drv: NixValue) =>
|
|
(_out: NixValue): never => {
|
|
throw new Error("Not implemented: outputOf (part of dynamic-derivation)");
|
|
};
|
|
|
|
export const parseDrvName = (s: NixValue): NixAttrs => {
|
|
const fullName = forceStringNoCtx(s);
|
|
let name = fullName;
|
|
let version = "";
|
|
for (let i = 0; i < fullName.length; ++i) {
|
|
if (fullName[i] === "-" && i + 1 < fullName.length && !/[a-zA-Z]/.test(fullName[i + 1])) {
|
|
name = fullName.substring(0, i);
|
|
version = fullName.substring(i + 1);
|
|
break;
|
|
}
|
|
}
|
|
return new Map<string, NixValue>([
|
|
["name", name],
|
|
["version", version],
|
|
]);
|
|
};
|
|
|
|
export const placeholder = (output: NixValue): NixValue => {
|
|
const outputStr = forceStringNoCtx(output);
|
|
return Deno.core.ops.op_make_placeholder(outputStr);
|
|
};
|
|
|
|
export const replaceStrings =
|
|
(from: NixValue) =>
|
|
(to: NixValue) =>
|
|
(s: NixValue): NixValue => {
|
|
const fromList = forceList(from);
|
|
const toList = forceList(to);
|
|
const inputStr = forceString(s);
|
|
const inputStrValue = getStringValue(inputStr);
|
|
const resultContext: NixStringContext = getStringContext(inputStr);
|
|
|
|
if (fromList.length !== toList.length) {
|
|
throw new Error("'from' and 'to' arguments passed to builtins.replaceStrings have different lengths");
|
|
}
|
|
|
|
const toCache = new Map<number, string>();
|
|
const toContextCache = new Map<number, NixStringContext>();
|
|
|
|
let result = "";
|
|
let pos = 0;
|
|
|
|
while (pos <= inputStrValue.length) {
|
|
let found = false;
|
|
|
|
for (let i = 0; i < fromList.length; i++) {
|
|
const pattern = forceStringValue(fromList[i]);
|
|
|
|
if (inputStrValue.substring(pos).startsWith(pattern)) {
|
|
found = true;
|
|
|
|
if (!toCache.has(i)) {
|
|
const replacementStr = forceString(toList[i]);
|
|
const replacementValue = getStringValue(replacementStr);
|
|
const replacementContext = getStringContext(replacementStr);
|
|
toCache.set(i, replacementValue);
|
|
toContextCache.set(i, replacementContext);
|
|
for (const elem of replacementContext) {
|
|
resultContext.add(elem);
|
|
}
|
|
}
|
|
const replacement = toCache.get(i) as string;
|
|
|
|
result += replacement;
|
|
|
|
if (pattern.length === 0) {
|
|
if (pos < inputStrValue.length) {
|
|
result += inputStrValue[pos];
|
|
}
|
|
pos++;
|
|
} else {
|
|
pos += pattern.length;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
if (pos < inputStrValue.length) {
|
|
result += inputStrValue[pos];
|
|
}
|
|
pos++;
|
|
}
|
|
}
|
|
|
|
if (resultContext.size === 0) {
|
|
return result;
|
|
}
|
|
return mkStringWithContext(result, resultContext);
|
|
};
|
|
|
|
export const splitVersion = (s: NixValue): NixValue => {
|
|
const version = forceStringValue(s);
|
|
const components: string[] = [];
|
|
let idx = 0;
|
|
|
|
while (idx < version.length) {
|
|
const result = nextComponent(version, idx);
|
|
if (result.component === "") {
|
|
break;
|
|
}
|
|
components.push(result.component);
|
|
idx = result.nextIndex;
|
|
}
|
|
|
|
return components;
|
|
};
|
|
|
|
export const traceVerbose =
|
|
(_e1: NixValue) =>
|
|
(e2: NixValue): NixStrictValue => {
|
|
// TODO: implement traceVerbose
|
|
return force(e2);
|
|
};
|
|
|
|
export const tryEval = (e: NixValue): NixAttrs => {
|
|
try {
|
|
return new Map<string, NixValue>([
|
|
["success", true],
|
|
["value", force(e)],
|
|
]);
|
|
} catch (err) {
|
|
if (err instanceof CatchableError) {
|
|
return new Map<string, NixValue>([
|
|
["success", false],
|
|
["value", false],
|
|
]);
|
|
} else {
|
|
throw err;
|
|
}
|
|
}
|
|
};
|