feat(formula): dependency tree and calculation api (#6258)

This commit is contained in:
Univer
2025-12-08 12:04:56 +08:00
committed by GitHub
parent 300e60ba7e
commit e67908fc29
10 changed files with 764 additions and 36 deletions
@@ -146,6 +146,19 @@ export interface IFormulaIdMap {
c: number; c: number;
} }
export interface IFormulaStringMap {
[unitId: string]: Nullable<{ [sheetId: string]: IObjectMatrixPrimitiveType<string[]> }>;
}
export interface IFormulaExecuteResultItem {
value: Nullable<number | string | boolean | Array<Array<number | string | boolean | null>>>;
formula: string;
}
export interface IFormulaExecuteResultMap {
[unitId: string]: Nullable<{ [sheetId: string]: IObjectMatrixPrimitiveType<IFormulaExecuteResultItem[]> }>;
}
export interface IFormulaIdMapData { export interface IFormulaIdMapData {
[unitId: string]: Nullable<{ [subUnitId: string]: Nullable<{ [formulaId: string]: IFormulaIdMap }> }>; [unitId: string]: Nullable<{ [subUnitId: string]: Nullable<{ [formulaId: string]: IFormulaIdMap }> }>;
} }
@@ -16,10 +16,13 @@
import type { IExecutionOptions, IMutation, Nullable } from '@univerjs/core'; import type { IExecutionOptions, IMutation, Nullable } from '@univerjs/core';
import type { import type {
IFormulaExecuteResultMap,
IFormulaStringMap,
IRuntimeOtherUnitDataType, IRuntimeOtherUnitDataType,
IRuntimeUnitDataPrimitiveType, IRuntimeUnitDataPrimitiveType,
} from '../../basics/common'; } from '../../basics/common';
import type { IFormulaDependencyTreeFullJson, IFormulaDependencyTreeJson } from '../../engine/dependency/dependency-tree';
import type { IFormulaDirtyData } from '../../services/current-data.service'; import type { IFormulaDirtyData } from '../../services/current-data.service';
import type { FormulaExecutedStateType, IExecutionInProgressParams } from '../../services/runtime.service'; import type { FormulaExecutedStateType, IExecutionInProgressParams } from '../../services/runtime.service';
import { CommandType } from '@univerjs/core'; import { CommandType } from '@univerjs/core';
@@ -27,6 +30,34 @@ import { CommandType } from '@univerjs/core';
export interface ISetFormulaCalculationStartMutation extends IFormulaDirtyData { export interface ISetFormulaCalculationStartMutation extends IFormulaDirtyData {
options: Nullable<IExecutionOptions>; options: Nullable<IExecutionOptions>;
} }
export interface ISetFormulaStringBatchCalculationMutation {
formulas: IFormulaStringMap;
}
export interface ISetFormulaStringBatchCalculationResultMutation {
result: IFormulaExecuteResultMap;
}
export interface ISetFormulaStringBatchCalculationResultMutation {
result: IFormulaExecuteResultMap;
}
export interface ISetFormulaDependencyCalculationMutation {
unitId: string;
sheetId: string;
row: number;
column: number;
}
export interface ISetFormulaDependencyCalculationResultMutation {
result: IFormulaDependencyTreeJson[];
}
export interface ISetCellFormulaDependencyCalculationResultMutation {
result: IFormulaDependencyTreeFullJson | undefined;
}
/** /**
* TODO: @DR-Univer * TODO: @DR-Univer
* Trigger the calculation of the formula and stop the formula * Trigger the calculation of the formula and stop the formula
@@ -37,7 +68,19 @@ export const SetFormulaCalculationStartMutation: IMutation<ISetFormulaCalculatio
handler: () => true, handler: () => true,
}; };
export interface ISetFormulaCalculationStopMutation {} export const SetFormulaStringBatchCalculationMutation: IMutation<ISetFormulaStringBatchCalculationMutation> = {
id: 'formula.mutation.set-formula-string-batch-calculation',
type: CommandType.MUTATION,
handler: () => true,
};
export const SetFormulaStringBatchCalculationResultMutation: IMutation<ISetFormulaStringBatchCalculationResultMutation> = {
id: 'formula.mutation.set-formula-string-batch-calculation-result',
type: CommandType.MUTATION,
handler: () => true,
};
export interface ISetFormulaCalculationStopMutation { }
export const SetFormulaCalculationStopMutation: IMutation<ISetFormulaCalculationStopMutation> = { export const SetFormulaCalculationStopMutation: IMutation<ISetFormulaCalculationStopMutation> = {
id: 'formula.mutation.set-formula-calculation-stop', id: 'formula.mutation.set-formula-calculation-stop',
@@ -67,3 +110,27 @@ export const SetFormulaCalculationResultMutation: IMutation<ISetFormulaCalculati
type: CommandType.MUTATION, type: CommandType.MUTATION,
handler: () => true, handler: () => true,
}; };
export const SetFormulaDependencyCalculationMutation: IMutation<{}> = {
id: 'formula.mutation.set-formula-dependency-calculation',
type: CommandType.MUTATION,
handler: () => true,
};
export const SetFormulaDependencyCalculationResultMutation: IMutation<ISetFormulaDependencyCalculationResultMutation> = {
id: 'formula.mutation.set-formula-dependency-calculation-result',
type: CommandType.MUTATION,
handler: () => true,
};
export const SetCellFormulaDependencyCalculationMutation: IMutation<ISetFormulaDependencyCalculationMutation> = {
id: 'formula.mutation.set-cell-formula-dependency-calculation',
type: CommandType.MUTATION,
handler: () => true,
};
export const SetCellFormulaDependencyCalculationResultMutation: IMutation<ISetCellFormulaDependencyCalculationResultMutation> = {
id: 'formula.mutation.set-cell-formula-dependency-calculation-result',
type: CommandType.MUTATION,
handler: () => true,
};
@@ -16,17 +16,23 @@
import type { ICommandInfo } from '@univerjs/core'; import type { ICommandInfo } from '@univerjs/core';
import type { ISetArrayFormulaDataMutationParams } from '../commands/mutations/set-array-formula-data.mutation'; import type { ISetArrayFormulaDataMutationParams } from '../commands/mutations/set-array-formula-data.mutation';
import type { ISetFormulaCalculationStartMutation } from '../commands/mutations/set-formula-calculation.mutation'; import type { ISetFormulaCalculationStartMutation, ISetFormulaDependencyCalculationMutation, ISetFormulaStringBatchCalculationMutation } from '../commands/mutations/set-formula-calculation.mutation';
import type { IFormulaDirtyData } from '../services/current-data.service'; import type { IFormulaDirtyData } from '../services/current-data.service';
import type { IAllRuntimeData } from '../services/runtime.service'; import type { IAllRuntimeData } from '../services/runtime.service';
import { Disposable, ICommandService, Inject } from '@univerjs/core'; import { Disposable, ICommandService, Inject } from '@univerjs/core';
import { convertRuntimeToUnitData } from '../basics/runtime'; import { convertRuntimeToUnitData } from '../basics/runtime';
import { SetArrayFormulaDataMutation } from '../commands/mutations/set-array-formula-data.mutation'; import { SetArrayFormulaDataMutation } from '../commands/mutations/set-array-formula-data.mutation';
import { import {
SetCellFormulaDependencyCalculationMutation,
SetCellFormulaDependencyCalculationResultMutation,
SetFormulaCalculationNotificationMutation, SetFormulaCalculationNotificationMutation,
SetFormulaCalculationResultMutation, SetFormulaCalculationResultMutation,
SetFormulaCalculationStartMutation, SetFormulaCalculationStartMutation,
SetFormulaCalculationStopMutation, SetFormulaCalculationStopMutation,
SetFormulaDependencyCalculationMutation,
SetFormulaDependencyCalculationResultMutation,
SetFormulaStringBatchCalculationMutation,
SetFormulaStringBatchCalculationResultMutation,
} from '../commands/mutations/set-formula-calculation.mutation'; } from '../commands/mutations/set-formula-calculation.mutation';
import { SetImageFormulaDataMutation } from '../commands/mutations/set-image-formula-data.mutation'; import { SetImageFormulaDataMutation } from '../commands/mutations/set-image-formula-data.mutation';
import { FormulaDataModel } from '../models/formula-data.model'; import { FormulaDataModel } from '../models/formula-data.model';
@@ -67,6 +73,12 @@ export class CalculateController extends Disposable {
// TODO@Dushusir: Merge the array formula data into the formulaDataModel // TODO@Dushusir: Merge the array formula data into the formulaDataModel
this._formulaDataModel.setArrayFormulaRange(arrayFormulaRange); this._formulaDataModel.setArrayFormulaRange(arrayFormulaRange);
this._formulaDataModel.setArrayFormulaCellData(arrayFormulaCellData); this._formulaDataModel.setArrayFormulaCellData(arrayFormulaCellData);
} else if (command.id === SetFormulaStringBatchCalculationMutation.id) {
this._calculateFormulaString(command.params as ISetFormulaStringBatchCalculationMutation);
} else if (command.id === SetFormulaDependencyCalculationMutation.id) {
this._generateAllDependencyTreeJson();
} else if (command.id === SetCellFormulaDependencyCalculationMutation.id) {
this._generateCellDependencyTreeJson(command.params as ISetFormulaDependencyCalculationMutation);
} }
}) })
); );
@@ -98,6 +110,62 @@ export class CalculateController extends Disposable {
}); });
} }
private async _generateAllDependencyTreeJson() {
const result = await this._calculateFormulaService.getAllDependencyJson();
this._commandService.executeCommand(
SetFormulaDependencyCalculationResultMutation.id,
{
result,
},
{
onlyLocal: true,
}
);
}
private async _generateCellDependencyTreeJson(param: ISetFormulaDependencyCalculationMutation) {
const { unitId, sheetId, row, column } = param;
const result = await this._calculateFormulaService.getCellDependencyJson(unitId, sheetId, row, column);
this._commandService.executeCommand(
SetCellFormulaDependencyCalculationResultMutation.id,
{
result,
},
{
onlyLocal: true,
}
);
}
private async _calculateFormulaString(params: ISetFormulaStringBatchCalculationMutation) {
const formulaData = this._formulaDataModel.getFormulaData();
const arrayFormulaCellData = this._formulaDataModel.getArrayFormulaCellData();
// array formula range is used to check whether the newly added array formula conflicts with the existing array formula
const arrayFormulaRange = this._formulaDataModel.getArrayFormulaRange();
const rowData = this._formulaDataModel.getHiddenRowsFiltered();
const result = await this._calculateFormulaService.executeFormulas(
params.formulas,
formulaData,
arrayFormulaCellData,
arrayFormulaRange,
rowData
);
this._commandService.executeCommand(
SetFormulaStringBatchCalculationResultMutation.id,
{
result,
},
{
onlyLocal: true,
}
);
}
// Notification // Notification
private _initialExecuteFormulaListener() { private _initialExecuteFormulaListener() {
/** /**
@@ -25,10 +25,16 @@ import { SetArrayFormulaDataMutation } from '../commands/mutations/set-array-for
import { RemoveDefinedNameMutation, SetDefinedNameMutation } from '../commands/mutations/set-defined-name.mutation'; import { RemoveDefinedNameMutation, SetDefinedNameMutation } from '../commands/mutations/set-defined-name.mutation';
import { RemoveFeatureCalculationMutation, SetFeatureCalculationMutation } from '../commands/mutations/set-feature-calculation.mutation'; import { RemoveFeatureCalculationMutation, SetFeatureCalculationMutation } from '../commands/mutations/set-feature-calculation.mutation';
import { import {
SetCellFormulaDependencyCalculationMutation,
SetCellFormulaDependencyCalculationResultMutation,
SetFormulaCalculationNotificationMutation, SetFormulaCalculationNotificationMutation,
SetFormulaCalculationResultMutation, SetFormulaCalculationResultMutation,
SetFormulaCalculationStartMutation, SetFormulaCalculationStartMutation,
SetFormulaCalculationStopMutation, SetFormulaCalculationStopMutation,
SetFormulaDependencyCalculationMutation,
SetFormulaDependencyCalculationResultMutation,
SetFormulaStringBatchCalculationMutation,
SetFormulaStringBatchCalculationResultMutation,
} from '../commands/mutations/set-formula-calculation.mutation'; } from '../commands/mutations/set-formula-calculation.mutation';
import { SetFormulaDataMutation } from '../commands/mutations/set-formula-data.mutation'; import { SetFormulaDataMutation } from '../commands/mutations/set-formula-data.mutation';
import { SetImageFormulaDataMutation } from '../commands/mutations/set-image-formula-data.mutation'; import { SetImageFormulaDataMutation } from '../commands/mutations/set-image-formula-data.mutation';
@@ -76,10 +82,17 @@ export class FormulaController extends Disposable {
SetArrayFormulaDataMutation, SetArrayFormulaDataMutation,
SetImageFormulaDataMutation, SetImageFormulaDataMutation,
SetFormulaCalculationStartMutation, SetFormulaCalculationStartMutation,
SetFormulaStringBatchCalculationMutation,
SetFormulaStringBatchCalculationResultMutation,
SetFormulaCalculationStopMutation, SetFormulaCalculationStopMutation,
SetFormulaCalculationNotificationMutation, SetFormulaCalculationNotificationMutation,
SetFormulaCalculationResultMutation, SetFormulaCalculationResultMutation,
SetFormulaDependencyCalculationMutation,
SetFormulaDependencyCalculationResultMutation,
SetCellFormulaDependencyCalculationMutation,
SetCellFormulaDependencyCalculationResultMutation,
SetDefinedNameMutation, SetDefinedNameMutation,
RemoveDefinedNameMutation, RemoveDefinedNameMutation,
SetFeatureCalculationMutation, SetFeatureCalculationMutation,
@@ -511,3 +511,111 @@ export class FormulaDependencyTree extends FormulaDependencyTreeCalculator {
// this.parents.add(tree.treeId); // this.parents.add(tree.treeId);
// } // }
} }
interface IFormulaDependencyTreeJsonBase {
treeId: number;
formula: string;
row: number;
column: number;
unitId: string;
subUnitId: string;
refOffsetX: number;
refOffsetY: number;
rangeList: IUnitRange[];
refTreeId: number | undefined;
}
export interface IFormulaDependencyTreeJson extends IFormulaDependencyTreeJsonBase {
children: number[];
parents: number[];
}
export interface IFormulaDependencyTreeFullJson extends IFormulaDependencyTreeJsonBase {
children: IFormulaDependencyTreeJson[];
parents: IFormulaDependencyTreeJson[];
}
export class FormulaDependencyTreeModel {
children: Set<FormulaDependencyTreeModel> = new Set();
parents: Set<FormulaDependencyTreeModel> = new Set();
treeId: number = -1;
formula: string = '';
refOffsetX: number = 0;
refOffsetY: number = 0;
row: number = -1;
column: number = -1;
unitId: string = '';
subUnitId: string = '';
rangeList: IUnitRange[] = [];
refTreeId: number | undefined;
constructor(tree: IFormulaDependencyTree) {
this.treeId = tree.treeId;
this.row = tree.row;
this.column = tree.column;
this.unitId = tree.unitId;
this.subUnitId = tree.subUnitId;
this.refOffsetX = tree.refOffsetX;
this.refOffsetY = tree.refOffsetY;
this.rangeList = tree.rangeList;
if (tree.isVirtual) {
this.refTreeId = (tree as FormulaDependencyTreeVirtual).refTree?.treeId ?? -1;
} else {
this.formula = tree.formula;
}
}
toJson(): IFormulaDependencyTreeJson {
return {
children: Array.from(this.children).map((child) => child.treeId),
parents: Array.from(this.parents).map((parent) => parent.treeId),
treeId: this.treeId,
formula: this.formula,
row: this.row,
column: this.column,
unitId: this.unitId,
subUnitId: this.subUnitId,
refOffsetX: this.refOffsetX,
refOffsetY: this.refOffsetY,
rangeList: this.rangeList,
refTreeId: this.refTreeId,
};
}
toFullJson(): IFormulaDependencyTreeFullJson {
return {
children: Array.from(this.children).map((child) => child.toJson()),
parents: Array.from(this.parents).map((parent) => parent.toJson()),
treeId: this.treeId,
formula: this.formula,
row: this.row,
column: this.column,
unitId: this.unitId,
subUnitId: this.subUnitId,
refOffsetX: this.refOffsetX,
refOffsetY: this.refOffsetY,
rangeList: this.rangeList,
refTreeId: this.refTreeId,
};
}
addParent(parent: FormulaDependencyTreeModel) {
this.parents.add(parent);
}
addChild(child: FormulaDependencyTreeModel) {
this.children.add(child);
}
}
@@ -25,21 +25,22 @@ import type { BaseAstNode } from '../ast-node/base-ast-node';
import type { BaseReferenceObject, FunctionVariantType } from '../reference-object/base-reference-object'; import type { BaseReferenceObject, FunctionVariantType } from '../reference-object/base-reference-object';
import type { IExecuteAstNodeData } from '../utils/ast-node-tool'; import type { IExecuteAstNodeData } from '../utils/ast-node-tool';
import type { PreCalculateNodeType } from '../utils/node-type'; import type { PreCalculateNodeType } from '../utils/node-type';
import type { IFormulaDependencyTree } from './dependency-tree'; import type { IFormulaDependencyTree, IFormulaDependencyTreeFullJson, IFormulaDependencyTreeJson } from './dependency-tree';
import { createIdentifier, Disposable, Inject, ObjectMatrix, RTree } from '@univerjs/core'; import { createIdentifier, Disposable, Inject, ObjectMatrix, RTree } from '@univerjs/core';
import { prefixToken, suffixToken } from '../../basics/token'; import { prefixToken, suffixToken } from '../../basics/token';
import { IFormulaCurrentConfigService } from '../../services/current-data.service'; import { IFormulaCurrentConfigService } from '../../services/current-data.service';
import { IDependencyManagerService } from '../../services/dependency-manager.service'; import { IDependencyManagerService } from '../../services/dependency-manager.service';
import { IFeatureCalculationManagerService } from '../../services/feature-calculation-manager.service'; import { IFeatureCalculationManagerService } from '../../services/feature-calculation-manager.service';
import { IOtherFormulaManagerService } from '../../services/other-formula-manager.service'; import { IOtherFormulaManagerService } from '../../services/other-formula-manager.service';
import { IFormulaRuntimeService } from '../../services/runtime.service'; import { IFormulaRuntimeService } from '../../services/runtime.service';
import { Lexer } from '../analysis/lexer'; import { Lexer } from '../analysis/lexer';
import { LexerTreeBuilder } from '../analysis/lexer-tree-builder';
import { AstTreeBuilder } from '../analysis/parser'; import { AstTreeBuilder } from '../analysis/parser';
import { NodeType } from '../ast-node/node-type'; import { NodeType } from '../ast-node/node-type';
import { Interpreter } from '../interpreter/interpreter'; import { Interpreter } from '../interpreter/interpreter';
import { FORMULA_AST_CACHE, generateAstNode, includeDefinedName } from '../utils/generate-ast-node'; import { FORMULA_AST_CACHE, generateAstNode, includeDefinedName } from '../utils/generate-ast-node';
import { FormulaDependencyTree, FormulaDependencyTreeType, FormulaDependencyTreeVirtual } from './dependency-tree'; import { FormulaDependencyTree, FormulaDependencyTreeModel, FormulaDependencyTreeType, FormulaDependencyTreeVirtual } from './dependency-tree';
const FORMULA_CACHE_LRU_COUNT = 5000; const FORMULA_CACHE_LRU_COUNT = 5000;
@@ -56,6 +57,8 @@ export function generateRandomDependencyTreeId(dependencyManagerService: IDepend
export interface IFormulaDependencyGenerator { export interface IFormulaDependencyGenerator {
generate(): Promise<IFormulaDependencyTree[]>; generate(): Promise<IFormulaDependencyTree[]>;
getAllDependencyJson(): Promise<IFormulaDependencyTreeJson[]>;
getCellDependencyJson(unitId: string, sheetId: string, row: number, column: number): Promise<IFormulaDependencyTreeFullJson | undefined>;
} }
export const IFormulaDependencyGenerator = createIdentifier<IFormulaDependencyGenerator>('engine-formula.dependency-generator'); export const IFormulaDependencyGenerator = createIdentifier<IFormulaDependencyGenerator>('engine-formula.dependency-generator');
@@ -74,7 +77,8 @@ export class FormulaDependencyGenerator extends Disposable {
@Inject(Interpreter) private readonly _interpreter: Interpreter, @Inject(Interpreter) private readonly _interpreter: Interpreter,
@Inject(AstTreeBuilder) protected readonly _astTreeBuilder: AstTreeBuilder, @Inject(AstTreeBuilder) protected readonly _astTreeBuilder: AstTreeBuilder,
@Inject(Lexer) protected readonly _lexer: Lexer, @Inject(Lexer) protected readonly _lexer: Lexer,
@IDependencyManagerService protected readonly _dependencyManagerService: IDependencyManagerService @IDependencyManagerService protected readonly _dependencyManagerService: IDependencyManagerService,
@Inject(LexerTreeBuilder) protected readonly _lexerTreeBuilder: LexerTreeBuilder
) { ) {
super(); super();
} }
@@ -1298,4 +1302,140 @@ export class FormulaDependencyGenerator extends Disposable {
return formulaRunList; return formulaRunList;
} }
protected async _initializeGenerateTreeList() {
const formulaData = this._currentConfigService.getFormulaData();
const otherFormulaData = this._otherFormulaManagerService.getOtherFormulaData();
const unitData = this._currentConfigService.getUnitData();
const treeList = await this._generateTreeList(formulaData, otherFormulaData, unitData);
return treeList;
}
protected async _getAllTreeList() {
const treeList: IFormulaDependencyTree[] = await this._initializeGenerateTreeList();
const allTree: IFormulaDependencyTree[] = this._dependencyManagerService.buildDependencyTree(treeList);
return allTree;
}
protected _formulaDependencyTreeModel = new Map<number, FormulaDependencyTreeModel>();
protected _getTreeModel(treeId: number): FormulaDependencyTreeModel {
let treeModel = this._formulaDependencyTreeModel.get(treeId);
if (!treeModel) {
const tree = this._getTreeById(treeId);
if (!tree) {
throw new Error('FormulaDependencyTree is null');
}
treeModel = new FormulaDependencyTreeModel(tree);
// if (tree.isVirtual) {
// const formula = this._lexerTreeBuilder.moveFormulaRefOffset(
// tree.formula,
// tree.refOffsetX,
// tree.refOffsetY
// );
// treeModel.formula = formula;
// }
this._formulaDependencyTreeModel.set(treeId, treeModel);
}
return treeModel;
}
protected _getDependencyTreeParenIds(tree: IFormulaDependencyTree): Set<number> {
return tree.parents;
}
protected _getDependencyTreeChildrenIds(tree: IFormulaDependencyTree): Set<number> {
return tree.children;
}
protected _getFormulaDependencyTreeModel(tree: IFormulaDependencyTree): FormulaDependencyTreeModel {
const treeModel = this._getTreeModel(tree.treeId);
const parentIds = this._getDependencyTreeParenIds(tree);
for (const parentId of parentIds) {
const parentTreeModel = this._getTreeModel(parentId);
treeModel.addParent(parentTreeModel);
parentTreeModel.addChild(treeModel);
}
return treeModel;
}
protected _endFormulaDependencyTreeModel() {
this._formulaDependencyTreeModel.clear();
}
protected _startFormulaDependencyTreeModel() {
}
async getAllDependencyJson(): Promise<IFormulaDependencyTreeJson[]> {
const treeList = await this._getAllTreeList();
this._startFormulaDependencyTreeModel();
const results: FormulaDependencyTreeModel[] = [];
for (const tree of treeList) {
const treeModel = this._getFormulaDependencyTreeModel(tree);
results[tree.treeId] = treeModel;
}
const resultsJson: IFormulaDependencyTreeJson[] = [];
for (const result of results) {
if (result) {
resultsJson.push(result.toJson());
}
}
this._endFormulaDependencyTreeModel();
return resultsJson;
}
async getCellDependencyJson(unitId: string, sheetId: string, row: number, column: number): Promise<IFormulaDependencyTreeFullJson | undefined> {
await this._initializeGenerateTreeList();
const treeId = this._dependencyManagerService.getFormulaDependency(unitId, sheetId, row, column);
if (treeId == null) {
return;
}
const tree = this._getTreeById(treeId);
if (!tree) {
return;
}
this._startFormulaDependencyTreeModel();
const treeModel = this._getFormulaDependencyTreeModel(tree);
const formula = this._lexerTreeBuilder.moveFormulaRefOffset(
tree.formula,
tree.refOffsetX,
tree.refOffsetY
);
treeModel.formula = formula;
const childIds = this._getDependencyTreeChildrenIds(tree);
for (const childId of childIds) {
const childTreeModel = this._getTreeModel(childId);
const tree = this._getTreeById(treeId);
if (!tree) {
continue;
}
const formula = this._lexerTreeBuilder.moveFormulaRefOffset(
tree.formula,
tree.refOffsetX,
tree.refOffsetY
);
childTreeModel.formula = formula;
treeModel.addChild(childTreeModel);
}
this._endFormulaDependencyTreeModel();
return treeModel.toFullJson();
}
} }
+171 -2
View File
@@ -15,10 +15,10 @@
*/ */
import type { ICommandInfo, IDisposable } from '@univerjs/core'; import type { ICommandInfo, IDisposable } from '@univerjs/core';
import type { FormulaExecutedStateType, IExecutionInProgressParams, ISequenceNode, ISetFormulaCalculationNotificationMutation, ISetFormulaCalculationStartMutation } from '@univerjs/engine-formula'; import type { FormulaExecutedStateType, IExecutionInProgressParams, IFormulaDependencyTreeFullJson, IFormulaDependencyTreeJson, IFormulaExecuteResultMap, IFormulaStringMap, ISequenceNode, ISetCellFormulaDependencyCalculationResultMutation, ISetFormulaCalculationNotificationMutation, ISetFormulaCalculationStartMutation, ISetFormulaDependencyCalculationResultMutation, ISetFormulaStringBatchCalculationResultMutation } from '@univerjs/engine-formula';
import { ICommandService, IConfigService, Inject, Injector } from '@univerjs/core'; import { ICommandService, IConfigService, Inject, Injector } from '@univerjs/core';
import { FBase } from '@univerjs/core/facade'; import { FBase } from '@univerjs/core/facade';
import { ENGINE_FORMULA_CYCLE_REFERENCE_COUNT, GlobalComputingStatusService, LexerTreeBuilder, SetFormulaCalculationNotificationMutation, SetFormulaCalculationStartMutation, SetFormulaCalculationStopMutation } from '@univerjs/engine-formula'; import { ENGINE_FORMULA_CYCLE_REFERENCE_COUNT, GlobalComputingStatusService, LexerTreeBuilder, SetCellFormulaDependencyCalculationMutation, SetCellFormulaDependencyCalculationResultMutation, SetFormulaCalculationNotificationMutation, SetFormulaCalculationStartMutation, SetFormulaCalculationStopMutation, SetFormulaDependencyCalculationMutation, SetFormulaDependencyCalculationResultMutation, SetFormulaStringBatchCalculationMutation, SetFormulaStringBatchCalculationResultMutation } from '@univerjs/engine-formula';
import { filter, firstValueFrom, map, race, timer } from 'rxjs'; import { filter, firstValueFrom, map, race, timer } from 'rxjs';
/** /**
@@ -248,4 +248,173 @@ export class FFormula extends FBase {
setMaxIteration(maxIteration: number): void { setMaxIteration(maxIteration: number): void {
this._configService.setConfig(ENGINE_FORMULA_CYCLE_REFERENCE_COUNT, maxIteration); this._configService.setConfig(ENGINE_FORMULA_CYCLE_REFERENCE_COUNT, maxIteration);
} }
/**
* Execute a batch of formulas asynchronously and receive computed results.
*
* Each formula cell is represented as a string array:
* [fullFormula, ...subFormulas]
*
* Where:
* - fullFormula (index 0) is the complete formula expression written in the cell.
* Example: "=SUM(A1:A10) + SQRT(D7)".
*
* - subFormulas (index 1+) are **optional decomposed expressions** extracted from
* the full formula. Each of them can be independently computed by the formula engine.
*
* These sub-expressions can include:
* - Single-cell references: "A2", "B2", "C5"
* - Range references: "A1:A10"
* - Function calls: "SQRT(D7)", "ABS(A2-B2)"
* - Any sub-formula that was parsed out of the original formula and can be
* evaluated on its own.
*
* The batch execution engine may use these sub-formulas for dependency resolution,
* incremental computation, or performance optimizations.
*
* @param {IFormulaStringMap} formulas
* Nested structure (unit → sheet → row → column) describing formulas and
* their decomposed sub-expressions.
*
* @param {(result: IFormulaExecuteResultMap) => void} callback
* Receives the computed value map mirroring the input structure.
*
* @returns {IDisposable}
* A disposer to stop listening for batch results.
*
* @example
* ```ts
* const formulaEngine = univerAPI.getFormula();
* const formulas = {
* Book1: {
* Sheet1: {
* 2: {
* 3: [
* // Full formula:
* "=SUM(A1:A10) + SQRT(D7)",
*
* // Decomposed sub-formulas (each one can be evaluated independently):
* "SUM(A1:A10)", // sub-formula 1
* "SQRT(D7)", // sub-formula 2
* "A1:A10", // range reference
* "D7", // single-cell reference
* ],
* },
* 4: {
* 5: [
* "=A2 + B2 + SQRT(C5)",
* "A2",
* "B2",
* "SQRT(C5)",
* ],
* }
* },
* },
* };
*
* const disposer = formulaEngine.executeFormulas(formulas, (result) => {
* console.log(result);
* });
*
* ```
*/
executeFormulas(formulas: IFormulaStringMap, callback: (result: IFormulaExecuteResultMap) => void): void {
this._commandService.executeCommand(SetFormulaStringBatchCalculationMutation.id, { formulas }, { onlyLocal: true });
const disposable = this._commandService.onCommandExecuted((command: ICommandInfo) => {
if (command.id !== SetFormulaStringBatchCalculationResultMutation.id) {
return;
}
const params = command.params as ISetFormulaStringBatchCalculationResultMutation;
if (params.result != null) {
callback(params.result);
}
disposable.dispose();
});
}
/**
* Retrieve all formula dependency trees that were produced during the latest
* dependency-analysis run. This triggers a local dependency-calculation command
* and returns the complete set of dependency trees once the calculation finishes.
*
* @param callback A function invoked with the resulting array of dependency trees.
*
* @returns {IDisposable} An object that disposes the internal event listener.
*
* @example
* ```ts
* const formulaEngine = univerAPI.getFormula();
*
* // Fetch all dependency trees generated for the current workbook.
* const disposable = formulaEngine.getAllDependencyTrees((trees) => {
* console.log('All dependency trees:', trees);
* });
*
* ```
*/
getAllDependencyTrees(callback: (result: IFormulaDependencyTreeJson[]) => void): void {
this._commandService.executeCommand(SetFormulaDependencyCalculationMutation.id, undefined, { onlyLocal: true });
const disposable = this._commandService.onCommandExecuted((command: ICommandInfo) => {
if (command.id !== SetFormulaDependencyCalculationResultMutation.id) {
return;
}
const params = command.params as ISetFormulaDependencyCalculationResultMutation;
if (params.result != null) {
callback(params.result);
}
disposable.dispose();
});
}
/**
* Retrieve the dependency tree of a specific cell. This triggers a local
* dependency-calculation command for the given unit, sheet, and cell location,
* and returns the computed dependency tree when the calculation is completed.
*
* @param param The target cell location:
* - `unitId` The workbook ID.
* - `sheetId` The sheet ID.
* - `row` The zero-based row index.
* - `column` The zero-based column index.
*
* @param callback A function invoked with the resulting dependency tree or
* `undefined` if no dependency tree exists for that cell.
*
* @returns {IDisposable} An object that disposes the internal event listener.
*
* @example
* ```ts
* const formulaEngine = univerAPI.getFormula();
*
* // Query the dependency tree for cell B2 in a specific sheet.
* const disposable = formulaEngine.getCellDependencyTree(
* { unitId: 'workbook1', sheetId: 'sheet1', row: 1, column: 1 },
* (tree) => {
* console.log('Cell dependency tree:', tree);
* }
* );
*
* ```
*/
getCellDependencyTree(param: { unitId: string; sheetId: string; row: number; column: number }, callback: (result: IFormulaDependencyTreeFullJson | undefined) => void): void {
this._commandService.executeCommand(SetCellFormulaDependencyCalculationMutation.id, param, { onlyLocal: true });
const disposable = this._commandService.onCommandExecuted((command: ICommandInfo) => {
if (command.id !== SetCellFormulaDependencyCalculationResultMutation.id) {
return;
}
const params = command.params as ISetCellFormulaDependencyCalculationResultMutation;
if (params.result !== undefined) {
callback(params.result);
}
disposable.dispose();
});
}
} }
+13 -1
View File
@@ -26,6 +26,8 @@ export type {
IFormulaData, IFormulaData,
IFormulaDataItem, IFormulaDataItem,
IFormulaDatasetConfig, IFormulaDatasetConfig,
IFormulaExecuteResultMap,
IFormulaStringMap,
IRuntimeImageFormulaDataType, IRuntimeImageFormulaDataType,
IRuntimeUnitDataType, IRuntimeUnitDataType,
ISheetData, ISheetData,
@@ -53,13 +55,23 @@ export { type ISetDefinedNameMutationParam, type ISetDefinedNameMutationSearchPa
export { SetDefinedNameMutationFactory } from './commands/mutations/set-defined-name.mutation'; export { SetDefinedNameMutationFactory } from './commands/mutations/set-defined-name.mutation';
export { RemoveFeatureCalculationMutation, SetFeatureCalculationMutation } from './commands/mutations/set-feature-calculation.mutation'; export { RemoveFeatureCalculationMutation, SetFeatureCalculationMutation } from './commands/mutations/set-feature-calculation.mutation';
export { export {
type ISetCellFormulaDependencyCalculationResultMutation,
type ISetFormulaCalculationNotificationMutation, type ISetFormulaCalculationNotificationMutation,
type ISetFormulaCalculationResultMutation, type ISetFormulaCalculationResultMutation,
type ISetFormulaCalculationStartMutation, type ISetFormulaCalculationStartMutation,
type ISetFormulaDependencyCalculationMutation,
type ISetFormulaDependencyCalculationResultMutation,
type ISetFormulaStringBatchCalculationResultMutation,
SetCellFormulaDependencyCalculationMutation,
SetCellFormulaDependencyCalculationResultMutation,
SetFormulaCalculationNotificationMutation, SetFormulaCalculationNotificationMutation,
SetFormulaCalculationResultMutation, SetFormulaCalculationResultMutation,
SetFormulaCalculationStartMutation, SetFormulaCalculationStartMutation,
SetFormulaCalculationStopMutation, SetFormulaCalculationStopMutation,
SetFormulaDependencyCalculationMutation,
SetFormulaDependencyCalculationResultMutation,
SetFormulaStringBatchCalculationMutation,
SetFormulaStringBatchCalculationResultMutation,
} from './commands/mutations/set-formula-calculation.mutation'; } from './commands/mutations/set-formula-calculation.mutation';
export { type ISetFormulaDataMutationParams, SetFormulaDataMutation } from './commands/mutations/set-formula-data.mutation'; export { type ISetFormulaDataMutationParams, SetFormulaDataMutation } from './commands/mutations/set-formula-data.mutation';
export { type ISetImageFormulaDataMutationParams, SetImageFormulaDataMutation } from './commands/mutations/set-image-formula-data.mutation'; export { type ISetImageFormulaDataMutationParams, SetImageFormulaDataMutation } from './commands/mutations/set-image-formula-data.mutation';
@@ -83,7 +95,7 @@ export { ReferenceNodeFactory } from './engine/ast-node/reference-node';
export { SuffixNodeFactory } from './engine/ast-node/suffix-node'; export { SuffixNodeFactory } from './engine/ast-node/suffix-node';
export { UnionNodeFactory } from './engine/ast-node/union-node'; export { UnionNodeFactory } from './engine/ast-node/union-node';
export { ValueNodeFactory } from './engine/ast-node/value-node'; export { ValueNodeFactory } from './engine/ast-node/value-node';
export { FormulaDependencyTree, type IFormulaDependencyTree } from './engine/dependency/dependency-tree'; export { FormulaDependencyTree, FormulaDependencyTreeModel, type IFormulaDependencyTree, type IFormulaDependencyTreeFullJson, type IFormulaDependencyTreeJson } from './engine/dependency/dependency-tree';
export { FormulaDependencyTreeType } from './engine/dependency/dependency-tree'; export { FormulaDependencyTreeType } from './engine/dependency/dependency-tree';
export { FormulaDependencyTreeVirtual } from './engine/dependency/dependency-tree'; export { FormulaDependencyTreeVirtual } from './engine/dependency/dependency-tree';
export { FormulaDependencyGenerator, IFormulaDependencyGenerator } from './engine/dependency/formula-dependency'; export { FormulaDependencyGenerator, IFormulaDependencyGenerator } from './engine/dependency/formula-dependency';
@@ -18,15 +18,24 @@ import type { IUnitRange } from '@univerjs/core';
import type { Observable } from 'rxjs'; import type { Observable } from 'rxjs';
import type { import type {
IArrayFormulaRangeType, IArrayFormulaRangeType,
IArrayFormulaUnitCellType,
IFeatureDirtyRangeType, IFeatureDirtyRangeType,
IFormulaData,
IFormulaDatasetConfig, IFormulaDatasetConfig,
IFormulaExecuteResultItem,
IFormulaExecuteResultMap,
IFormulaStringMap,
IRuntimeUnitDataType, IRuntimeUnitDataType,
IUnitExcludedCell, IUnitExcludedCell,
IUnitRowData,
} from '../basics/common'; } from '../basics/common';
import type { IUniverEngineFormulaConfig } from '../controller/config.schema'; import type { IUniverEngineFormulaConfig } from '../controller/config.schema';
import type { LexerNode } from '../engine/analysis/lexer-node'; import type { LexerNode } from '../engine/analysis/lexer-node';
import type { FunctionVariantType } from '../engine/reference-object/base-reference-object'; import type { IFormulaDependencyTreeFullJson, IFormulaDependencyTreeJson } from '../engine/dependency/dependency-tree';
import type { BaseReferenceObject, FunctionVariantType } from '../engine/reference-object/base-reference-object';
import type { ArrayValueObject } from '../engine/value-object/array-value-object';
import type { BaseValueObject } from '../engine/value-object/base-value-object';
import type { IAllRuntimeData, IExecutionInProgressParams } from './runtime.service'; import type { IAllRuntimeData, IExecutionInProgressParams } from './runtime.service';
import { import {
AsyncLock, AsyncLock,
@@ -43,7 +52,6 @@ import { CELL_INVERTED_INDEX_CACHE } from '../basics/inverted-index-cache';
import { DEFAULT_CYCLE_REFERENCE_COUNT, ENGINE_FORMULA_PLUGIN_CONFIG_KEY } from '../controller/config.schema'; import { DEFAULT_CYCLE_REFERENCE_COUNT, ENGINE_FORMULA_PLUGIN_CONFIG_KEY } from '../controller/config.schema';
import { Lexer } from '../engine/analysis/lexer'; import { Lexer } from '../engine/analysis/lexer';
import { AstTreeBuilder } from '../engine/analysis/parser'; import { AstTreeBuilder } from '../engine/analysis/parser';
import { ErrorNode } from '../engine/ast-node/base-ast-node';
import { IFormulaDependencyGenerator } from '../engine/dependency/formula-dependency'; import { IFormulaDependencyGenerator } from '../engine/dependency/formula-dependency';
import { Interpreter } from '../engine/interpreter/interpreter'; import { Interpreter } from '../engine/interpreter/interpreter';
import { FORMULA_REF_TO_ARRAY_CACHE } from '../engine/reference-object/base-reference-object'; import { FORMULA_REF_TO_ARRAY_CACHE } from '../engine/reference-object/base-reference-object';
@@ -66,6 +74,9 @@ export interface ICalculateFormulaService {
execute(formulaDatasetConfig: IFormulaDatasetConfig): Promise<void>; execute(formulaDatasetConfig: IFormulaDatasetConfig): Promise<void>;
stopFormulaExecution(): void; stopFormulaExecution(): void;
calculate(formulaString: string, transformSuffix?: boolean): void; calculate(formulaString: string, transformSuffix?: boolean): void;
executeFormulas(formulas: IFormulaStringMap, formulaData: IFormulaData, arrayFormulaCellData: IArrayFormulaUnitCellType, arrayFormulaRange: IArrayFormulaRangeType, rowData: IUnitRowData): Promise<IFormulaExecuteResultMap>;
getAllDependencyJson(): Promise<IFormulaDependencyTreeJson[]>;
getCellDependencyJson(unitId: string, sheetId: string, row: number, column: number): Promise<IFormulaDependencyTreeFullJson | undefined>;
} }
export const ICalculateFormulaService = createIdentifier<ICalculateFormulaService>('engine-formula.calculate-formula.service'); export const ICalculateFormulaService = createIdentifier<ICalculateFormulaService>('engine-formula.calculate-formula.service');
@@ -367,42 +378,147 @@ export class CalculateFormulaService extends Disposable implements ICalculateFor
return this._runtimeService.getAllRuntimeData(); return this._runtimeService.getAllRuntimeData();
} }
calculate(formulaString: string, transformSuffix: boolean = true) { async executeFormulas(formulas: IFormulaStringMap, formulaData: IFormulaData, arrayFormulaCellData: IArrayFormulaUnitCellType, arrayFormulaRange: IArrayFormulaRangeType, rowData?: IUnitRowData) {
// TODO how to observe @alex this._currentConfigService.loadDataLite(
// this.getObserver('onBeforeFormulaCalculateObservable')?.notifyObservers(formulaString); formulaData,
const lexerNode = this._lexer.treeBuilder(formulaString, transformSuffix); arrayFormulaCellData,
arrayFormulaRange,
rowData
);
if (Object.values(ErrorType).includes(lexerNode as ErrorType)) { const unitData = this._currentConfigService.getUnitData();
return ErrorNode.create(lexerNode as ErrorType);
this._runtimeService.reset();
const result: IFormulaExecuteResultMap = {};
for (const unitId of Object.keys(formulas)) {
const sheetFormulas = formulas[unitId];
if (sheetFormulas == null) continue;
result[unitId] = {};
for (const sheetId of Object.keys(sheetFormulas)) {
const rowFormulas = sheetFormulas[sheetId];
if (rowFormulas == null) continue;
const sheetItem = unitData[unitId]?.[sheetId];
if (sheetItem == null) continue;
result[unitId]![sheetId] = {};
for (const rowString of Object.keys(rowFormulas)) {
const row = Number.parseInt(rowString);
result[unitId]![sheetId]![row] = {};
const cellData = rowFormulas[row];
if (!cellData) continue;
for (const columnString of Object.keys(cellData)) {
const column = Number.parseInt(columnString);
const formulaStrings = cellData[column];
if (!formulaStrings) continue;
const rowCount = sheetItem.rowCount || 0;
const columnCount = sheetItem.columnCount || 0;
this._runtimeService.setCurrent(
row,
column,
rowCount,
columnCount,
sheetId,
unitId
);
const item: IFormulaExecuteResultItem[] = [];
for (const formulaString of formulaStrings) {
let resultCell = await this.calculate(formulaString);
if (!resultCell) {
item.push({
value: null,
formula: formulaString,
});
continue;
}
if (resultCell.isReferenceObject()) {
resultCell = (resultCell as BaseReferenceObject).toArrayValueObject();
}
const resultRefObject = resultCell as BaseValueObject;
if (resultRefObject.isArray()) {
const resultRefArrayObject = resultRefObject as ArrayValueObject;
if (resultRefArrayObject.getRowCount() === 1 && resultRefArrayObject.getColumnCount() === 1) {
item.push({
value: resultRefArrayObject.getFirstCell().getValue(),
formula: formulaString,
});
continue;
}
item.push({
value: resultRefArrayObject.toValue(),
formula: formulaString,
});
continue;
}
item.push({
value: resultRefObject.getValue(),
formula: formulaString,
});
}
result[unitId]![sheetId]![row]![column] = item;
}
}
}
} }
// this.lexerTreeBuilder.suffixExpressionHandler(lexerNode); // suffix Express, 1+(3*4=4)*5+1 convert to 134*4=5*1++ return result;
}
// console.log('sequence', this.lexerTreeBuilder.sequenceNodesBuilder(formulaString)); async calculate(formulaString: string) {
// TODO how to observe @alex
// this.getObserver('onBeforeFormulaCalculateObservable')?.notifyObservers(formulaString);
const lexerNode = this._lexer.treeBuilder(formulaString);
// this.getObserver('onAfterFormulaLexerObservable')?.notifyObservers(lexerNode); if (Object.values(ErrorType).includes(lexerNode as ErrorType)) {
return;
// const astTreeBuilder = new AstTreeBuilder(); }
const astNode = this._astTreeBuilder.parse(lexerNode as LexerNode); const astNode = this._astTreeBuilder.parse(lexerNode as LexerNode);
// console.log('astNode', astNode?.serialize()); const interpreter = this._interpreter;
astNode?.serialize();
// const interpreter = Interpreter.create(); if (astNode == null) {
return;
}
// if (astNode == null) { const nodeData = {
// return; node: astNode,
// } refOffsetX: 0,
refOffsetY: 0,
};
// if (interpreter.checkAsyncNode(astNode)) { if (interpreter.checkAsyncNode(astNode)) {
// const resultPromise = interpreter.executeAsync(astNode); return await interpreter.executeAsync(nodeData);
} else {
return interpreter.execute(nodeData);
}
}
// resultPromise.then((value) => { async getAllDependencyJson(): Promise<IFormulaDependencyTreeJson[]> {
// console.log('formulaResult', value); return this._formulaDependencyGenerator.getAllDependencyJson();
// }); }
// } else {
// console.log(interpreter.execute(astNode)); async getCellDependencyJson(unitId: string, sheetId: string, row: number, column: number): Promise<IFormulaDependencyTreeFullJson | undefined> {
// } return this._formulaDependencyGenerator.getCellDependencyJson(unitId, sheetId, row, column);
} }
} }
@@ -17,6 +17,7 @@
import type { IUnitRange, LocaleType, Nullable, Workbook } from '@univerjs/core'; import type { IUnitRange, LocaleType, Nullable, Workbook } from '@univerjs/core';
import type { import type {
IArrayFormulaRangeType, IArrayFormulaRangeType,
IArrayFormulaUnitCellType,
IDirtyUnitFeatureMap, IDirtyUnitFeatureMap,
IDirtyUnitOtherFormulaMap, IDirtyUnitOtherFormulaMap,
IDirtyUnitSheetDefinedNameMap, IDirtyUnitSheetDefinedNameMap,
@@ -113,6 +114,8 @@ export interface IFormulaCurrentConfigService {
getFilteredOutRows(unitId: string, sheetId: string, startRow: number, endRow: number): number[]; getFilteredOutRows(unitId: string, sheetId: string, startRow: number, endRow: number): number[];
setSheetNameMap(sheetIdToNameMap: IUnitSheetIdToNameMap): void; setSheetNameMap(sheetIdToNameMap: IUnitSheetIdToNameMap): void;
loadDataLite(formulaData: IFormulaData, arrayFormulaCellData: IArrayFormulaUnitCellType, arrayFormulaRange: IArrayFormulaRangeType, rowData?: IUnitRowData): void;
} }
export class FormulaCurrentConfigService extends Disposable implements IFormulaCurrentConfigService { export class FormulaCurrentConfigService extends Disposable implements IFormulaCurrentConfigService {
@@ -345,6 +348,25 @@ export class FormulaCurrentConfigService extends Disposable implements IFormulaC
this._mergeNameMap(this._sheetNameMap, this._dirtyNameMap); this._mergeNameMap(this._sheetNameMap, this._dirtyNameMap);
} }
loadDataLite(formulaData: IFormulaData, arrayFormulaCellData: IArrayFormulaUnitCellType, arrayFormulaRange: IArrayFormulaRangeType, rowData?: IUnitRowData) {
const { allUnitData, unitSheetNameMap, unitStylesData } = this._loadSheetData();
this._unitData = allUnitData;
this._unitStylesData = unitStylesData;
this._sheetNameMap = unitSheetNameMap;
this._formulaData = formulaData;
this._arrayFormulaCellData = convertUnitDataToRuntime(arrayFormulaCellData);
this._arrayFormulaRange = arrayFormulaRange;
// apply row data, including rows hidden by filters
rowData && this._applyUnitRowData(rowData);
}
getDirtyData(): IFormulaDirtyData { getDirtyData(): IFormulaDirtyData {
return { return {
forceCalculation: this._forceCalculate, forceCalculation: this._forceCalculate,