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
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:
@@ -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 };
|
||||
}
|
||||
@@ -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",
|
||||
}));
|
||||
});
|
||||
});
|
||||
@@ -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',
|
||||
]);
|
||||
});
|
||||
});
|
||||
94
packages/engine-formula/src/engine/ast-node/ast-root-node.ts
Normal file
94
packages/engine-formula/src/engine/ast-node/ast-root-node.ts
Normal 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());
|
||||
@@ -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>;
|
||||
}
|
||||
214
packages/engine-formula/src/engine/ast-node/base-ast-node.ts
Normal file
214
packages/engine-formula/src/engine/ast-node/base-ast-node.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
565
packages/engine-formula/src/engine/ast-node/function-node.ts
Normal file
565
packages/engine-formula/src/engine/ast-node/function-node.ts
Normal 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;
|
||||
}
|
||||
26
packages/engine-formula/src/engine/ast-node/index.ts
Normal file
26
packages/engine-formula/src/engine/ast-node/index.ts
Normal 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';
|
||||
191
packages/engine-formula/src/engine/ast-node/lambda-node.ts
Normal file
191
packages/engine-formula/src/engine/ast-node/lambda-node.ts
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
44
packages/engine-formula/src/engine/ast-node/node-type.ts
Normal file
44
packages/engine-formula/src/engine/ast-node/node-type.ts
Normal 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],
|
||||
]);
|
||||
33
packages/engine-formula/src/engine/ast-node/null-node.ts
Normal file
33
packages/engine-formula/src/engine/ast-node/null-node.ts
Normal 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());
|
||||
}
|
||||
}
|
||||
179
packages/engine-formula/src/engine/ast-node/operator-node.ts
Normal file
179
packages/engine-formula/src/engine/ast-node/operator-node.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
193
packages/engine-formula/src/engine/ast-node/prefix-node.ts
Normal file
193
packages/engine-formula/src/engine/ast-node/prefix-node.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
385
packages/engine-formula/src/engine/ast-node/reference-node.ts
Normal file
385
packages/engine-formula/src/engine/ast-node/reference-node.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
157
packages/engine-formula/src/engine/ast-node/suffix-node.ts
Normal file
157
packages/engine-formula/src/engine/ast-node/suffix-node.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
209
packages/engine-formula/src/engine/ast-node/union-node.ts
Normal file
209
packages/engine-formula/src/engine/ast-node/union-node.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
98
packages/engine-formula/src/engine/ast-node/value-node.ts
Normal file
98
packages/engine-formula/src/engine/ast-node/value-node.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user