snapshot of https://github.com/dream-num/univer.git (dev, 2026-09-16; blobs > 131072B omitted)
Some checks failed
📦 Build / build (push) Has been cancelled
🧪 CI Test / test shard (1/4) (push) Has been cancelled
🧪 CI Test / test shard (2/4) (push) Has been cancelled
🧪 CI Test / test shard (3/4) (push) Has been cancelled
🧪 CI Test / test shard (4/4) (push) Has been cancelled
🧪 CI Test / test (push) Has been cancelled
🧪 CI Test / eslint (push) Has been cancelled
🧪 CI Test / typecheck (push) Has been cancelled
🔫 Sync to Pro / dispatch-sync (push) Has been cancelled
🔮 Sync Mirror / sync (push) Has been cancelled

This commit is contained in:
upstream-mirror
2026-09-16 05:09:45 +05:00
commit bcf6ef63ad
7118 changed files with 1236854 additions and 0 deletions

View File

@@ -0,0 +1,248 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { IWorkbookData, Workbook } from '@univerjs/core';
import type { LambdaValueObjectObject } from '../../value-object/lambda-value-object';
import { DateSystem, LocaleType, UniverInstanceType } from '@univerjs/core';
import { describe, expect, it } from 'vitest';
import { ErrorType } from '../../../basics/error-type';
import { createFunctionTestBed } from '../../../functions/__tests__/create-function-test-bed';
import { AsyncCustomFunction, CustomFunction } from '../../../functions/custom-function';
import { DateFunction } from '../../../functions/date/date';
import { FUNCTION_NAMES_DATE } from '../../../functions/date/function-names';
import { Accrint } from '../../../functions/financial/accrint';
import { FUNCTION_NAMES_FINANCIAL } from '../../../functions/financial/function-names';
import { Abs } from '../../../functions/math/abs';
import { FUNCTION_NAMES_MATH } from '../../../functions/math/function-names';
import { FUNCTION_NAMES_META } from '../../../functions/meta/function-names';
import { Plus } from '../../../functions/meta/plus';
import { FUNCTION_NAMES_TEXT } from '../../../functions/text/function-names';
import { Substitute } from '../../../functions/text/substitute';
import { getObjectValue } from '../../../functions/util';
import { IFormulaCurrentConfigService } from '../../../services/current-data.service';
import { IFunctionService } from '../../../services/function.service';
import { IFormulaRuntimeService } from '../../../services/runtime.service';
import { Lexer } from '../../analysis/lexer';
import { LexerNode } from '../../analysis/lexer-node';
import { AstTreeBuilder } from '../../analysis/parser';
import { Interpreter } from '../../interpreter/interpreter';
import { generateExecuteAstNodeData } from '../../utils/ast-node-tool';
import { BaseAstNode } from '../base-ast-node';
function createWorkbookData(): IWorkbookData {
return {
id: 'test',
appVersion: '3.0.0-alpha',
locale: LocaleType.FR_FR,
name: 'Locale-independent date parsing',
sheetOrder: ['sheet1'],
sheets: {
sheet1: {
id: 'sheet1',
name: 'Sheet1',
rowCount: 1,
columnCount: 1,
},
},
styles: {},
};
}
function createDateSystemWorkbookData(id: string, dateSystem: DateSystem): IWorkbookData {
return {
...createWorkbookData(),
id,
locale: LocaleType.EN_US,
name: id,
dateSystem,
};
}
describe('FunctionNode workbook context', () => {
it('does not use the executing workbook locale to parse formula text', () => {
const testBed = createFunctionTestBed(createWorkbookData());
const currentConfigService = testBed.get(IFormulaCurrentConfigService);
const runtimeService = testBed.get(IFormulaRuntimeService);
const lexer = testBed.get(Lexer);
const astTreeBuilder = testBed.get(AstTreeBuilder);
const interpreter = testBed.get(Interpreter);
currentConfigService.load({
formulaData: {},
arrayFormulaCellData: {},
arrayFormulaRange: {},
forceCalculate: false,
dirtyRanges: [],
dirtyNameMap: {},
dirtyDefinedNameMap: {},
dirtyUnitFeatureMap: {},
dirtyUnitOtherFormulaMap: {},
excludedCell: {},
allUnitData: { [testBed.unitId]: testBed.sheetData },
});
runtimeService.setCurrent(0, 0, 1, 1, testBed.sheetId, testBed.unitId);
testBed.get(IFunctionService).registerExecutors(new Accrint(FUNCTION_NAMES_FINANCIAL.ACCRINT));
testBed.get(IFunctionService).registerExecutors(new Plus(FUNCTION_NAMES_META.PLUS));
const calculate = (formula: string) => {
const lexerNode = lexer.treeBuilder(formula);
if (!(lexerNode instanceof LexerNode)) throw new TypeError(`Failed to parse formula: ${formula}`);
const astNode = astTreeBuilder.parse(lexerNode);
if (!(astNode instanceof BaseAstNode)) throw new TypeError(`Failed to build AST: ${formula}`);
return getObjectValue(interpreter.execute(generateExecuteAstNodeData(astNode)));
};
expect(calculate('=ACCRINT("31/01/2024","31/07/2024","01/05/2024",0.1,1000,2)'))
.toBe(ErrorType.VALUE);
expect(calculate('=1+"1,5"')).toBe(ErrorType.VALUE);
});
it('uses the executing workbook date system for implicit string coercion', async () => {
const testBed = createFunctionTestBed(createDateSystemWorkbookData('date-1900', DateSystem.Date1900));
const date1904Workbook = testBed.univer.createUnit<IWorkbookData, Workbook>(
UniverInstanceType.UNIVER_SHEET,
createDateSystemWorkbookData('date-1904', DateSystem.Date1904)
);
const currentConfigService = testBed.get(IFormulaCurrentConfigService);
const runtimeService = testBed.get(IFormulaRuntimeService);
const lexer = testBed.get(Lexer);
const astTreeBuilder = testBed.get(AstTreeBuilder);
const interpreter = testBed.get(Interpreter);
currentConfigService.load({
formulaData: {},
arrayFormulaCellData: {},
arrayFormulaRange: {},
forceCalculate: false,
dirtyRanges: [],
dirtyNameMap: {},
dirtyDefinedNameMap: {},
dirtyUnitFeatureMap: {},
dirtyUnitOtherFormulaMap: {},
excludedCell: {},
allUnitData: { [testBed.unitId]: testBed.sheetData },
});
testBed.get(IFunctionService).registerExecutors(new Plus(FUNCTION_NAMES_META.PLUS));
testBed.get(IFunctionService).registerExecutors(new Abs(FUNCTION_NAMES_MATH.ABS));
testBed.get(IFunctionService).registerExecutors(new Substitute(FUNCTION_NAMES_TEXT.SUBSTITUTE));
testBed.get(IFunctionService).registerExecutors(new DateFunction(FUNCTION_NAMES_DATE.DATE));
const customDate = new CustomFunction('CUSTOMDATE');
customDate.calculateCustom = () => '1904-1-1';
const customDateArray = new CustomFunction('CUSTOMDATEARRAY');
customDateArray.calculateCustom = () => [['1904-1-1']];
const asyncCustomDate = new AsyncCustomFunction('ASYNCCUSTOMDATE');
const date1904Result = createDeferred<string>();
const date1900Result = createDeferred<string>();
const date1904Started = createDeferred<void>();
const date1900Started = createDeferred<void>();
asyncCustomDate.calculateCustom = (dateSystem) => {
if (dateSystem === '1904') {
date1904Started.resolve();
return date1904Result.promise;
}
date1900Started.resolve();
return date1900Result.promise;
};
testBed.get(IFunctionService).registerExecutors(customDate, customDateArray, asyncCustomDate);
const calculate = (unitId: string, sheetId: string, formula = '="1904-1-1"+0') => {
runtimeService.setCurrent(0, 0, 1, 1, sheetId, unitId);
const lexerNode = lexer.treeBuilder(formula);
if (!(lexerNode instanceof LexerNode)) throw new TypeError('Failed to parse implicit date coercion formula');
const astNode = astTreeBuilder.parse(lexerNode);
if (!(astNode instanceof BaseAstNode)) throw new TypeError('Failed to build implicit date coercion AST');
return getObjectValue(interpreter.execute(generateExecuteAstNodeData(astNode)));
};
const calculateAsync = async (unitId: string, sheetId: string, formula: string) => {
runtimeService.setCurrent(0, 0, 1, 1, sheetId, unitId);
const lexerNode = lexer.treeBuilder(formula);
if (!(lexerNode instanceof LexerNode)) throw new TypeError('Failed to parse async custom function formula');
const astNode = astTreeBuilder.parse(lexerNode);
if (!(astNode instanceof BaseAstNode)) throw new TypeError('Failed to build async custom function AST');
return getObjectValue(await interpreter.executeAsync(generateExecuteAstNodeData(astNode)));
};
expect(calculate(testBed.unitId, testBed.sheetId)).toBe(1462);
expect(calculate(date1904Workbook.getUnitId(), date1904Workbook.getActiveSheet()!.getSheetId())).toBe(0);
expect(calculate(testBed.unitId, testBed.sheetId, '=DATE(1904,1,1)')).toBe(1462);
expect(calculate(
date1904Workbook.getUnitId(),
date1904Workbook.getActiveSheet()!.getSheetId(),
'=DATE(1904,1,1)'
)).toBe(0);
expect(calculate(
date1904Workbook.getUnitId(),
date1904Workbook.getActiveSheet()!.getSheetId(),
'=ABS("1904-1-1")'
)).toBe(0);
expect(calculate(
date1904Workbook.getUnitId(),
date1904Workbook.getActiveSheet()!.getSheetId(),
'=ABS(SUBSTITUTE("1904/1/1","/","-"))'
)).toBe(0);
expect(calculate(testBed.unitId, testBed.sheetId, '=ABS(SUBSTITUTE("1904/1/1","/","-"))')).toBe(1462);
expect(calculate(
date1904Workbook.getUnitId(),
date1904Workbook.getActiveSheet()!.getSheetId(),
'=ABS(CUSTOMDATE())'
)).toBe(0);
expect(calculate(
date1904Workbook.getUnitId(),
date1904Workbook.getActiveSheet()!.getSheetId(),
'=ABS(CUSTOMDATEARRAY())'
)).toStrictEqual([[0]]);
runtimeService.setCurrent(
0,
0,
1,
1,
date1904Workbook.getActiveSheet()!.getSheetId(),
date1904Workbook.getUnitId()
);
const lambdaLexerNode = lexer.treeBuilder('=LAMBDA(value,ABS(value))');
if (!(lambdaLexerNode instanceof LexerNode)) throw new TypeError('Failed to parse lambda formula');
const lambdaAstNode = astTreeBuilder.parse(lambdaLexerNode);
if (!(lambdaAstNode instanceof BaseAstNode)) throw new TypeError('Failed to build lambda AST');
const lambda = interpreter.execute(generateExecuteAstNodeData(lambdaAstNode)) as LambdaValueObjectObject;
expect(lambda.executeCustom('1904-1-1').getValue()).toBe(0);
const pending1904 = calculateAsync(
date1904Workbook.getUnitId(),
date1904Workbook.getActiveSheet()!.getSheetId(),
'=ABS(ASYNCCUSTOMDATE("1904"))'
);
await date1904Started.promise;
const pending1900 = calculateAsync(testBed.unitId, testBed.sheetId, '=ABS(ASYNCCUSTOMDATE("1900"))');
await date1900Started.promise;
date1904Result.resolve('1904-1-1');
expect(await pending1904).toBe(0);
date1900Result.resolve('1904-1-1');
expect(await pending1900).toBe(1462);
expect(calculate(testBed.unitId, testBed.sheetId)).toBe(1462);
});
});
function createDeferred<T>() {
let resolve!: (value: T) => void;
const promise = new Promise<T>((promiseResolve) => {
resolve = promiseResolve;
});
return { promise, resolve };
}

View File

@@ -0,0 +1,85 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { IFormulaCurrentConfigService } from '../../../services/current-data.service';
import type { IFormulaUnitReferenceResolver } from '../../../services/unit-reference-resolver.service';
import { describe, expect, it, vi } from 'vitest';
import { ReferenceObjectType } from '../../utils/value-object';
import { BaseAstNode } from '../base-ast-node';
import { ReferenceNode } from '../reference-node';
import { UnionNode } from '../union-node';
describe('ReferenceNode external range loading', () => {
it('loads the full colon range from the qualified left reference', async () => {
const load = vi.fn(async () => undefined);
const currentConfig = {
getSheetNameMap: () => ({}),
getUnitData: () => ({}),
getArrayFormulaCellData: () => ({}),
getArrayFormulaRange: () => ({}),
getUnitStylesData: () => ({}),
};
const runtime = {
currentUnitId: 'host',
currentSubUnitId: 'host-sheet',
currentRow: 0,
currentColumn: 0,
getUnitData: () => ({}),
getRuntimeArrayFormulaCellData: () => ({}),
getUnitArrayFormula: () => ({}),
getRuntimeFeatureCellData: () => ({}),
};
const resolver = {
resolve: () => ({
unitId: 'source',
externalReference: {
kind: 'host',
qualifier: 'Sales',
referenceId: 'sales',
},
}),
} satisfies IFormulaUnitReferenceResolver;
const superTableService = {
getTableMap: () => null,
getTableOptionMap: () => new Map(),
};
const left = new ReferenceNode(
currentConfig,
runtime,
"'[Sales]Data'!A1",
ReferenceObjectType.CELL,
resolver,
superTableService,
{ load },
true
);
const right = new BaseAstNode('B2');
const union = new UnionNode(':', {
getSheetsInfo: () => ({
sheetNameMap: {},
sheetOrder: [],
}),
} satisfies Pick<IFormulaCurrentConfigService, 'getSheetsInfo'>);
left.setParent(union);
right.setParent(union);
await left.executeAsync();
expect(load).toHaveBeenCalledWith(expect.objectContaining({
token: "'[Sales]Data'!A1:B2",
}));
});
});

View File

@@ -0,0 +1,284 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { IUnitRange, IWorkbookData } from '@univerjs/core';
import type { ISheetData } from '../../../basics/common';
import type { IFormulaDependencyTree } from '../../dependency/dependency-tree';
import { CellValueType, LocaleType } from '@univerjs/core';
import { beforeEach, describe, expect, it } from 'vitest';
import { createFunctionTestBed } from '../../../functions/__tests__/create-function-test-bed';
import { FUNCTION_NAMES_LOGICAL } from '../../../functions/logical/function-names';
import { Let } from '../../../functions/logical/let';
import { Choosecols } from '../../../functions/lookup/choosecols';
import { Filter } from '../../../functions/lookup/filter';
import { FUNCTION_NAMES_LOOKUP } from '../../../functions/lookup/function-names';
import { Vstack } from '../../../functions/lookup/vstack';
import { FUNCTION_NAMES_MATH } from '../../../functions/math/function-names';
import { Sum } from '../../../functions/math/sum';
import { Compare } from '../../../functions/meta/compare';
import { FUNCTION_NAMES_META } from '../../../functions/meta/function-names';
import { getObjectValue } from '../../../functions/util';
import { IFormulaCurrentConfigService } from '../../../services/current-data.service';
import { DependencyManagerService, IDependencyManagerService } from '../../../services/dependency-manager.service';
import {
FeatureCalculationManagerService,
IFeatureCalculationManagerService,
} from '../../../services/feature-calculation-manager.service';
import { IFunctionService } from '../../../services/function.service';
import { IFormulaRuntimeService } from '../../../services/runtime.service';
import { Lexer } from '../../analysis/lexer';
import { LexerNode } from '../../analysis/lexer-node';
import { AstTreeBuilder } from '../../analysis/parser';
import { FormulaDependencyTree } from '../../dependency/dependency-tree';
import { IFormulaDependencyGenerator } from '../../dependency/formula-dependency';
import { Interpreter } from '../../interpreter/interpreter';
import { generateExecuteAstNodeData } from '../../utils/ast-node-tool';
import { BaseAstNode } from '../base-ast-node';
class CapturingDependencyManagerService extends DependencyManagerService {
readonly capturedRanges = new Map<number, IUnitRange[]>();
override addDependencyRTreeCache(tree: IFormulaDependencyTree): void {
this.capturedRanges.set(tree.treeId, [...tree.rangeList]);
super.addDependencyRTreeCache(tree);
}
}
function createWorkbookData(): IWorkbookData {
const sampleRowsBySheet: Record<string, Array<Array<number | string>>> = {
jan: [
[45148, 'Red', 1, 9],
[45166, 'Blue', 7, 70],
[45163, 'Green', 2, 16],
],
feb: [
[45174, 'Purple', 6, 72],
[45177, 'Red', 2, 18],
[45184, 'Purple', 2, 24],
[45188, 'Green', 6, 48],
[45194, 'Purple', 1, 12],
],
mar: [
[45200, 'Blue', 5, 50],
[45205, 'Red', 4, 28],
[45210, 'Green', 4, 32],
[45213, 'Purple', 3, 36],
],
};
sampleRowsBySheet.sheet1 = sampleRowsBySheet.jan;
sampleRowsBySheet.sheet2 = sampleRowsBySheet.feb;
sampleRowsBySheet.sheet3 = sampleRowsBySheet.mar;
const createSheet = (id: string, name: string, values: number[]) => {
const cellData: Record<number, Record<number, { v: number | string; t: CellValueType }>> = {
1: {
1: { v: values[0], t: CellValueType.NUMBER },
2: { v: values[1], t: CellValueType.NUMBER },
},
2: {
1: { v: values[2], t: CellValueType.NUMBER },
2: { v: values[3], t: CellValueType.NUMBER },
},
};
sampleRowsBySheet[id]?.forEach((row, rowIndex) => {
cellData[rowIndex + 4] = Object.fromEntries(
row.map((value, columnIndex) => [
columnIndex + 1,
{
v: value,
t: typeof value === 'number' ? CellValueType.NUMBER : CellValueType.STRING,
},
])
);
});
return {
id,
name,
rowCount: 20,
columnCount: 20,
cellData,
};
};
return {
id: 'test',
appVersion: '3.0.0-alpha',
locale: LocaleType.EN_US,
name: '3D reference',
sheetOrder: ['jan', 'feb', 'mar', 'sheet1', 'sheet2', 'sheet3', 'result'],
sheets: {
jan: createSheet('jan', 'Jan', [1, 2, 3, 4]),
feb: createSheet('feb', 'Feb', [10, 20, 30, 40]),
mar: createSheet('mar', 'Mar', [100, 200, 300, 400]),
sheet1: createSheet('sheet1', 'Sheet1', [0, 0, 0, 0]),
sheet2: createSheet('sheet2', 'Sheet2', [0, 0, 0, 0]),
sheet3: createSheet('sheet3', 'Sheet3', [0, 0, 0, 0]),
result: createSheet('result', 'Result', [0, 0, 0, 0]),
},
styles: {},
};
}
describe('three-dimensional sheet reference', () => {
let calculate: (formula: string) => string | number | boolean | null | (string | number | boolean | null)[][];
let currentConfigService: IFormulaCurrentConfigService;
let dependencyGenerator: IFormulaDependencyGenerator;
let dependencyManager: CapturingDependencyManagerService;
let unitId: string;
let activeSheetId: string;
let sheetData: ISheetData;
beforeEach(() => {
const testBed = createFunctionTestBed(createWorkbookData(), [
[IFeatureCalculationManagerService, { useClass: FeatureCalculationManagerService }],
[IDependencyManagerService, { useClass: CapturingDependencyManagerService }],
]);
const lexer = testBed.get(Lexer);
const astTreeBuilder = testBed.get(AstTreeBuilder);
const interpreter = testBed.get(Interpreter);
currentConfigService = testBed.get(IFormulaCurrentConfigService);
dependencyGenerator = testBed.get(IFormulaDependencyGenerator);
const manager = testBed.get(IDependencyManagerService);
if (!(manager instanceof CapturingDependencyManagerService)) {
throw new TypeError('Expected CapturingDependencyManagerService');
}
dependencyManager = manager;
const runtimeService = testBed.get(IFormulaRuntimeService);
unitId = testBed.unitId;
activeSheetId = testBed.sheetId;
sheetData = testBed.sheetData;
currentConfigService.load({
formulaData: {},
arrayFormulaCellData: {},
arrayFormulaRange: {},
forceCalculate: false,
dirtyRanges: [],
dirtyNameMap: {},
dirtyDefinedNameMap: {},
dirtyUnitFeatureMap: {},
dirtyUnitOtherFormulaMap: {},
excludedCell: {},
allUnitData: {
[testBed.unitId]: testBed.sheetData,
},
});
const activeSheet = testBed.sheetData[testBed.sheetId];
runtimeService.setCurrent(
0,
0,
activeSheet.rowCount,
activeSheet.columnCount,
testBed.sheetId,
testBed.unitId
);
testBed.get(IFunctionService).registerExecutors(
new Sum(FUNCTION_NAMES_MATH.SUM),
new Let(FUNCTION_NAMES_LOGICAL.LET),
new Vstack(FUNCTION_NAMES_LOOKUP.VSTACK),
new Filter(FUNCTION_NAMES_LOOKUP.FILTER),
new Choosecols(FUNCTION_NAMES_LOOKUP.CHOOSECOLS),
new Compare(FUNCTION_NAMES_META.COMPARE)
);
calculate = (formula: string) => {
const lexerNode = lexer.treeBuilder(formula);
if (!(lexerNode instanceof LexerNode)) {
throw new TypeError(`Failed to parse formula: ${formula}`);
}
const astNode = astTreeBuilder.parse(lexerNode);
if (!(astNode instanceof BaseAstNode)) {
throw new TypeError(`Failed to build AST: ${formula}`);
}
const result = interpreter.execute(generateExecuteAstNodeData(astNode));
return getObjectValue(result);
};
});
it('sums the same cell from every sheet in the inclusive range', () => {
expect(calculate('=SUM(Jan:Mar!B2)')).toBe(111);
expect(calculate("=SUM('Jan':'Mar'!B2)")).toBe(111);
});
it('sums every cell from every sheet instead of only each area first cell', () => {
expect(calculate('=SUM(Jan:Mar!B2:C3)')).toBe(1110);
});
it('calculates the combine-data sample formula with all 12 source rows', () => {
expect(calculate('=LET(data,VSTACK(Sheet1:Sheet3!B5:E16),FILTER(data,CHOOSECOLS(data,1)<>""))')).toEqual([
[45148, 'Red', 1, 9],
[45166, 'Blue', 7, 70],
[45163, 'Green', 2, 16],
[45174, 'Purple', 6, 72],
[45177, 'Red', 2, 18],
[45184, 'Purple', 2, 24],
[45188, 'Green', 6, 48],
[45194, 'Purple', 1, 12],
[45200, 'Blue', 5, 50],
[45205, 'Red', 4, 28],
[45210, 'Green', 4, 32],
[45213, 'Purple', 3, 36],
]);
});
it('tracks every sheet in the range as a dependency', async () => {
currentConfigService.load({
formulaData: {
[unitId]: {
[activeSheetId]: {
0: {
0: { f: '=SUM(Jan:Mar!B2)' },
},
},
},
},
arrayFormulaCellData: {},
arrayFormulaRange: {},
forceCalculate: false,
dirtyRanges: [{
unitId,
sheetId: activeSheetId,
range: { startRow: 0, startColumn: 0, endRow: 0, endColumn: 0 },
}],
dirtyNameMap: {},
dirtyDefinedNameMap: {},
dirtyUnitFeatureMap: {},
dirtyUnitOtherFormulaMap: {},
excludedCell: {},
allUnitData: {
[unitId]: sheetData,
},
});
const trees = await dependencyGenerator.generate();
const tree = trees.find(
(item): item is FormulaDependencyTree =>
item instanceof FormulaDependencyTree && item.formula === '=SUM(Jan:Mar!B2)'
);
if (!tree) {
throw new TypeError('Expected a dependency tree for the 3D reference');
}
expect(dependencyManager.capturedRanges.get(tree.treeId)?.map(({ sheetId }) => sheetId)).toEqual([
'jan',
'feb',
'mar',
]);
});
});

View File

@@ -0,0 +1,94 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ErrorType } from '../../basics/error-type';
import { DEFAULT_TOKEN_TYPE_ROOT } from '../../basics/token-type';
import { LexerNode } from '../analysis/lexer-node';
import { ErrorValueObject } from '../value-object/base-value-object';
import { BaseAstNode } from './base-ast-node';
import { BaseAstNodeFactory, DEFAULT_AST_NODE_FACTORY_Z_INDEX } from './base-ast-node-factory';
import { NODE_ORDER_MAP, NodeType } from './node-type';
export class AstRootNode extends BaseAstNode {
override get nodeType() {
return NodeType.ROOT;
}
override execute() {
const children = this.getChildren();
if (children.length > 1) {
this.setValue(ErrorValueObject.create(ErrorType.VALUE));
return;
}
const node = children[0];
// if (node.nodeType === NodeType.FUNCTION) {
// await node.executeAsync(interpreterCalculateProps);
// } else {
// node.execute(interpreterCalculateProps);
// }
if (node == null) {
/**
* fix: https://github.com/dream-num/univer/issues/1415
*/
this.setValue(ErrorValueObject.create(ErrorType.VALUE));
} else {
this.setValue(node.getValue());
}
// return Promise.resolve(AstNodePromiseType.SUCCESS);
}
}
// export class AstVariantNode extends BaseAstNode {
// get nodeType() {
// return NodeType.Variant;
// }
// async executeAsync(interpreterCalculateProps: IInterpreterCalculateProps) {
// const children = this.getChildren();
// const childrenCount = children.length;
// for (let i = 0; i < childrenCount; i++) {
// const node = children[i];
// if (node.nodeType === NodeType.FUNCTION) {
// await node.executeAsync(interpreterCalculateProps);
// } else {
// node.execute(interpreterCalculateProps);
// }
// }
// return Promise.resolve(AstNodePromiseType.SUCCESS);
// }
// }
export class AstRootNodeFactory extends BaseAstNodeFactory {
override get zIndex() {
return NODE_ORDER_MAP.get(NodeType.ROOT) || DEFAULT_AST_NODE_FACTORY_Z_INDEX;
}
override checkAndCreateNodeType(param: LexerNode | string) {
if (!(param instanceof LexerNode)) {
return;
}
const token = param.getToken();
if (token === DEFAULT_TOKEN_TYPE_ROOT) {
return new AstRootNode(DEFAULT_TOKEN_TYPE_ROOT);
}
}
}
// FORMULA_AST_NODE_REGISTRY.add(new AstRootNodeFactory());

View File

@@ -0,0 +1,44 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { Nullable } from '@univerjs/core';
import { LexerNode } from '../analysis/lexer-node';
import { BaseAstNode } from './base-ast-node';
export const DEFAULT_AST_NODE_FACTORY_Z_INDEX = 100;
export abstract class BaseAstNodeFactory {
get zIndex() {
return 0;
}
dispose(): void {
}
create(param: LexerNode | string, currentRow?: number, currentColumn?: number): BaseAstNode {
let token;
if (param instanceof LexerNode) {
token = param.getToken();
} else {
token = param;
}
return new BaseAstNode(token);
}
abstract checkAndCreateNodeType(param: LexerNode | string): Nullable<BaseAstNode>;
}

View File

@@ -0,0 +1,214 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { DateSystem, Nullable } from '@univerjs/core';
import type { ErrorType } from '../../basics/error-type';
import type { FunctionVariantType } from '../reference-object/base-reference-object';
import { AstNodePromiseType } from '../../basics/common';
import { ErrorValueObject } from '../value-object/base-value-object';
import { NodeType } from './node-type';
export interface IAstNodeNodeJson {
token: string;
children?: IAstNodeNodeJson[];
nodeType: number;
}
export type LambdaPrivacyVarType = Map<string, Nullable<BaseAstNode>>;
export class BaseAstNode {
private _children: BaseAstNode[] = [];
private _definedNames: Nullable<Array<string>>;
private _parent: Nullable<BaseAstNode>;
private _valueObject: Nullable<FunctionVariantType>;
private _calculateState = false;
private _async = false;
private _address = false;
private _isForcedCalculateFunction = false;
constructor(private _token: string) {
}
dispose(): void {
this._children.forEach((node) => {
node.dispose();
});
this._valueObject?.dispose();
this._valueObject = null;
this._children = [];
this._definedNames = null;
this._parent = null;
}
get nodeType() {
return NodeType.BASE;
}
resetCalculationState() {
this._children.forEach((node) => {
node.resetCalculationState();
});
this._valueObject = null;
this._calculateState = false;
}
isAsync() {
return this._async;
}
isAddress() {
return this._address;
}
isForcedCalculateFunction() {
return this._isForcedCalculateFunction;
}
setAsync() {
this._async = true;
}
setAddress() {
this._address = true;
}
getParent() {
return this._parent;
}
setParent(node: BaseAstNode) {
this._parent = node;
node.addChildren(this);
}
setForcedCalculateFunction() {
this._isForcedCalculateFunction = true;
}
getChildren() {
return this._children;
}
addChildren(...astNode: BaseAstNode[]) {
this._children.push(...astNode);
}
getToken() {
return this._token;
}
setValue(value: Nullable<FunctionVariantType>) {
this._valueObject = value;
}
getValue(): Nullable<FunctionVariantType> {
return this._valueObject;
}
preservesLazyIfReferenceArray(_child: BaseAstNode): boolean {
return false;
}
isCalculated() {
return this._calculateState;
}
setCalculated() {
this._calculateState = true;
}
execute(_dateSystem?: DateSystem) {
/* abstract */
}
setNotEmpty(state = true) {
/* abstract */
}
async executeAsync(_dateSystem?: DateSystem): Promise<AstNodePromiseType> {
/* abstract */
return Promise.resolve(AstNodePromiseType.SUCCESS);
}
serialize() {
const token = this.getToken();
const children = this.getChildren();
const childrenSerialization: IAstNodeNodeJson[] = [];
const childrenCount = children.length;
for (let i = 0; i < childrenCount; i++) {
const item = children[i];
childrenSerialization.push(item.serialize());
}
const result: IAstNodeNodeJson = {
token,
nodeType: this.nodeType,
};
if (childrenCount > 0) {
result.children = childrenSerialization;
}
return result;
}
hasDefinedName(definedName: string) {
return this._definedNames?.includes(definedName) || false;
}
setDefinedNames(definedNames: Array<string>) {
this._definedNames = definedNames;
}
getDefinedNames() {
return this._definedNames;
}
}
export class ErrorNode extends BaseAstNode {
private _errorValueObject: ErrorValueObject;
constructor(errorType: ErrorType) {
super(errorType);
this._errorValueObject = ErrorValueObject.create(errorType);
}
override get nodeType() {
return NodeType.ERROR;
}
static create(errorType: ErrorType) {
return new ErrorNode(errorType);
}
override getValue() {
return this._errorValueObject;
}
}

View File

@@ -0,0 +1,565 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { BaseFunction } from '../../functions/base-function';
import type { LexerNode } from '../analysis/lexer-node';
import type {
AsyncArrayObject,
AsyncObject,
BaseReferenceObject,
FunctionVariantType,
NodeValueType,
} from '../reference-object/base-reference-object';
import type { BaseValueObject } from '../value-object/base-value-object';
import type { FormulaFunctionResultValueType } from '../value-object/primitive-object';
import { DateSystem, Inject, Injector } from '@univerjs/core';
import { AstNodePromiseType } from '../../basics/common';
import { ErrorType } from '../../basics/error-type';
import { matchToken } from '../../basics/token';
import { COLUMN_LIKE_FUNCTION_NAMES } from '../../functions/column-like-functions';
import { FUNCTION_NAMES_TEXT } from '../../functions/text/function-names';
import { FormulaDataModel } from '../../models/formula-data.model';
import { IFormulaCurrentConfigService } from '../../services/current-data.service';
import { IDefinedNamesService } from '../../services/defined-names.service';
import { IFunctionService } from '../../services/function.service';
import { IFormulaRuntimeService } from '../../services/runtime.service';
import { IFormulaUnitReferenceResolver } from '../../services/unit-reference-resolver.service';
import { prefixHandler } from '../utils/prefix-handler';
import { ArrayValueObject, transformToValueObject, ValueObjectFactory } from '../value-object/array-value-object';
import { ErrorValueObject } from '../value-object/base-value-object';
import { BaseAstNode, ErrorNode } from './base-ast-node';
import { BaseAstNodeFactory, DEFAULT_AST_NODE_FACTORY_Z_INDEX } from './base-ast-node-factory';
import { NODE_ORDER_MAP, NodeType } from './node-type';
export class FunctionNode extends BaseAstNode {
constructor(
token: string,
private _functionExecutor: BaseFunction,
private _currentConfigService: IFormulaCurrentConfigService,
private _runtimeService: IFormulaRuntimeService,
private _definedNamesService: IDefinedNamesService,
private _formulaDataModel: FormulaDataModel,
private _unitReferenceResolver: IFormulaUnitReferenceResolver
) {
super(token);
if (this._functionExecutor.isAsync()) {
this.setAsync();
}
if (this._functionExecutor.isAddress()) {
this.setAddress();
}
if (this._functionExecutor.needsLocale) {
this._setLocale();
}
if (this._functionExecutor.needsSheetsInfo) {
this._setSheetsInfo();
}
if (this._functionExecutor.needsFormulaDataModel) {
this._functionExecutor.setFormulaDataModel(this._formulaDataModel);
}
if (this._functionExecutor.needsUnitReferenceResolver) {
this._functionExecutor.setUnitReferenceResolver(this._unitReferenceResolver);
}
}
override get nodeType() {
return NodeType.FUNCTION;
}
override preservesLazyIfReferenceArray(child: BaseAstNode): boolean {
return this._functionExecutor.lazyIfReferenceArrayArgumentIndexes.includes(this.getChildren().indexOf(child));
}
override async executeAsync(dateSystem: DateSystem = DateSystem.Date1900) {
const children = this.getChildren();
this._compatibility();
const resultVariant = this._functionExecutor.needsAstChildren
? this._calculateAst(children, dateSystem)
: await this._calculateAsync(this._collectVariants(children, dateSystem), dateSystem);
let result: FunctionVariantType;
if (resultVariant.isAsyncObject() || resultVariant.isAsyncArrayObject()) {
result = await (resultVariant as AsyncObject | AsyncArrayObject).getValue() as FunctionVariantType;
} else {
result = resultVariant as FunctionVariantType;
}
result = this._normalizeTopLevelLegacyArrayResult(result).withDateSystem(dateSystem) as FunctionVariantType;
this._setEmbeddedArrayFormulaToResult(result);
this._setRefData(result);
this.setValue(result);
return Promise.resolve(AstNodePromiseType.SUCCESS);
}
override execute(dateSystem: DateSystem = DateSystem.Date1900) {
const children = this.getChildren();
this._compatibility();
const resultVariant = (this._functionExecutor.needsAstChildren
? this._calculateAst(children, dateSystem)
: this._calculate(this._collectVariants(children, dateSystem), dateSystem)) as FunctionVariantType;
const result = this._normalizeTopLevelLegacyArrayResult(resultVariant).withDateSystem(dateSystem) as FunctionVariantType;
this._setEmbeddedArrayFormulaToResult(result);
this._setRefData(result);
this.setValue(result as FunctionVariantType);
}
private _collectVariants(children: BaseAstNode[], dateSystem: DateSystem): BaseValueObject[] {
const variants: BaseValueObject[] = [];
const childrenCount = children.length;
for (let i = 0; i < childrenCount; i++) {
const object = this._getChildVariant(children[i], dateSystem);
if (object == null) {
continue;
}
variants.push(object as BaseValueObject);
}
return variants;
}
private _getChildVariant(child: BaseAstNode, dateSystem: DateSystem): FunctionVariantType | undefined {
const object = child.getValue();
if (object == null) {
return;
}
if (object.isReferenceObject() && this._functionExecutor.needsFilteredOutRows) {
this._setFilteredOutRows(object as BaseReferenceObject);
}
// In the SUBTOTAL function, we need to get rowData information, we can only use ReferenceObject
if (object.isReferenceObject() && !this._functionExecutor.needsReferenceObject) {
// Array converted from reference object needs to be marked
return (object as BaseReferenceObject).withDateSystem(dateSystem).toArrayValueObject();
}
return object.withDateSystem(dateSystem);
}
isFunctionExecutorArgumentsIgnoreNumberPattern() {
return this._functionExecutor.isArgumentsIgnoreNumberPattern();
}
/**
* If it contains an array formula, set the current cell to the cache and send itself as a ref outward
*/
private _setEmbeddedArrayFormulaToResult(result: FunctionVariantType) {
if (!result.isArray()) {
return;
}
const rowCount = (result as ArrayValueObject).getRowCount();
const colCount = (result as ArrayValueObject).getColumnCount();
if (rowCount <= 1 && colCount <= 1) {
return;
}
this._runtimeService.setUnitArrayFormulaEmbeddedMap();
}
private _normalizeTopLevelLegacyArrayResult(result: FunctionVariantType): FunctionVariantType {
const parent = this.getParent();
const isTopLevel = parent == null || parent.nodeType === NodeType.ROOT;
if (!this._functionExecutor.returnsLegacyArrayAsScalar || !isTopLevel || !result.isArray()) {
return result;
}
const array = result as ArrayValueObject;
if (array.getRowCount() <= 1 && array.getColumnCount() <= 1) {
return result;
}
if (
this._runtimeService.hasFunctionRefInfoOverride() &&
(this._runtimeService.currentRowCount > 1 || this._runtimeService.currentColumnCount > 1)
) {
return result;
}
return array.getFirstCell();
}
/**
* Compatibility handling for special functions.
*/
private _compatibility() {
this._lookupCompatibility();
}
/**
* The LOOKUP function follows the following rules when dealing with vectors of different sizes:
* If the lookup_vector is larger than the result_vector,
* the LOOKUP function will ignore the extra portion of the lookup_vector and only use the portion of the result_vector that is the same size as the lookup_vector for lookup and returning results.
* If the lookup_vector is smaller than the result_vector,
* the LOOKUP function will continue using the last value of the result_vector for lookup and returning results after the last value of the lookup_vector.
*/
private _lookupCompatibility() {
const children = this.getChildren();
const childrenCount = children.length;
if (!this._functionExecutor.needsExpandParams || childrenCount !== 3) {
return;
}
const lookupVectorOrArray = children[1].getValue();
const resultVector = children[2].getValue();
if (!lookupVectorOrArray?.isReferenceObject() && !resultVector?.isReferenceObject()) {
return;
}
let lookupCountRow: number;
let lookupCountColumn: number;
if (lookupVectorOrArray?.isReferenceObject()) {
const lookupVectorOrArrayRange = (lookupVectorOrArray as BaseReferenceObject).getRangeData();
const { startRow, startColumn, endRow, endColumn } = lookupVectorOrArrayRange;
lookupCountRow = endRow - startRow + 1;
lookupCountColumn = endColumn - startColumn + 1;
} else {
lookupCountRow = lookupVectorOrArray?.isArray() ? (lookupVectorOrArray as ArrayValueObject).getRowCount() : 1;
lookupCountColumn = lookupVectorOrArray?.isArray() ? (lookupVectorOrArray as ArrayValueObject).getColumnCount() : 1;
}
const resultVectorRange = (resultVector as BaseReferenceObject).getRangeData();
const { startRow: reStartRow, startColumn: reStartColumn, endRow: reEndRow, endColumn: reEndColumn } = resultVectorRange;
const resultCountRow = reEndRow - reStartRow + 1;
const resultCountColumn = reEndColumn - reStartColumn + 1;
if (lookupCountRow !== resultCountRow) {
resultVectorRange.endRow += lookupCountRow - resultCountRow;
}
if (lookupCountColumn !== resultCountColumn) {
resultVectorRange.endColumn += lookupCountColumn - resultCountColumn;
}
}
/**
* Transform the result of a custom function to a NodeValueType.
*/
private _handleCustomResult(
resultVariantCustom: FormulaFunctionResultValueType,
dateSystem: DateSystem
): NodeValueType {
if (typeof resultVariantCustom !== 'object' || resultVariantCustom == null) {
return ValueObjectFactory.create(resultVariantCustom, false, dateSystem);
}
const arrayValues = transformToValueObject(resultVariantCustom, false, dateSystem);
return ArrayValueObject.create({
calculateValueList: arrayValues,
rowCount: arrayValues.length,
columnCount: arrayValues[0]?.length || 0,
unitId: '',
sheetId: '',
row: -1,
column: -1,
});
}
private _handleAddressFunction() {
/**
* In Excel, to inject a defined name into a function that has positioning capabilities,
* such as using the INDIRECT function to reference a named range,
* you can write it as follows:
* =INDIRECT("DefinedName1")
*/
if (this._functionExecutor.isAddress()) {
this._setDefinedNamesForFunction();
}
}
private _mapVariantsToValues(variants: BaseValueObject[]) {
return variants.map((variant) => {
if (variant.isArray()) {
return (variant as ArrayValueObject).toValue();
}
if (variant.isLambda()) {
return variant;
}
return variant.getValue();
});
}
private _calculate(variants: BaseValueObject[], dateSystem: DateSystem) {
// Check the number of parameters
const { minParams, maxParams } = this._functionExecutor;
if (minParams !== -1 && maxParams !== -1 && (variants.length < minParams || variants.length > maxParams)) {
return ErrorValueObject.create(ErrorType.NA);
}
let resultVariant: NodeValueType;
this._setRefInfo();
if (this._functionExecutor.isCustom()) {
const resultVariantCustom = this._functionExecutor.calculateCustom(
...this._mapVariantsToValues(variants)
) as FormulaFunctionResultValueType;
resultVariant = this._handleCustomResult(resultVariantCustom, dateSystem);
} else {
this._handleAddressFunction();
resultVariant = this._functionExecutor.calculate(...variants);
}
return resultVariant;
}
private _calculateAst(children: BaseAstNode[], dateSystem: DateSystem) {
const { minParams, maxParams } = this._functionExecutor;
if (minParams !== -1 && maxParams !== -1 && (children.length < minParams || children.length > maxParams)) {
return ErrorValueObject.create(ErrorType.NA);
}
this._setRefInfo();
this._handleAddressFunction();
return this._functionExecutor.calculateAst(children, (node) => this._getChildVariant(node, dateSystem) ?? null);
}
private async _calculateAsync(variants: BaseValueObject[], dateSystem: DateSystem) {
// Check the number of parameters
const { minParams, maxParams } = this._functionExecutor;
if (minParams !== -1 && maxParams !== -1 && (variants.length < minParams || variants.length > maxParams)) {
return ErrorValueObject.create(ErrorType.NA);
}
let resultVariant: NodeValueType;
this._setRefInfo();
if (this._functionExecutor.isCustom()) {
const resultVariantCustom = await this._functionExecutor.calculateCustom(
...this._mapVariantsToValues(variants)
);
resultVariant = this._handleCustomResult(resultVariantCustom, dateSystem);
} else {
this._handleAddressFunction();
resultVariant = this._functionExecutor.calculate(...variants);
}
return resultVariant;
}
private _setDefinedNamesForFunction() {
const editorUnitId = this._runtimeService.currentUnitId;
if (editorUnitId == null) {
return;
}
const definedNames = this._definedNamesService.getDefinedNameMap(editorUnitId);
if (definedNames == null) {
return;
}
this._functionExecutor.setDefinedNames(definedNames);
}
private _setRefInfo() {
const { currentUnitId, currentSubUnitId, currentRow, currentColumn, currentRowCount, currentColumnCount } = this._runtimeService;
this._functionExecutor.setRefInfo(currentUnitId, currentSubUnitId, currentRow, currentColumn, currentRowCount, currentColumnCount);
this._functionExecutor.setDateSystem(this._currentConfigService.getDateSystem(currentUnitId));
if (this._functionExecutor.needsSheetRowColumnCount) {
const { rowCount, columnCount } = this._currentConfigService.getSheetRowColumnCount(currentUnitId, currentSubUnitId);
this._functionExecutor.setSheetRowColumnCount(rowCount, columnCount);
}
}
private _setRefData(variant: FunctionVariantType) {
if (!variant.isReferenceObject()) {
return;
}
const referenceObject = variant as BaseReferenceObject;
referenceObject.setForcedSheetId(this._currentConfigService.getSheetNameMap());
referenceObject.setUnitData(this._currentConfigService.getUnitData());
referenceObject.setArrayFormulaCellData(this._currentConfigService.getArrayFormulaCellData());
referenceObject.setArrayFormulaRange(this._currentConfigService.getArrayFormulaRange());
referenceObject.setRuntimeData(this._runtimeService.getUnitData());
referenceObject.setRuntimeArrayFormulaCellData(this._runtimeService.getRuntimeArrayFormulaCellData());
referenceObject.setRuntimeArrayFormulaRange(this._runtimeService.getUnitArrayFormula());
referenceObject.setRuntimeFeatureCellData(this._runtimeService.getRuntimeFeatureCellData());
}
private _setLocale() {
this._functionExecutor.setLocale(this._currentConfigService.getLocale());
}
private _setSheetsInfo() {
this._functionExecutor.setSheetsInfo(this._currentConfigService.getSheetsInfo());
}
private _setFilteredOutRows(referenceObject: BaseReferenceObject) {
const { startRow, endRow } = referenceObject.getRangePosition();
const filteredOutRows = this._currentConfigService.getFilteredOutRows(
referenceObject.getUnitId(),
referenceObject.getSheetId(),
startRow,
endRow
);
referenceObject.setFilteredOutRows(filteredOutRows);
}
}
export class ErrorFunctionNode extends BaseAstNode {
constructor(
token: string = 'Error'
) {
super(token);
}
override get nodeType() {
return NodeType.FUNCTION;
}
override async executeAsync() {
this.setValue(ErrorValueObject.create(ErrorType.NAME) as FunctionVariantType);
return Promise.resolve(AstNodePromiseType.SUCCESS);
}
override execute() {
this.setValue(ErrorValueObject.create(ErrorType.NAME) as FunctionVariantType);
}
}
export class FunctionNodeFactory extends BaseAstNodeFactory {
constructor(
@IFunctionService private readonly _functionService: IFunctionService,
@IFormulaCurrentConfigService private readonly _currentConfigService: IFormulaCurrentConfigService,
@IFormulaRuntimeService private readonly _runtimeService: IFormulaRuntimeService,
@IDefinedNamesService private readonly _definedNamesService: IDefinedNamesService,
@Inject(Injector) private readonly _injector: Injector,
@Inject(FormulaDataModel) private readonly _formulaDataModel: FormulaDataModel,
@IFormulaUnitReferenceResolver private readonly _unitReferenceResolver: IFormulaUnitReferenceResolver
) {
super();
}
override get zIndex() {
return NODE_ORDER_MAP.get(NodeType.FUNCTION) || DEFAULT_AST_NODE_FACTORY_Z_INDEX;
}
override create(token: string): BaseAstNode {
const functionExecutor = this._functionService.getExecutor(token);
if (!functionExecutor) {
console.error(`No function ${token}`);
return ErrorNode.create(ErrorType.NAME);
}
return new FunctionNode(
token,
functionExecutor,
this._currentConfigService,
this._runtimeService,
this._definedNamesService,
this._formulaDataModel,
this._unitReferenceResolver
);
}
override checkAndCreateNodeType(param: LexerNode | string) {
if (typeof param === 'string') {
return;
}
const token = param.getToken();
const { tokenTrim, minusPrefixNode, atPrefixNode } = prefixHandler(token.trim(), this._functionService, this._runtimeService);
const isColon = this._isParentUnionNode(param);
if (!Number.isNaN(Number(tokenTrim)) && !isColon) {
return ErrorNode.create(ErrorType.VALUE);
}
const tokenTrimUpper = normalizeFunctionToken(tokenTrim.toUpperCase());
if (isColon && COLUMN_LIKE_FUNCTION_NAMES.has(tokenTrimUpper)) {
return;
}
if (this._functionService.hasExecutor(tokenTrimUpper)) {
const functionNode = this.create(tokenTrimUpper);
if (atPrefixNode) {
functionNode.setParent(atPrefixNode);
// return atPrefixNode;
} else if (minusPrefixNode) {
functionNode.setParent(minusPrefixNode);
// return minusPrefixNode;
}
return functionNode;
}
}
private _isParentUnionNode(param: LexerNode) {
return param.getParent()?.getParent()?.getToken() === matchToken.COLON;
}
}
function normalizeFunctionToken(token: string): string {
if (token === 'REGEXTEST' || token === '_XLFN.REGEXTEST') {
return FUNCTION_NAMES_TEXT.REGEXMATCH;
}
for (const prefix of ['_XLFN.', '_XLWS.', '_XLETA.']) {
if (token.startsWith(prefix)) {
return token.slice(prefix.length);
}
}
return token;
}

View File

@@ -0,0 +1,26 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export * from './ast-root-node';
export * from './function-node';
export * from './lambda-node';
export * from './lambda-parameter-node';
export * from './operator-node';
export * from './prefix-node';
export * from './reference-node';
export * from './suffix-node';
export * from './union-node';
export * from './value-node';

View File

@@ -0,0 +1,191 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { Nullable } from '@univerjs/core';
import type { LambdaPrivacyVarType } from './base-ast-node';
import { generateRandomId, Inject } from '@univerjs/core';
import { ErrorType } from '../../basics/error-type';
import {
DEFAULT_TOKEN_LAMBDA_FUNCTION_NAME,
DEFAULT_TOKEN_TYPE_LAMBDA_OMIT_PARAMETER,
DEFAULT_TOKEN_TYPE_LAMBDA_PARAMETER,
} from '../../basics/token-type';
import { IFormulaRuntimeService } from '../../services/runtime.service';
import { LexerNode } from '../analysis/lexer-node';
import { Interpreter } from '../interpreter/interpreter';
import { updateLambdaStatement } from '../utils/update-lambda-statement';
import { LambdaValueObjectObject } from '../value-object/lambda-value-object';
import { BaseAstNode, ErrorNode } from './base-ast-node';
import { BaseAstNodeFactory, DEFAULT_AST_NODE_FACTORY_Z_INDEX } from './base-ast-node-factory';
import { NODE_ORDER_MAP, NodeType } from './node-type';
export class LambdaNode extends BaseAstNode {
private _isNotEmpty = true;
constructor(
token: string,
private _lambdaId: string,
private _interpreter: Interpreter,
private _lambdaPrivacyVarKeys: string[],
private _runtimeService: IFormulaRuntimeService
) {
super(token);
}
override get nodeType() {
return NodeType.LAMBDA;
}
override setNotEmpty(state = false) {
this._isNotEmpty = state;
}
isEmptyParamFunction() {
return this.getChildren().length < 2 && this._isNotEmpty;
}
isFunctionParameter() {
return this._lambdaId === null;
}
getLambdaId() {
return this._lambdaId;
}
override execute() {
if (this.isEmptyParamFunction()) {
this.setValue(LambdaValueObjectObject.create(this, this._interpreter, this._lambdaPrivacyVarKeys));
} else {
const children = this.getChildren();
const childrenCount = children.length;
this.setValue(children[childrenCount - 1].getValue());
}
this._runtimeService.setUnitArrayFormulaEmbeddedMap();
}
// override async executeAsync() {
// if (this.isEmptyParamFunction()) {
// await this.setValue(LambdaValueObjectObject.create(this, this._interpreter, this._lambdaPrivacyVarKeys));
// } else {
// const children = this.getChildren();
// const childrenCount = children.length;
// await this.setValue(children[childrenCount - 1].getValue());
// }
// return Promise.resolve(AstNodePromiseType.SUCCESS);
// }
}
export class LambdaNodeFactory extends BaseAstNodeFactory {
constructor(
@IFormulaRuntimeService private readonly _runtimeService: IFormulaRuntimeService,
@Inject(Interpreter) private readonly _interpreter: Interpreter
) {
super();
}
override get zIndex() {
return NODE_ORDER_MAP.get(NodeType.LAMBDA) || DEFAULT_AST_NODE_FACTORY_Z_INDEX;
}
override create(param: LexerNode): BaseAstNode {
const children = param.getChildren();
const lambdaVar = children[0];
let parameterArray = children.slice(1, -1);
const functionStatementNode = children[children.length - 1];
if (!(lambdaVar instanceof LexerNode && functionStatementNode instanceof LexerNode)) {
return ErrorNode.create(ErrorType.NAME);
}
if (lambdaVar.getToken() === DEFAULT_TOKEN_TYPE_LAMBDA_PARAMETER) {
const lambdaVarChildren = lambdaVar.getChildren();
if (parameterArray.length !== lambdaVarChildren.length) {
return ErrorNode.create(ErrorType.VALUE);
}
} else {
parameterArray = children.slice(0, -1);
}
// const lambdaId = nanoid(8);
const lambdaId = generateRandomId(8);
// const lambdaRuntime = parserDataLoader.getLambdaRuntime();
const currentLambdaPrivacyVar = new Map<string, Nullable<BaseAstNode>>();
for (let i = 0; i < parameterArray.length; i++) {
const parameter = parameterArray[i];
if (parameter instanceof LexerNode) {
const variant = getLambdaParameterName(parameter);
if (variant == null) {
return ErrorNode.create(ErrorType.VALUE);
}
parameter.setToken(DEFAULT_TOKEN_TYPE_LAMBDA_OMIT_PARAMETER);
currentLambdaPrivacyVar.set(variant, undefined);
} else {
return ErrorNode.create(ErrorType.VALUE);
}
}
this._runtimeService.registerFunctionDefinitionPrivacyVar(lambdaId, currentLambdaPrivacyVar);
this._updateLambdaStatement(functionStatementNode, lambdaId, currentLambdaPrivacyVar);
return new LambdaNode(param.getToken(), lambdaId, this._interpreter, [...currentLambdaPrivacyVar.keys()], this._runtimeService);
}
override checkAndCreateNodeType(param: LexerNode | string) {
if (!(param instanceof LexerNode)) {
return;
}
const token = param.getToken().trim().toUpperCase();
if (token !== DEFAULT_TOKEN_LAMBDA_FUNCTION_NAME) {
return;
}
return this.create(param);
}
private _updateLambdaStatement(
functionStatementNode: LexerNode,
lambdaId: string,
currentLambdaPrivacyVar: LambdaPrivacyVarType
) {
updateLambdaStatement(functionStatementNode, lambdaId, currentLambdaPrivacyVar);
}
}
function getLambdaParameterName(parameter: LexerNode): Nullable<string> {
const children = parameter.getChildren();
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (typeof child === 'string') {
const token = child.trim();
if (token.length > 0) {
return token;
}
} else {
const token = getLambdaParameterName(child);
if (token != null) {
return token;
}
}
}
return null;
}

View File

@@ -0,0 +1,113 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { Nullable } from '@univerjs/core';
import type { LambdaPrivacyVarType } from './base-ast-node';
import { ErrorType } from '../../basics/error-type';
import { DEFAULT_TOKEN_TYPE_LAMBDA_RUNTIME_PARAMETER } from '../../basics/token-type';
import { LexerNode } from '../analysis/lexer-node';
import { ErrorValueObject } from '../value-object/base-value-object';
import { BaseAstNode, ErrorNode } from './base-ast-node';
import { BaseAstNodeFactory, DEFAULT_AST_NODE_FACTORY_Z_INDEX } from './base-ast-node-factory';
import { NODE_ORDER_MAP, NodeType } from './node-type';
function getRootLexerNode(node: Nullable<BaseAstNode>): Nullable<BaseAstNode> {
if (!node) {
return;
}
if (node.getToken() !== DEFAULT_TOKEN_TYPE_LAMBDA_RUNTIME_PARAMETER) {
return node;
}
const parameterNode = node as LambdaParameterNode;
const currentLambdaPrivacyVar = parameterNode.getCurrentLambdaPrivacyVar();
const lambdaParameter = parameterNode.getLambdaParameter();
if (!currentLambdaPrivacyVar) {
return;
}
return getRootLexerNode(currentLambdaPrivacyVar.get(lambdaParameter));
}
export class LambdaParameterNode extends BaseAstNode {
constructor(
token: string,
private _lambdaParameter: string,
private _currentLambdaPrivacyVar: LambdaPrivacyVarType
) {
super(token);
}
getLambdaParameter() {
return this._lambdaParameter;
}
getCurrentLambdaPrivacyVar() {
return this._currentLambdaPrivacyVar;
}
override get nodeType() {
return NodeType.LAMBDA_PARAMETER;
}
override execute() {
/**
* In the case of =lambda(x,y,lambda(a,b,a*b)(x,y))(1,2)
*/
const node = getRootLexerNode(this._currentLambdaPrivacyVar.get(this._lambdaParameter));
if (!node) {
const value = this.getValue();
if (value == null || value.isError()) {
this.setValue(ErrorValueObject.create(ErrorType.NAME));
}
} else {
this.setValue(node.getValue());
}
}
}
export class LambdaParameterNodeFactory extends BaseAstNodeFactory {
override get zIndex() {
return NODE_ORDER_MAP.get(NodeType.LAMBDA_PARAMETER) || DEFAULT_AST_NODE_FACTORY_Z_INDEX;
}
override create(param: LexerNode): BaseAstNode {
// const lambdaId = param.getLambdaId();
const currentLambdaPrivacyVar = param.getFunctionDefinitionPrivacyVar();
const lambdaParameter = param.getLambdaParameter();
if (!currentLambdaPrivacyVar) {
return new ErrorNode(ErrorType.NAME);
}
return new LambdaParameterNode(param.getToken(), lambdaParameter, currentLambdaPrivacyVar);
}
override checkAndCreateNodeType(param: LexerNode | string) {
if (!(param instanceof LexerNode)) {
return;
}
const token = param.getToken().trim();
if (token !== DEFAULT_TOKEN_TYPE_LAMBDA_RUNTIME_PARAMETER) {
return;
}
return this.create(param);
}
}

View File

@@ -0,0 +1,44 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export enum NodeType {
REFERENCE = 1,
VALUE = 2,
OPERATOR = 3,
FUNCTION = 4,
LAMBDA = 5,
LAMBDA_PARAMETER = 6,
ERROR = 7,
BASE = 8,
ROOT = 9,
UNION = 10,
PREFIX = 11,
SUFFIX = 12,
NULL = 13,
}
export const NODE_ORDER_MAP = new Map([
[NodeType.REFERENCE, 7],
[NodeType.VALUE, 9],
[NodeType.OPERATOR, 8],
[NodeType.FUNCTION, 6],
[NodeType.LAMBDA, 1],
[NodeType.LAMBDA_PARAMETER, 2],
[NodeType.ROOT, 10],
[NodeType.UNION, 3],
[NodeType.PREFIX, 4],
[NodeType.SUFFIX, 5],
]);

View File

@@ -0,0 +1,33 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { NullValueObject } from '../value-object/primitive-object';
import { BaseAstNode } from './base-ast-node';
import { NodeType } from './node-type';
export class NullNode extends BaseAstNode {
constructor(private _operatorString: string) {
super(_operatorString);
}
override get nodeType() {
return NodeType.NULL;
}
override execute() {
this.setValue(NullValueObject.create());
}
}

View File

@@ -0,0 +1,179 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { compareToken } from '../../basics/token';
import type { BaseFunction } from '../../functions/base-function';
import type { Compare } from '../../functions/meta/compare';
import type { BaseReferenceObject, FunctionVariantType } from '../reference-object/base-reference-object';
import type { ArrayValueObject } from '../value-object/array-value-object';
import type { BaseValueObject } from '../value-object/base-value-object';
import { DateSystem } from '@univerjs/core';
import { ErrorType } from '../../basics/error-type';
import { OPERATOR_TOKEN_COMPARE_SET, OPERATOR_TOKEN_SET, operatorToken } from '../../basics/token';
import { FUNCTION_NAMES_MATH } from '../../functions/math/function-names';
import { FUNCTION_NAMES_META } from '../../functions/meta/function-names';
import { FUNCTION_NAMES_TEXT } from '../../functions/text/function-names';
import { IFunctionService } from '../../services/function.service';
import { IFormulaRuntimeService } from '../../services/runtime.service';
import { LexerNode } from '../analysis/lexer-node';
import { ErrorValueObject } from '../value-object/base-value-object';
import { NullValueObject } from '../value-object/primitive-object';
import { BaseAstNode, ErrorNode } from './base-ast-node';
import { BaseAstNodeFactory, DEFAULT_AST_NODE_FACTORY_Z_INDEX } from './base-ast-node-factory';
import { NODE_ORDER_MAP, NodeType } from './node-type';
export class OperatorNode extends BaseAstNode {
constructor(
operatorString: string,
private _functionExecutor: BaseFunction,
private _runtimeService: IFormulaRuntimeService
) {
super(operatorString);
}
override get nodeType() {
return NodeType.OPERATOR;
}
override execute(dateSystem: DateSystem = DateSystem.Date1900) {
const children = this.getChildren();
if (this._functionExecutor.name === FUNCTION_NAMES_META.COMPARE) {
(this._functionExecutor as Compare).setCompareType(this.getToken() as compareToken);
}
const child1 = children[0];
const child2 = children[1];
let object1 = child1?.getValue();
let object2 = child2?.getValue();
const token = this.getToken();
if ((object1 == null || object2 == null) && token !== operatorToken.MINUS && token !== operatorToken.PLUS) {
this.setValue(ErrorValueObject.create(ErrorType.VALUE));
return;
}
if (object1 == null) {
object1 = NullValueObject.create();
}
if (object2 == null) {
object2 = NullValueObject.create();
}
if (object1.isReferenceObject()) {
object1 = this._referenceToOperatorValue(object1 as BaseReferenceObject);
}
if (object2.isReferenceObject()) {
object2 = this._referenceToOperatorValue(object2 as BaseReferenceObject);
}
object1 = object1.withDateSystem(dateSystem);
object2 = object2.withDateSystem(dateSystem);
const result = this._functionExecutor.calculate(
object1 as BaseValueObject,
object2 as BaseValueObject
) as FunctionVariantType;
this._setEmbeddedArrayFormulaToResult(result);
this.setValue(result.withDateSystem(dateSystem));
}
private _referenceToOperatorValue(reference: BaseReferenceObject): BaseValueObject {
const array = reference.toArrayValueObject();
if (array.getRowCount() === 1 && array.getColumnCount() === 1) {
return array.get(0, 0) as BaseValueObject;
}
return array;
}
/**
* If it contains an array formula, set the current cell to the cache and send itself as a ref outward
*/
private _setEmbeddedArrayFormulaToResult(result: FunctionVariantType) {
if (!result.isArray()) {
return;
}
const rowCount = (result as ArrayValueObject).getRowCount();
const colCount = (result as ArrayValueObject).getColumnCount();
if (rowCount <= 1 && colCount <= 1) {
return;
}
this._runtimeService.setUnitArrayFormulaEmbeddedMap();
}
}
export class OperatorNodeFactory extends BaseAstNodeFactory {
constructor(
@IFunctionService private readonly _functionService: IFunctionService,
@IFormulaRuntimeService private readonly _runtimeService: IFormulaRuntimeService
) {
super();
}
override get zIndex() {
return NODE_ORDER_MAP.get(NodeType.OPERATOR) || DEFAULT_AST_NODE_FACTORY_Z_INDEX;
}
override create(param: string): BaseAstNode {
let functionName = '';
const tokenTrim = param;
if (tokenTrim === operatorToken.PLUS) {
functionName = FUNCTION_NAMES_META.PLUS;
} else if (tokenTrim === operatorToken.MINUS) {
functionName = FUNCTION_NAMES_META.MINUS;
} else if (tokenTrim === operatorToken.MULTIPLY) {
functionName = FUNCTION_NAMES_META.MULTIPLY;
} else if (tokenTrim === operatorToken.DIVIDED) {
functionName = FUNCTION_NAMES_META.DIVIDED;
} else if (tokenTrim === operatorToken.CONCATENATE) {
functionName = FUNCTION_NAMES_TEXT.CONCATENATE;
} else if (tokenTrim === operatorToken.POWER) {
functionName = FUNCTION_NAMES_MATH.POWER;
} else if (OPERATOR_TOKEN_COMPARE_SET.has(tokenTrim)) {
functionName = FUNCTION_NAMES_META.COMPARE;
}
const functionExecutor = this._functionService.getExecutor(functionName);
if (!functionExecutor) {
console.error(`No function ${param}`);
return ErrorNode.create(ErrorType.NAME);
}
return new OperatorNode(tokenTrim, functionExecutor, this._runtimeService);
}
override checkAndCreateNodeType(param: LexerNode | string) {
if (param instanceof LexerNode) {
return;
}
const tokenTrim = param.trim();
if (tokenTrim.charAt(0) === '"' && tokenTrim.charAt(tokenTrim.length - 1) === '"') {
return;
}
if (OPERATOR_TOKEN_SET.has(tokenTrim)) {
return this.create(tokenTrim);
}
}
}

View File

@@ -0,0 +1,193 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { Nullable } from '@univerjs/core';
import type { BaseFunction } from '../../functions/base-function';
import type { BaseReferenceObject, FunctionVariantType } from '../reference-object/base-reference-object';
import type { BaseValueObject } from '../value-object/base-value-object';
import { DateSystem } from '@univerjs/core';
import { ErrorType } from '../../basics/error-type';
import { OPERATOR_TOKEN_COMPARE_SET, prefixToken } from '../../basics/token';
import { FUNCTION_NAMES_LOOKUP } from '../../functions/lookup/function-names';
import { FUNCTION_NAMES_META } from '../../functions/meta/function-names';
import { IFunctionService } from '../../services/function.service';
import { IFormulaRuntimeService } from '../../services/runtime.service';
import { LexerNode } from '../analysis/lexer-node';
import { ArrayValueObject } from '../value-object/array-value-object';
import { ErrorValueObject } from '../value-object/base-value-object';
import { NumberValueObject } from '../value-object/primitive-object';
import { BaseAstNode, ErrorNode } from './base-ast-node';
import { BaseAstNodeFactory, DEFAULT_AST_NODE_FACTORY_Z_INDEX } from './base-ast-node-factory';
import { NODE_ORDER_MAP, NodeType } from './node-type';
export class PrefixNode extends BaseAstNode {
constructor(
private _runtimeService: IFormulaRuntimeService,
private _operatorString: string,
private _functionExecutor?: Nullable<BaseFunction>
) {
super(_operatorString);
}
override get nodeType() {
return NodeType.PREFIX;
}
override execute(dateSystem: DateSystem = DateSystem.Date1900) {
const children = this.getChildren();
let value = children[0].getValue();
let result: FunctionVariantType;
if (value == null) {
console.error('PrefixNode execute value is null');
this.setValue(ErrorValueObject.create(ErrorType.VALUE));
return;
}
if (this._operatorString === prefixToken.MINUS) {
if (value.isReferenceObject()) {
value = (value as BaseReferenceObject).withDateSystem(dateSystem).toArrayValueObject();
}
result = this._functionExecutor!.calculate(
NumberValueObject.create(0),
(value as BaseValueObject).withDateSystem(dateSystem)
) as FunctionVariantType;
} else if (this._operatorString === prefixToken.AT) {
result = this._handlerAT(value);
} else {
result = ErrorValueObject.create(ErrorType.VALUE);
}
this.setValue(result.withDateSystem(dateSystem));
}
private _handlerAT(value: FunctionVariantType) {
if (value instanceof ArrayValueObject) {
return value.get(0, 0) ?? ErrorValueObject.create(ErrorType.VALUE);
}
if (!value.isReferenceObject()) {
return value;
}
const currentValue = value as BaseReferenceObject;
if (currentValue.isCell()) {
return currentValue.getCellByPosition();
}
if (this._preserveAtRangeForFormula2LookupArray()) {
return currentValue;
}
const runtimeService = this._runtimeService;
const currentRow = runtimeService.currentRow || 0;
const currentColumn = runtimeService.currentColumn || 0;
const rangePos = currentValue.getRangePosition();
const { startRow, startColumn, endRow, endColumn } = rangePos;
if (endColumn !== startColumn && endRow !== startRow) {
return ErrorValueObject.create(ErrorType.VALUE);
}
if (startRow === endRow && startColumn === endColumn) {
return ErrorValueObject.create(ErrorType.VALUE);
}
// @ projection to current
if (endRow === startRow && currentColumn >= startColumn && currentColumn <= endColumn) {
return currentValue.getCellByColumn(currentColumn);
} else if (startColumn === endColumn && currentRow >= startRow && currentRow <= endRow) {
return currentValue.getCellByRow(currentRow);
}
if (currentValue.isTable()) {
return currentValue.getCellByPosition(currentRow);
}
return ErrorValueObject.create(ErrorType.VALUE);
}
private _preserveAtRangeForFormula2LookupArray(): boolean {
let node: BaseAstNode = this;
let parent = node.getParent();
let sawCompareOperator = false;
while (parent != null) {
if (parent.nodeType === NodeType.OPERATOR) {
sawCompareOperator ||= OPERATOR_TOKEN_COMPARE_SET.has(parent.getToken());
}
if (parent.nodeType === NodeType.FUNCTION) {
const functionName = parent.getToken().toUpperCase();
if (
sawCompareOperator &&
functionName === FUNCTION_NAMES_LOOKUP.XMATCH
) {
return parent.getChildren().indexOf(node) === 1;
}
return false;
}
node = parent;
parent = parent.getParent();
}
return false;
}
}
export class PrefixNodeFactory extends BaseAstNodeFactory {
constructor(
@IFunctionService private readonly _functionService: IFunctionService,
@IFormulaRuntimeService private readonly _runtimeService: IFormulaRuntimeService
) {
super();
}
override get zIndex() {
return NODE_ORDER_MAP.get(NodeType.PREFIX) || DEFAULT_AST_NODE_FACTORY_Z_INDEX;
}
override checkAndCreateNodeType(param: LexerNode | string) {
if (!(param instanceof LexerNode)) {
return;
}
const token = param.getToken();
const tokenTrim = token.trim();
if (tokenTrim.charAt(0) === '"' && tokenTrim.charAt(tokenTrim.length - 1) === '"') {
return;
}
let functionName = '';
if (tokenTrim === prefixToken.MINUS) {
functionName = FUNCTION_NAMES_META.MINUS;
} else if (tokenTrim === prefixToken.AT) {
return new PrefixNode(this._runtimeService, tokenTrim);
} else {
return;
}
const functionExecutor = this._functionService.getExecutor(functionName);
if (!functionExecutor) {
console.error(`No function ${token}`);
return ErrorNode.create(ErrorType.NAME);
}
return new PrefixNode(this._runtimeService, tokenTrim, functionExecutor);
}
}

View File

@@ -0,0 +1,385 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { Nullable } from '@univerjs/core';
import type { BaseReferenceObject } from '../reference-object/base-reference-object';
import { AstNodePromiseType } from '../../basics/common';
import { ErrorType } from '../../basics/error-type';
import {
regexTestReferenceTableAllColumn,
regexTestReferenceTableMultipleColumn,
regexTestReferenceTableSingleColumn,
regexTestReferenceTableTitleOnlyAnyHash,
regexTestSingeRange,
regexTestSingleColumn,
regexTestSingleRow,
} from '../../basics/regex';
import { matchToken } from '../../basics/token';
import { IFormulaCurrentConfigService } from '../../services/current-data.service';
import { IFormulaExternalReferenceDataLoader } from '../../services/external-reference-data-loader.service';
import { IFunctionService } from '../../services/function.service';
import { IFormulaRuntimeService } from '../../services/runtime.service';
import { ISuperTableService } from '../../services/super-table.service';
import { IFormulaUnitReferenceResolver } from '../../services/unit-reference-resolver.service';
import { LexerNode } from '../analysis/lexer-node';
import { TableReferenceObject } from '../reference-object/table-reference-object';
import { prefixHandler } from '../utils/prefix-handler';
import { splitTableStructuredRef } from '../utils/reference';
import { getReferenceObjectFromCache, ReferenceObjectType } from '../utils/value-object';
import { ErrorValueObject } from '../value-object/base-value-object';
import { BaseAstNode } from './base-ast-node';
import { BaseAstNodeFactory, DEFAULT_AST_NODE_FACTORY_Z_INDEX } from './base-ast-node-factory';
import { NODE_ORDER_MAP, NodeType } from './node-type';
interface ITableReferenceDescriptor {
unitQualifier: string;
tableName: string;
columnStruct: string | undefined;
}
type ReferenceNodeCurrentConfigService = Pick<
IFormulaCurrentConfigService,
'getArrayFormulaCellData' | 'getArrayFormulaRange' | 'getSheetNameMap' | 'getUnitData' | 'getUnitStylesData'
>;
type ReferenceNodeRuntimeService = Pick<
IFormulaRuntimeService,
| 'currentColumn'
| 'currentRow'
| 'currentSubUnitId'
| 'currentUnitId'
| 'getRuntimeArrayFormulaCellData'
| 'getRuntimeFeatureCellData'
| 'getUnitArrayFormula'
| 'getUnitData'
>;
type ReferenceNodeSuperTableService = Pick<
ISuperTableService,
'getTableMap' | 'getTableOptionMap'
>;
export class ReferenceNode extends BaseAstNode {
private _refOffsetX = 0;
private _refOffsetY = 0;
constructor(
private _currentConfigService: ReferenceNodeCurrentConfigService,
private _runtimeService: ReferenceNodeRuntimeService,
operatorString: string,
private _referenceObjectType: ReferenceObjectType,
private _unitReferenceResolver: IFormulaUnitReferenceResolver,
private _superTableService: ReferenceNodeSuperTableService,
private _externalReferenceDataLoader: IFormulaExternalReferenceDataLoader,
private _isPrepareMerge: boolean = false,
private _tableReference?: ITableReferenceDescriptor
) {
super(operatorString);
const unitQualifier = _tableReference
? _tableReference.unitQualifier
: getReferenceObjectFromCache(operatorString, _referenceObjectType).getUnitQualifier();
if (unitQualifier) {
this.setAsync();
}
}
override get nodeType() {
return NodeType.REFERENCE;
}
override execute() {
const currentConfigService = this._currentConfigService;
const runtimeService = this._runtimeService;
let referenceObject: BaseReferenceObject;
if (this._tableReference) {
const { unitQualifier, tableName, columnStruct } = this._tableReference;
const resolution = this._unitReferenceResolver.resolve({
hostUnitId: runtimeService.currentUnitId,
qualifier: unitQualifier,
referenceKind: 'table',
});
if (typeof resolution === 'string') {
this.setValue(ErrorValueObject.create(resolution));
return;
}
const tableMap = this._superTableService.getTableMap(resolution.unitId);
const tableData = Array.from(tableMap?.entries() || []).find(([name]) => name.toLocaleLowerCase() === tableName.toLocaleLowerCase())?.[1];
if (!tableData) {
this.setValue(ErrorValueObject.create(ErrorType.REF));
return;
}
referenceObject = new TableReferenceObject(
this.getToken(),
tableData,
columnStruct,
this._superTableService.getTableOptionMap()
);
referenceObject.setUnitQualifier(unitQualifier);
referenceObject.setForcedUnitIdDirect(resolution.unitId);
} else {
referenceObject = getReferenceObjectFromCache(this.getToken(), this._referenceObjectType);
const unitQualifier = referenceObject.getUnitQualifier();
if (unitQualifier) {
const resolution = this._unitReferenceResolver.resolve({
hostUnitId: runtimeService.currentUnitId,
qualifier: unitQualifier,
referenceKind: 'a1',
});
if (typeof resolution === 'string') {
this.setValue(ErrorValueObject.create(resolution));
return;
}
referenceObject.setForcedUnitIdDirect(resolution.unitId);
}
}
this._configureReferenceObject(referenceObject, currentConfigService, runtimeService);
if (!this._isPrepareMerge && referenceObject.isExceedRange()) {
this.setValue(ErrorValueObject.create(ErrorType.NAME));
} else {
this.setValue(referenceObject);
}
}
override async executeAsync(): Promise<AstNodePromiseType> {
const hostUnitId = this._runtimeService.currentUnitId;
const unitQualifier = this._tableReference
? this._tableReference.unitQualifier
: getReferenceObjectFromCache(this.getToken(), this._referenceObjectType).getUnitQualifier();
if (!unitQualifier) {
this.execute();
return AstNodePromiseType.SUCCESS;
}
const referenceKind = this._tableReference ? 'table' : 'a1';
const resolution = this._unitReferenceResolver.resolve({
hostUnitId,
qualifier: unitQualifier,
referenceKind,
});
if (typeof resolution === 'string') {
this.setValue(ErrorValueObject.create(resolution));
return AstNodePromiseType.ERROR;
}
if (resolution.externalReference) {
const token = this._getExternalLoadToken();
const error = await this._externalReferenceDataLoader.load({
hostUnitId,
qualifier: unitQualifier,
referenceKind,
token,
tableName: this._tableReference?.tableName,
columnStruct: this._tableReference?.columnStruct,
resolution,
});
if (error) {
this.setValue(ErrorValueObject.create(error));
return AstNodePromiseType.ERROR;
}
}
this.execute();
return AstNodePromiseType.SUCCESS;
}
/**
* A1 ranges are represented as two ReferenceNodes under a `:` UnionNode.
* The qualified left node owns the external read, so include the right
* boundary and materialize the whole rectangular range in one request.
*/
private _getExternalLoadToken(): string {
const parent = this.getParent();
if (parent?.nodeType !== NodeType.UNION || parent.getToken() !== matchToken.COLON) {
return this.getToken();
}
const [left, right] = parent.getChildren();
if (left !== this || right == null) {
return this.getToken();
}
return `${this.getToken()}${matchToken.COLON}${right.getToken()}`;
}
private _configureReferenceObject(
referenceObject: BaseReferenceObject,
currentConfigService: ReferenceNodeCurrentConfigService,
runtimeService: ReferenceNodeRuntimeService
): void {
referenceObject.setDefaultUnitId(runtimeService.currentUnitId);
referenceObject.setDefaultSheetId(runtimeService.currentSubUnitId);
referenceObject.setForcedSheetId(currentConfigService.getSheetNameMap());
referenceObject.setUnitData(currentConfigService.getUnitData());
referenceObject.setArrayFormulaCellData(currentConfigService.getArrayFormulaCellData());
referenceObject.setArrayFormulaRange(currentConfigService.getArrayFormulaRange());
referenceObject.setRuntimeData(runtimeService.getUnitData());
referenceObject.setUnitStylesData(currentConfigService.getUnitStylesData());
referenceObject.setRuntimeArrayFormulaCellData(runtimeService.getRuntimeArrayFormulaCellData());
referenceObject.setRuntimeArrayFormulaRange(runtimeService.getUnitArrayFormula());
referenceObject.setRuntimeFeatureCellData(runtimeService.getRuntimeFeatureCellData());
referenceObject.setCurrentRowAndColumn(runtimeService.currentRow, runtimeService.currentColumn);
const { x, y } = this.getRefOffset();
referenceObject.setRefOffset(x, y);
}
setRefOffset(x: number = 0, y: number = 0) {
this._refOffsetX = x;
this._refOffsetY = y;
}
getRefOffset() {
return {
x: this._refOffsetX,
y: this._refOffsetY,
};
}
}
export class ReferenceNodeFactory extends BaseAstNodeFactory {
constructor(
@IFormulaCurrentConfigService private readonly _currentConfigService: IFormulaCurrentConfigService,
@IFormulaRuntimeService private readonly _formulaRuntimeService: IFormulaRuntimeService,
@IFunctionService private readonly _functionService: IFunctionService,
@ISuperTableService private readonly _superTableService: ISuperTableService,
@IFormulaUnitReferenceResolver private readonly _unitReferenceResolver: IFormulaUnitReferenceResolver,
@IFormulaExternalReferenceDataLoader
private readonly _externalReferenceDataLoader: IFormulaExternalReferenceDataLoader
) {
super();
}
override get zIndex() {
return NODE_ORDER_MAP.get(NodeType.REFERENCE) || DEFAULT_AST_NODE_FACTORY_Z_INDEX;
}
override checkAndCreateNodeType(param: LexerNode | string) {
let isLexerNode = false;
let tokenTrimPre: string;
let isPrepareMerge = false;
if (param instanceof LexerNode) {
isLexerNode = true;
tokenTrimPre = param.getToken().trim();
/**
* If this node is a reference to a range,
* it is necessary to determine whether it will be combined into a single range by a union operation.
*/
if (param.getParent()?.getParent()?.getToken().trim() === matchToken.COLON) {
isPrepareMerge = true;
}
} else {
tokenTrimPre = param.trim();
}
// const tokenTrim = param.trim();
// if (regexTestSingeRange(tokenTrim)) {
// return true;
// }
const { tokenTrim, minusPrefixNode, atPrefixNode } = prefixHandler(tokenTrimPre, this._functionService, this._formulaRuntimeService);
if (!isLexerNode && tokenTrim.charAt(0) === '"' && tokenTrim.charAt(tokenTrim.length - 1) === '"') {
return;
}
const node: Nullable<ReferenceNode> = this._getNode(tokenTrim, isLexerNode, isPrepareMerge, param);
if (node) {
if (atPrefixNode) {
node.setParent(atPrefixNode);
} else if (minusPrefixNode) {
node.setParent(minusPrefixNode);
}
return node;
}
}
private _getTableMap() {
const unitId = this._formulaRuntimeService.currentUnitId;
if (!unitId) {
return;
}
return this._superTableService.getTableMap(unitId);
}
private _getNode(
tokenTrim: string,
isLexerNode: boolean,
isPrepareMerge: boolean,
param: LexerNode | string
) {
const currentConfigService = this._currentConfigService;
const runtimeService = this._formulaRuntimeService;
const makeRef = (type: ReferenceObjectType) =>
new ReferenceNode(currentConfigService, runtimeService, tokenTrim, type, this._unitReferenceResolver, this._superTableService, this._externalReferenceDataLoader, isPrepareMerge);
const tableMap = this._getTableMap();
const isSuperTableDirect = tableMap?.has(tokenTrim) ?? false;
if (isSuperTableDirect) {
return this._getTableReferenceNode(tokenTrim, isPrepareMerge, true);
}
const isCellRange = regexTestSingeRange(tokenTrim);
if (isCellRange) {
return makeRef(ReferenceObjectType.CELL);
}
const parentIsUnion = isLexerNode && this._checkParentIsUnionOperator(param as LexerNode);
const isRowRef = parentIsUnion && regexTestSingleRow(tokenTrim);
if (isRowRef) {
return makeRef(ReferenceObjectType.ROW);
}
const isColRef = parentIsUnion && regexTestSingleColumn(tokenTrim);
if (isColRef) {
return makeRef(ReferenceObjectType.COLUMN);
}
return this._getTableReferenceNode(tokenTrim, isPrepareMerge, false);
}
private _getTableReferenceNode(tokenTrim: string, isPrepareMerge: boolean, isSuperTableDirectly: boolean = false) {
if (!this._checkTokenIsTableReference(tokenTrim) && !isSuperTableDirectly) {
return;
}
const { unitQualifier, tableName, columnStruct } = splitTableStructuredRef(tokenTrim);
const tableMap = this._getTableMap();
const hasLocalTable = Array.from(tableMap?.keys() || []).some((name) => name.toLocaleLowerCase() === tableName.toLocaleLowerCase());
if (unitQualifier || hasLocalTable) {
return new ReferenceNode(
this._currentConfigService,
this._formulaRuntimeService,
tokenTrim,
ReferenceObjectType.COLUMN,
this._unitReferenceResolver,
this._superTableService,
this._externalReferenceDataLoader,
isPrepareMerge,
{ unitQualifier, tableName, columnStruct }
);
}
}
private _checkTokenIsTableReference(token: string): boolean {
return regexTestReferenceTableAllColumn(token) || regexTestReferenceTableSingleColumn(token) || regexTestReferenceTableMultipleColumn(token) || regexTestReferenceTableTitleOnlyAnyHash(token);
}
private _checkParentIsUnionOperator(param: LexerNode) {
return param.getParent()?.getParent()?.getToken().trim() === matchToken.COLON;
}
}

View File

@@ -0,0 +1,157 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { BaseFunction } from '../../functions/base-function';
import type { BaseReferenceObject, FunctionVariantType } from '../reference-object/base-reference-object';
import type { CellReferenceObject } from '../reference-object/cell-reference-object';
import type { BaseValueObject } from '../value-object/base-value-object';
import { DateSystem, Inject } from '@univerjs/core';
import { ErrorType } from '../../basics/error-type';
import { suffixToken } from '../../basics/token';
import { FUNCTION_NAMES_META } from '../../functions/meta/function-names';
import { IFormulaCurrentConfigService } from '../../services/current-data.service';
import { IFunctionService } from '../../services/function.service';
import { Lexer } from '../analysis/lexer';
import { LexerNode } from '../analysis/lexer-node';
import { ErrorValueObject } from '../value-object/base-value-object';
import { NumberValueObject } from '../value-object/primitive-object';
import { BaseAstNode, ErrorNode } from './base-ast-node';
import { BaseAstNodeFactory, DEFAULT_AST_NODE_FACTORY_Z_INDEX } from './base-ast-node-factory';
import { NODE_ORDER_MAP, NodeType } from './node-type';
export class SuffixNode extends BaseAstNode {
constructor(
private _currentConfigService: IFormulaCurrentConfigService,
private _lexer: Lexer,
private _operatorString: string,
private _functionExecutor?: BaseFunction
) {
super(_operatorString);
}
override get nodeType() {
return NodeType.SUFFIX;
}
override execute(dateSystem: DateSystem = DateSystem.Date1900) {
const children = this.getChildren();
let value = children[0]?.getValue();
let result: FunctionVariantType;
if (value == null) {
this.setValue(ErrorValueObject.create(ErrorType.ERROR));
return;
}
if (this._operatorString === suffixToken.PERCENTAGE) {
if (value.isReferenceObject()) {
value = (value as BaseReferenceObject).withDateSystem(dateSystem).toArrayValueObject();
}
result = this._functionExecutor!.calculate(
(value as BaseValueObject).withDateSystem(dateSystem),
NumberValueObject.create(100)
) as FunctionVariantType;
// set number format
if ((result as NumberValueObject).isNumber()) {
const value = Number((result as NumberValueObject).getValue());
result = NumberValueObject.create(value, '0.00%');
}
} else if (this._operatorString === suffixToken.POUND) {
result = this._handlerPound(value);
} else {
result = ErrorValueObject.create(ErrorType.VALUE);
}
this.setValue(result.withDateSystem(dateSystem));
}
private _handlerPound(value: FunctionVariantType) {
// const sheetData = interpreterDatasetConfig.sheetData;
// if (!sheetData) {
// return false;
// }
if (!value.isReferenceObject()) {
return ErrorValueObject.create(ErrorType.VALUE);
}
if (!(value as BaseReferenceObject).isCell()) {
return ErrorValueObject.create(ErrorType.VALUE);
}
// const currentConfigService = this._accessor.get(IFormulaCurrentConfigService);
// const lexer = this._accessor.get(Lexer);
const cellValue = value as CellReferenceObject;
const range = cellValue.getRangePosition();
const unitId = cellValue.getUnitId();
const sheetId = cellValue.getSheetId();
const formulaData = this._currentConfigService.getFormulaData();
const formulaString = formulaData?.[unitId]?.[sheetId]?.[range.startRow]?.[range.startColumn]?.f;
if (!formulaString) {
return ErrorValueObject.create(ErrorType.VALUE);
}
const lexerNode = this._lexer.treeBuilder(formulaString, true, unitId);
return ErrorValueObject.create(ErrorType.VALUE);
/** todo */
}
}
export class SuffixNodeFactory extends BaseAstNodeFactory {
constructor(
@IFunctionService private readonly _functionService: IFunctionService,
@Inject(Lexer) private readonly _lexer: Lexer,
@IFormulaCurrentConfigService private readonly _currentConfigService: IFormulaCurrentConfigService
) {
super();
}
override get zIndex() {
return NODE_ORDER_MAP.get(NodeType.SUFFIX) || DEFAULT_AST_NODE_FACTORY_Z_INDEX;
}
override checkAndCreateNodeType(param: LexerNode | string) {
if (!(param instanceof LexerNode)) {
return;
}
const tokenTrim = param.getToken().trim();
if (tokenTrim.charAt(0) === '"' && tokenTrim.charAt(tokenTrim.length - 1) === '"') {
return;
}
let functionName = '';
if (tokenTrim === suffixToken.PERCENTAGE) {
functionName = FUNCTION_NAMES_META.DIVIDED;
} else if (tokenTrim === suffixToken.POUND) {
return new SuffixNode(this._currentConfigService, this._lexer, tokenTrim);
} else {
return;
}
const functionExecutor = this._functionService.getExecutor(functionName);
if (!functionExecutor) {
console.error(`No function ${param}`);
return ErrorNode.create(ErrorType.NAME);
}
return new SuffixNode(this._currentConfigService, this._lexer, tokenTrim, functionExecutor);
}
}

View File

@@ -0,0 +1,209 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { FunctionVariantType } from '../reference-object/base-reference-object';
import { ErrorType } from '../../basics/error-type';
import { matchToken } from '../../basics/token';
import { IFormulaCurrentConfigService } from '../../services/current-data.service';
import { IFunctionService } from '../../services/function.service';
import { LexerNode } from '../analysis/lexer-node';
import { BaseReferenceObject } from '../reference-object/base-reference-object';
import { MultiAreaArrayMode, MultiAreaReferenceObject } from '../reference-object/multi-area-reference-object';
import { RangeReferenceObject } from '../reference-object/range-reference-object';
import { getRangeReferenceObjectFromCache } from '../utils/value-object';
import { ErrorValueObject } from '../value-object/base-value-object';
import { BaseAstNode } from './base-ast-node';
import { BaseAstNodeFactory, DEFAULT_AST_NODE_FACTORY_Z_INDEX } from './base-ast-node-factory';
import { NODE_ORDER_MAP, NodeType } from './node-type';
// const UNION_EXECUTOR_NAME = 'UNION';
export class UnionNode extends BaseAstNode {
constructor(
operatorString: string,
private readonly _currentConfigService: Pick<IFormulaCurrentConfigService, 'getSheetsInfo'>
) {
super(operatorString);
}
override get nodeType() {
return NodeType.UNION;
}
override execute() {
const children = this.getChildren();
const leftChild = children[0];
const rightChild = children[1];
const leftNode = leftChild.getValue();
const rightNode = rightChild.getValue();
if (leftNode == null || rightNode == null) {
console.error('UnionNode execute leftNode or rightNode is null');
this.setValue(ErrorValueObject.create(ErrorType.VALUE));
return;
}
let result: FunctionVariantType;
if (this.getToken() === matchToken.COLON) {
result = this._createThreeDimensionalReference(leftChild.getToken(), rightNode)
?? this._unionFunction(leftNode, rightNode);
} else {
result = ErrorValueObject.create(ErrorType.NAME);
}
this.setValue(result);
}
private _createThreeDimensionalReference(
firstSheetToken: string,
rightNode: FunctionVariantType
): MultiAreaReferenceObject | undefined {
if (!(rightNode instanceof BaseReferenceObject)) {
return;
}
const source = rightNode;
const firstSheetName = this._normalizeSheetName(firstSheetToken);
const lastSheetName = this._normalizeSheetName(source.getForcedSheetName());
if (!firstSheetName || !lastSheetName) {
return;
}
const { sheetOrder, sheetNameMap } = this._currentConfigService.getSheetsInfo();
const sheetIdsByName = new Map(
Object.entries(sheetNameMap).map(([sheetId, sheetName]) => [sheetName.toLocaleLowerCase(), sheetId])
);
const firstSheetId = sheetIdsByName.get(firstSheetName.toLocaleLowerCase());
const lastSheetId = sheetIdsByName.get(lastSheetName.toLocaleLowerCase());
const firstIndex = firstSheetId == null ? -1 : sheetOrder.indexOf(firstSheetId);
const lastIndex = lastSheetId == null ? -1 : sheetOrder.indexOf(lastSheetId);
if (firstIndex < 0 || lastIndex < 0) {
return;
}
const startIndex = Math.min(firstIndex, lastIndex);
const endIndex = Math.max(firstIndex, lastIndex);
const areas = sheetOrder.slice(startIndex, endIndex + 1).map((sheetId) => {
const reference = new RangeReferenceObject(source.getRangeData(), sheetId, source.getUnitId());
this._copyReferenceContext(source, reference);
reference.setForcedSheetIdDirect(sheetId);
reference.setForcedSheetName(sheetNameMap[sheetId] ?? '');
return [reference];
});
return new MultiAreaReferenceObject(
`${firstSheetToken}:${source.getToken()}`,
areas,
MultiAreaArrayMode.STACK_AREAS
);
}
private _normalizeSheetName(token: string): string {
const trimmed = token.trim();
if (trimmed.startsWith("'") && trimmed.endsWith("'")) {
return trimmed.slice(1, -1).replace(/''/g, "'");
}
return trimmed;
}
private _copyReferenceContext(source: BaseReferenceObject, target: BaseReferenceObject): void {
target.setDefaultUnitId(source.getDefaultUnitId());
target.setDefaultSheetId(source.getDefaultSheetId());
target.setUnitData(source.getUnitData());
target.setUnitStylesData(source.getUnitStylesData());
target.setFilteredOutRows(source.getFilteredOutRows());
target.setRuntimeData(source.getRuntimeData());
target.setArrayFormulaCellData(source.getArrayFormulaCellData());
target.setArrayFormulaRange(source.getArrayFormulaRange());
target.setRuntimeArrayFormulaCellData(source.getRuntimeArrayFormulaCellData());
target.setRuntimeArrayFormulaRange(source.getRuntimeArrayFormulaRange());
target.setRuntimeFeatureCellData(source.getRuntimeFeatureCellData());
const currentRow = source.getCurrentRow();
const currentColumn = source.getCurrentColumn();
if (currentRow != null && currentColumn != null) {
target.setCurrentRowAndColumn(currentRow, currentColumn);
}
const { x, y } = source.getRefOffset();
target.setRefOffset(x, y);
}
private _unionFunction(
variant1: FunctionVariantType,
variant2: FunctionVariantType
): FunctionVariantType {
if (variant1.isError() || variant2.isError()) {
return ErrorValueObject.create(ErrorType.REF);
}
if (!(variant1 instanceof BaseReferenceObject) || !(variant2 instanceof BaseReferenceObject)) {
return ErrorValueObject.create(ErrorType.REF);
}
return getRangeReferenceObjectFromCache(variant1, variant2);
// if (variant1.isCell() && variant2.isCell()) {
// return variant1.unionBy(variant2);
// }
// if (variant1.isRow() && variant2.isRow()) {
// return variant1.unionBy(variant2);
// }
// if (variant1.isColumn() && variant2.isColumn()) {
// return variant1.unionBy(variant2);
// }
// // =A1:A gets #NAME?
// return ErrorValueObject.create(ErrorType.NAME);
}
}
export class UnionNodeFactory extends BaseAstNodeFactory {
constructor(
@IFunctionService private readonly _functionService: IFunctionService,
@IFormulaCurrentConfigService private readonly _currentConfigService: IFormulaCurrentConfigService
) {
super();
}
override get zIndex() {
return NODE_ORDER_MAP.get(NodeType.UNION) || DEFAULT_AST_NODE_FACTORY_Z_INDEX;
}
override create(param: string): BaseAstNode {
return new UnionNode(param, this._currentConfigService);
}
override checkAndCreateNodeType(param: LexerNode | string) {
if (!(param instanceof LexerNode)) {
return;
}
const token = param.getToken();
const tokenTrim = token.trim();
if (tokenTrim.charAt(0) === '"' && tokenTrim.charAt(tokenTrim.length - 1) === '"') {
return;
}
if (tokenTrim !== matchToken.COLON) {
return;
}
return this.create(tokenTrim);
}
}

View File

@@ -0,0 +1,98 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { ErrorType } from '../../basics/error-type';
import type { FunctionNode } from './';
import { DateSystem } from '@univerjs/core';
import { BooleanValue } from '../../basics/common';
import { ERROR_TYPE_SET } from '../../basics/error-type';
import { LexerNode } from '../analysis/lexer-node';
import { ValueObjectFactory } from '../value-object/array-value-object';
import { StringValueObject } from '../value-object/primitive-object';
import { BaseAstNode } from './base-ast-node';
import { BaseAstNodeFactory, DEFAULT_AST_NODE_FACTORY_Z_INDEX } from './base-ast-node-factory';
import { NODE_ORDER_MAP, NodeType } from './node-type';
export class ValueNode extends BaseAstNode {
constructor(operatorString: string) {
super(operatorString);
}
override get nodeType(): NodeType {
return NodeType.VALUE;
}
override execute(dateSystem: DateSystem = DateSystem.Date1900): void {
const token = this.getToken();
const tokenTrim = token.trim();
if (tokenTrim.startsWith('"') && tokenTrim.endsWith('"')) {
this.setValue(
StringValueObject.create(tokenTrim.slice(1, -1).replace(/""/g, '"')).withDateSystem(dateSystem)
);
return;
}
const parent = this.getParent();
let isIgnoreNumberPattern = true;
if (parent?.nodeType === NodeType.FUNCTION) {
isIgnoreNumberPattern = (parent as FunctionNode).isFunctionExecutorArgumentsIgnoreNumberPattern?.() ?? true;
}
this.setValue(ValueObjectFactory.create(token, isIgnoreNumberPattern, dateSystem));
}
}
export class ValueNodeFactory extends BaseAstNodeFactory {
override get zIndex(): number {
return NODE_ORDER_MAP.get(NodeType.VALUE) || DEFAULT_AST_NODE_FACTORY_Z_INDEX;
}
_checkValueNode(token: string): BaseAstNode | undefined {
if (Number.isNaN(Number(token))) {
const tokenTrim = token.trim();
const startToken = tokenTrim.charAt(0);
const endToken = tokenTrim.charAt(tokenTrim.length - 1);
if (ERROR_TYPE_SET.has(tokenTrim as ErrorType)) {
return this.create(tokenTrim);
}
if (startToken === '"' && endToken === '"') {
return this.create(tokenTrim);
}
if (startToken === '{' && endToken === '}') {
return this.create(tokenTrim);
}
const tokenTrimUpper = tokenTrim.toUpperCase();
if (tokenTrimUpper === BooleanValue.TRUE || tokenTrimUpper === BooleanValue.FALSE) {
return this.create(tokenTrimUpper);
}
} else {
return this.create(token);
}
}
override create(param: string): BaseAstNode {
return new ValueNode(param);
}
override checkAndCreateNodeType(param: LexerNode | string): BaseAstNode | undefined {
if (param instanceof LexerNode) {
return;
}
return this._checkValueNode(param);
}
}