Having parsed your expression, your work is only half done (if that).
The typical next step, after converting your input string into a nested structure of tokens, is to recursively walk this structure and interpret your markers such as "Number", "AND", etc. and perform the associated functions.
However, doing so will be pretty much just retracing the steps already done by pyparsing, and the generated infixNotation expression.
I recommend defining eval'able node classes for each expression in your grammar, and pass them to pyparsing as parse actions. Then when you are done, you can just call result.eval()
. The actual functionality of the evaluation is implemented in each related node class. This way, pyparsing does the construction of your nodes while it parses, instead of you having to do it again after the fact.
Here are the classes for your functions (and I have slightly modified stat_function
to accommodate them):
class Node:
"""
Base class for all of the parsed node classes.
"""
def __init__(self, tokens):
self.tokens = tokens[0]
class Function(Node):
def __init__(self, tokens):
super().__init__(tokens)
self.arg = self.tokens[1][0]
class Number(Function):
def eval(self):
return self.arg
class Time(Function):
def eval(self):
from datetime import datetime
if self.arg == "IST":
return int(datetime.now().strftime("%H%M"))
else:
return 0
class Date(Function):
def eval(self):
return 0
def stat_function(name, eval_fn):
return (
Group(CaselessKeyword(name) + Group(LPAR + delimitedList(expr) + RPAR)) |
Group(CaselessKeyword(name) + Group(LPAR + delimitedList(alps) + RPAR))
).addParseAction(eval_fn)
timeFunc = stat_function("Time", Time)
dateFunc = stat_function("Date", Date)
numFunc = stat_function("Number", Number)
funcCall = timeFunc | dateFunc | numFunc
(I had to guess at what "Time(IST)" was supposed to do, but since you were comparing to 1030, I assumed it would return 24-hour HHMM time as an int.)
Now you can parse these simple operands and evaluate them:
s1 = "Number(27)"
s1 = "Time(IST)"
result = expr.parseString(s1)
print(result)
print(result[0].eval())
The same now goes for your logical, comparison, and arithmetic nodes:
class Logical(Node):
def eval(self):
# do not make this a list comprehension, else
# you will defeat any/all short-circuiting
eval_exprs = (t.eval() for t in self.tokens[::2])
return self.logical_fn(eval_exprs)
class AndLogical(Logical):
logical_fn = all
class OrLogical(Logical):
logical_fn = any
class Comparison(Node):
op_map = {
'<': operator.lt,
'>': operator.gt,
'=': operator.eq,
'!=': operator.ne,
'<>': operator.ne,
'>=': operator.ge,
'<=': operator.le,
}
def eval(self):
op1, op, op2 = self.tokens
comparison_fn = self.op_map[op]
return comparison_fn(op1.eval(), op2.eval())
class BinArithOp(Node):
op_map = {
'*': operator.mul,
'/': operator.truediv,
'+': operator.add,
'-': operator.sub,
}
def eval(self):
# start by eval()'ing the first operand
ret = self.tokens[0].eval()
# get following operators and operands in pairs
ops = self.tokens[1::2]
operands = self.tokens[2::2]
for op, operand in zip(ops, operands):
# update cumulative value by add/subtract/mult/divide the next operand
arith_fn = self.op_map[op]
ret = arith_fn(ret, operand.eval())
return ret
operand = numericLiteral | funcCall | alps
Each operator level to infixNotation
takes an optional fourth argument, which is used as a parse action for that operation. (I also broke out AND and OR as separate levels, as AND's are typically evaluated at higher precedence than OR).
arithExpr = infixNotation(
operand, [(multOp, 2, opAssoc.LEFT, BinArithOp),
(addOp, 2, opAssoc.LEFT, BinArithOp),
(Compop, 2, opAssoc.LEFT, Comparison),
(CaselessKeyword("and"), 2, opAssoc.LEFT, AndLogical),
(CaselessKeyword("or"), 2, opAssoc.LEFT, OrLogical),
]
)
expr <<= arithExpr
Now to evaluate your input string:
s1 = "(Number(3) > Number(5)) AND (Time(IST) < Number(1030))"
result = expr.parseString(s1)
print(result)
print(result[0].eval())
Prints:
[<__main__.AndLogical object at 0xb64bc370>]
False
If you change s1 to compare 3 < 5 (and run this example before 10:30am):
s1 = "(Number(3) < Number(5)) AND (Time(IST) < Number(1030))"
you get:
[<__main__.AndLogical object at 0xb6392110>]
True
There are similar examples in the pyparsing examples directory, search for those using infixNotation
.