import { b as SortableValue, o as ColumnDef } from "./types-Cqk1_BXq.js"; import { t as UrlStateAdapter } from "./adapter-BD3RX3cl.js"; //#region src/formula/parse.d.ts /** * Parsing a spreadsheet formula into a tree. * * The one rule this file exists to keep: **a formula is text, and it is * parsed.** It is never handed to `eval`, `new Function`, or anything else * that would run it as JavaScript. A user-typed formula is untrusted input in * exactly the way a URL is, and a table that evaluates one has handed the * page to whoever typed it — including, in a shared saved view, to whoever * sent the link. * * The grammar is deliberately small, because a formula language grows one * "just add" at a time until it is a programming language nobody can secure: * * ``` * expression → comparison * comparison → concat ( ("=" | "<>" | "<" | "<=" | ">" | ">=") concat )? * concat → sum ( "&" sum )* * sum → product ( ("+" | "-") product )* * product → unary ( ("*" | "/") unary )* * unary → "-" unary | primary * primary → number | string | reference | call | "(" expression ")" * call → NAME "(" ( expression ( "," expression )* )? ")" * reference → NAME | "[" any-text "]" * ``` * * `&` concatenates, as it does in a spreadsheet — and it binds BELOW `+` and * `-`, also as it does in a spreadsheet: `="a" & 2 + 3` is `"a5"`, because the * arithmetic finishes before the join. Sharing the additive level instead read * it as `("a" & 2) + 3` and answered `#VALUE!`, which is the arithmetic of a * language nobody writes formulas in. * * Bracketed references exist so a column called "Unit Price" can be named * without inventing an escaping rule for spaces. * * A parse failure is a returned error, not an exception: the formula bar has * to show something useful while someone is still typing, and half a formula * is the normal state of one being written. */ /** A binary operator the grammar accepts. */ type BinaryOp = "+" | "-" | "*" | "/" | "&" | "=" | "<>" | "<" | "<=" | ">" | ">="; /** One node of a parsed formula. */ type FormulaNode = { readonly kind: "number"; readonly value: number; } | { readonly kind: "string"; readonly value: string; } | { readonly kind: "ref"; readonly key: string; } | { readonly kind: "unary"; readonly operand: FormulaNode; } | { readonly kind: "binary"; readonly op: BinaryOp; readonly left: FormulaNode; readonly right: FormulaNode; } | { readonly kind: "call"; readonly name: string; readonly args: readonly FormulaNode[]; }; /** What {@link parseFormula} answers with. */ interface ParseResult { /** Whether the text parsed. */ readonly ok: boolean; /** The tree, when it did. */ readonly node?: FormulaNode; /** What was wrong, when it did not — in words a formula bar can show. */ readonly message?: string; } /** * Parse a formula. * * A leading `=` is accepted and ignored, because that is how people type one. * * @param text - The formula as the user typed it. * @returns The tree, or the reason it could not be parsed. Never throws. */ declare function parseFormula(text: string): ParseResult; /** * Every column a formula reads, so a cache knows what to watch. * * @param node - A parsed formula. * @returns The referenced keys, each once, in the order first seen. */ declare function formulaRefs(node: FormulaNode): string[]; //#endregion //#region src/formula/evaluate.d.ts /** The error values a formula can produce, spelled as a spreadsheet spells them. */ declare const FORMULA_ERRORS: { /** A column the formula names does not exist. */readonly name: "#NAME?"; /** A number was needed and the value was not one. */ readonly value: "#VALUE!"; /** Division by zero. */ readonly divideByZero: "#DIV/0!"; /** The formula depends on itself, directly or through others. */ readonly cycle: "#CYCLE!"; /** The formula could not be parsed at all. */ readonly syntax: "#ERROR!"; }; /** One of the error codes above. */ type FormulaErrorCode = (typeof FORMULA_ERRORS)[keyof typeof FORMULA_ERRORS]; /** What a formula evaluates to. */ type FormulaValue = { readonly kind: "number"; readonly value: number; } | { readonly kind: "text"; readonly value: string; } | { readonly kind: "boolean"; readonly value: boolean; } | { readonly kind: "blank"; } | { readonly kind: "error"; readonly code: FormulaErrorCode; }; /** An empty cell. */ declare const FORMULA_BLANK: FormulaValue; /** A number value. */ declare function formulaNumber(value: number): FormulaValue; /** A text value. */ declare function formulaText(value: string): FormulaValue; /** A boolean value. */ declare function formulaBoolean(value: boolean): FormulaValue; /** An error value. */ declare function formulaError(code: FormulaErrorCode): FormulaValue; /** * Whether a value is an error rather than an answer. * * @param value - Any formula result. * @returns Whether it failed. */ declare function isFormulaError(value: FormulaValue): boolean; /** * Turn a raw field off a row into a formula value. * * Anything the engine has no kind for — an object, a function — is `#VALUE!` * rather than its stringification. `[object Object]` in a cell is not a * rendering of the data, it is a rendering of the fact that nobody decided * what to do, and it would go on to be concatenated into totals and exports. * * @param raw - The field as it sits on the row. * @returns The value a formula sees. */ declare function toFormulaValue(raw: unknown): FormulaValue; /** * How a value reads in a cell. * * @param value - The evaluated value. * @returns Its display text; an error shows as its code. */ declare function formulaDisplay(value: FormulaValue): string; /** How the evaluator reads a column off the row it was given. */ type FormulaScope = (key: string) => FormulaValue | undefined; /** * Evaluate a parsed formula against one row. * * Never throws. Every failure is one of {@link FORMULA_ERRORS}, and an error * anywhere in an expression comes out of it rather than being counted as * zero. * * @param node - The parsed formula. * @param scope - Reads a column's value; `undefined` for a column that is not * there, which becomes `#NAME?`. * @returns The value for the cell. */ declare function evaluateFormula(node: FormulaNode, scope: FormulaScope): FormulaValue; /** The function names the engine knows — for a formula bar's autocomplete. */ declare const FORMULA_FUNCTIONS: readonly string[]; /** * How a value sorts, for a formula column's `sortValue`. * * Each kind sorts as what it is: a number numerically, text as text, a boolean * with FALSE before TRUE. A key is not the number a value could be coerced to — * coercing text to a number gives every row in an `=UPPER(name)` column the * same key, and a column where every key is equal is a column whose header * does nothing when it is clicked. * * A blank and an error have no place in an ordering, so both come back as * `null`: the table's comparator groups those at the END in either direction, * which is where a spreadsheet leaves an error too. Ties among them keep the * order the rows already had, so the grouping is deterministic rather than * merely consistent-looking. * * @param value - The evaluated value. * @returns The key the table's comparator orders by. */ declare function formulaSortValue(value: FormulaValue): SortableValue; //#endregion //#region src/formula/formulaColumn.d.ts /** One user-typed formula column. */ interface FormulaColumnSpec { /** Column key — also the name other formulas reference it by. */ key: string; /** Header caption. Defaults to the key. */ header?: string; /** The formula text, as the user typed it. A leading `=` is fine. */ formula: string; /** Format the result for display. The raw value still sorts and exports. */ format?: (value: FormulaValue) => string; } /** What {@link buildFormulaColumns} reports back. */ interface FormulaColumnsResult { /** The columns, ready to concatenate with the declared ones. */ columns: readonly ColumnDef[]; /** Formulas that would not parse, by key, with the parser's message. */ errors: Readonly>; /** Keys that take part in a dependency cycle, if any. */ cycles: readonly string[]; } /** * Build columns from user-typed formulas. * * @typeParam TRow - The row type. * @param specs - The formula columns, in the order to show them. * @returns The columns, plus any formula that would not parse and any cycle. */ declare function buildFormulaColumns(specs: readonly FormulaColumnSpec[]): FormulaColumnsResult; //#endregion //#region src/formula/formulaUrlCodec.d.ts /** * Write formula columns as a URL parameter value. * * @param specs - The columns to serialize, in the order to show them. * @returns The parameter value, or `""` when there is nothing to say. */ declare function serializeFormulaColumns(specs: readonly FormulaColumnSpec[]): string; /** * Read formula columns back from a URL parameter value. * * A malformed entry is dropped rather than thrown: a URL is user input, and a * hand-edited one should degrade to the columns it still describes instead of * an error page. The formula text is carried through untouched and unparsed. * * @param raw - The parameter value. * @returns The columns it describes, in order, each key appearing once. */ declare function deserializeFormulaColumns(raw: string | null): FormulaColumnSpec[]; //#endregion //#region src/formula/useFormulaUrlState.d.ts /** * Trailing debounce for URL persistence. A formula bar that writes as it is * typed commits one list per keystroke, and `history.replaceState` at that rate * trips Safari's limit (~100 calls per 30s, then it throws). Reads stay instant * through the optimistic overlay below; only the URL write waits. */ declare const FORMULA_URL_WRITE_DEBOUNCE_MS = 150; /** What {@link useFormulaUrlState} needs. */ interface UseFormulaUrlStateOptions { /** URL-state backend. Defaults to the browser History API. */ urlAdapter?: UrlStateAdapter; /** When `false`, keep the columns in a local memory store. Defaults `true`. */ urlSync?: boolean; /** Namespace, when several tables share one URL (`left.formula`). */ urlKey?: string; /** The columns applied while the URL carries none. Defaults to none. */ defaultFormulas?: readonly FormulaColumnSpec[]; } /** The controlled pair to hand a formula bar and {@link buildFormulaColumns}. */ interface UseFormulaUrlStateResult { /** The columns — from the URL, or the default while the URL is silent. */ formulas: readonly FormulaColumnSpec[]; /** Persist a new list. Wire to whatever adds and removes a column. */ onFormulasChange: (next: readonly FormulaColumnSpec[]) => void; } /** * Keep the formula columns in the URL. * * @param options - See {@link UseFormulaUrlStateOptions}. * @returns The current columns and a change handler that persists them. */ declare function useFormulaUrlState(options?: UseFormulaUrlStateOptions): UseFormulaUrlStateResult; //#endregion export { type BinaryOp, FORMULA_BLANK, FORMULA_ERRORS, FORMULA_FUNCTIONS, FORMULA_URL_WRITE_DEBOUNCE_MS, type FormulaColumnSpec, type FormulaColumnsResult, type FormulaErrorCode, type FormulaNode, type FormulaScope, type FormulaValue, type ParseResult, type UseFormulaUrlStateOptions, type UseFormulaUrlStateResult, buildFormulaColumns, deserializeFormulaColumns, evaluateFormula, formulaBoolean, formulaDisplay, formulaError, formulaNumber, formulaRefs, formulaSortValue, formulaText, isFormulaError, parseFormula, serializeFormulaColumns, toFormulaValue, useFormulaUrlState }; //# sourceMappingURL=formula.d.ts.map