Esempio n. 1
0
 def parse(self, expr):
     self.names = []
     self.funcs = []
     try:
         yacc.parse(expr)
     except NineMLMathParseError, e:
         raise NineMLMathParseError(str(e) + " Expression was: '%s'" % expr)
Esempio n. 2
0
 def t_NUMBER(self, t):
     r'(\d*\.\d+)|(\d+\.\d*)|(\d+)'
     try:
         t.value = float(t.value)
     except ValueError:
         raise NineMLMathParseError("Invalid number %s" % t.value)
     return t
Esempio n. 3
0
 def _check_valid_funcs(cls, expr):
     """Checks if the provided Sympy function is a valid 9ML function"""
     if (isinstance(expr, sympy.Function) and str(type(expr)) not in chain(
             cls._valid_funcs, iter(cls.inline_randoms_dict.keys()))):
         raise NineMLMathParseError(
             "'{}' is a valid function in Sympy but not in 9ML".format(
                 type(expr)))
     for arg in expr.args:
         cls._check_valid_funcs(arg)
Esempio n. 4
0
    def p_func(self, p):
        """expression : LFUNC expression RPAREN\n | LFUNC RPAREN
                        | LFUNC expression COMMA expression RPAREN
                        | LFUNC expression COMMA expression COMMA expression RPAREN
        """
        # EM: Supports up to 3 args.  Don't know how to support N.

        func_name = p[1][:-1].strip()
        if not is_builtin_math_function(func_name):
            raise NineMLMathParseError("Undefined function '%s'" % func_name)
        self.funcs.append(func_name)
Esempio n. 5
0
 def _parse_expr(self, expr):
     # Strip non-space whitespace
     expr = self._whitespace_re.sub(' ', expr)
     expr = self.escape_random_namespace(expr)
     if self._logic_relation_re.search(expr):
         expr = self._parse_relationals(expr)
     self.escaped_names = set()
     try:
         expr = sympy_parse(expr,
                            transformations=([self] +
                                             self._sympy_transforms),
                            local_dict=self.inline_randoms_dict)
     except Exception as e:
         raise NineMLMathParseError(
             "Could not parse math-inline expression: "
             "{}\n\n{}".format(expr, e))
     return self._postprocess(expr)
Esempio n. 6
0
 def p_error(self, p):
     if p:
         raise NineMLMathParseError("Syntax error at '%s'" % p.value)
     else:
         raise NineMLMathParseError("Syntax error at EOF, probably "
                                    "unmatched parenthesis.")
Esempio n. 7
0
 def t_error(self, t):
     raise NineMLMathParseError("Illegal character '%s' in '%s'" %
                                (t.value[0], t))
Esempio n. 8
0
 def _parse_relationals(cls, expr_string, escape='__'):
     """
     Based on shunting-yard algorithm
     (see http://en.wikipedia.org/wiki/Shunting-yard_algorithm)
     with modifications for skipping over non logical/relational operators
     and associated parentheses.
     """
     if isinstance(expr_string, bool):
         return expr_string
     # Splits and throws away empty tokens (between parens and operators)
     # and encloses the whole expression in parens
     tokens = (['('] + [
         t for t in cls._tokenize_logic_re.split(expr_string.strip()) if t
     ] + [')'])
     if len([1 for t in tokens if t.strip().endswith('(')
             ]) != tokens.count(')'):
         raise NineMLMathParseError(
             "Unbalanced parentheses in expression: {}".format(expr_string))
     tokens = tokens
     operators = []  # stack (in SY algorithm terminology)
     operands = []  # output stream
     is_relational = []  # whether the current parens should be parsed
     # Num operands to concat when not parsing relation/bool. Because we are
     # also splitting on parenthesis, non-logic/relational expressions will
     # still be split on parenthesis and we need to keep track of how many
     # pieces they are in.
     num_args = [0]  # top-level should always be set to 1
     for tok in tokens:
         # If opening paren or function name + paren
         if cls._left_paren_func_re.match(tok):
             operators.append(tok)
             is_relational.append(False)
             num_args.append(0)
         # Closing paren.
         elif tok == ')':
             # Join together sub-expressions that have been split by parens
             # not used for relational/boolean expressions (e.g. functions)
             n = num_args.pop()
             if n > 1:
                 operands = operands[:-n] + [''.join(operands[-n:])]
             # If parens enclosed relat/logic (i.e. '&', '|', '<', '>', etc)
             if is_relational.pop():
                 # Get all the operators within the enclosing parens.
                 # Need to sort by order of precedence
                 try:
                     # Get index of last open paren
                     i = -1
                     while not cls._left_paren_func_re.match(operators[i]):
                         i -= 1
                 except IndexError:
                     raise NineMLMathParseError(
                         "Unbalanced parentheses in expression: {}".format(
                             expr_string))
                 # Get lists of operators and operands at this level
                 # (i.e. after the last left paren)
                 level_operators = operators[(i + 1):]
                 level_operands = operands[i:]
                 # Pop these operators and operands off the list
                 # (along with the paren/function-call)
                 open_paren = operators[i]
                 operators = operators[:i]
                 operands = operands[:i]
                 while level_operators:
                     # Sort in order of precedence
                     prec = [cls._precedence[o] for o in level_operators]
                     i = sorted(list(range(len(prec))),
                                key=prec.__getitem__)[0]
                     # Pop first operator
                     operator = level_operators.pop(i)
                     arg1, arg2 = (level_operands.pop(i),
                                   level_operands.pop(i))
                     if operator.startswith('&'):
                         func = "And{}({}, {})".format(escape, arg1, arg2)
                     elif operator.startswith('|'):
                         func = "Or{}({}, {})".format(escape, arg1, arg2)
                     elif operator.startswith('='):
                         func = "Eq{}({}, {})".format(escape, arg1, arg2)
                     elif operator == '<':
                         func = "Lt{}({}, {})".format(escape, arg1, arg2)
                     elif operator == '>':
                         func = "Gt{}({}, {})".format(escape, arg1, arg2)
                     elif operator == '<=':
                         func = "Le{}({}, {})".format(escape, arg1, arg2)
                     elif operator == '>=':
                         func = "Ge{}({}, {})".format(escape, arg1, arg2)
                     else:
                         assert False
                     level_operands.insert(i, func)
                 new_operand = level_operands[0]
                 if open_paren != '(':  # Function/logical negation
                     new_operand = open_paren + new_operand + ')'
                 operands.append(new_operand)
             # If parens enclosed something else
             else:
                 try:
                     # Apply the function/parens to the last operand
                     operands[-1] = operators.pop() + operands[-1] + ')'
                 except IndexError:
                     raise NineMLMathParseError(
                         "Unbalanced parentheses in expression: {}".format(
                             expr_string))
             num_args[-1] += 1
         # If the token is one of ('&', '|', '<', '>', '<=', '>=' or '==')
         elif cls._tokenize_logic_re.match(tok):
             operators.append(tok)
             is_relational[-1] = True  # parse the last set of parenthesis
             # Check if there are more than one LHS sub-expr. to concatenate
             n = num_args[-1]
             if n == 0:
                 raise NineMLMathParseError(
                     "Logical/relational operator directly after a "
                     "parenthesis or start of expression: {}".format(
                         expr_string))
             elif n > 1:
                 operands = operands[:-n] + [''.join(operands[-n:])]
             num_args[-1] = 0
         # If the token is an atom or a subexpr not containing any
         # logic/relational operators or parens.
         else:
             operands.append(tok)
             num_args[-1] += 1
     # After it is processed, operands should contain the parsed expression
     # as a single item
     return operands[0]