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(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([ ["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(); const toContextCache = new Map(); 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([ ["success", true], ["value", force(e)], ]); } catch (err) { if (err instanceof CatchableError) { return new Map([ ["success", false], ["value", false], ]); } else { throw err; } } };