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
+94
View File
@@ -0,0 +1,94 @@
/**
* Shared types for the formula engine.
*
* The engine is intentionally framework-agnostic: it knows nothing about Vue,
* the store or the DOM. The host application implements {@link EngineHost} to
* expose the current document to the evaluator.
*/
/** All error values the engine can produce. They propagate to dependent cells. */
export type ErrorCode =
| '#REF' // invalid / deleted reference or unknown sheet name
| '#DIV/0!' // division by zero
| '#NAME?' // unknown function
| '#ERR' // parse error
| '#CIRC' // circular reference
| '#VALUE!'; // wrong value type in arithmetic (documented extension)
export const ERROR_CODES: ErrorCode[] = [
'#REF',
'#DIV/0!',
'#NAME?',
'#ERR',
'#CIRC',
'#VALUE!',
];
/** A formula error. Instances are immutable and comparable by `code`. */
export class FormulaError {
constructor(
public readonly code: ErrorCode,
public readonly detail?: string,
) {}
toString(): string {
return this.code;
}
}
export function isError(v: unknown): v is FormulaError {
return v instanceof FormulaError;
}
/** A plain scalar a cell can hold once evaluated. `null` means "empty cell". */
export type Scalar = number | string | null;
/** The fully evaluated value of a cell. */
export type CellValue = Scalar | FormulaError;
/** 0-based cell coordinate inside a single table. */
export interface Coord {
col: number;
row: number;
}
/** A cell address that also carries the id of the table it lives on. */
export interface CellRef extends Coord {
tableId: string;
}
/** Stable string key for a cell across the whole document. */
export function keyOf(tableId: string, col: number, row: number): string {
return `${tableId}␟${col}␟${row}`;
}
export function parseKey(key: string): CellRef {
const [tableId, col, row] = key.split('␟');
return { tableId, col: Number(col), row: Number(row) };
}
/**
* The document as seen by the engine. Implemented by the Vue store.
*
* The addressable unit is a **table** (a table object on a Numbers-style
* canvas). Table names are unique across the whole document, so a qualified
* reference like `Umsatz!B2` / `Umsatz::B2` resolves the same no matter which
* sheet the formula or the target table sits on.
*/
export interface EngineHost {
/** Id of the table the formula currently being evaluated lives on. */
homeTableId: string;
/**
* Resolve a table *name* (as written in a formula) to its id. Matching is
* case-insensitive and tolerant of surrounding whitespace. Returns `null`
* for an unknown / deleted table.
*/
resolveTableName(name: string): string | null;
/** Dimensions of a table, used for range clamping and #REF detection. */
tableSize(tableId: string): { cols: number; rows: number } | null;
/**
* Already-computed value of another cell. The recalculation driver guarantees
* precedents are evaluated first (topological order), so this never triggers
* a nested evaluation.
*/
getComputed(tableId: string, col: number, row: number): CellValue;
}