IF-Funktion als Formel: echte Kurzschluss-Auswertung
Neue eingebaute Funktion IF(Bedingung; Dann; Sonst?), nutzbar in jeder
gewöhnlichen Zellformel (nicht nur in "Mit Schleife"-Funktionskörpern).
- functions.ts: IF als Platzhalter-Eintrag registriert, nur damit
hasFunction('IF') greift (Namensreservierung -- ein Benutzer kann
keine gleichnamige eigene Funktion mehr anlegen) und als defensiver
Fallback. Trägt einen Kommentar, der auf die echte Implementierung
verweist.
- evaluator.ts: die echte, kurzschließende Auswertung sitzt in
evalScalar()s case 'func', VOR dem generischen eager-args-Pfad.
evalIf() wertet nur die Bedingung immer aus, danach ausschließlich
den genommenen Zweig -- der andere Zweig wird nie evaluiert, ein
Fehler darin (z. B. =IF(B1=0; 0; A1/B1)) schlägt also nie durch.
Gleiche Wahrheits-Konvention wie überall sonst im Engine (Zahl != 0
bzw. nicht-leerer Text = wahr).
- collectRefs() bleibt unverändert: sein generischer case 'func' läuft
bereits konservativ durch alle Argumente, unabhängig vom zur
Laufzeit genommenen Zweig -- exakt wie Excels IF().
Tests: 8 neue (evaluator.spec.ts: beide Zweige, 2-/3-Argument-Form,
Kurzschluss-Beweis, Fehler in der Bedingung, Arity-Fehler, Vergleiche
als Bedingung; document.spec.ts: Namenskollision mit eingebautem IF).
257 -> 265 Tests, alle grün. vue-tsc/eslint/build sauber. Live per
Playwright verifiziert (inkl. Kurzschluss- und Neuberechnungsverhalten).
READMEs aktualisiert (Funktionen-Liste, Auswertungsmodell, Testzahl).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
+21
-1
@@ -13,7 +13,7 @@
|
||||
* - text -> ignored by the numeric aggregates
|
||||
*/
|
||||
|
||||
import { FormulaError, type CellValue } from './types';
|
||||
import { FormulaError, isError, type CellValue } from './types';
|
||||
|
||||
export type FunctionFn = (values: CellValue[]) => CellValue;
|
||||
|
||||
@@ -50,6 +50,26 @@ export const FUNCTIONS: Record<string, FunctionFn> = {
|
||||
COUNT(values) {
|
||||
return nums(values).length;
|
||||
},
|
||||
|
||||
// IF(cond; then; else?) -- evaluator.ts special-cases this name *before*
|
||||
// ever reaching FUNCTIONS, giving it real short-circuit evaluation (only
|
||||
// the taken branch is computed, so e.g. IF(B1=0; 0; A1/B1) never runs the
|
||||
// division when B1=0). This entry only exists so IF is a registered
|
||||
// built-in for hasFunction()'s sake (the store's custom-function name
|
||||
// check then correctly refuses to let a user shadow it) and as a
|
||||
// defensive, best-effort fallback if this were ever reached directly
|
||||
// (values are already-eagerly-computed here, so it can't short-circuit --
|
||||
// see evaluator.ts's evalIf() for the real implementation).
|
||||
IF(values) {
|
||||
if (values.length !== 2 && values.length !== 3) {
|
||||
return new FormulaError('#ERR', 'IF expects 2 or 3 arguments');
|
||||
}
|
||||
const [cond, thenValue, elseValue] = values;
|
||||
if (isError(cond)) return cond;
|
||||
const truthy = typeof cond === 'number' ? cond !== 0 : cond !== null && cond !== '';
|
||||
if (truthy) return thenValue;
|
||||
return values.length === 3 ? elseValue : 0;
|
||||
},
|
||||
};
|
||||
|
||||
// AVERAGE is an alias of AVG.
|
||||
|
||||
Reference in New Issue
Block a user