Discussion:
[Pyparsing] Using pyparsing to parse and convert algebraic expressions
Alexander Bruy
2015-05-28 11:32:21 UTC
Permalink
Hi all,

I try to use pyparsing to parse mathematics expressions with functions and
variables and convert this expression in different notation. Also
during conversion
variables should be replaced with corresponding path using
substitution dictionary.
For example, input expression

var1 + var2

should be transformer into

binarymath(/path/to/file1, /path/to/file2, substract)

Here is a bit more complex example

var1 / sin(var2) + var3

should be transformed into

binarymath(binarymath(/path/to/file1, sin(/path/to/file2), divide),
/path/to/file3, add)

I looked over examples and decided to use fourfn.py as base for my parser. To
distinguish variables from functions I decide to include variables into double
quotes e.g. "var1" + log10("var2"). After some investigating I come with next
code

import re
from pyparsing import (Literal, CaselessLiteral, Word, Group, Combine, Optional,
ZeroOrMore, Forward, nums, alphas, Regex, ParseException)

exprStack = []
rasters = dict()

def pushFirst(strg, loc, toks):
exprStack.append(toks[0])

def pushUMinus(strg, loc, toks):
for t in toks:
if t == '-':
exprStack.append('unary -')
else:
break

def rasterPath(rasterName):
global rasters
return rasters[rasterName]

# Define grammar
bnf = None

def BNF():
global bnf
if not bnf:
point = Literal('.')
colon = Literal(',')
e = CaselessLiteral('E')
pi = CaselessLiteral( "PI" )
fnumber = Regex(r'[+-]?\d+(:?\.\d*)?(:?[eE][+-]?\d+)?')
ident = Combine('"' + Word(alphas, alphas + nums + '_') + '"')
func = Word(alphas)
plus = Literal('+')
minus = Literal('-')
mult = Literal('*')
div = Literal('/')
mod = Literal('%')
lpar = Literal('(').suppress()
rpar = Literal(')').suppress()
addop = plus | minus
multop = mult | div | mod
expop = Literal('^')

expr = Forward()
atom = ((0, None) * minus + (pi | e | fnumber | ident | func +
lpar + expr + rpar | ident).setParseAction(pushFirst) |
Group(lpar + expr + rpar)).setParseAction(pushUMinus)

# by defining exponentiation as "atom [ ^ factor ]..." instead of
# "atom [ ^ atom ]...", we get right-to-left exponents, instead of
# left-to-righ that is, 2^3^2 = 2^(3^2), not (2^3)^2.
factor = Forward()
factor << atom + ZeroOrMore((expop + factor).setParseAction(pushFirst))

term = factor + ZeroOrMore((multop + factor).setParseAction(pushFirst))
expr << term + ZeroOrMore((addop + term).setParseAction(pushFirst))
bnf = expr
return bnf

# map operator symbols to corresponding arithmetic operations
opn = {'+': lambda x, y: 'binarymathraster({}, {}, add)'.format(x, y),
'-': lambda x, y: 'binarymathraster({}, {}, substract)'.format(x, y),
'*': lambda x, y: 'binarymathraster({}, {}, times)'.format(x, y),
'/': lambda x, y: 'binarymathraster({}, {}, divide)'.format(x, y),
'%': lambda x, y: 'binarymathraster({}, {}, mod)'.format(x, y),
'^': lambda x, y: 'binarymathraster({}, {}, power)'.format(x, y)}

fn = {'sin': lambda x: 'sin({})'.format(x),
'cos': lambda x: 'cos({})'.format(x),
'tan': lambda x: 'tan({})'.format(x),
'asin': lambda x: 'asin({})'.format(x),
'acos': lambda x: 'acos({})'.format(x),
'atan': lambda x: 'atan({})'.format(x),
'log10': lambda x: 'log10({})'.format(x),
'ln': lambda x: 'ln({})'.format(x),
'abs': lambda x: 'abs({})'.format(x),
'sqrt': lambda x: 'sqrt({})'.format(x),
'ceil': lambda x: 'ceil({})'.format(x),
'floor': lambda x: 'floor({})'.format(x),
'sign': lambda x: 'sign({})'.format(x),
'sinh': lambda x: 'sinh({})'.format(x)}

def evaluateStack(s):
op = s.pop()
if op == 'unary -':
return -evaluateStack(s)
if op in '+-*/^':
op2 = evaluateStack(s)
op1 = evaluateStack(s)
return opn[op](op1, op2)
elif op == 'PI':
return math.pi # 3.1415926535
elif op == 'E':
return math.e # 2.718281828
elif op in fn:
return fn[op](evaluateStack(s))
elif re.search('\"(.+?)\"', op):
return rasterPath(op.strip('"'))
elif op[0].isalpha():
raise Exception('invalid identifier "%s"' % op)
else:
return float(op)

Here is same code at pastibin http://pastebin.com/McQzfg2d.

Is this correct approach? As I'm new to pyparsing I would be very thankful for
feedback and suggestions how to improve this code and make it reliable and
easy to extend with new functions/operators.

Thanks and sorry for my English
--
Alexander Bruy

------------------------------------------------------------------------------
Loading...