def c2py(plural):
"""Gets a C expression as used in PO files for plural forms and returns a
- Python lambda function that implements an equivalent expression.
+ Python function that implements an equivalent expression.
"""
- # Security check, allow only the "n" identifier
- import token, tokenize
- tokens = tokenize.generate_tokens(io.StringIO(plural).readline)
- try:
- danger = [x for x in tokens if x[0] == token.NAME and x[1] != 'n']
- except tokenize.TokenError:
- raise ValueError('plural forms expression error, maybe unbalanced parenthesis')
- else:
- if danger:
- raise ValueError('plural forms expression could be dangerous')
-
- # Replace some C operators by their Python equivalents
- plural = plural.replace('&&', ' and ')
- plural = plural.replace('||', ' or ')
-
- expr = re.compile(r'\!([^=])')
- plural = expr.sub(' not \\1', plural)
-
- # Regular expression and replacement function used to transform
- # "a?b:c" to "b if a else c".
- expr = re.compile(r'(.*?)\?(.*?):(.*)')
- def repl(x):
- return "(%s if %s else %s)" % (x.group(2), x.group(1),
- expr.sub(repl, x.group(3)))
-
- # Code to transform the plural expression, taking care of parentheses
- stack = ['']
- for c in plural:
- if c == '(':
- stack.append('')
- elif c == ')':
- if len(stack) == 1:
- # Actually, we never reach this code, because unbalanced
- # parentheses get caught in the security check at the
- # beginning.
- raise ValueError('unbalanced parenthesis in plural form')
- s = expr.sub(repl, stack.pop())
- stack[-1] += '(%s)' % s
- else:
- stack[-1] += c
- plural = expr.sub(repl, stack.pop())
-
- return eval('lambda n: int(%s)' % plural)
- except RuntimeError:
+ if len(plural) > 1000:
+ raise ValueError('plural form expression is too long')
+ try:
+ result, nexttok = _parse(_tokenize(plural))
+ if nexttok:
+ raise _error(nexttok)
+
+ depth = 0
+ for c in result:
+ if c == '(':
+ depth += 1
+ if depth > 20:
+ # Python compiler limit is about 90.
+ # The most complex example has 2.
+ raise ValueError('plural form expression is too complex')
+ elif c == ')':
+ depth -= 1
+
+ ns = {}
+ exec('''if True:
+ def func(n):
+ if not isinstance(n, int):
+ raise ValueError('Plural value must be an integer.')
+ return int(%s)
+ ''' % result, ns)
+ return ns['func']
++ except RecursionError:
+ # Recursion error can be raised in _parse() or exec().
+ raise ValueError('plural form expression is too complex')
def _expand_lang(loc):