65 lines
1.8 KiB
TypeScript
65 lines
1.8 KiB
TypeScript
/**
|
|
* 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()];
|
|
}
|