Files
nix-js/nix-js/runtime-ts/src/operators.ts

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) };
},
};