59
59
60
60
_default_localedir = os.path.join(sys.prefix, 'share', 'locale')
61
61
62
-
63
-
def test(condition, true, false):
64
-
"""
65
-
Implements the C expression:
66
-
67
-
condition ? true : false
68
-
69
-
Required to correctly interpret plural forms.
70
-
"""
71
-
if condition:
72
-
return true
62
+
# Expression parsing for plural form selection.
63
+
#
64
+
# The gettext library supports a small subset of C syntax. The only
65
+
# incompatible difference is that integer literals starting with zero are
66
+
# decimal.
67
+
#
68
+
# https://www.gnu.org/software/gettext/manual/gettext.html#Plural-forms
69
+
# http://git.savannah.gnu.org/cgit/gettext.git/tree/gettext-runtime/intl/plural.y
70
+
71
+
_token_pattern = re.compile(r"""
72
+
(?P<WHITESPACES>[ \t]+) | # spaces and horizontal tabs
73
+
(?P<NUMBER>[0-9]+\b) | # decimal integer
74
+
(?P<NAME>n\b) | # only n is allowed
75
+
(?P<PARENTHESIS>[()]) |
76
+
(?P<OPERATOR>[-*/%+?:]|[><!]=?|==|&&|\|\|) | # !, *, /, %, +, -, <, >,
77
+
# <=, >=, ==, !=, &&, ||,
78
+
# ? :
79
+
# unary and bitwise ops
80
+
# not allowed
81
+
(?P<INVALID>\w+|.) # invalid token
82
+
""", re.VERBOSE|re.DOTALL)
83
+
84
+
def _tokenize(plural):
85
+
for mo in re.finditer(_token_pattern, plural):
86
+
kind = mo.lastgroup
87
+
if kind == 'WHITESPACES':
88
+
continue
89
+
value = mo.group(kind)
90
+
if kind == 'INVALID':
91
+
raise ValueError('invalid token in plural form: %s' % value)
92
+
yield value
93
+
yield ''
94
+
95
+
def _error(value):
96
+
if value:
97
+
return ValueError('unexpected token in plural form: %s' % value)
73
98
else:
74
-
return false
75
-
99
+
return ValueError('unexpected end of plural form')
100
+
101
+
_binary_ops = (
102
+
('||',),
103
+
('&&',),
104
+
('==', '!='),
105
+
('<', '>', '<=', '>='),
106
+
('+', '-'),
107
+
('*', '/', '%'),
108
+
)
109
+
_binary_ops = {op: i for i, ops in enumerate(_binary_ops, 1) for op in ops}
110
+
_c2py_ops = {'||': 'or', '&&': 'and', '/': '//'}
111
+
112
+
def _parse(tokens, priority=-1):
113
+
result = ''
114
+
nexttok = next(tokens)
115
+
while nexttok == '!':
116
+
result += 'not '
117
+
nexttok = next(tokens)
118
+
119
+
if nexttok == '(':
120
+
sub, nexttok = _parse(tokens)
121
+
result = '%s(%s)' % (result, sub)
122
+
if nexttok != ')':
123
+
raise ValueError('unbalanced parenthesis in plural form')
124
+
elif nexttok == 'n':
125
+
result = '%s%s' % (result, nexttok)
126
+
else:
127
+
try:
128
+
value = int(nexttok, 10)
129
+
except ValueError:
130
+
raise _error(nexttok)
131
+
result = '%s%d' % (result, value)
132
+
nexttok = next(tokens)
133
+
134
+
j = 100
135
+
while nexttok in _binary_ops:
136
+
i = _binary_ops[nexttok]
137
+
if i < priority:
138
+
break
139
+
# Break chained comparisons
140
+
if i in (3, 4) and j in (3, 4): # '==', '!=', '<', '>', '<=', '>='
141
+
result = '(%s)' % result
142
+
# Replace some C operators by their Python equivalents
143
+
op = _c2py_ops.get(nexttok, nexttok)
144
+
right, nexttok = _parse(tokens, i + 1)
145
+
result = '%s %s %s' % (result, op, right)
146
+
j = i
147
+
if j == priority == 4: # '<', '>', '<=', '>='
148
+
result = '(%s)' % result
149
+
150
+
if nexttok == '?' and priority <= 0:
151
+
if_true, nexttok = _parse(tokens, 0)
152
+
if nexttok != ':':
153
+
raise _error(nexttok)
154
+
if_false, nexttok = _parse(tokens)
155
+
result = '%s if %s else %s' % (if_true, result, if_false)
156
+
if priority == 0:
157
+
result = '(%s)' % result
158
+
159
+
return result, nexttok
76
160
77
161
def c2py(plural):
78
162
"""Gets a C expression as used in PO files for plural forms and returns a
79
-
Python lambda function that implements an equivalent expression.
163
+
Python function that implements an equivalent expression.
80
164
"""
81
-
# Security check, allow only the "n" identifier
82
-
try:
83
-
from cStringIO import StringIO
84
-
except ImportError:
85
-
from StringIO import StringIO
86
-
import token, tokenize
87
-
tokens = tokenize.generate_tokens(StringIO(plural).readline)
88
-
try:
89
-
danger = [x for x in tokens if x[0] == token.NAME and x[1] != 'n']
90
-
except tokenize.TokenError:
91
-
raise ValueError, \
92
-
'plural forms expression error, maybe unbalanced parenthesis'
93
-
else:
94
-
if danger:
95
-
raise ValueError, 'plural forms expression could be dangerous'
96
-
97
-
# Replace some C operators by their Python equivalents
98
-
plural = plural.replace('&&', ' and ')
99
-
plural = plural.replace('||', ' or ')
100
-
101
-
expr = re.compile(r'\!([^=])')
102
-
plural = expr.sub(' not \\1', plural)
103
-
104
-
# Regular expression and replacement function used to transform
105
-
# "a?b:c" to "test(a,b,c)".
106
-
expr = re.compile(r'(.*?)\?(.*?):(.*)')
107
-
def repl(x):
108
-
return "test(%s, %s, %s)" % (x.group(1), x.group(2),
109
-
expr.sub(repl, x.group(3)))
110
-
111
-
# Code to transform the plural expression, taking care of parentheses
112
-
stack = ['']
113
-
for c in plural:
114
-
if c == '(':
115
-
stack.append('')
116
-
elif c == ')':
117
-
if len(stack) == 1:
118
-
# Actually, we never reach this code, because unbalanced
119
-
# parentheses get caught in the security check at the
120
-
# beginning.
121
-
raise ValueError, 'unbalanced parenthesis in plural form'
122
-
s = expr.sub(repl, stack.pop())
123
-
stack[-1] += '(%s)' % s
124
-
else:
125
-
stack[-1] += c
126
-
plural = expr.sub(repl, stack.pop())
127
-
128
-
return eval('lambda n: int(%s)' % plural)
129
165
166
+
if len(plural) > 1000:
167
+
raise ValueError('plural form expression is too long')
168
+
try:
169
+
result, nexttok = _parse(_tokenize(plural))
170
+
if nexttok:
171
+
raise _error(nexttok)
172
+
173
+
depth = 0
174
+
for c in result:
175
+
if c == '(':
176
+
depth += 1
177
+
if depth > 20:
178
+
# Python compiler limit is about 90.
179
+
# The most complex example has 2.
180
+
raise ValueError('plural form expression is too complex')
181
+
elif c == ')':
182
+
depth -= 1
183
+
184
+
ns = {}
185
+
exec('''if 1:
186
+
def func(n):
187
+
if not isinstance(n, int):
188
+
raise ValueError('Plural value must be an integer.')
189
+
return int(%s)
190
+
''' % result, ns)
191
+
return ns['func']
192
+
except RuntimeError:
193
+
# Recursion error can be raised in _parse() or exec().
194
+
raise ValueError('plural form expression is too complex')
130
195
131
196
132
197
def _expand_lang(locale):
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4