math syntax checker written in python

4.1k views Asked by At

All I need is to check, using python, if a string is a valid math expression or not.

For simplicity let's say I just need + - * / operators (+ - as unary too) with numbers and nested parenthesis. I add also simple variable names for completeness.

So I can test this way:

test("-3 * (2 + 1)") #valid
test("-3 * ")        #NOT valid

test("v1 + v2")      #valid
test("v2 - 2v")      #NOT valid ("2v" not a valid variable name)

I tried pyparsing but just trying the example: "simple algebraic expression parser, that performs +,-,*,/ and ^ arithmetic operations" I get passed invalid code and also trying to fix it I always get wrong syntaxes being parsed without raising Exceptions

just try:

>>>test('9', 9)
9 qwerty = 9.0 ['9'] => ['9']
>>>test('9 qwerty', 9)
9 qwerty = 9.0 ['9'] => ['9']

both test pass... o_O

Any advice?

5

There are 5 answers

5
AudioBubble On BEST ANSWER

This is because the pyparsing code allows functions. (And by the way, it does a lot more than what you need, i.e. create a stack and evaluate that.)

For starters, you could remove pi and ident (and possibly something else I'm missing right now) from the code to disallow characters.

The reason is different: PyParsing parsers won't try to consume the whole input by default. You have to add + StringEnd() (and import it, of course) to the end of expr to make it fail if it can't parse the whole input. In that case, pyparsing.ParseException will be raised. (Source: http://pyparsing-public.wikispaces.com/FAQs)

If you care to learn a bit of parsing, what you need can propably be built in less than thirty lines with any decent parsing library (I like LEPL).

0
Noctis Skytower On

If you are interested in modifying a custom math evaluator engine written in Python so that it is a validator instead, you could start out with Evaluator 2.0 (Python 3.x) and Math_Evaluator (Python 2.x). They are not ready-made solutions but would allow you to fully customize whatever it is you are trying to do exactly using (hopefully) easy-to-read Python code. Note that "and" & "or" are treated as operators.

15
Blender On

Why not just evaluate it and catch the syntax error?

from math import *

def validateSyntax(expression):
  functions = {'__builtins__': None}
  variables = {'__builtins__': None}

  functions = {'acos': acos,
               'asin': asin,
               'atan': atan,
               'atan2': atan2,
               'ceil': ceil,
               'cos': cos,
               'cosh': cosh,
               'degrees': degrees,
               'exp': exp,
               'fabs':fabs,
               'floor': floor,
               'fmod': fmod,
               'frexp': frexp,
               'hypot': hypot,
               'ldexp': ldexp,
               'log': log,
               'log10': log10,
               'modf': modf,
               'pow': pow,
               'radians': radians,
               'sin': sin,
               'sinh': sinh,
               'sqrt': sqrt,
               'tan': tan,
               'tanh': tanh}

  variables = {'e': e, 'pi': pi}

  try:
    eval(expression, variables, functions)
  except (SyntaxError, NameError, ZeroDivisionError):
    return False
  else:
    return True

Here are some samples:

> print validSyntax('a+b-1') # a, b are undefined, so a NameError arises.
> False

> print validSyntax('1 + 2')
> True

> print validSyntax('1 - 2')
> True

> print validSyntax('1 / 2')
> True

> print validSyntax('1 * 2')
> True

> print validSyntax('1 +/ 2')
> False

> print validSyntax('1 + (2')
> False

> print validSyntax('import os')
> False

> print validSyntax('print "asd"')
> False

> print validSyntax('import os; os.delete("~\test.txt")')
> False # And the file was not removed

It's restricted to only mathematical operations, so it should work a bit better than a crude eval.

7
Jordan On

You could try building a simple parser yourself to tokenize the string of the arithmetic expression and then build an expression tree, if the tree is valid (the leaves are all operands and the internal nodes are all operators) then you can say that the expression is valid.

The basic concept is to make a few helper functions to create your parser.

def extract() will get the next character from the expression
def peek() similar to extract but used if there is no whitespace to check the next character
get_expression()
get_next_token()

Alternatively if you can guarantee whitespace between characters you could use split() to do all the tokenizing.

Then you build your tree and evaluate if its structured correctly

Try this for more info: http://effbot.org/zone/simple-top-down-parsing.htm

1
PaulMcG On

Adding parseAll=True to the call to parseString will convert this parser into a validator.