Skip to content
Apertura
API reference

@apertura/formula

The Excel formula language: lexer, parser, evaluator and function library

69 exported symbols · 69 declared here · 0 re-exported

Classes

Evaluator
class Evaluator
missingFunctions
Set<string>
Functions the workbook used and this engine does not have.
workbook
WorkbookContext
evaluateFormula
(formula: string, position: CellPosition, options?: EvaluateOptions) => FormulaValue
Parses and evaluates a formula in the scope of one cell.
parse
(formula: string) => Node | FormulaError
Parses with a cache; a syntax error becomes `#NAME?`, as in Excel.
evaluate
(node: Node, position: CellPosition, options?: EvaluateOptions) => FormulaValue
materialize
(value: FormulaValue) => Matrix
Reads every value behind a value, whatever shape it arrived in.
referenceToMatrix
(reference: Reference) => Matrix
areaToMatrix
(area: Area) => Matrix
boundArea
(area: Area) => Area
Clips a reference to what the sheet actually holds. `SUM(A:A)` must not read a million cells. Excel bounds the same way, which is why a value far down a column is what makes such a formula slow rather than the reference itself.
lookupBinding
(name: string) => FormulaValue | undefined
The innermost binding of a name, if any scope holds one.
withBindings
<T>(bindings: ReadonlyMap<string, FormulaValue>, run: () => T) => T
Runs a function with extra bindings in scope.
captureScope
() => Map<string, FormulaValue>
The bindings currently in scope, flattened, for a lambda to close over.
callLambda
(lambda: Lambda, values: readonly FormulaValue[], position: CellPosition) => FormulaValue
Applies a lambda to arguments. The closure is restored first and the parameters bound over it, so that a lambda written inside a `LET` still sees that `LET`'s names when it is called from somewhere else entirely.
argument
(node: Node, position: CellPosition, array: boolean) => Argument
Wraps a node as a lazily evaluated argument.
toScalarContext
(value: FormulaValue, position: CellPosition, array?: boolean) => FormulaValue
Public form of the intersection rule, for functions that need it.
FormulaError
class FormulaError

An Excel error value. Interned, so identity comparison works.

null
FormulaError
divideByZero
FormulaError
value
FormulaError
reference
FormulaError
name
FormulaError
number
FormulaError
notAvailable
FormulaError
gettingData
FormulaError
spill
FormulaError
calc
FormulaError
all
readonly FormulaError[]
parse
(text: string) => FormulaError | undefined
Parses `#REF!` and friends; `undefined` when the text is not an error.
toString
() => string
FormulaSyntaxError
class FormulaSyntaxError extends Error
Reference
class Reference

A reference. Kept as a value in its own right rather than resolved to numbers on sight, because a dozen functions care *where* their argument is and not only what is in it: `ROW`, `COLUMN`, `OFFSET`, `INDEX` (which returns a reference), `CELL`, `ISREF`, and every function whose criteria argument is a range. More than one area happens through the union operator: `SUM((A1:A5,C1:C5))`.

cell
(sheet: number, row: number, column: number) => Reference
single
Area | undefined
isSingleCell
boolean
rowCount
number
columnCount
number

Functions

buildMatrix
function buildMatrix(rows: number, columns: number, produce: (row: number, column: number) => Scalar): Matrix

Builds a matrix of the given shape.

builtinFormatCode
function builtinFormatCode(id: number): string | undefined

The format code of a built-in id, or `undefined` when the id is not reserved.

columnFromLetters
function columnFromLetters(letters: string): number
compareScalars
function compareScalars(left: Scalar, right: Scalar): number

Compares two scalars the way `<` and `MATCH` do. Returns a negative number, zero or a positive number. Blanks are compared as the zero or empty text of whatever they meet, which is why `A1=0` and `A1=""` are both true when A1 is empty.

compileCriterion
function compileCriterion(raw: Scalar): Criterion

Compiles a `COUNTIF`-style criterion. The grammar: an optional comparison operator, then a value. Without an operator it is equality — and equality against text applies wildcards, which is why `COUNTIF(A:A,"a*")` counts everything beginning with an `a`.

compileFormat
function compileFormat(code: string): CompiledFormat

Compiles a format code, or returns the cached compilation.

constantValue
function constantValue(node: Node): Scalar | undefined

A constant node's value, for the few places that only accept constants.

dateToSerial
function dateToSerial(date: Date, date1904?: boolean): number

The inverse: a `Date` as the serial number Excel would store for it.

defineFunction
function defineFunction(names: string | readonly string[], definition: FunctionDefinition): void
emptyContext
function emptyContext(overrides?: Partial<WorkbookContext>): WorkbookContext

A workbook context with nothing in it, for evaluating a bare expression.

firstError
function firstError(value: FormulaValue): FormulaError | undefined

The first error anywhere in a value, which is what propagates.

formatNumberGeneral
function formatGeneral(value: number): string

Excel's `General`. Not "the number as JavaScript prints it": Excel keeps about eleven significant digits and falls back to scientific notation outside a fixed range, which is why `0.1 + 0.2` shows as `0.3` in a spreadsheet and as something longer in a console.

formatNumberGenerally
function formatNumberGenerally(value: number): string

A number as `General` renders it. Fifteen significant digits, which is the precision Excel keeps and the reason a spreadsheet shows `0.3` where a console shows the rounding error.

formatValue
function formatValue(value: string | number | boolean | Date | null, code: string | undefined, options?: FormatOptions): FormattedValue

Formats a value the way Excel would with the given code. `code` is the format string, not the id: resolving an id through the workbook's `numFmts` and the built-in table happens in the style layer, which is the only place that knows both.

indexedColor
function indexedColor(index: number, palette?: readonly string[]): string | undefined

Looks up an indexed colour, falling back to the default palette.

isDateFormat
function isDateFormat(code: string | undefined): boolean

Whether a format code shows a date or a time rather than a number.

isError
function isError(value: unknown): value is FormulaError
isMatrix
function isMatrix(value: unknown): value is Matrix
isReference
function isReference(value: unknown): value is Reference
isScalar
function isScalar(value: FormulaValue): value is Scalar
isTextFormat
function isTextFormat(code: string | undefined): boolean

Whether the code contains a text section, which is what makes it apply to strings.

lettersFromColumn
function lettersFromColumn(index: number): string
matrixAt
function matrixAt(matrix: Matrix, row: number, column: number): Scalar

Reads a matrix with Excel's broadcast rules: a single row or column repeats.

matrixSize
function matrixSize(matrix: Matrix): { rows: number; columns: number; }
normalizeFunctionName
function normalizeFunctionName(name: string): string

Strips the `_xlfn.` prefix. Functions added after 2007 are stored with it so that older versions fail predictably instead of silently. `_xlfn.IFS` and `IFS` are the same function and the library should only have to know one name. The `_xlws.` prefix, for worksheet-only functions, works the same way.

parseFormula
function parseFormula(input: string): Node
parseNumericText
function parseNumericText(text: string): number | undefined

Text that Excel accepts as a number. Deliberately stricter than `Number()`: `"1e5"` and `"0x10"` are numbers to JavaScript and text to Excel, and `""` is zero to `Number()` and an error here. Percentages, leading currency symbols and thousands separators are accepted, because Excel accepts them.

roundHalfAwayFromZero
function roundHalfAwayFromZero(value: number, digits: number): number

Rounds the way a spreadsheet does: half away from zero, at a decimal place.

serialToDate
function serialToDate(serial: number, date1904?: boolean): Date | undefined

Converts a serial number to a JavaScript `Date` in UTC.

serialToDateParts
function serialToDateParts(serial: number, date1904?: boolean): DateParts | undefined

Converts a serial number to calendar parts. Returns `undefined` for values Excel itself refuses to show as a date: negative serials in the 1900 system produce `#####`, not a date before the epoch.

supportedFunctions
function supportedFunctions(): string[]
toBoolean
function toBoolean(value: Scalar): boolean | FormulaError
toCellValue
function toCellValue(value: FormulaValue, evaluator?: Evaluator): Scalar

The single value a cell shows for a computed result.

tokenize
function tokenize(input: string): Token[]
toMatrix
function toMatrix(value: FormulaValue): Matrix
toNumber
function toNumber(value: Scalar): number | FormulaError

Coerces to a number, as an arithmetic operator would.

toText
function toText(value: Scalar): string | FormulaError

Coerces to text. A number becomes what `General` would show, not what JavaScript prints: concatenating 0.1 + 0.2 must produce `0.3`, not `0.30000000000000004`.

wildcardToRegExp
function wildcardToRegExp(text: string, anchored?: boolean): RegExp | undefined

Excel's wildcards: `*` any run, `?` one character, `~` escapes either. Returns `undefined` when the text holds no wildcard at all, so the caller can take the cheaper equality path.

Interfaces

Area
interface Area

One rectangular area of a sheet, zero-based and inclusive.

sheet
number
startRow
number
startColumn
number
endRow
number
endColumn
number
Argument
interface Argument

One argument of a function call, evaluated on demand.

node
Node
missing
boolean
value
() => FormulaValue
The value, computed once and cached.
reference
() => Reference | undefined
The reference this argument is, when it is one.
scalar
() => Scalar
A single value: a scalar, or the intersection of a reference.
matrix
() => Matrix
The rectangular values behind the argument.
values
() => Scalar[]
Every value behind the argument, in reading order. Separate from {@link matrix} because a union has no rectangular shape: `SUM((A1:A3,C1:C3))` reads six cells that no single matrix can hold.
CallContext
interface CallContext
workbook
WorkbookContext
position
CellPosition
evaluator
Evaluator
array
boolean
CellPosition
interface CellPosition

Where a formula lives. Every relative reference is resolved against it.

sheet
number
row
number
column
number
Criterion
interface Criterion
test
(value: Scalar) => boolean
DateParts
interface DateParts

Whole and fractional parts of a serial number, in calendar terms.

year
number
month
number
1..12.
day
number
1..31.
hours
number
minutes
number
seconds
number
milliseconds
number
weekday
number
0 = Sunday.
EvaluateOptions
interface EvaluateOptions
array?
boolean | undefined
Array (CSE) semantics: operands broadcast instead of intersecting, and the result may be a matrix.
FormatOptions
interface FormatOptions
date1904?
boolean | undefined
The workbook's 1904 date system flag.
locale?
string | undefined
BCP 47 locale for month and weekday names; overridden by `[$-…]` in the code.
palette?
readonly string[] | undefined
The workbook's own indexed palette, when it overrides the default.
FormattedValue
interface FormattedValue

The result of formatting a value with a format code.

text
string
The text as Excel would display it.
color
string | undefined
Colour requested by the format code (`[Red]`), as CSS.
fill
string | undefined
The character of a `*` fill token, when the code has one. Excel repeats it until the cell is full — the usual use is `_(* #,##0_)`, the accounting format, where it pushes the number to the right edge.
kind
"number" | "boolean" | "error" | "date" | "text" | "empty"
What the value turned out to be; the renderer aligns on it.
FunctionDefinition
interface FunctionDefinition
minArgs
number
maxArgs
number
volatile?
boolean | undefined
Recomputed whenever anything changes: `TODAY`, `NOW`, `RAND`, `OFFSET`.
arrayArgs?
boolean | undefined
The arguments are arrays by nature, so they are evaluated with array semantics whether or not the formula was entered as an array. This is what makes `SUMPRODUCT((A1:A3>1)*(B1:B3))` work: without it the comparison would implicitly intersect down to one cell, and the idiom that half the spreadsheets in the world use for a conditional sum would return zero.
elementwise?
boolean | undefined
Every argument is a single value, so a range is applied element by element. `SQRT(B2:F3)` entered as an array formula is a matrix of roots, one per cell — and `SUM(B2:F3)` is not, because a sum takes the range whole. The difference is in the *arguments*, and nothing else can tell them apart, so a function that takes only single values says so here.
call
(args: readonly Argument[], context: CallContext) => FormulaValue
RawReference
interface RawReference

A reference as written, before it is resolved against a workbook.

sheet
string | undefined
Sheet name, or `undefined` for the sheet the formula is on.
lastSheet
string | undefined
Last sheet of a 3D span, `Sheet1:Sheet3!A1`.
workbook
number | undefined
Index of an external workbook, `[1]Sheet1!A1`.
startColumn
number | undefined
startColumnAbsolute
boolean
startRow
number | undefined
startRowAbsolute
boolean
endColumn
number | undefined
Set for whole-column (`A:C`) and whole-row (`2:4`) references.
endColumnAbsolute
boolean
endRow
number | undefined
endRowAbsolute
boolean
broken
boolean
The reference was written as `#REF!`, so it is already broken.
TableInfo
interface TableInfo

A table, for structured references.

range
Area
headerRowCount
number
totalsRowCount
number
columns
readonly string[]
Token
interface Token
kind
TokenKind
text
string
position
number
spaceBefore
boolean
Whitespace stood before this token, which may make it an intersection.
number?
number | undefined
string?
string | undefined
error?
FormulaError | undefined
reference?
RawReference | undefined
sheet?
string | undefined
The sheet a name was qualified with, as in `Hitab!Divisor`.
structured?
{ table: string | undefined; specification: string; } | undefined
WorkbookContext
interface WorkbookContext
date1904
boolean
The 1904 date system, which shifts every date function by four years.
sheetIndex
(name: string) => number | undefined
Index of a sheet by name, case-insensitively.
sheetName
(index: number) => string | undefined
sheetCount
number
cell
(sheet: number, row: number, column: number) => Scalar
A cell's stored value. `null` for a blank cell.
formulaAt?
((sheet: number, row: number, column: number) => string | undefined) | undefined
A cell's formula, without its leading equals sign. Only `FORMULATEXT` and `ISFORMULA` ask, and both exist so that a sheet can document itself. A context that does not track formulas may leave it out.
usedRange
(sheet: number) => { rowCount: number; columnCount: number; }
The bounds of what the sheet actually contains. A whole-column reference is a million cells, and `SUM(A:A)` must not walk them. Excel bounds the same way, which is why adding a value far down a column is what makes it slow rather than the reference itself.
definedName
(name: string, sheet: number) => string | undefined
A defined name, as the formula it stands for. Names are formulas, not ranges: `Rates` is usually `Sheet1!$A$1:$B$9` but may be `OFFSET(...)` or a constant. Returning the text lets the engine evaluate it in the scope of the cell that used it, which is what makes relative names work.
table
(name: string) => TableInfo | undefined
rowHidden?
((sheet: number, row: number) => boolean) | undefined
Whether a row is hidden, for `SUBTOTAL`'s hundred-and-something codes. `SUBTOTAL(109, …)` is `SUM` over the rows a filter left visible, and a workbook that reports nothing gets the plain sum — which is what `SUBTOTAL(9, …)` means anyway.
workbookName?
string | undefined
The workbook's file name, for `CELL("filename")`.
columnWidth?
((sheet: number, column: number) => number | undefined) | undefined
A column's width in characters, for `CELL("width")`. The one piece of the *sheet's* appearance a formula can ask about, and a workbook that does not track widths may leave it out — the answer is then the default width, which is what Excel shows for a column nobody resized.
now
() => Date
The clock, so that `TODAY` and `NOW` can be made deterministic in tests.
formatNumber?
((value: Scalar, code: string) => string | undefined) | undefined
Applies a number format code, for `TEXT` and `DOLLAR`. Supplied by the workbook rather than implemented here: the same codes drive how every cell is displayed, and there should be one implementation of them rather than one for the grid and another for the formula.
callUnknown?
((name: string, args: readonly unknown[]) => Scalar | undefined) | undefined
A function the engine does not implement. The hook exists for user-defined functions from a macro module: they cannot be evaluated here and never will be. Returning `undefined` produces `#NAME?`, which is what Excel itself shows when the macro is not enabled.

Type aliases

BinaryOperator
type BinaryOperator = '+' | '-' | '*' | '/' | '^' | '&' | '=' | '<>' | '<' | '>' | '<=' | '>=' | ':' | ' ' | ','
FormulaValue
type FormulaValue = Scalar | Matrix | Reference | Lambda

Everything a formula can evaluate to.

Matrix
type Matrix = Scalar[][]

A rectangular block of values, row-major. Arrays in Excel are always 2D.

Node
type Node = | { readonly kind: 'number'; readonly value: number } | { readonly kind: 'string'; readonly value: string } | { readonly kind: 'boolean'; readonly value: boolean } | { readonly kind: 'error'; readonly value: FormulaError } | { readonly kind: 'reference'; readonly reference: RawReference } | { readonly kind: 'name'; readonly name: string; readonly sheet?: string } | { readonly kind: 'structured'; readonly table: string | undefined; readonly specification: string; } | { readonly kind: 'call'; readonly name: string; readonly args: readonly Node[] } | { readonly kind: 'unary'; readonly operator: '-' | '+' | '@'; readonly operand: Node } | { readonly kind: 'percent'; readonly operand: Node } /** Calling what an expression produced: `LAMBDA(x, x + 1)(2)`. */ | { readonly kind: 'apply'; readonly callee: Node; readonly args: readonly Node[] } | { readonly kind: 'binary'; readonly operator: BinaryOperator; readonly left: Node; readonly right: Node; } | { readonly kind: 'array'; readonly rows: readonly (readonly Node[])[] } | { readonly kind: 'missing' }
Scalar
type Scalar = number | string | boolean | FormulaError | null

A single value: a number, text, a logical, an error, or a blank.

Values

functionModules
functionModules: number

How many modules the library is built from. Reading it keeps them alive.

FUNCTIONS
FUNCTIONS: Map<string, FunctionDefinition>

The function library. Modules add to it as they are imported.

INDEX_AUTOMATIC
INDEX_AUTOMATIC: 64

Automatic colour: whatever the window text colour is.

INDEX_BACKGROUND
INDEX_BACKGROUND: 65

The window background colour.

INDEXED_COLORS
INDEXED_COLORS: readonly string[]

The legacy indexed colour palette. Before themes, a workbook referred to colours by index into a 56-entry table that lived in the file (`<indexedColors>`) and, when it did not, was assumed to be this one. Files still arrive with `indexed="10"` on a font, and number format codes still say `[Color 10]`, so the default table has to be here even though nothing has written it deliberately in twenty years. Indices 0-7 repeat as 8-15: the first eight are the "system" colours and the second eight are the same colours in the user-editable part of the palette. Index 64 is "automatic" — the window text colour — and 65 the window background; both are resolved by the renderer rather than by a table.

MAX_COLUMNS
MAX_COLUMNS: 16384
MAX_ROWS
MAX_ROWS: 1048576

Enums

TokenKind
TokenKind: typeof TokenKind