This commit is contained in:
wkh
2026-08-29 17:47:12 +02:00
commit c7885067e7
53 changed files with 13458 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
/**
* Built-in functions: a plain table { NAME -> fn(values) }.
*
* By the time a function runs, the evaluator has already:
* - resolved every range argument into a flat list of cell values,
* - flattened scalar arguments into that same list,
* - checked for errors among the values (an error short-circuits and is
* returned instead of calling the function).
*
* So a function only ever sees `(number | string | null)[]`. Each function
* decides for itself how to treat text and empty cells. Per spec:
* - empty cell -> ignored by every aggregate, never counted by COUNT
* - text -> ignored by the numeric aggregates
*/
import { FormulaError, type CellValue } from './types';
export type FunctionFn = (values: CellValue[]) => CellValue;
/** Keep only real numbers (finite). */
function nums(values: CellValue[]): number[] {
const out: number[] = [];
for (const v of values) {
if (typeof v === 'number' && Number.isFinite(v)) out.push(v);
}
return out;
}
export const FUNCTIONS: Record<string, FunctionFn> = {
SUM(values) {
return nums(values).reduce((a, b) => a + b, 0);
},
AVG(values) {
const n = nums(values);
if (n.length === 0) return new FormulaError('#DIV/0!', 'AVG of no numbers');
return n.reduce((a, b) => a + b, 0) / n.length;
},
MIN(values) {
const n = nums(values);
return n.length ? Math.min(...n) : 0;
},
MAX(values) {
const n = nums(values);
return n.length ? Math.max(...n) : 0;
},
COUNT(values) {
return nums(values).length;
},
};
// AVERAGE is an alias of AVG.
FUNCTIONS.AVERAGE = FUNCTIONS.AVG;
export function hasFunction(name: string): boolean {
return Object.prototype.hasOwnProperty.call(FUNCTIONS, name.toUpperCase());
}
export function getFunction(name: string): FunctionFn | undefined {
return FUNCTIONS[name.toUpperCase()];
}