UNPKG

hyperformula

Version:

HyperFormula is a JavaScript engine for efficient processing of spreadsheet-like data and formulas

195 lines 5.84 kB
/** * @license * Copyright (c) 2025 Handsoncode. All rights reserved. */ import { AbsoluteCellRange } from "../AbsoluteCellRange.mjs"; import { ArraySize } from "../ArraySize.mjs"; import { ArrayValue, ErroredArray, NotComputedArray } from "../ArrayValue.mjs"; import { CellError, equalSimpleCellAddress, ErrorType } from "../Cell.mjs"; import { ErrorMessage } from "../error-message.mjs"; import { EmptyValue, getRawValue } from "../interpreter/InterpreterValue.mjs"; import { ColumnsSpan, RowsSpan } from "../Span.mjs"; export class FormulaVertex { constructor(formula, cellAddress, version) { this.formula = formula; this.cellAddress = cellAddress; this.version = version; } get width() { return 1; } get height() { return 1; } static fromAst(formula, address, size, version) { if (size.isScalar()) { return new ScalarFormulaVertex(formula, address, version); } else { return new ArrayFormulaVertex(formula, address, size, version); } } /** * Returns formula stored in this vertex */ getFormula(updatingService) { this.ensureRecentData(updatingService); return this.formula; } ensureRecentData(updatingService) { if (this.version != updatingService.version()) { const [newAst, newAddress, newVersion] = updatingService.applyTransformations(this.formula, this.cellAddress, this.version); this.formula = newAst; this.cellAddress = newAddress; this.version = newVersion; } } /** * Returns address of the cell associated with vertex */ getAddress(updatingService) { this.ensureRecentData(updatingService); return this.cellAddress; } } export class ArrayFormulaVertex extends FormulaVertex { constructor(formula, cellAddress, size, version = 0) { super(formula, cellAddress, version); if (size.isRef) { this.array = new ErroredArray(new CellError(ErrorType.REF, ErrorMessage.NoSpaceForArrayResult), ArraySize.error()); } else { this.array = new NotComputedArray(size); } } get width() { return this.array.width(); } get height() { return this.array.height(); } get sheet() { return this.cellAddress.sheet; } get leftCorner() { return this.cellAddress; } setCellValue(value) { if (value instanceof CellError) { this.setErrorValue(value); return value; } const array = ArrayValue.fromInterpreterValue(value); array.resize(this.array.size); this.array = array; return value; } getCellValue() { if (this.array instanceof NotComputedArray) { throw Error('Array not computed yet.'); } return this.array.simpleRangeValue(); } valueOrUndef() { if (this.array instanceof NotComputedArray) { return undefined; } return this.array.simpleRangeValue(); } getArrayCellValue(address) { const col = address.col - this.cellAddress.col; const row = address.row - this.cellAddress.row; try { return this.array.get(col, row); } catch (e) { return new CellError(ErrorType.REF); } } getArrayCellRawValue(address) { const val = this.getArrayCellValue(address); if (val instanceof CellError || val === EmptyValue) { return undefined; } else { return getRawValue(val); } } setArrayCellValue(address, value) { const col = address.col - this.cellAddress.col; const row = address.row - this.cellAddress.row; if (this.array instanceof ArrayValue) { this.array.set(col, row, value); } } setNoSpace() { this.array = new ErroredArray(new CellError(ErrorType.SPILL, ErrorMessage.NoSpaceForArrayResult), ArraySize.error()); return this.getCellValue(); } getRange() { return AbsoluteCellRange.spanFrom(this.cellAddress, this.width, this.height); } getRangeOrUndef() { return AbsoluteCellRange.spanFromOrUndef(this.cellAddress, this.width, this.height); } setAddress(address) { this.cellAddress = address; } setFormula(newFormula) { this.formula = newFormula; } spansThroughSheetRows(sheet, startRow, endRow = startRow) { return this.cellAddress.sheet === sheet && this.cellAddress.row <= endRow && startRow < this.cellAddress.row + this.height; } spansThroughSheetColumn(sheet, col, columnEnd = col) { return this.cellAddress.sheet === sheet && this.cellAddress.col <= columnEnd && col < this.cellAddress.col + this.width; } isComputed() { return !(this.array instanceof NotComputedArray); } columnsFromArray() { return ColumnsSpan.fromNumberOfColumns(this.cellAddress.sheet, this.cellAddress.col, this.width); } rowsFromArray() { return RowsSpan.fromNumberOfRows(this.cellAddress.sheet, this.cellAddress.row, this.height); } /** * No-op as array vertices are transformed eagerly. */ ensureRecentData(_updatingService) {} isLeftCorner(address) { return equalSimpleCellAddress(this.cellAddress, address); } setErrorValue(error) { this.array = new ErroredArray(error, this.array.size); } } /** * Represents vertex which keeps formula */ export class ScalarFormulaVertex extends FormulaVertex { constructor(/** Formula in AST format */ formula, /** Address which this vertex represents */ address, version) { super(formula, address, version); } valueOrUndef() { return this.cachedCellValue; } /** * Sets computed cell value stored in this vertex */ setCellValue(cellValue) { this.cachedCellValue = cellValue; return this.cachedCellValue; } /** * Returns cell value stored in vertex */ getCellValue() { if (this.cachedCellValue !== undefined) { return this.cachedCellValue; } else { throw Error('Value of the formula cell is not computed.'); } } isComputed() { return this.cachedCellValue !== undefined; } }