Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(formula): formula select and negative #1444

Merged
merged 9 commits into from
Mar 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/engine-formula/src/basics/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,5 @@ export enum prefixToken {
AT = '@',
MINUS = '-',
}

export const SPACE_TOKEN = ' ';
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ describe('lexer nodeMaker test', () => {
expect(JSON.stringify(node.serialize())).toStrictEqual('{"token":"R_1","st":-1,"ed":-1,"children":["2","1","-","1","-","1","-","1","-"]}');
});

it('minus test Complex', () => {
it('minus test complex', () => {
const node = lexerTreeBuilder.treeBuilder('= ( 2019-09-09 ) ') as LexerNode;
expect(JSON.stringify(node.serialize())).toStrictEqual('{"token":"R_1","st":-1,"ed":-1,"children":[" 2019","09","-","09 ","-"]}');
});
Expand All @@ -154,6 +154,31 @@ describe('lexer nodeMaker test', () => {
const node = lexerTreeBuilder.treeBuilder('=SUM(SUM(Sheet5:Sheet6!A1:B10) + 100, 1)') as LexerNode;
expect(JSON.stringify(node.serialize())).toStrictEqual('{"token":"R_1","st":-1,"ed":-1,"children":[{"token":"SUM","st":0,"ed":2,"children":[{"token":"P_1","st":0,"ed":2,"children":[{"token":"SUM","st":4,"ed":6,"children":[{"token":"P_1","st":4,"ed":6,"children":[{"token":":","st":-1,"ed":-1,"children":[{"token":"P_1","st":-1,"ed":-1,"children":[{"token":"Sheet5","st":-1,"ed":-1,"children":[]}]},{"token":"P_1","st":-1,"ed":-1,"children":[{"token":":","st":-1,"ed":-1,"children":[{"token":"P_1","st":-1,"ed":-1,"children":[{"token":"Sheet6!A1","st":-1,"ed":-1,"children":[]}]},{"token":"P_1","st":-1,"ed":-1,"children":[{"token":"B10","st":-1,"ed":-1,"children":[]}]}]}]}]}]}]}," 100"," +"]},{"token":"P_1","st":32,"ed":34,"children":[" 1"]}]}]}');
});

it('minus ref', () => {
const node = lexerTreeBuilder.treeBuilder('= 1--A1 ') as LexerNode;
expect(JSON.stringify(node.serialize())).toStrictEqual('{"token":"R_1","st":-1,"ed":-1,"children":[" 1","-A1 ","-"]}');
});

it('negative ref', () => {
const node = lexerTreeBuilder.treeBuilder('= ------A1 ') as LexerNode;
expect(JSON.stringify(node.serialize())).toStrictEqual('{"token":"R_1","st":-1,"ed":-1,"children":[" -","-","-","-","-","A1 ","-"]}');
});

it('scientific notation', () => {
const node = lexerTreeBuilder.treeBuilder('=3e+1+1') as LexerNode;
expect(JSON.stringify(node.serialize())).toStrictEqual('{"token":"R_1","st":-1,"ed":-1,"children":["3e+1","1","+"]}');
});

it('parentheses and arithmetic', () => {
const node = lexerTreeBuilder.treeBuilder('=-(+2)+2') as LexerNode;
expect(JSON.stringify(node.serialize())).toStrictEqual('{"token":"R_1","st":-1,"ed":-1,"children":["0","2","-","2","+"]}');
});

it('plus token error', () => {
const node = lexerTreeBuilder.treeBuilder('=-(2)+*9') as LexerNode;
expect(JSON.stringify(node.serialize())).toStrictEqual('{"token":"R_1","st":-1,"ed":-1,"children":["0","2","-","9","+"]}');
});
});

describe('checkIfAddBracket', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ import type { BaseValueObject } from '../../value-object/base-value-object';
import { Lexer } from '../lexer';
import type { LexerNode } from '../lexer-node';
import { AstTreeBuilder } from '../parser';
import type { ArrayValueObject } from '../../value-object/array-value-object';
import { Minus } from '../../../functions/meta/minus';
import { createCommandTestBed } from './create-command-test-bed';

describe('Test indirect', () => {
Expand Down Expand Up @@ -79,7 +81,7 @@ describe('Test indirect', () => {
testBed.unitId
);

functionService.registerExecutors(new Sum(FUNCTION_NAMES_MATH.SUM), new Plus(FUNCTION_NAMES_META.PLUS));
functionService.registerExecutors(new Sum(FUNCTION_NAMES_MATH.SUM), new Plus(FUNCTION_NAMES_META.PLUS), new Minus(FUNCTION_NAMES_META.MINUS));
});

describe('normal', () => {
Expand All @@ -102,5 +104,65 @@ describe('Test indirect', () => {

expect((result as BaseValueObject).getValue()).toStrictEqual(ErrorType.NAME);
});

it('Minus Minus one', async () => {
const lexerNode = lexer.treeBuilder('=--1');

const astNode = astTreeBuilder.parse(lexerNode as LexerNode);

const result = interpreter.execute(astNode as BaseAstNode);

expect((result as BaseValueObject).getValue()).toStrictEqual(1);
});

it('Minus Minus Minus one', async () => {
const lexerNode = lexer.treeBuilder('=---1');

const astNode = astTreeBuilder.parse(lexerNode as LexerNode);

const result = interpreter.execute(astNode as BaseAstNode);

expect((result as BaseValueObject).getValue()).toStrictEqual(-1);
});

it('Plus Plus Plus Plus Plus Plus one', async () => {
const lexerNode = lexer.treeBuilder('=++++++1');

const astNode = astTreeBuilder.parse(lexerNode as LexerNode);

const result = interpreter.execute(astNode as BaseAstNode);

expect((result as BaseValueObject).getValue()).toStrictEqual(1);
});

it('Plus Plus Plus ref', async () => {
const lexerNode = lexer.treeBuilder('=+++++++A1');

const astNode = astTreeBuilder.parse(lexerNode as LexerNode);

const result = interpreter.execute(astNode as BaseAstNode);

expect((result as ArrayValueObject).getFirstCell().getValue()).toStrictEqual(1);
});

it('Minus Minus Minus ref', async () => {
const lexerNode = lexer.treeBuilder('=---A1');

const astNode = astTreeBuilder.parse(lexerNode as LexerNode);

const result = interpreter.execute(astNode as BaseAstNode);

expect((result as ArrayValueObject).getFirstCell().getValue()).toStrictEqual(-1);
});

it('Minus Minus Minus Minus sum', async () => {
const lexerNode = lexer.treeBuilder('=----sum(A1:A2)');

const astNode = astTreeBuilder.parse(lexerNode as LexerNode);

const result = interpreter.execute(astNode as BaseAstNode);

expect((result as BaseValueObject).getValue()).toStrictEqual(4);
});
});
});
101 changes: 87 additions & 14 deletions packages/engine-formula/src/engine/analysis/lexer-tree-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
OPERATOR_TOKEN_SET,
operatorToken,
prefixToken,
SPACE_TOKEN,
SUFFIX_TOKEN_SET,
suffixToken,
} from '../../basics/token';
Expand Down Expand Up @@ -213,7 +214,6 @@ export class LexerTreeBuilder extends Disposable {
/**
* Estimate the number of right brackets that need to be automatically added to the end of the formula.
* @param formulaString
* @returns
*/
checkIfAddBracket(formulaString: string) {
let lastBracketCount = 0;
Expand Down Expand Up @@ -312,7 +312,7 @@ export class LexerTreeBuilder extends Disposable {

const preItem = sequenceArray[i - 1];

const { segment, currentString, cur } = item;
const { segment, currentString } = item;

if (currentString === matchToken.DOUBLE_QUOTATION) {
maybeString = true;
Expand All @@ -325,11 +325,11 @@ export class LexerTreeBuilder extends Disposable {

let preSegment = preItem?.segment || '';

const startIndex = i - preSegment.length;
let startIndex = i - preSegment.length;

let endIndex = i - 1;

const deleteEndIndex = i - 1;
let deleteEndIndex = i - 1;

if (i === len - 1 && this._isLastMergeString(currentString)) {
preSegment += currentString;
Expand Down Expand Up @@ -359,6 +359,17 @@ export class LexerTreeBuilder extends Disposable {
deleteEndIndex
);
} else if (new RegExp(REFERENCE_SINGLE_RANGE_REGEX).test(preSegmentNotPrefixToken)) {
/**
* =-A1 Separate the negative sign from the ref string.
*/
if (preSegmentNotPrefixToken.length !== preSegmentTrim.length) {
const minusCount = preSegmentTrim.length - preSegmentNotPrefixToken.length;
deleteEndIndex += minusCount;
startIndex += minusCount;

preSegment = this._replacePrefixString(preSegment);
}

this._pushSequenceNode(
sequenceNodes,
{
Expand Down Expand Up @@ -481,7 +492,20 @@ export class LexerTreeBuilder extends Disposable {
}

private _replacePrefixString(token: string) {
return token.replace(new RegExp(prefixToken.AT, 'g'), '').replace(new RegExp(prefixToken.MINUS, 'g'), '');
const tokenArray = [];
let isNotPreFix = false;
for (let i = 0, len = token.length; i < len; i++) {
const char = token[i];
if (char === SPACE_TOKEN && !isNotPreFix) {
tokenArray.push(char);
} else if (!isNotPreFix && (char === prefixToken.AT || char === prefixToken.MINUS)) {
continue;
} else {
tokenArray.push(char);
isNotPreFix = true;
}
}
return tokenArray.join('');
}

nodeMakerTest(formulaString: string) {
Expand Down Expand Up @@ -575,6 +599,14 @@ export class LexerTreeBuilder extends Disposable {
}

if (OPERATOR_TOKEN_SET.has(char)) {
// fix =-(+2)+2
if (char === operatorToken.PLUS && this._deletePlusForPreNode(children[i - 1])) {
continue;
}
// =-(2)+*9
if (char !== operatorToken.PLUS && char !== operatorToken.MINUS && this._deletePlusForPreNode(children[i - 1])) {
continue;
}
while (symbolStack.length > 0) {
const lastSymbol = symbolStack[symbolStack.length - 1]?.trim();
if (!lastSymbol || lastSymbol === matchToken.OPEN_BRACKET) {
Expand Down Expand Up @@ -625,6 +657,21 @@ export class LexerTreeBuilder extends Disposable {
lexerNode.setChildren(baseStack);
}

private _deletePlusForPreNode(preNode: Nullable<string | LexerNode>) {
if (preNode == null) {
return true;
}

if (!(preNode instanceof LexerNode)) {
const preChar = preNode.trim();
if (OPERATOR_TOKEN_SET.has(preChar) || preChar === matchToken.OPEN_BRACKET) {
return true;
}
}

return false;
}

private _resetCurrentLexerNode() {
this._currentLexerNode = new LexerNode();
}
Expand Down Expand Up @@ -904,6 +951,13 @@ export class LexerTreeBuilder extends Disposable {
if (formulaString.substring(0, 1) === operatorToken.EQUALS) {
formulaString = formulaString.substring(1);
}

let isZeroAdded = false;
if (formulaString.substring(0, 1) === operatorToken.MINUS) {
formulaString = `0${formulaString}`;
isZeroAdded = true;
}

const formulaStringArray = formulaString.split('');
const formulaStringArrayCount = formulaStringArray.length;
let cur = 0;
Expand Down Expand Up @@ -1218,21 +1272,38 @@ export class LexerTreeBuilder extends Disposable {
) {
let trimSegment = this._segment.trim();

if (currentString === operatorToken.MINUS && trimSegment === '') {
if (currentString === operatorToken.MINUS && (trimSegment === '')) {
// negative number
const prevString = this._findPreviousToken(formulaStringArray, cur - 1) || '';
if (this._negativeCondition(prevString)) {
this._pushSegment(operatorToken.MINUS);

if (!(isZeroAdded && cur === 0)) {
sequenceArray?.push({
segment: this._segment,
currentString,
cur,
currentLexerNode: this._currentLexerNode,
});
}

cur++;
continue;
}
} else if (this._segment.length > 0 && formulaStringArray[cur - 1] && formulaStringArray[cur - 1].toUpperCase() === 'E' && (currentString === operatorToken.MINUS || currentString === operatorToken.PLUS)) {
this._pushSegment(currentString);

if (!(isZeroAdded && cur === 0)) {
sequenceArray?.push({
segment: this._segment,
currentString,
cur,
currentLexerNode: this._currentLexerNode,
});
cur++;
continue;
}

cur++;
continue;
} else if (this._segment.length > 0 && trimSegment === '') {
trimSegment = this._segment;
} else {
Expand All @@ -1256,12 +1327,14 @@ export class LexerTreeBuilder extends Disposable {
this._pushSegment(currentString);
}

sequenceArray?.push({
segment: this._segment,
currentString,
cur,
currentLexerNode: this._currentLexerNode,
});
if (!(isZeroAdded && cur === 0)) {
sequenceArray?.push({
segment: this._segment,
currentString,
cur,
currentLexerNode: this._currentLexerNode,
});
}
cur++;
}

Expand Down
19 changes: 3 additions & 16 deletions packages/engine-formula/src/engine/analysis/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import { SuffixNodeFactory } from '../ast-node/suffix-node';
import { UnionNodeFactory } from '../ast-node/union-node';
import { ValueNodeFactory } from '../ast-node/value-node';
import { isChildRunTimeParameter, isFirstChildParameter } from '../utils/function-definition';
import { getAstNodeTopParent } from '../utils/ast-node-tool';
import { LexerNode } from './lexer-node';

export class AstTreeBuilder extends Disposable {
Expand Down Expand Up @@ -173,15 +174,6 @@ export class AstTreeBuilder extends Disposable {
return newLambdaNode;
}

private _getTopParent(node: BaseAstNode) {
let parent: Nullable<BaseAstNode> = node;
while (parent?.getParent()) {
parent = parent.getParent();
// console.log(parent);
}
return parent;
}

private _parse(lexerNode: LexerNode, parent: BaseAstNode): Nullable<BaseAstNode> {
const children = lexerNode.getChildren();
const childrenCount = children.length;
Expand Down Expand Up @@ -277,7 +269,7 @@ export class AstTreeBuilder extends Disposable {
return ErrorNode.create(ErrorType.NAME);
}

astNode = this._getTopParent(astNode);
astNode = getAstNodeTopParent(astNode);
if (astNode == null) {
return;
}
Expand All @@ -297,18 +289,13 @@ export class AstTreeBuilder extends Disposable {
case NodeType.OPERATOR: {
const parameterNode1 = calculateStack.pop();
const parameterNode2 = calculateStack.pop();

if (parameterNode2) {
parameterNode2.setParent(astNode);
} else {
// console.log('error4', currentAstNode, lexerNode, children, i);
return ErrorNode.create(ErrorType.ERROR);
}

if (parameterNode1) {
parameterNode1.setParent(astNode);
} else {
// console.log('error5', currentAstNode, lexerNode, children, i);
return ErrorNode.create(ErrorType.ERROR);
}

calculateStack.push(astNode);
Expand Down
Loading
Loading