Re-implement ShuntingYard.infix_to_postfix()

The previous implementation had no concept of associativity.

Signed-off-by: Jan Lindemann <jan@janware.com>
This commit is contained in:
Jan Lindemann 2017-07-27 15:18:17 +02:00
commit 1259b16837

View file

@ -1,7 +1,9 @@
from collections import namedtuple
import re
Operator = namedtuple("Operator", [ "func", "nargs" , "prec" ])
Operator = namedtuple("Operator", [ "func", "nargs" , "prec", "assoc" ]) # export
L, R = 'Left Right'.split()
ARG, KEYW, LPAREN, RPAREN = 'arg kw ( )'.split()
class Stack:
@ -25,10 +27,22 @@ class Stack:
class ShuntingYard(object): # export
def __init__(self, operators):
def __init__(self, operators = None):
self.debug = True
self.__ops = {}
if operators is not None:
for k, v in operators.iteritems():
self.add_operator(k, v.func, v.nargs, v.prec)
self.add_operator(k, v.func, v.nargs, v.prec, v.assoc)
def token_string():
for k, v in self.__ops:
buf = ", " + k
if v.nargs > 1:
buf += "xxx"
r = r + buf
if len(r):
return r[2:]
return r
def tokenize(self, spec):
@ -46,25 +60,96 @@ class ShuntingYard(object): # export
tokens, remainder = scanner.scan(spec)
if len(remainder)>0:
raise Expression("Failed to tokenize " + spec + ", remaining bit is ", remainder)
raise Exception("Failed to tokenize " + spec + ", remaining bit is ", remainder)
print tokens
#print tokens
return 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 add_operator(self, name, func, nargs, precedence, assoc):
self.__ops[name] = Operator(func, nargs, precedence, assoc)
def infix_to_postfix(self, infix):
tokenized = self.tokenize(infix)
print "tokenized = ", tokenized
outq, stack = [], []
table = ['TOKEN,ACTION,RPN OUTPUT,OP STACK,NOTES'.split(',')]
for toktype, token in tokenized:
print "Checking token", token
note = action = ''
if toktype == ARG:
action = 'Add arg to output'
outq.append(token)
table.append( (token, action, ' '.join(outq), ' '.join(s[0] for s in stack), note) )
elif toktype == KEYW:
val = self.__ops[token]
t1, (f1, n1, p1, a1) = token, val
v = t1
note = 'Pop ops from stack to output'
while stack:
t2, (f2, n2, p2, a2) = stack[-1]
if (a1 == L and p1 <= p2) or (a1 == R and p1 < p2):
if t1 != RPAREN:
if t2 != LPAREN:
stack.pop()
action = '(Pop op)'
outq.append(t2)
else:
break
else:
if t2 != LPAREN:
stack.pop()
action = '(Pop op)'
outq.append(t2)
else:
stack.pop()
action = '(Pop & discard "(")'
table.append( (v, action, ' '.join(outq), ' '.join(s[0] for s in stack), note) )
break
table.append( (v, action, ' '.join(outq), ' '.join(s[0] for s in stack), note) )
v = note = ''
else:
note = ''
break
note = ''
note = ''
if t1 != RPAREN:
stack.append((token, val))
action = 'Push op token to stack'
else:
action = 'Discard ")"'
table.append( (v, action, ' '.join(outq), ' '.join(s[0] for s in stack), note) )
note = 'Drain stack to output'
while stack:
v = ''
t2, (f2, n2, p2, a2) = stack[-1]
action = '(Pop op)'
stack.pop()
outq.append(t2)
table.append( (v, action, ' '.join(outq), ' '.join(s[0] for s in stack), note) )
v = note = ''
if self.debug:
maxcolwidths = [len(max(x, key=len)) for x in zip(*table)]
row = table[0]
print( ' '.join('{cell:^{width}}'.format(width=width, cell=cell) for (width, cell) in zip(maxcolwidths, row)))
for row in table[1:]:
print( ' '.join('{cell:<{width}}'.format(width=width, cell=cell) for (width, cell) in zip(maxcolwidths, row)))
return table[-1][2].split()
def infix_to_postfix_orig(self, infix):
s = Stack()
r = []
tokens = self.tokenize(infix)
for token in tokens:
for tokinfo in tokens:
print tokinfo
toktype, token = tokinfo[0], tokinfo[1]
print "Checking token ", token
@ -105,16 +190,18 @@ class ShuntingYard(object): # export
for token in postfixexpr:
print "Checking token %s" % (token)
if token not in self.__ops.keys():
vals.push(token)
continue
op = self.__ops[token]
args = []
#print "Adding %d arguments" % (op.nargs)
print "Adding %d arguments" % (op.nargs)
for i in range(0, op.nargs):
#print "Adding argument %d" % (i)
print "Adding argument %d" % (i)
args.append(vals.pop())
#print "running %s(%s)" % (token, ', '.join(reversed(args)))
val = op.func(*reversed(args))
print "%s(%s) = %s" % (token, ', '.join(reversed(args)), val)
vals.push(val)
@ -134,8 +221,8 @@ if __name__ == '__main__':
class Calculator(ShuntingYard):
def tokenize(self, string):
return string.split()
#def tokenize(self, string):
# return string.split()
def f_mult(self, a, b):
return str(atof(a) * atof(b));
@ -152,16 +239,18 @@ if __name__ == '__main__':
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)
'^': Op(None, 2, 4, R),
'*': Op(self.f_mult, 2, 3, L),
'/': Op(self.f_div, 2, 3, L),
'+': Op(self.f_add, 2, 2, L),
'-': Op(self.f_sub, 2, 2, L),
'(': Op(None, 0, 9, L),
')': Op(None, 0, 0, L),
}
super(Calculator, self).__init__(operators)
print Calculator().eval("( 2 * 3 + 4 * 5 ) / ( 5 - 3 )")
rr = Calculator().eval("( 2 * 3 + 4 * 5 ) / ( 5 - 3 )")
print "Result =", rr
# ------------- testbed match object
@ -182,7 +271,7 @@ if __name__ == '__main__':
def f_is_not(self, a):
if a == "True":
return "False"
return "False"
return "True"
def f_and(self, a_, b_):
a = a_ == "True"
@ -194,14 +283,14 @@ if __name__ == '__main__':
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),
'(': Op(None, 2, 9, L),
')': Op(None, 2, 0, L),
'name=': Op(self.f_is_name, 1, 3, R),
'and': Op(self.f_and, 2, 3, L),
'label~=': Op(self.f_matches_label, 1, 3, R),
'False': Op(None, 0, 3, L),
'True': Op(None, 0, 3, L),
'not': Op(self.f_is_not, 1, 3, R),
}
super(Matcher, self).__init__(operators)