Skip to content
Open
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
8 changes: 7 additions & 1 deletion Lib/gettext.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,9 @@ def _error(value):
def _parse(tokens, priority=-1):
result = ''
nexttok = next(tokens)
negations = 0
while nexttok == '!':
result += 'not '
negations += 1
nexttok = next(tokens)

if nexttok == '(':
Expand All @@ -136,6 +137,11 @@ def _parse(tokens, priority=-1):
except ValueError:
raise _error(nexttok) from None
result = '%s%d' % (result, value)
# In C the unary '!' binds tighter than any binary operator, but Python's
# 'not' binds looser, so negate the operand as a parenthesised unit before
# the binary-operator loop below ('!n + 1' means '(!n) + 1', not '!(n + 1)').
for _ in range(negations):
result = '(not %s)' % result
nexttok = next(tokens)

j = 100
Expand Down
13 changes: 13 additions & 0 deletions Lib/test/test_gettext.py
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,19 @@ def test_negation(self):
self.assertEqual(f(1), 0)
self.assertEqual(f(2), 0)

def test_negation_precedence(self):
# gh-157451: in C the unary '!' binds tighter than any binary
# operator, so '!n + 1' is '(!n) + 1', not '!(n + 1)'.
f = gettext.c2py('!n + 1')
self.assertEqual(f(0), 2)
self.assertEqual(f(1), 1)
self.assertEqual(gettext.c2py('!n < 3')(0), 1)
self.assertEqual(gettext.c2py('!n * 2')(0), 2)
self.assertEqual(gettext.c2py('!n * 2')(1), 0)
# Double negation still normalises to 0/1 (C semantics).
self.assertEqual(gettext.c2py('!!n')(5), 1)
self.assertEqual(gettext.c2py('!!n')(0), 0)

def test_nested_condition_operator(self):
self.assertEqual(gettext.c2py('n?1?2:3:4')(0), 4)
self.assertEqual(gettext.c2py('n?1?2:3:4')(1), 2)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Fix :func:`gettext.c2py` mistranslating the unary ``!`` operator when it is
followed by a binary operator (for example ``!n + 1``). ``!`` binds tighter
than any binary operator in C, so the operand is now negated as a
self-contained unit, matching the C semantics used by ``Plural-Forms`` rules.
Loading