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:
HadiAyache
2026-02-17 01:23:15 -08:00
committed by GitHub
parent 253530e239
commit f7227f4e62
3 changed files with 42 additions and 12 deletions

View File

@@ -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);

View File

@@ -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 };

View File

@@ -0,0 +1,6 @@
---
category: Bugfix
authors: [HadiAyache]
---
Fix arithmetic expression parsing so operators with the same precedence (`*`/`/`, `+`/`-`) are evaluated left-to-right.