jw-python/tools/python/jwutils/algo/ShuntingYard.py

212 lines
5.5 KiB
Python
Raw Normal View History

from collections import namedtuple
import re
Operator = namedtuple("Operator", [ "func", "nargs" , "prec" ])
class Stack:
def __init__(self, itemlist=[]):
self.items = itemlist
def isEmpty(self):
if self.items == []:
return True
return False
def peek(self):
return self.items[-1:][0]
def pop(self):
return self.items.pop()
def push(self, item):
self.items.append(item)
return 0
class ShuntingYard(object): # export
def __init__(self, operators):
self.__ops = {}
for k, v in operators.iteritems():
self.add_operator(k, v.func, v.nargs, v.prec)
def tokenize(self, spec):
regex = ""
for k in self.__ops.keys():
regex = regex + "|" + re.escape(k)
regex = regex[1:]
scanner = re.Scanner([
(regex, lambda scanner,token:("kw", token)),
(r"\w+", lambda scanner,token:("arg", token)),
(r"\s+", None), # None == skip token.
])
tokens, remainder = scanner.scan(spec)
if len(remainder)>0:
raise Expression("Failed to tokenize " + spec + ", remaining bit is ", remainder)
print tokens
r = []
for e in tokens:
r.append(e[1])
return r
def add_operator(self, name, func, nargs, precedence):
self.__ops[name] = Operator(func, nargs, precedence)
def infix_to_postfix(self, infix):
s = Stack()
r = []
tokens = self.tokenize(infix)
for token in tokens:
print "Checking token ", token
if token not in self.__ops.keys():
r.append(token)
continue
if token == '(':
s.push(token)
continue
if token == ')':
topToken = s.pop()
while topToken != '(':
r.append(topToken)
topToken = s.pop()
continue
while (not s.isEmpty()) and (self.__ops[s.peek()].prec >= self.__ops[token].prec):
#print token
r.append(s.pop())
#print r
s.push(token)
print (s.peek())
while not s.isEmpty():
opToken = s.pop()
r.append(opToken)
#print r
return r
#return " ".join(r)
def eval_postfix(self, postfixexpr):
vals = Stack()
for token in postfixexpr:
if token not in self.__ops.keys():
vals.push(token)
continue
op = self.__ops[token]
args = []
#print "Adding %d arguments" % (op.nargs)
for i in range(0, op.nargs):
#print "Adding argument %d" % (i)
args.append(vals.pop())
val = op.func(*reversed(args))
print "%s(%s) = %s" % (token, ', '.join(reversed(args)), val)
vals.push(val)
return vals.pop()
def eval(self, infix):
postfix = self.infix_to_postfix(infix)
print infix, "-->", postfix
return self.eval_postfix(postfix)
if __name__ == '__main__':
# ------------- testbed calculator
from string import atof
class Calculator(ShuntingYard):
def tokenize(self, string):
return string.split()
def f_mult(self, a, b):
return str(atof(a) * atof(b));
def f_div(self, a, b):
return str(atof(a) / atof(b));
def f_add(self, a, b):
return str(atof(a) + atof(b));
def f_sub(self, a, b):
return str(atof(a) - atof(b));
def __init__(self):
Op = Operator
operators = {
'/': Op(self.f_div, 2, 3),
'*': Op(self.f_mult, 2, 3),
'+': Op(self.f_add, 2, 2),
'-': Op(self.f_sub, 2, 2),
'(': Op(None, 2, 1),
')': Op(None, 2, 1)
}
super(Calculator, self).__init__(operators)
print Calculator().eval("( 2 * 3 + 4 * 5 ) / ( 5 - 3 )")
# ------------- testbed match object
Object = namedtuple("Object", [ "Name", "Label" ])
class Matcher(ShuntingYard):
def f_is_name(self, a):
if obj.Name == a:
return "True"
return "False"
def f_matches_label(self, a):
if re.match(a, obj.Label):
return "True"
return "False"
def f_is_not(self, a):
if a == "True":
return "False"
return "False"
def f_and(self, a_, b_):
a = a_ == "True"
b = b_ == "True"
if a and b:
return "True"
return "False"
def __init__(self, obj):
Op = Operator
operators = {
'(': Op(None, 2, 1),
')': Op(None, 2, 1),
'name=': Op(self.f_is_name, 1, 3),
'and': Op(self.f_and, 2, 3),
'label~=': Op(self.f_matches_label, 1, 3),
'False': Op(None, 0, 3),
'True': Op(None, 0, 3),
'not': Op(self.f_is_not, 1, 3),
}
super(Matcher, self).__init__(operators)
obj = Object("hans", "wurst")
r = Matcher(obj).eval("name=hans and (not label~=worst)")
print "Result =", r