From: Armin Ronacher Date: Wed, 28 Dec 2016 09:07:52 +0000 (+0100) Subject: Fixed long standing wrong operator precedence X-Git-Tag: 2.9~83 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4fa37123b93c25de6f6596825df8b297a0433eee;p=thirdparty%2Fjinja.git Fixed long standing wrong operator precedence --- diff --git a/CHANGES b/CHANGES index c7b5f876..8a878f19 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,8 @@ Version 2.9 regression introduced in 2.8. - Added support for `generator_stop` on supported Python versions (Python 3.5 and later) +- Corrected a long standing issue with operator precedence of math operations + not being what was expected. Version 2.8.1 ------------- diff --git a/jinja2/parser.py b/jinja2/parser.py index d24da180..cdb3fe88 100644 --- a/jinja2/parser.py +++ b/jinja2/parser.py @@ -19,6 +19,15 @@ _statement_keywords = frozenset(['for', 'if', 'block', 'extends', 'print', 'set']) _compare_operators = frozenset(['eq', 'ne', 'lt', 'lteq', 'gt', 'gteq']) +_math_nodes = { + 'add': nodes.Add, + 'sub': nodes.Sub, + 'mul': nodes.Mul, + 'div': nodes.Div, + 'floordiv': nodes.FloorDiv, + 'mod': nodes.Mod, +} + class Parser(object): """This is the central parsing class Jinja2 uses. It's passed to @@ -429,19 +438,19 @@ class Parser(object): def parse_compare(self): lineno = self.stream.current.lineno - expr = self.parse_add() + expr = self.parse_math1() ops = [] while 1: token_type = self.stream.current.type if token_type in _compare_operators: next(self.stream) - ops.append(nodes.Operand(token_type, self.parse_add())) + ops.append(nodes.Operand(token_type, self.parse_math1())) elif self.stream.skip_if('name:in'): - ops.append(nodes.Operand('in', self.parse_add())) + ops.append(nodes.Operand('in', self.parse_math1())) elif (self.stream.current.test('name:not') and self.stream.look().test('name:in')): self.stream.skip(2) - ops.append(nodes.Operand('notin', self.parse_add())) + ops.append(nodes.Operand('notin', self.parse_math1())) else: break lineno = self.stream.current.lineno @@ -449,73 +458,35 @@ class Parser(object): return expr return nodes.Compare(expr, ops, lineno=lineno) - def parse_add(self): - lineno = self.stream.current.lineno - left = self.parse_sub() - while self.stream.current.type == 'add': - next(self.stream) - right = self.parse_sub() - left = nodes.Add(left, right, lineno=lineno) - lineno = self.stream.current.lineno - return left - - def parse_sub(self): + def parse_math1(self): lineno = self.stream.current.lineno left = self.parse_concat() - while self.stream.current.type == 'sub': + while self.stream.current.type in ('add', 'sub'): + cls = _math_nodes[self.stream.current.type] next(self.stream) right = self.parse_concat() - left = nodes.Sub(left, right, lineno=lineno) + left = cls(left, right, lineno=lineno) lineno = self.stream.current.lineno return left def parse_concat(self): lineno = self.stream.current.lineno - args = [self.parse_mul()] + args = [self.parse_math2()] while self.stream.current.type == 'tilde': next(self.stream) - args.append(self.parse_mul()) + args.append(self.parse_math2()) if len(args) == 1: return args[0] return nodes.Concat(args, lineno=lineno) - def parse_mul(self): - lineno = self.stream.current.lineno - left = self.parse_div() - while self.stream.current.type == 'mul': - next(self.stream) - right = self.parse_div() - left = nodes.Mul(left, right, lineno=lineno) - lineno = self.stream.current.lineno - return left - - def parse_div(self): - lineno = self.stream.current.lineno - left = self.parse_floordiv() - while self.stream.current.type == 'div': - next(self.stream) - right = self.parse_floordiv() - left = nodes.Div(left, right, lineno=lineno) - lineno = self.stream.current.lineno - return left - - def parse_floordiv(self): - lineno = self.stream.current.lineno - left = self.parse_mod() - while self.stream.current.type == 'floordiv': - next(self.stream) - right = self.parse_mod() - left = nodes.FloorDiv(left, right, lineno=lineno) - lineno = self.stream.current.lineno - return left - - def parse_mod(self): + def parse_math2(self): lineno = self.stream.current.lineno left = self.parse_pow() - while self.stream.current.type == 'mod': + while self.stream.current.type in ('mul', 'div', 'floordiv', 'mod'): + cls = _math_nodes[self.stream.current.type] next(self.stream) right = self.parse_pow() - left = nodes.Mod(left, right, lineno=lineno) + left = cls(left, right, lineno=lineno) lineno = self.stream.current.lineno return left diff --git a/tests/test_lexnparse.py b/tests/test_lexnparse.py index ff334bf0..7a49ba24 100644 --- a/tests/test_lexnparse.py +++ b/tests/test_lexnparse.py @@ -395,6 +395,10 @@ class TestSyntax(): tmpl = env.from_string('''{{ not 42 in bar }}''') assert tmpl.render(bar=bar) == text_type(not 42 in bar) + def test_operator_precedence(self, env): + tmpl = env.from_string('''{{ 2 * 3 + 4 % 2 + 1 - 2 }}''') + assert tmpl.render() == text_type(2 * 3 + 4 % 2 + 1 - 2) + def test_implicit_subscribed_tuple(self, env): class Foo(object): def __getitem__(self, x):