Alexander Bruy
2015-05-28 11:32:21 UTC
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
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
------------------------------------------------------------------------------
Alexander Bruy
------------------------------------------------------------------------------