mirror of
https://github.com/actualbudget/actual.git
synced 2026-04-30 10:14:53 -05:00
Fix operator precedence grouping for */ and +/- (#6993)
* Fix operator precedence grouping for */ and +/- * Add release note for #6993 * Fix exponent associativity and add regression test --------- Co-authored-by: Hadi Ayache <hadiayache@Hadis-Mac-mini.local>
This commit is contained in:
@@ -39,6 +39,17 @@ describe('arithmetic', () => {
|
|||||||
expect(evalArithmetic('20^3 - 5 * (10 / 2)')).toEqual(7975);
|
expect(evalArithmetic('20^3 - 5 * (10 / 2)')).toEqual(7975);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('handles exponent as right-associative', () => {
|
||||||
|
expect(evalArithmetic('2^3^2')).toEqual(512);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('handles same-precedence operators left-to-right', () => {
|
||||||
|
expect(evalArithmetic('24 / 3 * 2')).toEqual(16);
|
||||||
|
expect(evalArithmetic('24 * 3 / 2')).toEqual(36);
|
||||||
|
expect(evalArithmetic('10 - 2 + 1')).toEqual(9);
|
||||||
|
expect(evalArithmetic('10 + 2 - 1')).toEqual(11);
|
||||||
|
});
|
||||||
|
|
||||||
test('respects current number format', () => {
|
test('respects current number format', () => {
|
||||||
expect(evalArithmetic('1,222.45')).toEqual(1222.45);
|
expect(evalArithmetic('1,222.45')).toEqual(1222.45);
|
||||||
|
|
||||||
|
|||||||
@@ -91,21 +91,34 @@ function parseParens(state: ParserState): AstNode {
|
|||||||
return parsePrimary(state);
|
return parsePrimary(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeOperatorParser<T extends readonly Operator[]>(...ops: T) {
|
function parseExponent(state: ParserState): AstNode {
|
||||||
type ParserFn = (state: ParserState) => AstNode;
|
let node = parseParens(state);
|
||||||
return ops.reduce<ParserFn>((prevParser: ParserFn, op: Operator) => {
|
if (nextOperator(state, '^')) {
|
||||||
return (state: ParserState) => {
|
node = { op: '^', left: node, right: parseExponent(state) };
|
||||||
let node = prevParser(state);
|
}
|
||||||
while (nextOperator(state, op)) {
|
return node;
|
||||||
node = { op, left: node, right: prevParser(state) };
|
}
|
||||||
}
|
|
||||||
return node;
|
function parseMultiplicative(state: ParserState): AstNode {
|
||||||
};
|
let node = parseExponent(state);
|
||||||
}, parseParens);
|
while (char(state) === '*' || char(state) === '/') {
|
||||||
|
const op = next(state) as '*' | '/';
|
||||||
|
node = { op, left: node, right: parseExponent(state) };
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseAdditive(state: ParserState): AstNode {
|
||||||
|
let node = parseMultiplicative(state);
|
||||||
|
while (char(state) === '+' || char(state) === '-') {
|
||||||
|
const op = next(state) as '+' | '-';
|
||||||
|
node = { op, left: node, right: parseMultiplicative(state) };
|
||||||
|
}
|
||||||
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
// These operators go from high to low order of precedence
|
// These operators go from high to low order of precedence
|
||||||
const parseOperator = makeOperatorParser('^', '/', '*', '-', '+');
|
const parseOperator = parseAdditive;
|
||||||
|
|
||||||
function parse(expression: string): AstNode {
|
function parse(expression: string): AstNode {
|
||||||
const state = { str: expression.replace(/\s/g, ''), index: 0 };
|
const state = { str: expression.replace(/\s/g, ''), index: 0 };
|
||||||
|
|||||||
6
upcoming-release-notes/6993.md
Normal file
6
upcoming-release-notes/6993.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
category: Bugfix
|
||||||
|
authors: [HadiAyache]
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix arithmetic expression parsing so operators with the same precedence (`*`/`/`, `+`/`-`) are evaluated left-to-right.
|
||||||
Reference in New Issue
Block a user