Makros: Engine- und Store-Fundament (Zellen/Zeilen/Spalten/Tabellen/Blätter per Skript)

Erster von zwei Commits (UI folgt separat): neue, explizit ausgelöste
Makro-Sprache — dieselbe block-mode-Grammatik wie bei "Mit Schleife"-
Funktionen (FOR/IF/RETURN), plus ein neues Schreib-Vokabular (SETVALUE,
INSERTROW/DELETEROW/INSERTCOL/DELETECOL, ADDTABLE/RENAMETABLE/DELETETABLE,
ADDSHEET/RENAMESHEET/DELETESHEET), adressiert mit der bestehenden
Blatt!Tabelle::Zelle-Syntax.

Bewusst außerhalb der normalen Neuberechnung: ein Makro läuft nie aus einer
Zellformel heraus, sondern nur explizit getriggert (store.runMacro), und
mutiert ausschließlich über die bereits vorhandenen Store-Primitiven
(setCellInput, insertRow, addTable, addSheet, …) — Workbook/EngineHost/
DependencyGraph bleiben unverändert rein lesend. Ein ganzer synchroner
Makro-Lauf wird automatisch zu einem einzigen Undo-Schritt.

50 neue Tests (Engine + Store), inkl. eines gezielten Regressionstests für
die zentrale Korrektheits-Fallgrube: eine strukturelle Aktion mitten im
Lauf ersetzt die Workbook-Instanz komplett, jeder Host-Zugriff muss daher
frisch aufgelöst werden statt eine Referenz zu cachen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
wkh
2026-09-09 09:35:02 +02:00
co-authored by Claude Sonnet 5
parent c7b316de1b
commit e670f13077
13 changed files with 1188 additions and 36 deletions
+39
View File
@@ -140,3 +140,42 @@ export interface EngineHost {
*/
getComputed(tableId: string, col: number, row: number): CellValue;
}
/**
* The document's WRITE surface, as seen by a running macro (see
* evaluator.ts's `runMacroActions`/`execAction` and engine/README.md's
* "Makros" section). Deliberately separate from {@link EngineHost} -- that
* interface stays pure/read-only, implemented by `Workbook` alone, used by
* every ordinary formula evaluation; `MacroWriteHost` is only ever
* implemented by the store (`document.ts#runMacro`), only ever passed to
* the interpreter for an explicitly-triggered macro run, and never reaches
* the passive recalculation pass at all -- a macro's write is
* indistinguishable, from the engine's point of view, from the same edit
* made by hand through the UI.
*
* Every table/sheet argument below is a plain qualifier STRING (e.g.
* `"Umsatz"` or `"Blatt2!Kosten"`, split on the first `!`) rather than a
* resolved id or a dedicated reference AST node -- resolved fresh, by the
* implementation, exactly like a formula's own `Tabelle!Zelle` qualifier
* is resolved fresh on every read (never cached), so a macro that renames
* or adds a table earlier in its own run can address it by its new name
* later in the same run.
*/
export interface MacroWriteHost {
/** Write the same raw cell text (auto-detected as a formula if it starts
* with "=", exactly like typing it manually) into every given cell --
* one cell for a `SETVALUE` on a single reference, several for a range
* target (broadcasting the same value, Excel's Ctrl+Enter semantics). */
setValue(cells: CellRef[], text: string): void;
insertRow(tableRef: string, at: number): void;
deleteRow(tableRef: string, at: number): void;
insertCol(tableRef: string, at: number): void;
deleteCol(tableRef: string, at: number): void;
/** `sheetRef` is a plain sheet name (not table-qualified). */
addTable(sheetRef: string, name: string): void;
renameTable(tableRef: string, newName: string): void;
deleteTable(tableRef: string): void;
addSheet(name: string): void;
renameSheet(sheetRef: string, newName: string): void;
deleteSheet(sheetRef: string): void;
}