mirror of
https://github.com/actualbudget/actual.git
synced 2026-03-09 06:03:01 -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);
|
||||
});
|
||||
|
||||
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', () => {
|
||||
expect(evalArithmetic('1,222.45')).toEqual(1222.45);
|
||||
|
||||
|
||||
@@ -91,21 +91,34 @@ function parseParens(state: ParserState): AstNode {
|
||||
return parsePrimary(state);
|
||||
}
|
||||
|
||||
function makeOperatorParser<T extends readonly Operator[]>(...ops: T) {
|
||||
type ParserFn = (state: ParserState) => AstNode;
|
||||
return ops.reduce<ParserFn>((prevParser: ParserFn, op: Operator) => {
|
||||
return (state: ParserState) => {
|
||||
let node = prevParser(state);
|
||||
while (nextOperator(state, op)) {
|
||||
node = { op, left: node, right: prevParser(state) };
|
||||
}
|
||||
return node;
|
||||
};
|
||||
}, parseParens);
|
||||
function parseExponent(state: ParserState): AstNode {
|
||||
let node = parseParens(state);
|
||||
if (nextOperator(state, '^')) {
|
||||
node = { op: '^', left: node, right: parseExponent(state) };
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
function parseMultiplicative(state: ParserState): AstNode {
|
||||
let node = parseExponent(state);
|
||||
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
|
||||
const parseOperator = makeOperatorParser('^', '/', '*', '-', '+');
|
||||
const parseOperator = parseAdditive;
|
||||
|
||||
function parse(expression: string): AstNode {
|
||||
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