UNPKG

hyperformula

Version:

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

126 lines 5.13 kB
/** * @license * Copyright (c) 2025 Handsoncode. All rights reserved. */ import { AbsoluteCellRange } from "./AbsoluteCellRange.mjs"; import { absolutizeDependencies } from "./absolutizeDependencies.mjs"; import { CellError, ErrorType } from "./Cell.mjs"; import { ContentChanges } from "./ContentChanges.mjs"; import { ArrayVertex, RangeVertex } from "./DependencyGraph/index.mjs"; import { FormulaVertex } from "./DependencyGraph/FormulaCellVertex.mjs"; import { InterpreterState } from "./interpreter/InterpreterState.mjs"; import { EmptyValue, getRawValue } from "./interpreter/InterpreterValue.mjs"; import { SimpleRangeValue } from "./SimpleRangeValue.mjs"; import { StatType } from "./statistics/index.mjs"; export class Evaluator { constructor(config, stats, interpreter, lazilyTransformingAstService, dependencyGraph, columnSearch) { this.config = config; this.stats = stats; this.interpreter = interpreter; this.lazilyTransformingAstService = lazilyTransformingAstService; this.dependencyGraph = dependencyGraph; this.columnSearch = columnSearch; } run() { this.stats.start(StatType.TOP_SORT); const { sorted, cycled } = this.dependencyGraph.topSortWithScc(); this.stats.end(StatType.TOP_SORT); this.stats.measure(StatType.EVALUATION, () => { this.recomputeFormulas(cycled, sorted); }); } partialRun(vertices) { const changes = ContentChanges.empty(); this.stats.measure(StatType.EVALUATION, () => { this.dependencyGraph.graph.getTopSortedWithSccSubgraphFrom(vertices, vertex => { if (vertex instanceof FormulaVertex) { const currentValue = vertex.isComputed() ? vertex.getCellValue() : undefined; const newCellValue = this.recomputeFormulaVertexValue(vertex); if (newCellValue !== currentValue) { const address = vertex.getAddress(this.lazilyTransformingAstService); changes.addChange(newCellValue, address); this.columnSearch.change(getRawValue(currentValue), getRawValue(newCellValue), address); return true; } return false; } else if (vertex instanceof RangeVertex) { vertex.clearCache(); return true; } else { return true; } }, vertex => { if (vertex instanceof RangeVertex) { vertex.clearCache(); } else if (vertex instanceof FormulaVertex) { const address = vertex.getAddress(this.lazilyTransformingAstService); this.columnSearch.remove(getRawValue(vertex.valueOrUndef()), address); const error = new CellError(ErrorType.CYCLE, undefined, vertex); vertex.setCellValue(error); changes.addChange(error, address); } }); }); return changes; } runAndForget(ast, address, dependencies) { const tmpRanges = []; for (const dep of absolutizeDependencies(dependencies, address)) { if (dep instanceof AbsoluteCellRange) { const range = dep; if (this.dependencyGraph.getRange(range.start, range.end) === undefined) { const rangeVertex = new RangeVertex(range); this.dependencyGraph.rangeMapping.setRange(rangeVertex); tmpRanges.push(rangeVertex); } } } const ret = this.evaluateAstToCellValue(ast, new InterpreterState(address, this.config.useArrayArithmetic)); tmpRanges.forEach(rangeVertex => { this.dependencyGraph.rangeMapping.removeRange(rangeVertex); }); return ret; } /** * Recalculates formulas in the topological sort order */ recomputeFormulas(cycled, sorted) { cycled.forEach(vertex => { if (vertex instanceof FormulaVertex) { vertex.setCellValue(new CellError(ErrorType.CYCLE, undefined, vertex)); } }); sorted.forEach(vertex => { if (vertex instanceof FormulaVertex) { const newCellValue = this.recomputeFormulaVertexValue(vertex); const address = vertex.getAddress(this.lazilyTransformingAstService); this.columnSearch.add(getRawValue(newCellValue), address); } else if (vertex instanceof RangeVertex) { vertex.clearCache(); } }); } recomputeFormulaVertexValue(vertex) { const address = vertex.getAddress(this.lazilyTransformingAstService); if (vertex instanceof ArrayVertex && (vertex.array.size.isRef || !this.dependencyGraph.isThereSpaceForArray(vertex))) { return vertex.setNoSpace(); } else { const formula = vertex.getFormula(this.lazilyTransformingAstService); const newCellValue = this.evaluateAstToCellValue(formula, new InterpreterState(address, this.config.useArrayArithmetic, vertex)); return vertex.setCellValue(newCellValue); } } evaluateAstToCellValue(ast, state) { const interpreterValue = this.interpreter.evaluateAst(ast, state); if (interpreterValue instanceof SimpleRangeValue) { return interpreterValue; } else if (interpreterValue === EmptyValue && this.config.evaluateNullToZero) { return 0; } else { return interpreterValue; } } }