From fe9db7e2bf12a4f5a633804fa23f460930820995 Mon Sep 17 00:00:00 2001 From: Dushusir <1414556676@qq.com> Date: Fri, 13 Dec 2024 18:26:42 +0800 Subject: [PATCH 1/5] feat(formula): support lambda in function register --- .../src/engine/ast-node/function-node.ts | 5 +++++ .../src/engine/value-object/lambda-value-object.ts | 12 ++++++++++++ .../src/engine/value-object/primitive-object.ts | 2 +- packages/facade/src/apis/__tests__/facade.spec.ts | 10 ++++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/packages/engine-formula/src/engine/ast-node/function-node.ts b/packages/engine-formula/src/engine/ast-node/function-node.ts index f219b3011a7c..b2731da1e936 100644 --- a/packages/engine-formula/src/engine/ast-node/function-node.ts +++ b/packages/engine-formula/src/engine/ast-node/function-node.ts @@ -240,6 +240,11 @@ export class FunctionNode extends BaseAstNode { if (variant.isArray()) { return (variant as ArrayValueObject).toValue(); } + + if (variant.isLambda()) { + return variant; + } + return variant.getValue(); }); } diff --git a/packages/engine-formula/src/engine/value-object/lambda-value-object.ts b/packages/engine-formula/src/engine/value-object/lambda-value-object.ts index 86bd578eefb3..31b3cda2df93 100644 --- a/packages/engine-formula/src/engine/value-object/lambda-value-object.ts +++ b/packages/engine-formula/src/engine/value-object/lambda-value-object.ts @@ -20,10 +20,12 @@ import type { BaseAstNode } from '../ast-node/base-ast-node'; import type { LambdaParameterNode } from '../ast-node/lambda-parameter-node'; import type { Interpreter } from '../interpreter/interpreter'; import type { BaseReferenceObject, FunctionVariantType } from '../reference-object/base-reference-object'; +import type { PrimitiveValueType } from './primitive-object'; import { ErrorType } from '../../basics/error-type'; import { DEFAULT_TOKEN_TYPE_LAMBDA_RUNTIME_PARAMETER } from '../../basics/token-type'; import { AsyncObject } from '../reference-object/base-reference-object'; import { generateExecuteAstNodeData } from '../utils/ast-node-tool'; +import { ValueObjectFactory } from './array-value-object'; import { BaseValueObject, ErrorValueObject } from './base-value-object'; function getRootLexerHasValueNode(node: Nullable): Nullable { @@ -110,6 +112,16 @@ export class LambdaValueObjectObject extends BaseValueObject { return value; } + /** + * Execute custom lambda function, handle basic types + * @param variants + */ + executeCustom(...variants: PrimitiveValueType[]) { + // Create base value object from primitive value, then execute + const baseValueObjects = variants.map((variant) => ValueObjectFactory.create(variant)); + return this.execute(...baseValueObjects); + } + private _setLambdaNodeValue(node: Nullable) { if (!node) { return; diff --git a/packages/engine-formula/src/engine/value-object/primitive-object.ts b/packages/engine-formula/src/engine/value-object/primitive-object.ts index d7277a5df706..1ce8b76695dc 100644 --- a/packages/engine-formula/src/engine/value-object/primitive-object.ts +++ b/packages/engine-formula/src/engine/value-object/primitive-object.ts @@ -27,7 +27,7 @@ import { BaseValueObject, ErrorValueObject } from './base-value-object'; export type PrimitiveValueType = string | boolean | number | null; -export type FormulaFunctionValueType = PrimitiveValueType | PrimitiveValueType[][]; +export type FormulaFunctionValueType = PrimitiveValueType | PrimitiveValueType[][] | BaseValueObject; export class NullValueObject extends BaseValueObject { private static _instance: NullValueObject; diff --git a/packages/facade/src/apis/__tests__/facade.spec.ts b/packages/facade/src/apis/__tests__/facade.spec.ts index 83e90fa852b0..c2bb5481589d 100644 --- a/packages/facade/src/apis/__tests__/facade.spec.ts +++ b/packages/facade/src/apis/__tests__/facade.spec.ts @@ -183,6 +183,16 @@ describe('Test FUniver', () => { [function (...variants) { let sum = 0; + const last = variants[variants.length - 1]; + // @ts-ignore + if (last.isLambda()) { + variants.pop(); + + const variantsList = variants.map((variant) => Array.isArray(variant) ? variant[0][0] : variant); + // @ts-ignore + sum += last.executeCustom(...variantsList).getValue(); + } + for (const variant of variants) { sum += Number(variant) || 0; } From 1e410ac155e632f25b27c3c7814df57e7cb03411 Mon Sep 17 00:00:00 2001 From: Dushusir <1414556676@qq.com> Date: Wed, 18 Dec 2024 21:14:25 +0800 Subject: [PATCH 2/5] fix(facade): fix ts ignore --- packages/engine-formula/src/index.ts | 1 + packages/facade/src/apis/__tests__/facade.spec.ts | 14 ++++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/packages/engine-formula/src/index.ts b/packages/engine-formula/src/index.ts index e0bc678a0f9e..11aabc4a3fd2 100644 --- a/packages/engine-formula/src/index.ts +++ b/packages/engine-formula/src/index.ts @@ -167,3 +167,4 @@ export { ENGINE_FORMULA_CYCLE_REFERENCE_COUNT, ENGINE_FORMULA_PLUGIN_CONFIG_KEY, export { generateRandomDependencyTreeId } from './engine/dependency/formula-dependency'; export { DependencyManagerBaseService } from './services/dependency-manager.service'; +export { LambdaValueObjectObject } from './engine/value-object/lambda-value-object'; diff --git a/packages/facade/src/apis/__tests__/facade.spec.ts b/packages/facade/src/apis/__tests__/facade.spec.ts index c2bb5481589d..eae13bd4f8a4 100644 --- a/packages/facade/src/apis/__tests__/facade.spec.ts +++ b/packages/facade/src/apis/__tests__/facade.spec.ts @@ -15,6 +15,7 @@ */ import type { ICellData, Injector, Nullable } from '@univerjs/core'; +import type { LambdaValueObjectObject, PrimitiveValueType } from '@univerjs/engine-formula'; import type { ColumnHeaderLayout, RenderComponentType, @@ -24,8 +25,8 @@ import type { SpreadsheetRowHeader, } from '@univerjs/engine-render'; import type { FUniver } from '../everything'; -import { ICommandService, IUniverInstanceService } from '@univerjs/core'; +import { ICommandService, IUniverInstanceService } from '@univerjs/core'; import { RegisterFunctionMutation, SetFormulaCalculationStartMutation } from '@univerjs/engine-formula'; import { IRenderManagerService } from '@univerjs/engine-render'; import { SetRangeValuesCommand, SetRangeValuesMutation, SetStyleCommand } from '@univerjs/sheets'; @@ -178,19 +179,20 @@ describe('Test FUniver', () => { it('Function registerFunction', () => { const funcionName = 'CUSTOMSUM'; + const functionsDisposable = univerAPI.registerFunction({ calculate: [ [function (...variants) { let sum = 0; - const last = variants[variants.length - 1]; - // @ts-ignore + const last = variants[variants.length - 1] as LambdaValueObjectObject; + if (last.isLambda()) { variants.pop(); - const variantsList = variants.map((variant) => Array.isArray(variant) ? variant[0][0] : variant); - // @ts-ignore - sum += last.executeCustom(...variantsList).getValue(); + const variantsList = variants.map((variant) => Array.isArray(variant) ? variant[0][0] : variant) as PrimitiveValueType[]; + + sum += +last.executeCustom(...variantsList).getValue(); } for (const variant of variants) { From d5cb605abf40049c481e08d67f00ba3c3d814893 Mon Sep 17 00:00:00 2001 From: Dushusir <1414556676@qq.com> Date: Tue, 7 Jan 2025 15:09:20 +0800 Subject: [PATCH 3/5] chore(formula): add example for register function --- .../sheets-formula/src/facade/f-formula.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/sheets-formula/src/facade/f-formula.ts b/packages/sheets-formula/src/facade/f-formula.ts index a79ceb39efe7..1e2a4439bd9f 100644 --- a/packages/sheets-formula/src/facade/f-formula.ts +++ b/packages/sheets-formula/src/facade/f-formula.ts @@ -58,6 +58,28 @@ export interface IFFormulaSheetsMixin { * univerAPI.getActiveWorkbook().getActiveSheet().getRange('A2').setValue({ f: '=DISCOUNT(A1, 20)' }); * // A2 will display: 80 * ``` + * @example + * ```typescript + * // Registered formulas support lambda functions + * univerAPI.getFormula().registerFunction('CUSTOMSUM',(...variants) => { + * let sum = 0; + * + * const last = variants[variants.length - 1]; + * if (last.isLambda && last.isLambda()) { + * variants.pop(); + * + * const variantsList = variants.map((variant) => Array.isArray(variant) ? variant[0][0]: variant); + * + * sum += last.executeCustom(...variantsList).getValue(); + * } + * + * for (const variant of variants) { + * sum += Number(variant) || 0; + * } + * + * return sum; + * }, 'Adds its arguments'); + * ``` */ registerFunction(name: string, func: IRegisterFunction, description?: string): IDisposable; From e1146983856f192ae63ed3adf73a24ff4a360ada Mon Sep 17 00:00:00 2001 From: Dushusir <1414556676@qq.com> Date: Tue, 7 Jan 2025 15:50:39 +0800 Subject: [PATCH 4/5] fix(formula): custom function type --- .../engine-formula/src/engine/ast-node/function-node.ts | 6 +++--- .../src/engine/value-object/primitive-object.ts | 1 + packages/engine-formula/src/functions/base-function.ts | 4 ++-- packages/engine-formula/src/index.ts | 2 +- .../src/services/register-function.service.ts | 6 +++--- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/engine-formula/src/engine/ast-node/function-node.ts b/packages/engine-formula/src/engine/ast-node/function-node.ts index b2731da1e936..db9f54a83bff 100644 --- a/packages/engine-formula/src/engine/ast-node/function-node.ts +++ b/packages/engine-formula/src/engine/ast-node/function-node.ts @@ -23,7 +23,7 @@ import type { BaseReferenceObject, FunctionVariantType, NodeValueType } from '../reference-object/base-reference-object'; -import type { FormulaFunctionValueType } from '../value-object/primitive-object'; +import type { FormulaFunctionResultValueType } from '../value-object/primitive-object'; import { Inject, Injector } from '@univerjs/core'; import { AstNodePromiseType } from '../../basics/common'; import { ErrorType } from '../../basics/error-type'; @@ -206,7 +206,7 @@ export class FunctionNode extends BaseAstNode { /** * Transform the result of a custom function to a NodeValueType. */ - private _handleCustomResult(resultVariantCustom: FormulaFunctionValueType): NodeValueType { + private _handleCustomResult(resultVariantCustom: FormulaFunctionResultValueType): NodeValueType { if (typeof resultVariantCustom !== 'object' || resultVariantCustom == null) { return ValueObjectFactory.create(resultVariantCustom); } @@ -262,7 +262,7 @@ export class FunctionNode extends BaseAstNode { if (this._functionExecutor.isCustom()) { const resultVariantCustom = this._functionExecutor.calculateCustom( ...this._mapVariantsToValues(variants) - ) as FormulaFunctionValueType; + ) as FormulaFunctionResultValueType; resultVariant = this._handleCustomResult(resultVariantCustom); } else { diff --git a/packages/engine-formula/src/engine/value-object/primitive-object.ts b/packages/engine-formula/src/engine/value-object/primitive-object.ts index 1ce8b76695dc..b8da0932eb99 100644 --- a/packages/engine-formula/src/engine/value-object/primitive-object.ts +++ b/packages/engine-formula/src/engine/value-object/primitive-object.ts @@ -28,6 +28,7 @@ import { BaseValueObject, ErrorValueObject } from './base-value-object'; export type PrimitiveValueType = string | boolean | number | null; export type FormulaFunctionValueType = PrimitiveValueType | PrimitiveValueType[][] | BaseValueObject; +export type FormulaFunctionResultValueType = PrimitiveValueType | PrimitiveValueType[][]; export class NullValueObject extends BaseValueObject { private static _instance: NullValueObject; diff --git a/packages/engine-formula/src/functions/base-function.ts b/packages/engine-formula/src/functions/base-function.ts index 789660d58966..840f31a222ed 100644 --- a/packages/engine-formula/src/functions/base-function.ts +++ b/packages/engine-formula/src/functions/base-function.ts @@ -19,7 +19,7 @@ import type { IFunctionNames } from '../basics/function'; import type { BaseReferenceObject, FunctionVariantType, NodeValueType } from '../engine/reference-object/base-reference-object'; import type { ArrayBinarySearchType } from '../engine/utils/compare'; import type { ArrayValueObject } from '../engine/value-object/array-value-object'; -import type { FormulaFunctionValueType } from '../engine/value-object/primitive-object'; +import type { FormulaFunctionResultValueType, FormulaFunctionValueType } from '../engine/value-object/primitive-object'; import type { FormulaDataModel } from '../models/formula-data.model'; import type { IDefinedNameMapItem } from '../services/defined-names.service'; import { ErrorType } from '../basics/error-type'; @@ -193,7 +193,7 @@ export class BaseFunction { calculateCustom( ...arg: Array - ): FormulaFunctionValueType | Promise { + ): FormulaFunctionResultValueType | Promise { return null; } diff --git a/packages/engine-formula/src/index.ts b/packages/engine-formula/src/index.ts index 11aabc4a3fd2..993638796c8f 100644 --- a/packages/engine-formula/src/index.ts +++ b/packages/engine-formula/src/index.ts @@ -99,7 +99,7 @@ export { handleRefStringInfo } from './engine/utils/reference'; export { generateStringWithSequence, type ISequenceNode, sequenceNodeType } from './engine/utils/sequence'; export { ArrayValueObject, ValueObjectFactory } from './engine/value-object/array-value-object'; export { BaseValueObject, ErrorValueObject } from './engine/value-object/base-value-object'; -export type { FormulaFunctionValueType, PrimitiveValueType } from './engine/value-object/primitive-object'; +export type { FormulaFunctionResultValueType, FormulaFunctionValueType, PrimitiveValueType } from './engine/value-object/primitive-object'; export { BooleanValueObject, NullValueObject, NumberValueObject, StringValueObject } from './engine/value-object/primitive-object'; export { functionArray } from './functions/array/function-map'; export { FUNCTION_NAMES_ARRAY } from './functions/array/function-names'; diff --git a/packages/sheets-formula/src/services/register-function.service.ts b/packages/sheets-formula/src/services/register-function.service.ts index c1e14632f1cf..7d9ed1b0490b 100644 --- a/packages/sheets-formula/src/services/register-function.service.ts +++ b/packages/sheets-formula/src/services/register-function.service.ts @@ -15,7 +15,7 @@ */ import type { IDisposable, ILocales } from '@univerjs/core'; -import type { FormulaFunctionValueType, IFunctionInfo } from '@univerjs/engine-formula'; +import type { FormulaFunctionResultValueType, FormulaFunctionValueType, IFunctionInfo } from '@univerjs/engine-formula'; import { createIdentifier, Disposable, DisposableCollection, Inject, LocaleService, Optional, toDisposable } from '@univerjs/core'; import { AsyncCustomFunction, CustomFunction, FunctionType, IFunctionService } from '@univerjs/engine-formula'; import { IDescriptionService } from './description.service'; @@ -23,11 +23,11 @@ import { IRemoteRegisterFunctionService } from './remote/remote-register-functio export type IRegisterFunction = ( ...arg: Array -) => FormulaFunctionValueType; +) => FormulaFunctionResultValueType; export type IRegisterAsyncFunction = ( ...arg: Array -) => Promise; +) => Promise; // [[Function, FunctionName, Description]] export type IRegisterFunctionList = [[IRegisterFunction, string, string?]]; From a7f7ab2a8e134ef70daece30bf464fcd09caa0c9 Mon Sep 17 00:00:00 2001 From: Dushusir <1414556676@qq.com> Date: Fri, 10 Jan 2025 10:55:25 +0800 Subject: [PATCH 5/5] Update packages/sheets-formula/src/facade/f-formula.ts Co-authored-by: Wenzhao Hu --- packages/sheets-formula/src/facade/f-formula.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sheets-formula/src/facade/f-formula.ts b/packages/sheets-formula/src/facade/f-formula.ts index 1e2a4439bd9f..45bf767b4a03 100644 --- a/packages/sheets-formula/src/facade/f-formula.ts +++ b/packages/sheets-formula/src/facade/f-formula.ts @@ -61,7 +61,7 @@ export interface IFFormulaSheetsMixin { * @example * ```typescript * // Registered formulas support lambda functions - * univerAPI.getFormula().registerFunction('CUSTOMSUM',(...variants) => { + * univerAPI.getFormula().registerFunction('CUSTOMSUM', (...variants) => { * let sum = 0; * * const last = variants[variants.length - 1];