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 from collections import namedtuple
import re 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: class Stack:
@ -25,10 +27,22 @@ class Stack:
class ShuntingYard(object): # export class ShuntingYard(object): # export
def __init__(self, operators): def __init__(self, operators = None):
self.debug = True
self.__ops = {} self.__ops = {}
for k, v in operators.iteritems(): if operators is not None:
self.add_operator(k, v.func, v.nargs, v.prec) for k, v in operators.iteritems():
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): def tokenize(self, spec):
@ -46,25 +60,96 @@ class ShuntingYard(object): # export
tokens, remainder = scanner.scan(spec) tokens, remainder = scanner.scan(spec)
if len(remainder)>0: 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 = [] r = []
for e in tokens: for e in tokens:
r.append(e[1]) r.append(e[1])
return r return r
def add_operator(self, name, func, nargs, precedence): def add_operator(self, name, func, nargs, precedence, assoc):
self.__ops[name] = Operator(func, nargs, precedence) self.__ops[name] = Operator(func, nargs, precedence, assoc)
def infix_to_postfix(self, infix): 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() s = Stack()
r = [] r = []
tokens = self.tokenize(infix) tokens = self.tokenize(infix)
for token in tokens: for tokinfo in tokens:
print tokinfo
toktype, token = tokinfo[0], tokinfo[1]
print "Checking token ", token print "Checking token ", token
@ -105,16 +190,18 @@ class ShuntingYard(object): # export
for token in postfixexpr: for token in postfixexpr:
print "Checking token %s" % (token)
if token not in self.__ops.keys(): if token not in self.__ops.keys():
vals.push(token) vals.push(token)
continue continue
op = self.__ops[token] op = self.__ops[token]
args = [] args = []
#print "Adding %d arguments" % (op.nargs) print "Adding %d arguments" % (op.nargs)
for i in range(0, op.nargs): for i in range(0, op.nargs):
#print "Adding argument %d" % (i) print "Adding argument %d" % (i)
args.append(vals.pop()) args.append(vals.pop())
#print "running %s(%s)" % (token, ', '.join(reversed(args)))
val = op.func(*reversed(args)) val = op.func(*reversed(args))
print "%s(%s) = %s" % (token, ', '.join(reversed(args)), val) print "%s(%s) = %s" % (token, ', '.join(reversed(args)), val)
vals.push(val) vals.push(val)
@ -134,8 +221,8 @@ if __name__ == '__main__':
class Calculator(ShuntingYard): class Calculator(ShuntingYard):
def tokenize(self, string): #def tokenize(self, string):
return string.split() # return string.split()
def f_mult(self, a, b): def f_mult(self, a, b):
return str(atof(a) * atof(b)); return str(atof(a) * atof(b));
@ -152,16 +239,18 @@ if __name__ == '__main__':
def __init__(self): def __init__(self):
Op = Operator Op = Operator
operators = { operators = {
'/': Op(self.f_div, 2, 3), '^': Op(None, 2, 4, R),
'*': Op(self.f_mult, 2, 3), '*': Op(self.f_mult, 2, 3, L),
'+': Op(self.f_add, 2, 2), '/': Op(self.f_div, 2, 3, L),
'-': Op(self.f_sub, 2, 2), '+': Op(self.f_add, 2, 2, L),
'(': Op(None, 2, 1), '-': Op(self.f_sub, 2, 2, L),
')': Op(None, 2, 1) '(': Op(None, 0, 9, L),
')': Op(None, 0, 0, L),
} }
super(Calculator, self).__init__(operators) 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 # ------------- testbed match object
@ -182,7 +271,7 @@ if __name__ == '__main__':
def f_is_not(self, a): def f_is_not(self, a):
if a == "True": if a == "True":
return "False" return "False"
return "False" return "True"
def f_and(self, a_, b_): def f_and(self, a_, b_):
a = a_ == "True" a = a_ == "True"
@ -194,14 +283,14 @@ if __name__ == '__main__':
def __init__(self, obj): def __init__(self, obj):
Op = Operator Op = Operator
operators = { operators = {
'(': Op(None, 2, 1), '(': Op(None, 2, 9, L),
')': Op(None, 2, 1), ')': Op(None, 2, 0, L),
'name=': Op(self.f_is_name, 1, 3), 'name=': Op(self.f_is_name, 1, 3, R),
'and': Op(self.f_and, 2, 3), 'and': Op(self.f_and, 2, 3, L),
'label~=': Op(self.f_matches_label, 1, 3), 'label~=': Op(self.f_matches_label, 1, 3, R),
'False': Op(None, 0, 3), 'False': Op(None, 0, 3, L),
'True': Op(None, 0, 3), 'True': Op(None, 0, 3, L),
'not': Op(self.f_is_not, 1, 3), 'not': Op(self.f_is_not, 1, 3, R),
} }
super(Matcher, self).__init__(operators) super(Matcher, self).__init__(operators)