186 lines
5.3 KiB
TypeScript
186 lines
5.3 KiB
TypeScript
/**
|
|
* Nix operators module
|
|
* Implements all binary and unary operators used by codegen
|
|
*/
|
|
|
|
import type { NixValue, NixList, NixAttrs, NixString } from "./types";
|
|
import { isStringWithContext } from "./types";
|
|
import { force } from "./thunk";
|
|
import { forceNumeric, forceList, forceAttrs, coerceNumeric } from "./type-assert";
|
|
import {
|
|
getStringValue,
|
|
getStringContext,
|
|
mergeContexts,
|
|
mkStringWithContext,
|
|
} from "./string-context";
|
|
import { coerceToString, StringCoercionMode } from "./builtins/conversion";
|
|
|
|
const isNixString = (v: unknown): v is NixString => {
|
|
return typeof v === "string" || isStringWithContext(v);
|
|
};
|
|
|
|
const canCoerceToString = (v: NixValue): boolean => {
|
|
const forced = force(v);
|
|
if (isNixString(forced)) return true;
|
|
if (typeof forced === "object" && forced !== null && !Array.isArray(forced)) {
|
|
if ("outPath" in forced || "__toString" in forced) return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Operator object exported as Nix.op
|
|
* All operators referenced by codegen (e.g., Nix.op.add, Nix.op.eq)
|
|
*/
|
|
export const op = {
|
|
add: (a: NixValue, b: NixValue): bigint | number | NixString => {
|
|
const av = force(a);
|
|
const bv = force(b);
|
|
|
|
if (isNixString(av) && isNixString(bv)) {
|
|
const strA = getStringValue(av);
|
|
const strB = getStringValue(bv);
|
|
const ctxA = getStringContext(av);
|
|
const ctxB = getStringContext(bv);
|
|
|
|
if (ctxA.size === 0 && ctxB.size === 0) {
|
|
return strA + strB;
|
|
}
|
|
|
|
return mkStringWithContext(strA + strB, mergeContexts(ctxA, ctxB));
|
|
}
|
|
|
|
if (canCoerceToString(a) && canCoerceToString(b)) {
|
|
const strA = coerceToString(a, StringCoercionMode.Interpolation, false);
|
|
const strB = coerceToString(b, StringCoercionMode.Interpolation, false);
|
|
return strA + strB;
|
|
}
|
|
|
|
const [numA, numB] = coerceNumeric(forceNumeric(a), forceNumeric(b));
|
|
return (numA as any) + (numB as any);
|
|
},
|
|
|
|
sub: (a: NixValue, b: NixValue): bigint | number => {
|
|
const [av, bv] = coerceNumeric(forceNumeric(a), forceNumeric(b));
|
|
return (av as any) - (bv as any);
|
|
},
|
|
|
|
mul: (a: NixValue, b: NixValue): bigint | number => {
|
|
const [av, bv] = coerceNumeric(forceNumeric(a), forceNumeric(b));
|
|
return (av as any) * (bv as any);
|
|
},
|
|
|
|
div: (a: NixValue, b: NixValue): bigint | number => {
|
|
const [av, bv] = coerceNumeric(forceNumeric(a), forceNumeric(b));
|
|
|
|
if (bv === 0 || bv === 0n) {
|
|
throw new RangeError("Division by zero");
|
|
}
|
|
|
|
return (av as any) / (bv as any);
|
|
},
|
|
|
|
eq: (a: NixValue, b: NixValue): boolean => {
|
|
const av = force(a);
|
|
const bv = force(b);
|
|
|
|
if (isNixString(av) && isNixString(bv)) {
|
|
return getStringValue(av) === getStringValue(bv);
|
|
}
|
|
|
|
if (typeof av === "bigint" && typeof bv === "number") {
|
|
return Number(av) === bv;
|
|
}
|
|
if (typeof av === "number" && typeof bv === "bigint") {
|
|
return av === Number(bv);
|
|
}
|
|
|
|
if (Array.isArray(av) && Array.isArray(bv)) {
|
|
if (av.length !== bv.length) return false;
|
|
for (let i = 0; i < av.length; i++) {
|
|
if (!op.eq(av[i], bv[i])) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (
|
|
typeof av === "object" &&
|
|
av !== null &&
|
|
!Array.isArray(av) &&
|
|
typeof bv === "object" &&
|
|
bv !== null &&
|
|
!Array.isArray(bv) &&
|
|
!isNixString(av) &&
|
|
!isNixString(bv)
|
|
) {
|
|
const keysA = Object.keys(av);
|
|
const keysB = Object.keys(bv);
|
|
if (keysA.length !== keysB.length) return false;
|
|
for (const key of keysA) {
|
|
if (!(key in bv)) return false;
|
|
if (!op.eq((av as NixAttrs)[key], (bv as NixAttrs)[key])) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return av === bv;
|
|
},
|
|
neq: (a: NixValue, b: NixValue): boolean => {
|
|
return !op.eq(a, b);
|
|
},
|
|
lt: (a: NixValue, b: NixValue): boolean => {
|
|
const av = force(a);
|
|
const bv = force(b);
|
|
|
|
if (isNixString(av) && isNixString(bv)) {
|
|
return getStringValue(av) < getStringValue(bv);
|
|
}
|
|
|
|
const [numA, numB] = coerceNumeric(forceNumeric(a), forceNumeric(b));
|
|
return (numA as any) < (numB as any);
|
|
},
|
|
lte: (a: NixValue, b: NixValue): boolean => {
|
|
const av = force(a);
|
|
const bv = force(b);
|
|
|
|
if (isNixString(av) && isNixString(bv)) {
|
|
return getStringValue(av) <= getStringValue(bv);
|
|
}
|
|
|
|
const [numA, numB] = coerceNumeric(forceNumeric(a), forceNumeric(b));
|
|
return (numA as any) <= (numB as any);
|
|
},
|
|
gt: (a: NixValue, b: NixValue): boolean => {
|
|
const av = force(a);
|
|
const bv = force(b);
|
|
|
|
if (isNixString(av) && isNixString(bv)) {
|
|
return getStringValue(av) > getStringValue(bv);
|
|
}
|
|
|
|
const [numA, numB] = coerceNumeric(forceNumeric(a), forceNumeric(b));
|
|
return (numA as any) > (numB as any);
|
|
},
|
|
gte: (a: NixValue, b: NixValue): boolean => {
|
|
const av = force(a);
|
|
const bv = force(b);
|
|
|
|
if (isNixString(av) && isNixString(bv)) {
|
|
return getStringValue(av) >= getStringValue(bv);
|
|
}
|
|
|
|
const [numA, numB] = coerceNumeric(forceNumeric(a), forceNumeric(b));
|
|
return (numA as any) >= (numB as any);
|
|
},
|
|
|
|
bnot: (a: NixValue): boolean => !force(a),
|
|
|
|
concat: (a: NixValue, b: NixValue): NixList => {
|
|
return Array.prototype.concat.call(forceList(a), forceList(b));
|
|
},
|
|
|
|
update: (a: NixValue, b: NixValue): NixAttrs => {
|
|
return { ...forceAttrs(a), ...forceAttrs(b) };
|
|
},
|
|
};
|