diff --git a/tools/python/jwutils/algo/ShuntingYard.py b/tools/python/jwutils/algo/ShuntingYard.py index 615cefb..2242720 100644 --- a/tools/python/jwutils/algo/ShuntingYard.py +++ b/tools/python/jwutils/algo/ShuntingYard.py @@ -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 = {} - for k, v in operators.iteritems(): - self.add_operator(k, v.func, v.nargs, v.prec) + if operators is not None: + 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): @@ -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)