AND/OR/NOT, Fehler-Literale und Zeilen/Spalten einfügen/löschen

- AND(...)/OR(...)/NOT(...) als gewöhnliche Funktionen (kein Kurzschluss-
  Sonderfall wie IF/XLOOKUP nötig, werten wie in echtem Excel alle
  Argumente eager aus). Dabei die bisher dreifach duplizierte
  Wahrheits-Prüfung zu functions.ts#isTruthy() zusammengezogen, jetzt
  von FUNCTIONS.IF, evalIf() und dem Block-IF-Statement gemeinsam genutzt.
  Schließt "kein AND/OR für zusammengesetzte Bedingungen" aus den
  Bekannten Grenzen, auch nutzbar in "Mit Schleife"-IF-Bedingungen.

- Neue Grammatik-Ergänzung: ErrorLiteralNode. Jeder der sieben Fehlercodes
  (#REF, #DIV/0!, #NAME?, #ERR, #CIRC, #VALUE!, #N/A) lässt sich jetzt
  direkt in eine Formel tippen (=IF(A1=0;#N/A;A1)) und wertet unbedingt zu
  genau diesem Fehler aus.

- Zeilen/Spalten einfügen/löschen mitten im Gitter (Rechtsklick auf einen
  Zeilen-/Spaltenkopf), nicht nur am Rand wie bisher: Zellinhalte, Formate
  und Zeilenhöhen/Spaltenbreiten verschieben sich innerhalb der Tabelle,
  und jede Formel im gesamten Dokument, die die Tabelle referenziert (auch
  über Tabelle!Zelle/Blatt!Tabelle::Zelle-Qualifier aus anderen Tabellen),
  wird automatisch mitgezogen. Ein Bereich, der die Stelle überspannt,
  wächst/schrumpft entsprechend; eine Referenz, die exakt die gelöschte
  Zeile/Spalte trifft, wird zu einem echten #REF-Literal im Formeltext
  (nutzt die neue ErrorLiteralNode-Grammatik). $-Sperren bleiben dabei
  bewusst wirkungslos (anders als beim Kopieren), unveränderte Referenzen
  bleiben byte-identisch. Token-Level-Splice wie shiftFormula (Kopieren/
  Ausschneiden), keine AST-Neuserialisierung.

406 Tests, vue-tsc/eslint/build sauber, Playwright-live-verifiziert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
wkh
2026-09-08 22:34:33 +02:00
co-authored by Claude Sonnet 5
parent 2c54537427
commit aef8f1349c
14 changed files with 937 additions and 22 deletions
+35 -2
View File
@@ -27,6 +27,17 @@ function nums(values: CellValue[]): number[] {
return out;
}
/** The one truthy convention used everywhere in this engine a condition is
* needed (IF's condition -- both the formula function and its real
* short-circuit implementation in evaluator.ts's evalIf() --, a "Mit
* Schleife" block's IF statement, and AND/OR/NOT below): a number is
* truthy unless it's exactly `0`; text is truthy unless empty; an empty
* cell (`null`) is never truthy. Exported so evaluator.ts can reuse the
* exact same rule instead of duplicating it. */
export function isTruthy(v: CellValue): boolean {
return typeof v === 'number' ? v !== 0 : v !== null && v !== '';
}
export const FUNCTIONS: Record<string, FunctionFn> = {
SUM(values) {
return nums(values).reduce((a, b) => a + b, 0);
@@ -78,11 +89,30 @@ export const FUNCTIONS: Record<string, FunctionFn> = {
}
const [cond, thenValue, elseValue] = values;
if (isError(cond)) return cond;
const truthy = typeof cond === 'number' ? cond !== 0 : cond !== null && cond !== '';
if (truthy) return thenValue;
if (isTruthy(cond)) return thenValue;
return values.length === 3 ? elseValue : 0;
},
// Ordinary (non-short-circuit) functions -- like real Excel, EVERY
// argument is evaluated regardless of the others (so e.g.
// =AND(1=2; 1/0) is still #DIV/0!, not FALSE, exactly as in Excel) --
// no special-casing in evaluator.ts needed, unlike IF/XLOOKUP. A range
// argument flattens into one truthy check per cell, same as Excel.
AND(values) {
if (values.length === 0) return new FormulaError('#ERR', 'AND expects at least 1 argument');
return values.every(isTruthy) ? 1 : 0;
},
OR(values) {
if (values.length === 0) return new FormulaError('#ERR', 'OR expects at least 1 argument');
return values.some(isTruthy) ? 1 : 0;
},
NOT(values) {
if (values.length !== 1) return new FormulaError('#ERR', 'NOT expects exactly 1 argument');
return isTruthy(values[0]) ? 0 : 1;
},
// Dates/times are plain numbers here (see dates.ts) -- DATE/TIME build one,
// TODAY/NOW read the current one. `null` (empty cell) arguments count as 0,
// same as everywhere else in this engine; a non-numeric text argument is
@@ -169,6 +199,9 @@ export const BUILTIN_SIGNATURES: ReadonlyArray<{ name: string; hint: string }> =
{ name: 'CONCAT', hint: 'CONCAT(Text1; Text2; …)' },
{ name: 'CONCATENATE', hint: 'CONCATENATE(Text1; Text2; …)' },
{ name: 'IF', hint: 'IF(Bedingung; Dann; Sonst?)' },
{ name: 'AND', hint: 'AND(Bedingung1; Bedingung2; …)' },
{ name: 'OR', hint: 'OR(Bedingung1; Bedingung2; …)' },
{ name: 'NOT', hint: 'NOT(Bedingung)' },
{ name: 'DATE', hint: 'DATE(Jahr; Monat; Tag)' },
{ name: 'TIME', hint: 'TIME(Std; Min; Sek)' },
{ name: 'TODAY', hint: 'TODAY()' },