diff --git a/lslopt/lslcommon.py b/lslopt/lslcommon.py index c32ee4a..9308aeb 100644 --- a/lslopt/lslcommon.py +++ b/lslopt/lslcommon.py @@ -19,6 +19,52 @@ import sys +_exclusions = frozenset(('nt','t','name','value','ch', 'X','SEF')) + +# Node Record type. Used for AST nodes. +class nr(object): + nt = None # node type + t = None # LSL type + ch = None # children + SEF = False # Side Effect-Free flag + def __init__(self, **kwargs): + for k in kwargs: + setattr(self, k, kwargs[k]) + + def copy(self): + new = nr() + for k, v in self.__dict__.items(): + setattr(new, k, v) + return new + + # Debug output + + def __str__(self, indent = 0): + spaces = ' ' * (4 * indent) + s = '\n{sp}{{ nt:{nr.nt}\n{sp} ,t:{nr.t}'.format(sp=spaces, nr=self) + if hasattr(self, 'name'): + s += '\n{sp} ,name:{nr.name}'.format(sp=spaces, nr=self) + if hasattr(self, 'value'): + s += '\n{sp} ,value:{v}'.format(sp=spaces, v=repr(self.value)) + for k in sorted(self.__dict__): + if k not in _exclusions: + v = self.__dict__[k] + s += '\n{sp} ,{k}:{v}'.format(sp=spaces, k=k, v=repr(v)) + if self.ch is not None: + if self.ch: + s += '\n{sp} ,ch:['.format(sp=spaces) + isFirst = True + for v in self.ch: + if not isFirst: + s += ',' + isFirst = False + s += v.__str__(indent + 1) + s += '\n{sp} ]'.format(sp=spaces) + else: + s += '\n{sp} ,ch:[]'.format(sp=spaces) + s += '\n{sp}}}'.format(sp=spaces) + return s if indent > 0 else s[1:] # remove leading \n at level 0 + # These types just wrap the Python types to make type() work on them. # There are no ops defined on them or anything. @@ -76,24 +122,24 @@ def warning(txt): sys.stderr.write(u"WARNING: " + txt + u"\n") # Debug function -def print_node(node, indent = 0): - nt = node['nt'] - write = sys.stdout.write - spaces = ' ' * (indent*4+2) - write('%s{ nt:%s\n' % (' '*(indent*4), nt)) - if 't' in node: - write('%s,t:%s\n' % (spaces, node['t'])) - if 'name' in node: - write('%s,name:%s\n' % (spaces, node['name'])) - if 'value' in node: - write('%s,value:%s\n' % (spaces, repr(node['value']))) - - for prop in node: - if prop not in ('ch', 'nt', 't', 'name', 'value','X','SEF'): - write('%s,%s:%s\n' % (spaces, prop, repr(node[prop]))) - if 'ch' in node: - write(spaces + ',ch:[\n') - for subnode in node['ch']: - print_node(subnode, indent+1) - write(spaces + ']\n') - write(' '*(indent*4) + '}\n\n') +#def print_node(node, indent = 0): +# nt = node['nt'] +# write = sys.stdout.write +# spaces = ' ' * (indent*4+2) +# write('%s{ nt:%s\n' % (' '*(indent*4), nt)) +# if 't' in node: +# write('%s,t:%s\n' % (spaces, node['t'])) +# if 'name' in node: +# write('%s,name:%s\n' % (spaces, node['name'])) +# if 'value' in node: +# write('%s,value:%s\n' % (spaces, repr(node['value']))) +# +# for prop in node: +# if prop not in ('ch', 'nt', 't', 'name', 'value','X','SEF'): +# write('%s,%s:%s\n' % (spaces, prop, repr(node[prop]))) +# if 'ch' in node: +# write(spaces + ',ch:[\n') +# for subnode in node['ch']: +# print_node(subnode, indent+1) +# write(spaces + ']\n') +# write(' '*(indent*4) + '}\n\n') diff --git a/lslopt/lsldeadcode.py b/lslopt/lsldeadcode.py index 0c55625..4792544 100644 --- a/lslopt/lsldeadcode.py +++ b/lslopt/lsldeadcode.py @@ -18,6 +18,7 @@ # Dead Code Removal optimization import lslfuncs +from lslcommon import nr class deadcode(object): @@ -30,25 +31,25 @@ class deadcode(object): # The 'X' key, when present, indicates whether a node is executed. # Its value means whether this instruction will proceed to the next # (True: it will; False: it won't). - if 'X' in node: - return node['X'] # branch already analyzed + if hasattr(node, 'X'): + return node.X # branch already analyzed - nt = node['nt'] - child = node['ch'] if 'ch' in node else None + nt = node.nt + child = node.ch # Control flow statements if nt == 'STSW': - node['X'] = False # Executed but path-breaking. - sym = self.symtab[0][node['name']] - if 'X' not in self.tree[sym['Loc']]: + node.X = False # Executed but path-breaking. + sym = self.symtab[0][node.name] + if not hasattr(self.tree[sym['Loc']], 'X'): self.MarkReferences(self.tree[sym['Loc']]) return False if nt == 'JUMP': - node['X'] = False # Executed but path-breaking. - sym = self.symtab[node['scope']][node['name']] + node.X = False # Executed but path-breaking. + sym = self.symtab[node.scope][node.name] if 'R' in sym: sym['R'] += 1 else: @@ -56,87 +57,87 @@ class deadcode(object): return False if nt == 'RETURN': - node['X'] = False # Executed but path-breaking. + node.X = False # Executed but path-breaking. if child: self.MarkReferences(child[0]) return False if nt == 'IF': # "When you get to a fork in the road, take it." - node['X'] = None # provisional value, refined later + node.X = None # provisional value, refined later self.MarkReferences(child[0]) condnode = child[0] - if condnode['nt'] == 'CONST': - if lslfuncs.cond(condnode['value']): + if condnode.nt == 'CONST': + if lslfuncs.cond(condnode.value): # TRUE - 'then' branch always executed. - node['X'] = self.MarkReferences(child[1]) - return node['X'] + node.X = self.MarkReferences(child[1]) + return node.X elif len(child) == 3: # FALSE - 'else' branch always executed. - node['X'] = self.MarkReferences(child[2]) - return node['X'] + node.X = self.MarkReferences(child[2]) + return node.X # else fall through else: cont = self.MarkReferences(child[1]) if len(child) == 3: if not cont: cont = self.MarkReferences(child[2]) - node['X'] = cont + node.X = cont return cont self.MarkReferences(child[2]) - node['X'] = True + node.X = True return True if nt == 'WHILE': - node['X'] = None # provisional value, refined later + node.X = None # provisional value, refined later self.MarkReferences(child[0]) - if child[0]['nt'] == 'CONST': - if lslfuncs.cond(child[0]['value']): + if child[0].nt == 'CONST': + if lslfuncs.cond(child[0].value): # Infinite loop - unless it returns, it stops # execution. But it is executed itself. self.MarkReferences(child[1]) - node['X'] = False - return node['X'] + node.X = False + return node.X # else the inside isn't executed at all, so don't mark it else: self.MarkReferences(child[1]) - node['X'] = True + node.X = True return True if nt == 'DO': - node['X'] = None # provisional value, refined later + node.X = None # provisional value, refined later if not self.MarkReferences(child[0]): - node['X'] = False + node.X = False return False self.MarkReferences(child[1]) # It proceeds to the next statement unless it's an infinite loop - node['X'] = not (child[1]['nt'] == 'CONST' and lslfuncs.cond(child[1]['value'])) - return node['X'] + node.X = not (child[1].nt == 'CONST' and lslfuncs.cond(child[1].value)) + return node.X if nt == 'FOR': - node['X'] = None # provisional value, refined later + node.X = None # provisional value, refined later self.MarkReferences(child[0]) self.MarkReferences(child[1]) - if child[1]['nt'] == 'CONST': - if lslfuncs.cond(child[1]['value']): + if child[1].nt == 'CONST': + if lslfuncs.cond(child[1].value): # Infinite loop - unless it returns, it stops # execution. But it is executed itself. - node['X'] = False + node.X = False self.MarkReferences(child[3]) self.MarkReferences(child[2]) # this can't stop execution - return node['X'] + return node.X # else the body and the iterator aren't executed at all, so # don't mark them - node['X'] = True + node.X = True else: - node['X'] = True + node.X = True self.MarkReferences(child[3]) self.MarkReferences(child[2]) # Mark the EXPRLIST as always executed, but not the subexpressions. # That forces the EXPRLIST (which is a syntactic requirement) to be # kept, while still simplifying the contents properly. - child[2]['X'] = True + child[2].X = True return True if nt == '{}': @@ -146,16 +147,16 @@ class deadcode(object): # True. continues = True - node['X'] = None # provisional + node.X = None # provisional for stmt in child: - if continues or stmt['nt'] == '@': + if continues or stmt.nt == '@': continues = self.MarkReferences(stmt) - node['X'] = continues + node.X = continues return continues if nt == 'FNCALL': - node['X'] = None # provisional - sym = self.symtab[0][node['name']] + node.X = None # provisional + sym = self.symtab[0][node.name] fdef = self.tree[sym['Loc']] if 'Loc' in sym else None for idx in xrange(len(child)-1, -1, -1): @@ -164,18 +165,18 @@ class deadcode(object): # writes to a and b. self.MarkReferences(child[idx]) if fdef is not None: - psym = self.symtab[fdef['pscope']][fdef['pnames'][idx]] + psym = self.symtab[fdef.pscope][fdef.pnames[idx]] if 'W' in psym: psym['W'] = False else: psym['W'] = child[idx] if 'Loc' in sym: - if 'X' not in self.tree[sym['Loc']]: + if not hasattr(self.tree[sym['Loc']], 'X'): self.MarkReferences(self.tree[sym['Loc']]) - node['X'] = self.tree[sym['Loc']]['X'] + node.X = self.tree[sym['Loc']].X else: - node['X'] = 'stop' not in sym + node.X = 'stop' not in sym # Note that JUMP analysis is incomplete. To do it correctly, we # should follow the jump right to its destination, in order to know # if that branch leads to a RETURN or completely stops the event. @@ -200,40 +201,40 @@ class deadcode(object): # fn2() { fn(); x = 1; } - return node['X'] + return node.X if nt == 'DECL': - sym = self.symtab[node['scope']][node['name']] + sym = self.symtab[node.scope][node.name] if child is not None: sym['W'] = child[0] else: - sym['W'] = {'nt':'CONST', 't':node['t'], - 'value':self.DefaultValues[node['t']]} + sym['W'] = nr(nt='CONST', t=node.t, + value=self.DefaultValues[node.t]) - node['X'] = True + node.X = True if child is not None: - if 'orig' in child[0]: - orig = child[0]['orig'] + if hasattr(child[0], 'orig'): + orig = child[0].orig self.MarkReferences(orig) - child[0]['X'] = orig['X'] - if orig['nt'] == 'LIST': + child[0].X = orig.X + if orig.nt == 'LIST': # Add fake writes to variables used in list elements in # 'orig', so they don't get deleted (Issue #3) - for subnode in orig['ch']: - if subnode['nt'] == 'IDENT': + for subnode in orig.ch: + if subnode.nt == 'IDENT': # can only happen in globals - assert subnode['scope'] == 0 - sym = self.symtab[0][subnode['name']] + assert subnode.scope == 0 + sym = self.symtab[0][subnode.name] sym['W'] = False - self.tree[sym['Loc']]['X'] = True - elif subnode['nt'] in ('VECTOR', 'ROTATION'): - for sub2node in subnode['ch']: - if sub2node['nt'] == 'IDENT': + self.tree[sym['Loc']].X = True + elif subnode.nt in ('VECTOR', 'ROTATION'): + for sub2node in subnode.ch: + if sub2node.nt == 'IDENT': # can only happen in globals - assert sub2node['scope'] == 0 - sym = self.symtab[0][sub2node['name']] + assert sub2node.scope == 0 + sym = self.symtab[0][sub2node.name] sym['W'] = False - self.tree[sym['Loc']]['X'] = True + self.tree[sym['Loc']].X = True else: self.MarkReferences(child[0]) return True @@ -241,14 +242,14 @@ class deadcode(object): # ---- Starting here, all node types return through the bottom # (except '='). - node['X'] = None # provisional + node.X = None # provisional if nt in self.assign_ops or nt in ('--V', '++V', 'V++', 'V--'): - ident = node['ch'][0] - if ident['nt'] == 'FLD': - ident = ident['ch'][0] - assert ident['nt'] == 'IDENT' - sym = self.symtab[ident['scope']][ident['name']] - if ident['scope'] == 0: + ident = node.ch[0] + if ident.nt == 'FLD': + ident = ident.ch[0] + assert ident.nt == 'IDENT' + sym = self.symtab[ident.scope][ident.name] + if ident.scope == 0: # Mark the global first. self.MarkReferences(self.tree[sym['Loc']]) # In any case, this is at least the second write, so mark it as such @@ -259,18 +260,18 @@ class deadcode(object): # Prevent the first node from being mistaken as a read, by # recursing only on the RHS node. self.MarkReferences(child[1]) - node['X'] = True + node.X = True return True elif nt == 'FLD': # Mark this variable as referenced by a Field (recursing will mark # the ident as read later) - self.symtab[child[0]['scope']][child[0]['name']]['Fld'] = True + self.symtab[child[0].scope][child[0].name]['Fld'] = True elif nt == 'IDENT': - sym = self.symtab[node['scope']][node['name']] + sym = self.symtab[node.scope][node.name] # Mark global if it's one. - if 'W' not in sym and node['scope'] == 0: + if 'W' not in sym and node.scope == 0: self.MarkReferences(self.tree[sym['Loc']]) # Increase read counter if 'R' in sym: @@ -278,7 +279,7 @@ class deadcode(object): else: sym['R'] = 1 - node['X'] = True + node.X = True if child is not None: for subnode in child: self.MarkReferences(subnode) @@ -304,7 +305,7 @@ class deadcode(object): # - Floats are removed if their value has no decimals or if # used no more than N times (for some N). # - Strings, keys and integers are just removed. - sym = self.symtab[curnode['scope']][curnode['name']] + sym = self.symtab[curnode.scope][curnode.name] if 'R' not in sym: return True # if not used, it can be removed @@ -316,21 +317,21 @@ class deadcode(object): if sym['W'] is not False: node = sym['W'] - nt = node['nt'] + nt = node.nt if nt == 'CONST': - tcurnode = curnode['t'] + tcurnode = curnode.t if tcurnode in ('integer', 'string', 'key'): return sym if tcurnode == 'float': - if sym['R'] <= 3 or type(node['value']) == int: + if sym['R'] <= 3 or type(node.value) == int: return sym elif tcurnode == 'vector' \ - or tcurnode == 'list' and len(node['value']) <= 3: + or tcurnode == 'list' and len(node.value) <= 3: if sym['R'] <= 1: return sym elif tcurnode == 'rotation' \ - or tcurnode == 'list' and len(node['value']) <= 4: + or tcurnode == 'list' and len(node.value) <= 4: if sym['R'] <= 1: return sym return False @@ -346,8 +347,8 @@ class deadcode(object): # the name i is redefined after j is assigned. shrinknames prevents # that. # FIXME: EMERGENCY FIX: shrinknames is not enough guarantee. See nposerlv.lsl. - #if not self.shrinknames or 'SEF' not in node: - if True or 'SEF' not in node: + #if not self.shrinknames or not node.SEF: + if True or not node.SEF: return False if nt not in ('VECTOR', 'ROTATION'): @@ -371,51 +372,50 @@ class deadcode(object): """Recursively checks if the children are used, deleting those that are not. """ - if 'ch' not in curnode or (curnode['nt'] == 'DECL' - and curnode['scope'] == 0): + if curnode.ch is None or (curnode.nt == 'DECL' + and curnode.scope == 0): return # NOTE: Should not depend on 'Loc', since the nodes that are the # destination of 'Loc' are renumbered as we delete stuff from globals. - index = int(curnode['nt'] in self.assign_ops) # don't recurse into a lvalue + index = int(curnode.nt in self.assign_ops) # don't recurse into a lvalue - while index < len(curnode['ch']): - node = curnode['ch'][index] + while index < len(curnode.ch): + node = curnode.ch[index] - if 'X' not in node: - del curnode['ch'][index] + if not hasattr(node, 'X'): + del curnode.ch[index] continue - nt = node['nt'] + nt = node.nt if nt == 'DECL': if self.SymbolReplacedOrDeleted(node): - if 'ch' not in node or 'SEF' in node['ch'][0]: - del curnode['ch'][index] + if not node.ch or node.ch[0].SEF: + del curnode.ch[index] continue - node = curnode['ch'][index] = {'nt':'EXPR', 't':node['t'], - 'ch':[self.Cast(node['ch'][0], node['t'])]} + node = curnode.ch[index] = nr(nt='EXPR', t=node.t, + ch=[self.Cast(node.ch[0], node.t)]) elif nt == 'FLD': - sym = self.SymbolReplacedOrDeleted(node['ch'][0]) + sym = self.SymbolReplacedOrDeleted(node.ch[0]) if sym: value = sym['W'] # Mark as executed, so it isn't optimized out. - value['X'] = True - fieldidx = 'xyzs'.index(node['fld']) - if value['nt'] == 'CONST': - value = value['value'][fieldidx] - value = {'nt':'CONST', 'X':True, 'SEF':True, - 't':self.PythonType2LSL[type(value)], 'value':value} + value.X = True + fieldidx = 'xyzs'.index(node.fld) + if value.nt == 'CONST': + value = value.value[fieldidx] + value = nr(nt='CONST', X=True, SEF=True, + t=self.PythonType2LSL[type(value)], value=value) value = self.Cast(value, 'float') SEF = True else: # assumed VECTOR or ROTATION per SymbolReplacedOrDeleted - SEF = 'SEF' in value - value = self.Cast(value['ch'][fieldidx], 'float') + SEF = value.SEF + value = self.Cast(value.ch[fieldidx], 'float') # Replace it - node = curnode['ch'][index] = value - if SEF: - node['SEF'] = True + node = curnode.ch[index] = value + node.SEF = SEF elif nt == 'IDENT': sym = self.SymbolReplacedOrDeleted(node) @@ -425,42 +425,40 @@ class deadcode(object): # TODO: Needs more analysis to see if it's correct or not. # (See constant_anomaly.lsl) new = sym['W'].copy() - if 'orig' in new: - del new['orig'] + if hasattr(new, 'orig'): + del new.orig - new['X'] = True + new.X = True # this part makes no sense? - #SEF = 'SEF' in sym['W'] - #if SEF: - # new['SEF'] = True + #new.SEF = sym['W'].SEF - if new['t'] != node['t']: - new = self.Cast(new, node['t']) - curnode['ch'][index] = node = new + if new.t != node.t: + new = self.Cast(new, node.t) + curnode.ch[index] = node = new # Delete orig if present, as we've eliminated the original - #if 'orig' in sym['W']: - # del sym['W']['orig'] + #if hasattr(sym['W'], 'orig'): + # del sym['W'].orig elif nt in self.assign_ops: - ident = node['ch'][0] - if ident['nt'] == 'FLD': - ident = ident['ch'][0] + ident = node.ch[0] + if ident.nt == 'FLD': + ident = ident.ch[0] sym = self.SymbolReplacedOrDeleted(ident) if sym: - node = curnode['ch'][index] = self.Cast(node['ch'][1], node['t']) + node = curnode.ch[index] = self.Cast(node.ch[1], node.t) elif nt in ('IF', 'WHILE', 'DO', 'FOR'): # If the mandatory statement is to be removed, replace it # with a ; to prevent leaving the statement empty. - child = node['ch'] + child = node.ch idx = 3 if nt == 'FOR' else 0 if nt == 'DO' else 1 - if 'X' not in child[idx]: - child[idx] = {'nt':';', 't':None, 'X':True, 'SEF':True} - if nt == 'DO' and 'X' not in child[1]: + if not hasattr(child[idx], 'X'): + child[idx] = nr(nt=';', t=None, X=True, SEF=True) + if nt == 'DO' and not hasattr(child[1],'X'): # Mandatory condition but not executed - replace - child[1] = {'nt':'CONST','X':True,'SEF':True,'t':'integer', - 'value':0} + child[1] = nr(nt='CONST', X=True, SEF=True, t='integer', + value=0) self.CleanNode(node) index += 1 @@ -495,7 +493,7 @@ class deadcode(object): return statedef = self.tree[self.symtab[0]['default']['Loc']] - assert statedef['nt'] == 'STDEF' and statedef['name'] == 'default' + assert statedef.nt == 'STDEF' and statedef.name == 'default' self.MarkReferences(statedef) # Track removal of global lines, to reasign locations later. @@ -511,9 +509,9 @@ class deadcode(object): node = self.tree[idx] delete = False - if 'X' not in node: + if not hasattr(node, 'X'): delete = True - elif node['nt'] == 'DECL': + elif node.nt == 'DECL': delete = self.SymbolReplacedOrDeleted(node) if delete: @@ -521,8 +519,8 @@ class deadcode(object): # We can't remove it here because there may be more references # that we will remove in CleanNode later, that hold the # original value. - if node['nt'] == 'DECL' or node['nt'] == 'STDEF': - GlobalDeletions.append(node['name']) + if node.nt == 'DECL' or node.nt == 'STDEF': + GlobalDeletions.append(node.name) del self.tree[idx] del LocMap[idx] else: diff --git a/lslopt/lslfoldconst.py b/lslopt/lslfoldconst.py index 46d275c..ebbffe0 100644 --- a/lslopt/lslfoldconst.py +++ b/lslopt/lslfoldconst.py @@ -18,7 +18,7 @@ # Constant folding and simplification of expressions and statements. import lslcommon -from lslcommon import Vector, Quaternion, warning +from lslcommon import Vector, Quaternion, warning, nr import lslfuncs from lslfuncs import ZERO_VECTOR, ZERO_ROTATION import math @@ -28,8 +28,8 @@ from lslfuncopt import OptimizeFunc, OptimizeArgs, FuncOptSetup class foldconst(object): def isLocalVar(self, node): - name = node['name'] - scope = node['scope'] + name = node.name + scope = node.scope return self.symtab[scope][name]['Kind'] == 'v' \ and 'Loc' not in self.symtab[scope][name] @@ -37,16 +37,16 @@ class foldconst(object): """Get the length of a list that is expressed as a CONST, LIST or CAST node, or False if it can't be determined. """ - assert node['t'] == 'list' - nt = node['nt'] + assert node.t == 'list' + nt = node.nt if nt == 'CAST': - if node['ch'][0]['t'] == 'list': - return self.GetListNodeLength(node['ch'][0]) + if node.ch[0].t == 'list': + return self.GetListNodeLength(node.ch[0]) return 1 if nt == 'CONST': # constant list - return len(node['value']) + return len(node.value) if nt == 'LIST': # list constructor - return len(node['ch']) + return len(node.ch) return False def GetListNodeElement(self, node, index): @@ -54,24 +54,24 @@ class foldconst(object): If the index is out of range, return False; otherwise the result can be either a node or a constant. """ - assert node['t'] == 'list' - nt = node['nt'] + assert node.t == 'list' + nt = node.nt if nt == 'CAST': # (list)list_expr should have been handled in CAST - assert node['ch'][0]['t'] != 'list' + assert node.ch[0].t != 'list' if index == 0 or index == -1: - return node['ch'][0] + return node.ch[0] return False if nt == 'CONST': try: - return node['value'][index] + return node.value[index] except IndexError: pass return False if nt == 'LIST': try: - return node['ch'][index] + return node.ch[index] except IndexError: return False return False @@ -80,9 +80,9 @@ class foldconst(object): """Return the constant if the value is a node and represents a constant, or if the value is directly a constant, and False otherwise. """ - if type(nodeOrConst) == dict: - if nodeOrConst['nt'] == 'CONST': - return nodeOrConst['value'] + if type(nodeOrConst) == nr: + if nodeOrConst.nt == 'CONST': + return nodeOrConst.value return False return nodeOrConst @@ -90,8 +90,8 @@ class foldconst(object): """Return the LSL type of a node or constant.""" if nodeOrConst is False: return False - if type(nodeOrConst) == dict: - return nodeOrConst['t'] + if type(nodeOrConst) == nr: + return nodeOrConst.t return lslcommon.PythonType2LSL[type(nodeOrConst)] def FoldAndRemoveEmptyStmts(self, lst): @@ -101,7 +101,7 @@ class foldconst(object): self.FoldTree(lst, idx) self.FoldStmt(lst, idx) # If eliminated, it must be totally removed. A ';' won't do. - if lst[idx]['nt'] == ';': + if lst[idx].nt == ';': del lst[idx] else: idx += 1 @@ -115,9 +115,9 @@ class foldconst(object): labels, and applies to a block's statement list, not to a node. """ maybe_label = ';' if labels else '@' - if maybe_label != node['nt'] != ';': - if node['nt'] == '{}': - for subnode in node['ch']: + if maybe_label != node.nt != ';': + if node.nt == '{}': + for subnode in node.ch: # Labels embedded in {} are not reachable. They do nothing. if self.DoesSomething(subnode, labels = False): return True @@ -128,48 +128,48 @@ class foldconst(object): def CompareTrees(self, node1, node2): """Try to compare two subtrees to see if they are equivalent.""" # They MUST be SEF and stable. - if 'SEF' not in node1 or 'SEF' not in node2: + if not node1.SEF or not node2.SEF: return False - if node1['t'] != node2['t']: + if node1.t != node2.t: return False # It's not complete yet. - nt1 = node1['nt'] - if nt1 == node2['nt']: + nt1 = node1.nt + if nt1 == node2.nt: if (nt1 == 'IDENT' - and node1['name'] == node2['name'] - and node1['scope'] == node2['scope'] + and node1.name == node2.name + and node1.scope == node2.scope ): return True if (nt1 == 'FNCALL' - and node1['name'] == node2['name'] - and 'uns' not in self.symtab[0][node1['name']] - and all(self.CompareTrees(node1['ch'][i], - node2['ch'][i]) - for i in xrange(len(node1['ch']))) + and node1.name == node2.name + and 'uns' not in self.symtab[0][node1.name] + and all(self.CompareTrees(node1.ch[i], + node2.ch[i]) + for i in xrange(len(node1.ch))) ): return True if (nt1 == 'CAST' - and self.CompareTrees(node1['ch'][0], node2['ch'][0]) + and self.CompareTrees(node1.ch[0], node2.ch[0]) ): return True - if nt1 == 'CONST' and node1['value'] == node2['value']: + if nt1 == 'CONST' and node1.value == node2.value: return True if (nt1 in ('!', '~', 'NEG') - and self.CompareTrees(node1['ch'][0], node2['ch'][0]) + and self.CompareTrees(node1.ch[0], node2.ch[0]) ): return True if (nt1 in self.binary_ops - and self.CompareTrees(node1['ch'][0], node2['ch'][0]) - and self.CompareTrees(node1['ch'][1], node2['ch'][1]) + and self.CompareTrees(node1.ch[0], node2.ch[0]) + and self.CompareTrees(node1.ch[1], node2.ch[1]) ): return True if ((nt1 in ('*', '^', '&', '|', '==') # commutative or nt1 == '+' - and node1['ch'][0]['t'] not in ('list', 'string') - and node2['ch'][0]['t'] not in ('list', 'string') + and node1.ch[0].t not in ('list', 'string') + and node2.ch[0].t not in ('list', 'string') ) - and self.CompareTrees(node1['ch'][0], node2['ch'][1]) - and self.CompareTrees(node1['ch'][1], node2['ch'][0]) + and self.CompareTrees(node1.ch[0], node2.ch[1]) + and self.CompareTrees(node1.ch[1], node2.ch[0]) ): return True return False @@ -178,18 +178,18 @@ class foldconst(object): '''Applied to function call nodes, return whether the node corresponds to a SEF function. ''' - assert node['nt'] == 'FNCALL' - sym = self.symtab[0][node['name']] + assert node.nt == 'FNCALL' + sym = self.symtab[0][node.name] return 'SEF' in sym and sym['SEF'] is True def FoldStmt(self, parent, index): """Simplify a statement.""" node = parent[index] - if node['nt'] == 'EXPR': - node = node['ch'][0] + if node.nt == 'EXPR': + node = node.ch[0] # If the statement is side-effect-free, remove it as it does nothing. - if 'SEF' in node: - # Side-effect free means that a statement does nothing except + if node.SEF: + # When a statement is side-effect free, it does nothing except # wasting CPU, and can thus be removed without affecting the # program. But side effect freedom is propagated from the # constituents of the statement, e.g. function calls in expressions @@ -201,22 +201,22 @@ class foldconst(object): # # Assignments do have side effects, except those of the form x = x. # Pre- and post-increment and decrement also have side effects. - # Type casts do not add side effects. Neither do binary operators. - parent[index] = {'nt':';', 't':None, 'SEF': True} + # Other unary and binary operators are side effect-free. + parent[index] = nr(nt=';', t=None, SEF=True) return # Post-increments take more space than pre-increments. - if node['nt'] in ('V++', 'V--'): - node['nt'] = '++V' if node['nt'] == 'V++' else '--V'; + if node.nt in ('V++', 'V--'): + node.nt = '++V' if node.nt == 'V++' else '--V'; # Function calls are SEF if both the function and the args are SEF. # If the statement is a function call and the function is marked as SEF # at this point, it means the arguments are not SEF. Replace the node - # in that case with a block. - if (node['nt'] == 'FNCALL' and 'Loc' in self.symtab[0][node['name']] + # in that case with a block of expressions. + if (node.nt == 'FNCALL' and 'Loc' in self.symtab[0][node.name] and self.FnSEF(node) ): - parent[index] = {'nt':'{}', 't':None, 'ch': - [{'nt':'EXPR','t':x['t'],'ch':[x]} for x in node['ch']]} + parent[index] = nr(nt='{}', t=None, ch=[ + nr(nt='EXPR', t=x.t, ch=[x]) for x in node.ch]) self.FoldTree(parent, index) return @@ -226,36 +226,35 @@ class foldconst(object): we expand them and let the optimizer optimize, for float, vector and rotation, and no matter the optimization in the case of list. """ - ctyp = parent[index]['t'] + ctyp = parent[index].t # Under LSO, this would break the fact that 1-element lists count as # false, so we don't do it for LSO lists. if (ctyp in ('float', 'vector', 'rotation', 'string') or ctyp == 'list' and not lslcommon.LSO ): - parent[index] = {'nt':'!=', 't':'integer', 'ch':[parent[index], - {'nt':'CONST', 't':ctyp, 'value': - 0.0 if ctyp == 'float' - else ZERO_VECTOR if ctyp == 'vector' - else ZERO_ROTATION if ctyp == 'rotation' - else u"" if ctyp == 'string' - else []}]} - parent[index]['SEF'] = 'SEF' in parent[index]['ch'][0] + parent[index] = nr(nt='!=', t='integer', ch=[parent[index], + nr(nt='CONST', t=ctyp, value=0.0 if ctyp == 'float' + else ZERO_VECTOR if ctyp == 'vector' + else ZERO_ROTATION if ctyp == 'rotation' + else u"" if ctyp == 'string' + else [])]) + parent[index].SEF = parent[index].ch[0].SEF def IsBool(self, node): """Some operators return 0 or 1, and that allows simplification of boolean expressions. This function returns whether we know for sure that the result is boolean. """ - nt = node['nt'] + nt = node.nt if nt in ('<', '!', '>', '<=', '>=', '==', '||', '&&') \ - or nt == '!=' and node['ch'][0]['t'] != 'list' \ - or nt == '&' and (self.IsBool(node['ch'][0]) or self.IsBool(node['ch'][1])) \ - or nt in ('|', '^', '*') and self.IsBool(node['ch'][0]) and self.IsBool(node['ch'][1]) \ - or nt == 'CONST' and node['t'] == 'integer' and node['value'] in (0, 1): + or nt == '!=' and node.ch[0].t != 'list' \ + or nt == '&' and (self.IsBool(node.ch[0]) or self.IsBool(node.ch[1])) \ + or nt in ('|', '^', '*') and self.IsBool(node.ch[0]) and self.IsBool(node.ch[1]) \ + or nt == 'CONST' and node.t == 'integer' and node.value in (0, 1): return True if nt == 'FNCALL': - sym = self.symtab[0][node['name']] + sym = self.symtab[0][node.name] if sym['Type'] == 'integer' and 'min' in sym and 'max' in sym \ and sym['min'] >= 0 and sym['max'] <= 1: return True @@ -268,52 +267,49 @@ class foldconst(object): with them. """ node = parent[index] - nt = node['nt'] + nt = node.nt if nt in ('CONST', 'IDENT', 'FLD'): - if node['nt'] == 'CONST': - node['t'] = 'integer' - node['value'] = 1 if lslfuncs.cond(node['value']) else 0 + if node.nt == 'CONST': + node.t = 'integer' + node.value = 1 if lslfuncs.cond(node.value) else 0 return # Nothing to do if it's already simplified. - child = node['ch'] if 'ch' in node else None + child = node.ch - if nt == 'FNCALL' and 'strlen' in self.symtab[0][node['name']]: + if nt == 'FNCALL' and 'strlen' in self.symtab[0][node.name]: # llStringLength(expr) -> !(expr == "") - node = {'nt':'==', 't':'integer', - 'ch':[child[0], - {'nt':'CONST', 't':'string', - 'value':u''}]} - node = {'nt':'!', 't':'integer', 'ch':[node]} # new node is SEF if the argument to llStringLength is - if 'SEF' in child[0]: - node['SEF'] = True - node['ch'][0]['SEF'] = True + node = nr(nt='==', t='integer', SEF=child[0].SEF, + ch=[child[0], + nr(nt='CONST', t='string', value=u'', SEF=True) + ]) + node = nr(nt='!', t='integer', ch=[node], SEF=child[0].SEF) parent[index] = node nt = '!' - child = node['ch'] + child = node.ch # fall through to keep optimizing if necessary if nt == '!': self.FoldCond(child, 0, True) - if child[0]['nt'] == '!': + if child[0].nt == '!': # bool(!!a) equals bool(a) - parent[index] = child[0]['ch'][0] + parent[index] = child[0].ch[0] return - if (child[0]['nt'] == '==' and child[0]['ch'][0]['t'] == 'integer' - and child[0]['ch'][1]['t'] == 'integer' + if (child[0].nt == '==' and child[0].ch[0].t == 'integer' + and child[0].ch[1].t == 'integer' ): # We have !(int == int). Replace with int ^ int or with int - 1 node = parent[index] = child[0] # remove the negation - child = child[0]['ch'] - if child[0]['nt'] == 'CONST' and child[0]['value'] == 1 \ - or child[1]['nt'] == 'CONST' and child[1]['value'] == 1: + child = child[0].ch + if child[0].nt == 'CONST' and child[0].value == 1 \ + or child[1].nt == 'CONST' and child[1].value == 1: # a != 1 -> a - 1 (which FoldTree will transform to ~-a) - node['nt'] = '-' + node.nt = '-' else: # This converts != to ^; FoldTree will simplify ^-1 to ~ # and optimize out ^0. - node['nt'] = '^' + node.nt = '^' self.FoldTree(parent, index) return @@ -324,13 +320,13 @@ class foldconst(object): self.FoldCond(parent, index, ParentIsNegation) return - if nt in self.binary_ops and child[0]['t'] == child[1]['t'] == 'integer': + if nt in self.binary_ops and child[0].t == child[1].t == 'integer': if nt == '==': - if child[0]['nt'] == 'CONST' and -1 <= child[0]['value'] <= 1 \ - or child[1]['nt'] == 'CONST' and -1 <= child[1]['value'] <= 1: + if child[0].nt == 'CONST' and -1 <= child[0].value <= 1 \ + or child[1].nt == 'CONST' and -1 <= child[1].value <= 1: # Transform a==b into !(a-b) if either a or b are in [-1, 1] - parent[index] = {'nt':'!', 't':'integer', 'ch':[node]} - node['nt'] = '-' + parent[index] = nr(nt='!', t='integer', ch=[node]) + node.nt = '-' self.FoldTree(parent, index) return @@ -342,13 +338,11 @@ class foldconst(object): # Deal with operands in any order a, b = 0, 1 # Put constant in child[b] if present - if child[b]['nt'] != 'CONST': + if child[b].nt != 'CONST': a, b = 1, 0 - if (child[b]['nt'] == 'CONST' and child[b]['value'] - and 'SEF' in child[a] - ): + if child[b].nt == 'CONST' and child[b].value and child[a].SEF: node = parent[index] = child[b] - node['value'] = -1 + node.value = -1 return del a, b @@ -356,19 +350,19 @@ class foldconst(object): # If b and c are constant powers of two: # !(a & b) | !(a & c) -> ~(a|~(b|c)) # e.g. if (a & 4 && a & 8) -> if (!~(a|-13)) - if (child[0]['nt'] == '!' and child[0]['ch'][0]['nt'] == '&' - and child[1]['nt'] == '!' and child[1]['ch'][0]['nt'] == '&' + if (child[0].nt == '!' and child[0].ch[0].nt == '&' + and child[1].nt == '!' and child[1].ch[0].nt == '&' ): - and1 = child[0]['ch'][0]['ch'] - and2 = child[1]['ch'][0]['ch'] + and1 = child[0].ch[0].ch + and2 = child[1].ch[0].ch a, b, c, d = 0, 1, 0, 1 - if and1[b]['nt'] != 'CONST': + if and1[b].nt != 'CONST': a, b = b, a - if and2[d]['nt'] != 'CONST': + if and2[d].nt != 'CONST': c, d = d, c - if and1[b]['nt'] == and2[d]['nt'] == 'CONST': - val1 = and1[b]['value'] - val2 = and2[d]['value'] + if and1[b].nt == and2[d].nt == 'CONST': + val1 = and1[b].value + val2 = and2[d].value if (val1 and val2 # power of 2 and (val1 & (val1 - 1) & 0xFFFFFFFF) == 0 @@ -378,11 +372,9 @@ class foldconst(object): # Check passed child[0] = and1[a] child[1] = and1[b] - child[1]['value'] = ~(val1 | val2) - parent[index] = {'nt':'~', 't':'integer', - 'ch':[node]} - if 'SEF' in node: - parent[index]['SEF'] = True + child[1].value = ~(val1 | val2) + parent[index] = nr(nt='~', t='integer', ch=[node], + SEF=node.SEF) self.FoldCond(parent, index, ParentIsNegation) return del val1, val2 @@ -402,30 +394,30 @@ class foldconst(object): # e is the child of ! which has x # f is the child of ! with the constant s a, b = 0, 1 - if child[a]['nt'] != '~': + if child[a].nt != '~': a, b = b, a c, d = 0, 1 - if child[a]['nt'] == '~' and child[a]['ch'][0]['nt'] == '|': - if child[a]['ch'][0]['ch'][d]['nt'] != 'CONST': + if child[a].nt == '~' and child[a].ch[0].nt == '|': + if child[a].ch[0].ch[d].nt != 'CONST': c, d = d, c e, f = 0, 1 - if child[b]['nt'] == '!' and child[b]['ch'][0]['nt'] == '&': - if child[b]['ch'][0]['ch'][f]['nt'] != 'CONST': + if child[b].nt == '!' and child[b].ch[0].nt == '&': + if child[b].ch[0].ch[f].nt != 'CONST': e, f = f, e # All pointers are ready to check applicability. - if (child[a]['nt'] == '~' and child[a]['ch'][0]['nt'] == '|' - and child[b]['nt'] == '!' and child[b]['ch'][0]['nt'] == '&' + if (child[a].nt == '~' and child[a].ch[0].nt == '|' + and child[b].nt == '!' and child[b].ch[0].nt == '&' ): - ch1 = child[a]['ch'][0]['ch'] - ch2 = child[b]['ch'][0]['ch'] - if (ch1[d]['nt'] == 'CONST' and ch2[f]['nt'] == 'CONST' - and (ch2[f]['value'] & (ch2[f]['value'] - 1) + ch1 = child[a].ch[0].ch + ch2 = child[b].ch[0].ch + if (ch1[d].nt == 'CONST' and ch2[f].nt == 'CONST' + and (ch2[f].value & (ch2[f].value - 1) & 0xFFFFFFFF) == 0 ): if self.CompareTrees(ch1[c], ch2[e]): # We're in that case. Apply optimization. parent[index] = child[a] - ch1[d]['value'] &= ~ch2[f]['value'] + ch1[d].value &= ~ch2[f].value return del ch1, ch2 @@ -437,19 +429,19 @@ class foldconst(object): # We only support '<' and some cases of '&' (are there more?) Invertible = [False, False] for a in (0, 1): - Invertible[a] = child[a]['nt'] == '!' - if child[a]['nt'] == '<' \ - and child[a]['ch'][0]['t'] == child[a]['ch'][1]['t'] == 'integer': - if child[a]['ch'][0]['nt'] == 'CONST' \ - and child[a]['ch'][0]['value'] != 2147483647 \ - or child[a]['ch'][1]['nt'] == 'CONST' \ - and child[a]['ch'][1]['value'] != int(-2147483648): + Invertible[a] = child[a].nt == '!' + if child[a].nt == '<' \ + and child[a].ch[0].t == child[a].ch[1].t == 'integer': + if child[a].ch[0].nt == 'CONST' \ + and child[a].ch[0].value != 2147483647 \ + or child[a].ch[1].nt == 'CONST' \ + and child[a].ch[1].value != int(-2147483648): Invertible[a] = True # Deal with our optimization of a<0 -> a&0x80000000 (see below) - if child[a]['nt'] == '&' and ( - child[a]['ch'][0]['nt'] == 'CONST' and child[a]['ch'][0]['value'] == int(-2147483648) - or child[a]['ch'][1]['nt'] == 'CONST' and child[a]['ch'][1]['value'] == int(-2147483648) + if child[a].nt == '&' and ( + child[a].ch[0].nt == 'CONST' and child[a].ch[0].value == int(-2147483648) + or child[a].ch[1].nt == 'CONST' and child[a].ch[1].value == int(-2147483648) ): Invertible[a] |= ParentIsNegation @@ -460,28 +452,28 @@ class foldconst(object): # negated version to a simple &. for a in (0, 1): if not Invertible[a]: - child[a] = {'nt':'!', 't':'integer', - 'ch':[{'nt':'!', 't':'integer', 'ch':[child[a]]}] - } + child[a] = nr(nt='!', t='integer', + ch=[nr(nt='!', t='integer', ch=[child[a]])] + ) Invertible[a] = True if Invertible[0] and Invertible[1]: # Both operands are negated, or negable. # Make them a negation if they aren't already. for a in (0, 1): - if child[a]['nt'] == '<': - if child[a]['ch'][0]['nt'] == 'CONST': - child[a]['ch'][0]['value'] += 1 + if child[a].nt == '<': + if child[a].ch[0].nt == 'CONST': + child[a].ch[0].value += 1 else: - child[a]['ch'][1]['value'] -= 1 - child[a]['ch'][0], child[a]['ch'][1] = \ - child[a]['ch'][1], child[a]['ch'][0] - child[a] = {'nt':'!','t':'integer','ch':[child[a]]} - elif child[a]['nt'] == '&': - child[a] = {'nt':'!', 't':'integer', - 'ch':[{'nt':'!', 't':'integer', 'ch':[child[a]]}] - } - self.FoldTree(child[a]['ch'], 0) + child[a].ch[1].value -= 1 + child[a].ch[0], child[a].ch[1] = \ + child[a].ch[1], child[a].ch[0] + child[a] = nr(nt='!', t='integer', ch=[child[a]]) + elif child[a].nt == '&': + child[a] = nr(nt='!', t='integer', + ch=[nr(nt='!', t='integer', ch=[child[a]])] + ) + self.FoldTree(child[a].ch, 0) # If they are boolean, the expression can be turned into # !(a&b) which hopefully will have a ! uptree if it came # from a '&&' and cancel out (if not, we still remove one @@ -491,68 +483,63 @@ class foldconst(object): # Deal with operands in any order a, b = 0, 1 - # Put the bool in child[b]['ch'][0]. - if not self.IsBool(child[b]['ch'][0]): + # Put the bool in child[b].ch[0]. + if not self.IsBool(child[b].ch[0]): a, b = 1, 0 - if self.IsBool(child[b]['ch'][0]): - if not self.IsBool(child[a]['ch'][0]): - child[b]['ch'][0] = {'nt':'NEG','t':'integer', - 'ch':[child[b]['ch'][0]]} + if self.IsBool(child[b].ch[0]): + if not self.IsBool(child[a].ch[0]): + child[b].ch[0] = nr(nt='NEG', t='integer', + ch=[child[b].ch[0]]) - node = parent[index] = {'nt':'!', 't':'integer', - 'ch':[{'nt':'&','t':'integer', - 'ch':[child[0]['ch'][0], - child[1]['ch'][0]] - }] - } + node = parent[index] = nr(nt='!', t='integer', + ch=[nr(nt='&', t='integer', + ch=[child[0].ch[0], child[1].ch[0]]) + ], SEF=child[0].ch[0].SEF and child[1].ch[0].SEF) # Fold the node we've just synthesized - # (this deals with SEF) self.FoldTree(parent, index) return - if nt == '<' and child[0]['t'] == child[1]['t'] == 'integer': + if nt == '<' and child[0].t == child[1].t == 'integer': sym = None for a in (0, 1): - if child[a]['nt'] == 'FNCALL': - sym = self.symtab[0][child[a]['name']] + if child[a].nt == 'FNCALL': + sym = self.symtab[0][child[a].name] break # cond(FNCALL < 0) -> cond(~FNCALL) if min == -1 - if (child[1]['nt'] == 'CONST' and child[1]['value'] == 0 - and child[0]['nt'] == 'FNCALL' + if (child[1].nt == 'CONST' and child[1].value == 0 + and child[0].nt == 'FNCALL' and 'min' in sym and sym['min'] == -1 ): - node = parent[index] = {'nt':'~', 't':'integer', - 'ch':[child[0]]} + node = parent[index] = nr(nt='~', t='integer', + ch=[child[0]]) self.FoldTree(parent, index) return # cond(FNCALL > -1) -> cond(!~FNCALL) if min == -1 - if (child[0]['nt'] == 'CONST' and child[0]['value'] == -1 - and child[1]['nt'] == 'FNCALL' + if (child[0].nt == 'CONST' and child[0].value == -1 + and child[1].nt == 'FNCALL' and 'min' in sym and sym['min'] == -1 ): - node = parent[index] = {'nt':'!', 't':'integer', - 'ch':[{'nt':'~', 't':'integer', - 'ch':[child[1]]} - ]} + node = parent[index] = nr(nt='!', t='integer', + ch=[nr(nt='~', t='integer', ch=[child[1]])]) self.FoldTree(parent, index) return # cond(FNCALL < 1) -> cond(!FNCALL) if min == 0 - if (child[1]['nt'] == 'CONST' and child[1]['value'] == 1 - and child[0]['nt'] == 'FNCALL' + if (child[1].nt == 'CONST' and child[1].value == 1 + and child[0].nt == 'FNCALL' and 'min' in sym and sym['min'] == 0 ): - node = parent[index] = {'nt':'!', 't':'integer', - 'ch':[child[0]]} + node = parent[index] = nr(nt='!', t='integer', + ch=[child[0]]) self.FoldTree(parent, index) return # cond(FNCALL > 0) -> cond(FNCALL) if min == 0 - if (child[0]['nt'] == 'CONST' and child[0]['value'] == 0 - and child[1]['nt'] == 'FNCALL' + if (child[0].nt == 'CONST' and child[0].value == 0 + and child[1].nt == 'FNCALL' and 'min' in sym and sym['min'] == 0 ): node = parent[index] = child[1] @@ -564,26 +551,22 @@ class foldconst(object): # Deal with operands in any order a, b = 0, 1 # Put constant in child[b], if present - if child[b]['nt'] != 'CONST': + if child[b].nt != 'CONST': a, b = 1, 0 - if child[b]['nt'] == 'CONST' and child[b]['value'] == int(-2147483648) \ - and child[a]['nt'] == 'FNCALL': - sym = self.symtab[0][child[a]['name']] + if child[b].nt == 'CONST' and child[b].value == int(-2147483648) \ + and child[a].nt == 'FNCALL': + sym = self.symtab[0][child[a].name] if 'min' in sym and sym['min'] == -1: - node = parent[index] = {'nt':'~', 't':'integer', - 'ch':[child[a]]} + node = parent[index] = nr(nt='~', t='integer', + ch=[child[a]]) self.FoldTree(parent, index) return def CopyNode(self, node): - '''This is mainly for simple_expr so no need to go deeper than 1 level - ''' + '''Deep copy of a node''' ret = node.copy() - if 'ch' in ret: - new = [] - for subnode in ret['ch']: - new.append(self.CopyNode(subnode)) - ret['ch'] = new + if ret.ch: + ret.ch = [self.CopyNode(subnode) for subnode in ret.ch] return ret def FoldTree(self, parent, index): @@ -593,77 +576,74 @@ class foldconst(object): Also optimizes away IF, WHILE, etc. """ node = parent[index] - nt = node['nt'] - child = node['ch'] if 'ch' in node else None + nt = node.nt + child = node.ch if nt == 'CONST': # Job already done. But mark as side-effect free. - node['SEF'] = True + node.SEF = True return if nt == 'CAST': self.FoldTree(child, 0) - if 'SEF' in child[0]: - node['SEF'] = True - if child[0]['nt'] == 'CONST': + node.SEF = child[0].SEF + if child[0].nt == 'CONST': # Enable key constants. We'll typecast them back on output, but # this enables some optimizations. - #if node['t'] != 'key': # key constants not possible + #if node.t != 'key': # key constants not possible - parent[index] = {'nt':'CONST', 't':node['t'], 'SEF':True, - 'value':lslfuncs.typecast( - child[0]['value'], lslcommon.LSLType2Python[node['t']])} + parent[index] = nr(nt='CONST', t=node.t, SEF=True, + value=lslfuncs.typecast( + child[0].value, lslcommon.LSLType2Python[node.t])) # Remove casts of a type to the same type (NOP in Mono) # This is not an optimization by itself, but it simplifies the job, # by not needing to look into nested casts like (key)((key)...) - while node['nt'] == 'CAST' and child[0]['t'] == node['t']: + while node.nt == 'CAST' and child[0].t == node.t: parent[index] = node = child[0] - if 'ch' not in node: + if node.ch is None: break - child = node['ch'] + child = node.ch return if nt == 'NEG': self.FoldTree(child, 0) + node.SEF = child[0].SEF - if child[0]['nt'] == '+' and (child[0]['ch'][0]['nt'] == 'NEG' - or child[0]['ch'][1]['nt'] == 'NEG'): + if child[0].nt == '+' and (child[0].ch[0].nt == 'NEG' + or child[0].ch[1].nt == 'NEG'): node = parent[index] = child[0] - child = node['ch'] + child = node.ch for a in (0, 1): - if child[a]['nt'] == 'NEG': - child[a] = child[a]['ch'][0] + if child[a].nt == 'NEG': + child[a] = child[a].ch[0] else: - child[a] = {'nt':'NEG','t':child[a]['t'],'ch':[child[a]]} + child[a] = nr(nt='NEG', t=child[a].t, ch=[child[a]], + SEF=child[a].SEF) self.FoldTree(child, a) return - if child[0]['nt'] == 'NEG': + if child[0].nt == 'NEG': # Double negation: - - expr -> expr - node = parent[index] = child[0]['ch'][0] - child = node['ch'] if 'ch' in node else None - elif child[0]['nt'] == 'CONST': + node = parent[index] = child[0].ch[0] + child = node.ch + elif child[0].nt == 'CONST': node = parent[index] = child[0] - node['value'] = lslfuncs.neg(node['value']) + node.value = lslfuncs.neg(node.value) child = None - elif 'SEF' in child[0]: - # propagate Side Effect Free flag - node['SEF'] = True - if child and node['nt'] == 'NEG' and child[0]['nt'] == '~': - track = child[0]['ch'][0] + if child and node.nt == 'NEG' and child[0].nt == '~': + track = child[0].ch[0] const = 1 - while track['nt'] == 'NEG' and track['ch'][0]['nt'] == '~': + while track.nt == 'NEG' and track.ch[0].nt == '~': const += 1 - track = track['ch'][0]['ch'][0] + track = track.ch[0].ch[0] if const > 2: # -~-~-~expr -> expr+3 - node = {'nt':'CONST', 't':'integer', 'SEF':True, 'value':const} - node = {'nt':'+', 't':'integer', 'ch':[node, track]} - if 'SEF' in track: - node['SEF'] = True + node = nr(nt='CONST', t='integer', SEF=True, value=const) + node = nr(nt='+', t='integer', ch=[node, track], + SEF=track.SEF) parent[index] = node return @@ -673,51 +653,48 @@ class foldconst(object): self.FoldCond(child, 0, True) # !! does *not* cancel out (unless in cond) subexpr = child[0] - snt = subexpr['nt'] + snt = subexpr.nt - if 'SEF' in subexpr: - node['SEF'] = True - if subexpr['nt'] == 'CONST': + node.SEF = subexpr.SEF + if snt == 'CONST': node = parent[index] = subexpr - node['value'] = int(not node['value']) + node.value = int(not node.value) return if snt == '<': - lop = subexpr['ch'][0] - rop = subexpr['ch'][1] - if lop['nt'] == 'CONST' and lop['t'] == rop['t'] == 'integer' \ - and lop['value'] < 2147483647: - lop['value'] += 1 - subexpr['ch'][0], subexpr['ch'][1] = subexpr['ch'][1], subexpr['ch'][0] - parent[index] = subexpr # remove ! + lop = subexpr.ch[0] + rop = subexpr.ch[1] + if lop.nt == 'CONST' and lop.t == rop.t == 'integer' \ + and lop.value < 2147483647: + lop.value += 1 + subexpr.ch[0], subexpr.ch[1] = subexpr.ch[1], subexpr.ch[0] + parent[index] = subexpr # remove the ! return - if rop['nt'] == 'CONST' and lop['t'] == rop['t'] == 'integer' \ - and rop['value'] > int(-2147483648): - rop['value'] -= 1 - subexpr['ch'][0], subexpr['ch'][1] = subexpr['ch'][1], subexpr['ch'][0] - parent[index] = subexpr # remove ! + if rop.nt == 'CONST' and lop.t == rop.t == 'integer' \ + and rop.value > int(-2147483648): + rop.value -= 1 + subexpr.ch[0], subexpr.ch[1] = subexpr.ch[1], subexpr.ch[0] + parent[index] = subexpr # remove the ! return if snt == '&': a, b = 0, 1 - if subexpr['ch'][b]['nt'] != 'CONST': + if subexpr.ch[b].nt != 'CONST': a, b = 1, 0 - if subexpr['ch'][b]['nt'] == 'CONST' and subexpr['ch'][b]['value'] == int(-2147483648): + if subexpr.ch[b].nt == 'CONST' and subexpr.ch[b].value == int(-2147483648): # !(i & 0x80000000) -> -1 < i (because one of our # optimizations can be counter-productive, see FoldCond) - subexpr['nt'] = '<' - subexpr['ch'][b]['value'] = -1 - subexpr['ch'] = [subexpr['ch'][b], subexpr['ch'][a]] + subexpr.nt = '<' + subexpr.ch[b].value = -1 + subexpr.ch = [subexpr.ch[b], subexpr.ch[a]] parent[index] = subexpr return if snt == '!=' or snt == '^' or snt == '-' or snt == '+': if snt == '+': # Change !(x + y) -> -x == y, and make another pass # to get rid of the signs where possible - subexpr['ch'][0] = {'nt':'NEG', 't':'integer', - 'ch':[subexpr['ch'][0]]} - if 'SEF' in subexpr['ch'][0]['ch'][0]: - subexpr['ch'][0]['SEF'] = True + subexpr.ch[0] = nr(nt='NEG', t='integer', + ch=[subexpr.ch[0]], SEF=subexpr.ch[0].SEF) - subexpr['nt'] = '==' + subexpr.nt = '==' parent[index] = subexpr self.FoldTree(parent, index) return @@ -727,35 +704,36 @@ class foldconst(object): if nt == '~': self.FoldTree(child, 0) subexpr = child[0] - if 'SEF' in subexpr: - node['SEF'] = True - if subexpr['nt'] == '~': + node.SEF = subexpr.SEF + + # TODO: ~-~-~-expr -> expr + -3 (see NEG for similar exp + 3) + + if subexpr.nt == '~': # Double negation: ~~expr - parent[index] = subexpr['ch'][0] - elif subexpr['nt'] == 'CONST': + parent[index] = subexpr.ch[0] + elif subexpr.nt == 'CONST': node = parent[index] = child[0] - node['value'] = ~node['value'] + node.value = ~node.value return if nt in self.binary_ops: # RTL evaluation self.FoldTree(child, 1) self.FoldTree(child, 0) - if 'SEF' in child[0] and 'SEF' in child[1]: - # Propagate SEF flag if both sides are side-effect free. - node['SEF'] = True + # Node is SEF if both sides are side-effect free. + node.SEF = child[0].SEF and child[1].SEF - optype = node['t'] + optype = node.t lval = child[0] - ltype = lval['t'] - lnt = lval['nt'] + ltype = lval.t + lnt = lval.nt rval = child[1] - rtype = rval['t'] - rnt = rval['nt'] + rtype = rval.t + rnt = rval.nt if lnt == rnt == 'CONST': - op1 = lval['value'] - op2 = rval['value'] + op1 = lval.value + op2 = rval.value if nt == '+': if ltype == rtype == 'string' and not self.addstrings: return @@ -799,50 +777,49 @@ class foldconst(object): result = int(bool(op1) and bool(op2)) else: assert False, 'Internal error: Operator not found: ' + nt # pragma: no cover - parent[index] = {'nt':'CONST', 't':node['t'], 'SEF':True, 'value':result} + parent[index] = nr(nt='CONST', t=node.t, SEF=True, value=result) return # Simplifications for particular operands if nt == '-': if optype in ('vector', 'rotation'): - if lnt == 'CONST' and all(component == 0 for component in lval['value']): + if lnt == 'CONST' and all(component == 0 + for component in lval.value): # Change <0,0,0[,0]>-expr -> -expr - parent[index] = {'nt':'NEG', 't':node['t'], 'ch':[rval]} - if 'SEF' in rval: - parent[index]['SEF'] = True - elif rnt == 'CONST' and all(component == 0 for component in rval['value']): + parent[index] = nr(nt='NEG', t=node.t, ch=[rval], + SEF=rval.SEF) + elif rnt == 'CONST' and all(component == 0 + for component in rval.value): # Change expr-<0,0,0[,0]> -> expr parent[index] = lval return # Change - to + - for int/float - nt = node['nt'] = '+' - if child[1]['nt'] == 'CONST': - rval['value'] = lslfuncs.neg(rval['value']) + nt = node.nt = '+' + if child[1].nt == 'CONST': + rval.value = lslfuncs.neg(rval.value) else: rnt = 'NEG' - RSEF = 'SEF' in rval - rval = child[1] = {'nt':rnt, 't':rval['t'], 'ch':[rval]} + rval = child[1] = nr(nt=rnt, t=rval.t, ch=[rval], + SEF=rval.SEF) self.FoldTree(child, 1) - if RSEF: - rval['SEF'] = True # rtype unchanged # Fall through to simplify it as '+' if nt == '+': - # Tough one. Remove neutral elements for the diverse types, + # Tough one. Remove neutral elements for the various types, # and more. # expr + -expr -> 0 # -expr + expr -> 0 - if (child[0]['nt'] == 'NEG' - and self.CompareTrees(child[0]['ch'][0], child[1]) - or child[1]['nt'] == 'NEG' - and self.CompareTrees(child[1]['ch'][0], child[0]) + if (child[0].nt == 'NEG' + and self.CompareTrees(child[0].ch[0], child[1]) + or child[1].nt == 'NEG' + and self.CompareTrees(child[1].ch[0], child[0]) ): - parent[index] = {'nt':'CONST', 't':'integer', 'value':0, - 'SEF':True} + parent[index] = nr(nt='CONST', t='integer', value=0, + SEF=True) return # Addition of integers, strings, and lists is associative. @@ -853,32 +830,33 @@ class foldconst(object): # the types of the operands may not be lists # so e.g. list+(integer+integer) != (list+integer)+integer. if optype == 'integer' or optype == 'string' and self.addstrings: - if lnt == '+' and rnt == 'CONST' and lval['ch'][1]['nt'] == 'CONST': + if lnt == '+' and rnt == 'CONST' and lval.ch[1].nt == 'CONST': # (var + ct1) + ct2 -> var + (ct1 + ct2) - child[1] = {'nt': '+', 't': optype, 'ch':[lval['ch'][1], rval], 'SEF':True} - lval = child[0] = lval['ch'][0] - lnt = lval['nt'] - ltype = lval['t'] + child[1] = nr(nt='+', t=optype, ch=[lval.ch[1], rval], + SEF=True) + lval = child[0] = lval.ch[0] + lnt = lval.nt + ltype = lval.t rtype = optype # Fold the RHS again now that we have it constant self.FoldTree(child, 1) rval = child[1] - rnt = rval['nt'] + rnt = rval.nt if optype == 'list' and not (ltype == rtype == 'list'): - if lnt == 'CONST' and not lval['value']: + if lnt == 'CONST' and not lval.value: # [] + nonlist -> (list)nonlist parent[index] = self.Cast(rval, optype) # node is SEF if rval is - parent[index]['SEF'] = 'SEF' in rval + parent[index].SEF = rval.SEF return if optype in ('vector', 'rotation'): # not much to do with vectors or quaternions either - if lnt == 'CONST' and all(x == 0 for x in lval['value']): + if lnt == 'CONST' and all(x == 0 for x in lval.value): # Change <0,0,0[,0]>+expr -> expr parent[index] = rval - elif rnt == 'CONST' and all(x == 0 for x in rval['value']): + elif rnt == 'CONST' and all(x == 0 for x in rval.value): # Change expr+<0,0,0[,0]> -> expr parent[index] = lval return @@ -887,72 +865,72 @@ class foldconst(object): # All these types evaluate to boolean False when they are # the neutral addition element. if optype in ('string', 'float', 'list'): - if lnt == 'CONST' and not lval['value']: + if lnt == 'CONST' and not lval.value: # 0. + expr -> expr # "" + expr -> expr # [] + expr -> expr parent[index] = self.Cast(rval, optype) # node is SEF if rval is - parent[index]['SEF'] = 'SEF' in rval + parent[index].SEF = rval.SEF return - if rnt == 'CONST' and not rval['value']: + if rnt == 'CONST' and not rval.value: # expr + 0. -> expr # expr + "" -> expr # expr + [] -> expr parent[index] = self.Cast(lval, optype) # node is SEF if lval is - parent[index]['SEF'] = 'SEF' in lval + parent[index].SEF = lval.SEF return if ltype == rtype == 'list': - if (rnt == 'LIST' and len(rval['ch']) == 1 - or rnt == 'CONST' and len(rval['value']) == 1 + if (rnt == 'LIST' and len(rval.ch) == 1 + or rnt == 'CONST' and len(rval.value) == 1 or rnt == 'CAST' ): # list + (list)element -> list + element # list + [element] -> list + element - while rnt == 'CAST' and rval['t'] == 'list': + while rnt == 'CAST' and rval.t == 'list': # Remove nested typecasts # e.g. list + (list)((list)x) -> list + x - rval = parent[index]['ch'][1] = rval['ch'][0] - rnt = rval['nt'] - if (rnt == 'LIST' and len(rval['ch']) == 1 - and rval['ch'][0]['t'] != 'list'): + rval = parent[index].ch[1] = rval.ch[0] + rnt = rval.nt + if (rnt == 'LIST' and len(rval.ch) == 1 + and rval.ch[0].t != 'list'): # Finally, remove [] wrapper if it's not # list within list - rval = child[1] = rval['ch'][0] - rnt = rval['nt'] - if rnt == 'CONST' and len(rval['value']) == 1: + rval = child[1] = rval.ch[0] + rnt = rval.nt + if rnt == 'CONST' and len(rval.value) == 1: # list + [constant] -> list + constant - rval['value'] = rval['value'][0] - rtype = rval['t'] = lslcommon.PythonType2LSL[ - type(rval['value'])] + rval.value = rval.value[0] + rtype = rval.t = lslcommon.PythonType2LSL[ + type(rval.value)] return - if (lnt == 'LIST' and len(lval['ch']) == 1 - or lnt == 'CONST' and len(lval['value']) == 1 + if (lnt == 'LIST' and len(lval.ch) == 1 + or lnt == 'CONST' and len(lval.value) == 1 or lnt == 'CAST' ): # (list)element + list -> element + list # [element] + list -> element + list # (list)[element] + list -> element + list - while lnt == 'CAST' and lval['t'] == 'list': + while lnt == 'CAST' and lval.t == 'list': # Remove nested typecasts # e.g. (list)((list)x) + list -> x + list - lval = parent[index]['ch'][0] = lval['ch'][0] - lnt = lval['nt'] - if (lnt == 'LIST' and len(lval['ch']) == 1 - and lval['ch'][0]['t'] != 'list'): + lval = parent[index].ch[0] = lval.ch[0] + lnt = lval.nt + if (lnt == 'LIST' and len(lval.ch) == 1 + and lval.ch[0].t != 'list'): # Finally, remove [] wrapper if it's not # list within list - lval = child[0] = lval['ch'][0] - lnt = lval['nt'] - if lnt == 'CONST' and len(lval['value']) == 1: + lval = child[0] = lval.ch[0] + lnt = lval.nt + if lnt == 'CONST' and len(lval.value) == 1: # [constant] + list -> constant + list - lval['value'] = lval['value'][0] - ltype = lval['t'] = lslcommon.PythonType2LSL[ - type(lval['value'])] + lval.value = lval.value[0] + ltype = lval.t = lslcommon.PythonType2LSL[ + type(lval.value)] return return @@ -960,11 +938,11 @@ class foldconst(object): # Must be two integers. This allows for a number of # optimizations. First the most obvious ones. - if lnt == 'CONST' and lval['value'] == 0: + if lnt == 'CONST' and lval.value == 0: parent[index] = rval return - if rnt == 'CONST' and rval['value'] == 0: + if rnt == 'CONST' and rval.value == 0: parent[index] = lval return @@ -977,42 +955,40 @@ class foldconst(object): # here. Note these are integers, no NaN involved. # TODO: Compare the subtrees if they are SEF. If they are # the same subtree, they can cancel out. + #FIXME: ^ That long-standing to-do item should be easy now if lnt == rnt == 'NEG': - node = {'nt':'+', 't':optype, 'ch':[lval['ch'][0], rval['ch'][0]]} - SEF = 'SEF' in lval['ch'][0] and 'SEF' in rval['ch'][0] - if SEF: - node['SEF'] = True - node = {'nt':'NEG', 't':optype, 'ch':[node]} - if SEF: - node['SEF'] = True + node = nr(nt='+', t=optype, ch=[lval.ch[0], rval.ch[0]], + SEF=lval.ch[0].SEF and rval.ch[0].SEF) + node = nr(nt='NEG', t=optype, ch=[node], SEF=node.SEF) parent[index] = node return if lnt == 'NEG': # Swap to treat always as expr + -expr for simplicity. lnt, lval, rnt, rval = rnt, rval, lnt, lval - if lnt == 'IDENT' and rnt == 'NEG' and rval['ch'][0]['nt'] == 'IDENT' \ - and lval['name'] == rval['ch'][0]['name']: + if lnt == 'IDENT' and rnt == 'NEG' and rval.ch[0].nt == 'IDENT' \ + and lval.name == rval.ch[0].name: # Replace with 0 - parent[index] = {'nt':'CONST', 'SEF': True, 't':optype, 'value':0} + parent[index] = nr(nt='CONST', t=optype, value=0, + SEF=True) return - if lnt == '+' and (lval['ch'][0]['nt'] == 'CONST' - or lval['ch'][1]['nt'] == 'CONST'): + if lnt == '+' and (lval.ch[0].nt == 'CONST' + or lval.ch[1].nt == 'CONST'): # We have expr + const + const or const + expr + const. # Addition of integers mod 2^32 is associative and # commutative, so constants can be merged. - if lval['ch'][0]['nt'] == 'CONST': - rval['value'] = lslfuncs.S32(rval['value'] + lval['ch'][0]['value']) - lval = child[0] = lval['ch'][1] + if lval.ch[0].nt == 'CONST': + rval.value = lslfuncs.S32(rval.value + lval.ch[0].value) + lval = child[0] = lval.ch[1] else: - rval['value'] = lslfuncs.S32(rval['value'] + lval['ch'][1]['value']) - lval = child[0] = lval['ch'][0] - lnt = lval['nt'] + rval.value = lslfuncs.S32(rval.value + lval.ch[1].value) + lval = child[0] = lval.ch[0] + lnt = lval.nt - if rnt == '+' and (rval['ch'][0]['nt'] == 'CONST' - or rval['ch'][1]['nt'] == 'CONST'): + if rnt == '+' and (rval.ch[0].nt == 'CONST' + or rval.ch[1].nt == 'CONST'): # const + (expr + const) or const + (const + expr) # same as above, join them # FIXME: Isn't this covered by the associative sum above? @@ -1022,49 +998,29 @@ class foldconst(object): if rnt == 'CONST': # Swap the vars to deal with const in lval always lval, lnt, rval, rnt = rval, rnt, lval, lnt - RSEF = 'SEF' in rval + RSEF = rval.SEF - if lval['value'] == -1 or lval['value'] == -2: + if lval.value == -1 or lval.value == -2: if rnt == 'NEG': # Cancel the NEG - node = {'nt':'~', 't':optype, 'ch':rval['ch']} - if RSEF: - node['SEF'] = True + node = nr(nt='~', t=optype, ch=rval.ch, SEF=RSEF) else: # Add the NEG - node = {'nt':'NEG', 't':optype, 'ch':[rval]} - if RSEF: - node['SEF'] = True - node = {'nt':'~', 't':optype, 'ch':[node]} - if RSEF: - node['SEF'] = True - if lval['value'] == -2: - node = {'nt':'NEG', 't':optype, 'ch':[node]} - if RSEF: - node['SEF'] = True - node = {'nt':'~', 't':optype, 'ch':[node]} - if RSEF: - node['SEF'] = True + node = nr(nt='NEG', t=optype, ch=[rval], SEF=RSEF) + node = nr(nt='~', t=optype, ch=[node], SEF=RSEF) + if lval.value == -2: + node = nr(nt='NEG', t=optype, ch=[node], SEF=RSEF) + node = nr(nt='~', t=optype, ch=[node], SEF=RSEF) parent[index] = node return - if lval['value'] == 1 or lval['value'] == 2: + if lval.value == 1 or lval.value == 2: if rnt == '~': # Cancel the ~ - node = {'nt':'NEG', 't':optype, 'ch':rval['ch']} - if RSEF: - node['SEF'] = True + node = nr(nt='NEG', t=optype, ch=rval.ch, SEF=RSEF) else: - node = {'nt':'~', 't':optype, 'ch':[rval]} - if RSEF: - node['SEF'] = True - node = {'nt':'NEG', 't':optype, 'ch':[node]} - if RSEF: - node['SEF'] = True - if lval ['value'] == 2: - node = {'nt':'~', 't':optype, 'ch':[node]} - if RSEF: - node['SEF'] = True - node = {'nt':'NEG', 't':optype, 'ch':[node]} - if RSEF: - node['SEF'] = True + node = nr(nt='~', t=optype, ch=[rval], SEF=RSEF) + node = nr(nt='NEG', t=optype, ch=[node], SEF=RSEF) + if lval.value == 2: + node = nr(nt='~', t=optype, ch=[node], SEF=RSEF) + node = nr(nt='NEG', t=optype, ch=[node], SEF=RSEF) parent[index] = node return @@ -1072,15 +1028,15 @@ class foldconst(object): return - if nt == '<<' and child[1]['nt'] == 'CONST': + if nt == '<<' and child[1].nt == 'CONST': # Transforming << into multiply saves some bytes. - if child[1]['value'] & 31: + if child[1].value & 31: # x << 3 --> x * 8 # we have {<<, something, {CONST n}} # we transform it into {*, something, {CONST n}} - nt = node['nt'] = '*' - child[1]['value'] = 1 << (child[1]['value'] & 31) + nt = node.nt = '*' + child[1].value = 1 << (child[1].value & 31) # Fall through to optimize product @@ -1088,53 +1044,52 @@ class foldconst(object): parent[index] = child[0] return - if nt == '%' \ - and child[1]['nt'] == 'CONST' \ - and child[1]['t'] == 'integer' \ - and abs(child[1]['value']) == 1: + if (nt == '%' and child[1].nt == 'CONST' + and child[1].t == 'integer' + and abs(child[1].value) == 1): # a%1 -> a&0 # a%-1 -> a&0 # (SEF analysis performed below) - nt = node['nt'] = '&' - child[1]['value'] = 0 - + nt = node.nt = '&' + child[1].value = 0 + self.FoldTree(parent, index) + return if nt in ('*', '/'): # Extract signs outside - if child[0]['nt'] == 'NEG' or child[1]['nt'] == 'NEG': + if child[0].nt == 'NEG' or child[1].nt == 'NEG': a, b = 0, 1 - if child[b]['nt'] == 'NEG': + if child[b].nt == 'NEG': a, b = 1, 0 - child[a] = child[a]['ch'][0] - parent[index] = node = {'nt':'NEG', 't':node['t'], 'ch':[node]} - if 'SEF' in node['ch'][0]: - node['SEF'] = True + child[a] = child[a].ch[0] + parent[index] = node = nr(nt='NEG', t=node.t, ch=[node], + SEF = node.SEF) # Fold the new expression self.FoldTree(parent, index) return # Deal with operands in any order a, b = 0, 1 - if child[a]['nt'] == 'CONST' and child[a]['t'] in ('float', 'integer'): + if child[a].nt == 'CONST' and child[a].t in ('float', 'integer'): a, b = 1, 0 - if child[b]['nt'] == 'CONST': - val = child[b]['value'] + if child[b].nt == 'CONST': + val = child[b].value # Optimize out signs if possible. # Note that (-intvar)*floatconst needs cornermath because # -intvar could equal intvar if intvar = -2147483648, # so the sign is a no-op and pushing it to floatconst would # make the result be different. - if child[a]['nt'] == 'NEG' \ + if child[a].nt == 'NEG' \ and (self.cornermath - or child[a]['t'] != 'integer' - or child[b]['t'] != 'float' + or child[a].t != 'integer' + or child[b].t != 'float' ): # Expression is of the form (-float)*const or (-float)/const or const/(-float) - if val != int(-2147483648) or child[a]['t'] == 'integer': # can't be optimized otherwise - child[a] = child[a]['ch'][0] # remove NEG - child[b]['value'] = val = -val + if val != int(-2147483648) or child[a].t == 'integer': # can't be optimized otherwise + child[a] = child[a].ch[0] # remove NEG + child[b].value = val = -val # Five optimizations corresponding to -2, -1, 0, 1, 2 # for product, and two for division: @@ -1145,58 +1100,54 @@ class foldconst(object): # ident * -2 -> -(ident + ident) (only if ident is local) # expr/1 -> expr # expr/-1 -> -expr - if nt == '*' and child[b]['t'] in ('float', 'integer') \ + if nt == '*' and child[b].t in ('float', 'integer') \ and val in (-2, -1, 0, 1, 2) \ or nt == '/' and b == 1 and val in (-1, 1): if val == 1: parent[index] = child[a] return if val == 0: - if 'SEF' in child[a]: + if child[a].SEF: parent[index] = child[b] return if val == -1: # Note 0.0*-1 equals -0.0 in LSL, so this is safe - node = parent[index] = {'nt':'NEG', 't':node['t'], 'ch':[child[a]]} - if 'SEF' in child[a]: - node['SEF'] = True + node = parent[index] = nr(nt='NEG', t=node.t, + ch=[child[a]], SEF=child[a].SEF) return # only -2, 2 remain - if child[a]['nt'] == 'IDENT' and self.isLocalVar(child[a]): + if child[a].nt == 'IDENT' and self.isLocalVar(child[a]): child[b] = child[a].copy() - node['nt'] = '+' + node.nt = '+' if val == -2: - parent[index] = {'nt':'NEG', 't':node['t'], 'ch':[node]} - if 'SEF' in node: - parent[index]['SEF'] = True + parent[index] = nr(nt='NEG', t=node.t, + ch=[node], SEF=node.SEF) return return if nt == '==': - if child[0]['t'] == child[1]['t'] == 'integer': + if child[0].t == child[1].t == 'integer': # Deal with operands in any order a, b = 0, 1 - if child[b]['nt'] != 'CONST': + if child[b].nt != 'CONST': a, b = 1, 0 # a == -1 (in any order) -> !~a, # a == 0 -> !a # a == 1 -> !~-a - if child[b]['nt'] == 'CONST': - if child[b]['value'] in (-1, 0, 1): + if child[b].nt == 'CONST': + if child[b].value in (-1, 0, 1): node = child[a] - SEF = 'SEF' in node - if child[b]['value'] == -1: - node = {'nt':'~', 't':'integer', 'ch':[node]} - if SEF: node['SEF'] = True - elif child[b]['value'] == 1: - node = {'nt':'NEG', 't':'integer', 'ch':[node]} - if SEF: node['SEF'] = True - node = {'nt':'~', 't':'integer', 'ch':[node]} - if SEF: node['SEF'] = True - node = parent[index] = {'nt':'!', 't':'integer', - 'ch':[node]} - if SEF: node['SEF'] = True + if child[b].value == -1: + node = nr(nt='~', t='integer', ch=[node], + SEF=node.SEF) + elif child[b].value == 1: + node = nr(nt='NEG', t='integer', ch=[node], + SEF=node.SEF) + node = nr(nt='~', t='integer', ch=[node], + SEF=node.SEF) + node = parent[index] = nr(nt='!', t='integer', + ch=[node], SEF=node.SEF) del child self.FoldTree(parent, index) return @@ -1204,106 +1155,109 @@ class foldconst(object): # -a == -b -> a == b with const variations. # Note this changes the sign of two CONSTs but that case # should not reach here, as those are resolved earlier. - if ((child[0]['nt'] == 'NEG' or child[0]['nt'] == 'CONST') + if ((child[0].nt == 'NEG' or child[0].nt == 'CONST') and - (child[1]['nt'] == 'NEG' or child[1]['nt'] == 'CONST') + (child[1].nt == 'NEG' or child[1].nt == 'CONST') ): for a in (0, 1): - if child[a]['nt'] == 'NEG': - child[a] = child[a]['ch'][0] # remove sign + if child[a].nt == 'NEG': + child[a] = child[a].ch[0] # remove sign else: - child[a]['value'] = lslfuncs.neg( - child[a]['value']) + child[a].value = lslfuncs.neg( + child[a].value) if self.CompareTrees(child[0], child[1]): # expr == expr -> 1 - parent[index] = {'nt':'CONST', 't':'integer', 'value':1, - 'SEF':True} + parent[index] = nr(nt='CONST', t='integer', value=1, + SEF=True) return return - if nt in ('<=', '>=') or nt == '!=' and child[0]['t'] != 'list': + if nt in ('<=', '>=') or nt == '!=' and child[0].t != 'list': # Except for list != list, all these comparisons are compiled # as !(a>b) etc. so we transform them here in order to reduce # the number of cases to check. # a<=b --> !(a>b); a>=b --> !(a !(a==b) - node['nt'] = {'<=':'>', '>=':'<', '!=':'=='}[nt] - parent[index] = {'nt':'!', 't':node['t'], 'ch':[node]} + node.nt = {'<=':'>', '>=':'<', '!=':'=='}[nt] + parent[index] = nr(nt='!', t=node.t, ch=[node]) self.FoldTree(parent, index) return - if nt == '>' and ('SEF' in child[0] and 'SEF' in child[1] - or child[0]['nt'] == 'CONST' or child[1]['nt'] == 'CONST' + if nt == '>' and (child[0].SEF and child[1].SEF + or child[0].nt == 'CONST' + or child[1].nt == 'CONST' ): # Invert the inequalities to avoid doubling the cases to check. - # a>b -> bb -> b 0 if self.CompareTrees(child[0], child[1]): - parent[index] = {'nt':'CONST', 't':'integer', 'value':0, - 'SEF':True} + parent[index] = nr(nt='CONST', t='integer', value=0, + SEF=True) return - if child[0]['t'] == child[1]['t'] in ('integer', 'float'): - if (child[0]['nt'] == 'CONST' - and child[1]['nt'] == 'FNCALL' + if child[0].t == child[1].t in ('integer', 'float'): + if (child[0].nt == 'CONST' + and child[1].nt == 'FNCALL' and self.FnSEF(child[1]) ): # CONST < FNCALL aka FNCALL > CONST # when FNCALL.max <= CONST: always false # when CONST < FNCALL.min: always true - if ('max' in self.symtab[0][child[1]['name']] - and not lslfuncs.less(child[0]['value'], - self.symtab[0][child[1]['name']]['max']) + if ('max' in self.symtab[0][child[1].name] + and not lslfuncs.less(child[0].value, + self.symtab[0][child[1].name]['max']) ): - parent[index] = {'nt':'CONST', 't':'integer', - 'SEF':True, 'value':0} + parent[index] = nr(nt='CONST', t='integer', value=0, + SEF=True) return - if ('min' in self.symtab[0][child[1]['name']] - and lslfuncs.less(child[0]['value'], - self.symtab[0][child[1]['name']]['min']) + if ('min' in self.symtab[0][child[1].name] + and lslfuncs.less(child[0].value, + self.symtab[0][child[1].name]['min']) ): - parent[index] = {'nt':'CONST', 't':'integer', - 'SEF':True, 'value':1} + parent[index] = nr(nt='CONST', t='integer', value=1, + SEF=True) return - if (child[1]['nt'] == 'CONST' - and child[0]['nt'] == 'FNCALL' + if (child[1].nt == 'CONST' + and child[0].nt == 'FNCALL' and self.FnSEF(child[0]) ): # FNCALL < CONST # when CONST > FNCALL.max: always true # when CONST <= FNCALL.min: always false - if ('max' in self.symtab[0][child[0]['name']] + if ('max' in self.symtab[0][child[0].name] and lslfuncs.less( - self.symtab[0][child[0]['name']]['max'] - , child[1]['value']) + self.symtab[0][child[0].name]['max'] + , child[1].value) ): - parent[index] = {'nt':'CONST', 't':'integer', - 'SEF':True, 'value':1} + parent[index] = nr(nt='CONST', t='integer', value=1, + SEF=True) return - if ('min' in self.symtab[0][child[0]['name']] + if ('min' in self.symtab[0][child[0].name] and not lslfuncs.less( - self.symtab[0][child[0]['name']]['min'], - child[1]['value']) + self.symtab[0][child[0].name]['min'], + child[1].value) ): - parent[index] = {'nt':'CONST', 't':'integer', - 'SEF':True, 'value':0} + parent[index] = nr(nt='CONST', t='integer', value=0, + SEF=True) return # Convert 2147483647 a # a&-1 -> a @@ -1333,35 +1287,35 @@ class foldconst(object): # a|-1 -> -1 if a is SEF # a|1 -> 1 if a is bool and SEF # a&0 -> 0 if a is SEF - if 'SEF' in child[a]: + if child[a].SEF: parent[index] = child[b] # Apply boolean distributivity applied = False opposite = '&' if nt == '|' else '|' - if child[0]['nt'] == child[1]['nt'] == opposite: - left = child[0]['ch'] - right = child[1]['ch'] + if child[0].nt == child[1].nt == opposite: + left = child[0].ch + right = child[1].ch for c, d in ((0, 0), (0, 1), (1, 0), (1, 1)): if self.CompareTrees(left[c], right[d]): - child[1]['nt'] = nt - nt = node['nt'] = opposite - opposite = child[1]['nt'] + child[1].nt = nt + nt = node.nt = opposite + opposite = child[1].nt right[d] = left[1 - c] child[0] = left[c] applied = True break # Apply absorption, possibly after distributivity - if child[0]['nt'] == opposite or child[1]['nt'] == opposite: - c = 0 if child[1]['nt'] == opposite else 1 + if child[0].nt == opposite or child[1].nt == opposite: + c = 0 if child[1].nt == opposite else 1 for d in (0, 1): - if (self.CompareTrees(child[c], child[1 - c]['ch'][d]) - and 'SEF' in child[1 - c]['ch'][1 - d] + if (self.CompareTrees(child[c], child[1 - c].ch[d]) + and child[1 - c].ch[1 - d].SEF ): node = parent[index] = child[c] - nt = node['nt'] - child = node['ch'] if 'ch' in node else None + nt = node.nt + child = node.ch applied = True break @@ -1374,47 +1328,38 @@ class foldconst(object): if nt == '^': # expr ^ expr -> 0 if self.CompareTrees(child[0], child[1]): - parent[index] = {'nt':'CONST', 't':'integer', 'value':0, - 'SEF':True} + parent[index] = nr(nt='CONST', t='integer', value=0, + SEF=True) return a, b = 0, 1 - if child[a]['nt'] == 'CONST': + if child[a].nt == 'CONST': a, b = 1, 0 - if child[b]['nt'] == 'CONST' and child[b]['value'] in (0, -1): - if child[b]['value'] == 0: + if child[b].nt == 'CONST' and child[b].value in (0, -1): + if child[b].value == 0: parent[index] = child[a] else: - node['nt'] = '~' - node['ch'] = [child[a]] + node.nt = '~' + node.ch = [child[a]] return + #FIXME: Could remove one comparison if nt == '&&' or nt == '||': - SEF = 'SEF' in node if nt == '||': - parent[index] = node = {'nt':'!', 't':'integer', 'ch':[ - {'nt':'!', 't':'integer', 'ch':[ - {'nt':'|', 't':'integer', 'ch':[child[0], child[1]]} - ]}]} - if SEF: - # propagate SEF to the two ! and the OR - node['SEF'] = node['ch'][0]['SEF'] = True - node['ch'][0]['ch'][0]['SEF'] = True + # Expand to its equivalent a || b -> !!(a | b) + node = nr(nt='|', t='integer', ch=[child[0], child[1]], + SEF=child[0].SEF and child[1].SEF) + node = nr(nt='!', t='integer', ch=[node], SEF=node.SEF) + node = nr(nt='!', t='integer', ch=[node], SEF=node.SEF) + parent[index] = node else: orchildren = [ - {'nt':'!', 't':'integer', 'ch':[child[0]]} - , - {'nt':'!', 't':'integer', 'ch':[child[1]]} - ] - parent[index] = node = {'nt':'!', 't':'integer', 'ch':[ - {'nt':'|', 't':'integer', 'ch':orchildren}]} - if SEF: - # propagate SEF to the the OR and parent ! - node['SEF'] = node['ch'][0]['SEF'] = True - # propagate SEF to the ! that are children of the OR - if 'SEF' in orchildren[0]['ch'][0]: - orchildren[0]['SEF'] = True - if 'SEF' in orchildren[1]['ch'][0]: - orchildren[1]['SEF'] = True + nr(nt='!',t='integer',ch=[child[0]],SEF=child[0].SEF), + nr(nt='!',t='integer',ch=[child[1]],SEF=child[1].SEF) + ] + node = nr(nt='|', t='integer', ch=orchildren, + SEF=child[0].SEF and child[1].SEF) + node = nr(nt='!', t='integer', ch=[node], SEF=node.SEF) + parent[index] = node # Make another pass with the substitution self.FoldTree(parent, index) return @@ -1428,74 +1373,73 @@ class foldconst(object): # An assignment has no side effects only if it's of the form x = x. if nt != '=': - # Replace the node with the expression alone + # Replace the node with the expression alone... # e.g. a += b -> a + b - node['nt'] = nt[:-1] + node.nt = nt[:-1] # Linden Craziness: int *= float; is valid (but no other # int op= float is). It's actually performed as # i = (integer)(i + (f)); # This breaks equivalence of x op= y as x = x op (y) so we add # the explicit type cast here. - if nt == '*=' and child[0]['t'] == 'integer' and child[1]['t'] == 'float': - node['t'] = 'float' # Addition shall return float. + if (nt == '*=' and child[0].t == 'integer' + and child[1].t == 'float'): + node.t = 'float' # Addition shall return float. node = self.Cast(node, 'integer') - # And wrap it in an assignment. + # ... and wrap it in an assignment. child = [child[0].copy(), node] - node = parent[index] = {'nt':'=', 't':child[0]['t'], 'ch':child} + node = parent[index] = nr(nt='=', t=child[0].t, ch=child) # We have a regular assignment either way now. Simplify the RHS. - self.FoldTree(node['ch'], 1) - chkequal = child[1]['ch'][0] if child[1]['nt'] == '=' else child[1] - if child[0]['nt'] == chkequal['nt'] == 'IDENT' \ - and chkequal['name'] == child[0]['name'] \ - and chkequal['scope'] == child[0]['scope'] \ - or child[0]['nt'] == chkequal['nt'] == 'FLD' \ - and chkequal['ch'][0]['name'] == child[0]['ch'][0]['name'] \ - and chkequal['ch'][0]['scope'] == child[0]['ch'][0]['scope'] \ - and chkequal['fld'] == child[0]['fld']: + self.FoldTree(node.ch, 1) + chkequal = child[1].ch[0] if child[1].nt == '=' else child[1] + if child[0].nt == chkequal.nt == 'IDENT' \ + and chkequal.name == child[0].name \ + and chkequal.scope == child[0].scope \ + or child[0].nt == chkequal.nt == 'FLD' \ + and chkequal.ch[0].name == child[0].ch[0].name \ + and chkequal.ch[0].scope == child[0].ch[0].scope \ + and chkequal.fld == child[0].fld: parent[index] = child[1] return if nt == 'IDENT' or nt == 'FLD': - node['SEF'] = True + node.SEF = True if self.globalmode: ident = child[0] if nt == 'FLD' else node # Resolve constant values so they can be optimized - sym = self.symtab[ident['scope']][ident['name']] + sym = self.symtab[ident.scope][ident.name] defn = self.tree[sym['Loc']] - assert defn['name'] == ident['name'] + assert defn.name == ident.name # Assume we already were there - if 'ch' in defn: - val = defn['ch'][0] - if val['nt'] != 'CONST' or ident['t'] == 'key': + if defn.ch: + val = defn.ch[0] + if val.nt != 'CONST' or ident.t == 'key': return val = val.copy() else: - val = {'nt':'CONST', 't':defn['t'], - 'value':self.DefaultValues[defn['t']]} + val = nr(nt='CONST', t=defn.t, + value=self.DefaultValues[defn.t], SEF=True) if nt == 'FLD': - val = {'nt':'CONST', 't':'float', - 'value':val['value']['xyzs'.index(node['fld'])]} + val = nr(nt='CONST', t='float', + value=val.value['xyzs'.index(node.fld)], SEF=True) parent[index] = val return if nt == 'FNCALL': - name = node['name'] + name = node.name SEFargs = True CONSTargs = True for idx in xrange(len(child)-1, -1, -1): self.FoldTree(child, idx) # Function is not SEF if any argument is not SEF - if 'SEF' not in child[idx]: - SEFargs = False + SEFargs = SEFargs and child[idx].SEF # Function is not a constant if any argument is not a constant - if child[idx]['nt'] != 'CONST': - CONSTargs = False + CONSTargs = CONSTargs and child[idx].nt == 'CONST' sym = self.symtab[0][name] OptimizeArgs(node, sym) @@ -1504,11 +1448,11 @@ class foldconst(object): # It's side-effect free if the children are and the function # is marked as SEF. if SEFargs: - node['SEF'] = True + node.SEF = True if CONSTargs: # Call it fn = sym['Fn'] - args = [arg['value'] for arg in child] + args = [arg.value for arg in child] assert len(args) == len(sym['ParamTypes']) try: @@ -1524,10 +1468,10 @@ class foldconst(object): if not self.foldtabs: generatesTabs = ( - isinstance(value, unicode) and '\t' in value + isinstance(value, unicode) and u'\t' in value or type(value) == list and any(isinstance(x, unicode) - and '\t' in x for x in value) + and u'\t' in x for x in value) ) if generatesTabs: if self.warntabs: @@ -1540,14 +1484,14 @@ class foldconst(object): % name.decode('utf8')) raise lslfuncs.ELSLCantCompute() # Replace with a constant - parent[index] = {'nt':'CONST', 't':node['t'], - 'value':value, 'SEF':True} + parent[index] = nr(nt='CONST', t=node.t, value=value, + SEF=True) return elif SEFargs and 'SEF' in self.symtab[0][name]: # The function is marked as SEF in the symbol table, and the # arguments are all side-effect-free. The result is SEF. - node['SEF'] = True + node.SEF = True except lslfuncs.ELSLCantCompute: # Don't transform the tree if function is not computable @@ -1567,23 +1511,22 @@ class foldconst(object): if nt == 'EXPR': self.FoldTree(child, 0) - if 'SEF' in child[0]: - node['SEF'] = True + node.SEF = child[0].SEF return if nt == 'FNDEF': # CurEvent is needed when folding llDetected* function calls - if 'scope' in node: + if hasattr(node, 'scope'): # function definition self.CurEvent = None else: # event definition - self.CurEvent = node['name'] + self.CurEvent = node.name self.FoldTree(child, 0) # Test if the event is empty and SEF, and remove it if so. - if ('scope' not in node and not self.DoesSomething(child[0], - labels = False) and 'SEF' in self.events[node['name']] + if (not hasattr(node, 'scope') and not self.DoesSomething(child[0], + labels = False) and 'SEF' in self.events[node.name] ): # Delete ourselves. del parent[index] @@ -1591,16 +1534,16 @@ class foldconst(object): # TODO: This works, but analysis of code paths is DCR's thing # and this is incomplete, e.g. x(){{return;}} is not detected. - while 'ch' in child[0] and child[0]['ch']: - last = child[0]['ch'][-1] - if last['nt'] != 'RETURN' or 'ch' in last: + while child[0].ch: + last = child[0].ch[-1] + if last.nt != 'RETURN' or last.ch: break - del child[0]['ch'][-1] - if 'SEF' in child[0]: - node['SEF'] = True - if node['name'] in self.symtab[0]: + del child[0].ch[-1] + if child[0].SEF: + node.SEF = True + if node.name in self.symtab[0]: # Mark the symbol table entry if it's not an event. - self.symtab[0][node['name']]['SEF'] = True + self.symtab[0][node.name]['SEF'] = True return if nt in ('VECTOR', 'ROTATION', 'LIST'): @@ -1608,21 +1551,18 @@ class foldconst(object): issef = True for idx in xrange(len(child)): self.FoldTree(child, idx) - if child[idx]['nt'] != 'CONST': - isconst = False - if 'SEF' not in child[idx]: - issef = False + isconst = isconst and child[idx].nt == 'CONST' + issef = issef and child[idx].SEF + if isconst: - value = [x['value'] for x in child] + value = [x.value for x in child] if nt == 'VECTOR': value = Vector([lslfuncs.ff(x) for x in value]) elif nt == 'ROTATION': value = Quaternion([lslfuncs.ff(x) for x in value]) - parent[index] = {'nt':'CONST', 'SEF':True, 't':node['t'], - 'value':value} + parent[index] = nr(nt='CONST', t=node.t, value=value, SEF=True) return - if issef: - node['SEF'] = True + node.SEF = issef return if nt == 'STDEF': @@ -1630,10 +1570,10 @@ class foldconst(object): self.FoldTree(child, idx) if not child: # All events removed - add a dummy timer() - child.append({'nt':'FNDEF', 't':None, 'name':'timer', - 'pscope':0, 'ptypes':[], 'pnames':[], - 'ch':[{'nt':'{}', 't':None, 'ch':[]}] - }) + child.append(nr(nt='FNDEF', t=None, name='timer', + pscope=0, ptypes=[], pnames=[], + ch=[nr(nt='{}', t=None, ch=[])] + )) return if nt == '{}': @@ -1642,27 +1582,26 @@ class foldconst(object): while idx < len(child): self.FoldTree(child, idx) self.FoldStmt(child, idx) - if 'SEF' not in child[idx]: - issef = False - if child[idx]['nt'] == ';' \ - or child[idx]['nt'] == '{}' and not child[idx]['ch']: + issef = issef and child[idx].SEF + if child[idx].nt == ';' \ + or child[idx].nt == '{}' and not child[idx].ch: del child[idx] else: idx += 1 if issef: - node['SEF'] = True + node.SEF = True return if nt == 'IF': self.ExpandCondition(child, 0) self.FoldTree(child, 0) self.FoldCond(child, 0) - if child[0]['nt'] == 'CONST': + if child[0].nt == 'CONST': # We might be able to remove one of the branches. - if lslfuncs.cond(child[0]['value']): + if lslfuncs.cond(child[0].value): self.FoldTree(child, 1) self.FoldStmt(child, 1) - if len(child) == 3 and child[2]['nt'] == '@': + if len(child) == 3 and child[2].nt == '@': # Corner case. The label is in the same scope as # this statement, so it must be preserved just in # case it's jumped to. @@ -1672,7 +1611,7 @@ class foldconst(object): elif len(child) == 3: self.FoldTree(child, 2) self.FoldStmt(child, 2) - if child[1]['nt'] == '@': + if child[1].nt == '@': # Corner case. The label is in the same scope as this # statement, so it must be preserved just in case it's # jumped to. @@ -1683,13 +1622,13 @@ class foldconst(object): return else: # No ELSE branch, replace the statement with an empty one. - if child[1]['nt'] == '@': + if child[1].nt == '@': # Corner case. The label is in the same scope as this # statement, so it must be preserved just in case it's # jumped to. parent[index] = child[1] return - parent[index] = {'nt':';', 't':None, 'SEF':True} + parent[index] = nr(nt=';', t=None, SEF=True) return else: self.FoldTree(child, 1) @@ -1702,34 +1641,34 @@ class foldconst(object): # with a label, as it will be wrapped in {} making it # become out of scope) if (self.DoesSomething(child[2]) - and (child[2]['nt'] != 'IF' - or len(child[2]['ch']) == 3 - or child[2]['ch'][1]['nt'] != '@') + and (child[2].nt != 'IF' + or len(child[2].ch) == 3 + or child[2].ch[1].nt != '@') ): # Check if we can gain something by negating the # expression. # Swap 'if' and 'else' branch when the condition has # a '!' prefix - if child[0]['nt'] == '!': - child[0] = child[0]['ch'][0] + if child[0].nt == '!': + child[0] = child[0].ch[0] child[1], child[2] = child[2], child[1] # Swap them if condition is '==' with integer operands - if (child[0]['nt'] == '==' - and child[0]['ch'][0]['t'] - == child[0]['ch'][1]['t'] == 'integer' + if (child[0].nt == '==' + and child[0].ch[0].t + == child[0].ch[1].t == 'integer' ): - child[0]['nt'] = '^' + child[0].nt = '^' child[1], child[2] = child[2], child[1] # Re-test just in case we swapped in the previous check. if (not self.DoesSomething(child[2]) - and child[1]['nt'] != '@'): + and child[1].nt != '@'): # no point in "... else ;" - remove else branch del child[2] if not self.DoesSomething(child[1]): # if (X) ; -> X; if len(child) == 2: - parent[index] = {'nt':'EXPR', 't':child[0]['t'], - 'ch':[child[0]]} + parent[index] = nr(nt='EXPR', t=child[0].t, + ch=[child[0]]) # It has been promoted to statement. Fold it as such. # (Will remove it if SEF) self.FoldStmt(parent, index) @@ -1738,21 +1677,21 @@ class foldconst(object): # If type(X) != Key, then: # if (X) ; else {stuff} -> if (!X) {stuff} # (being careful with labels again) - if (child[0]['t'] != 'key' - and (child[2]['nt'] != 'IF' - or len(child[2]['ch']) == 3 - or child[2]['ch'][1]['nt'] != '@') + if (child[0].t != 'key' + and (child[2].nt != 'IF' + or len(child[2].ch) == 3 + or child[2].ch[1].nt != '@') ): # We've already converted all other types to equivalent # comparisons - assert child[0]['t'] == 'integer' - child[0] = {'nt':'!', 't':'integer', 'ch':[child[0]]} + assert child[0].t == 'integer' + child[0] = nr(nt='!', t='integer', ch=[child[0]]) del child[1] self.FoldTree(child, 0) self.FoldCond(child, 0) - if all('SEF' in subnode for subnode in child): - node['SEF'] = True + if all(subnode.SEF for subnode in child): + node.SEF = True return if nt == 'WHILE': @@ -1764,22 +1703,22 @@ class foldconst(object): self.ExpandCondition(child, 0) self.FoldTree(child, 0) self.FoldCond(child, 0) - if child[0]['nt'] == 'CONST': + if child[0].nt == 'CONST': # See if the whole WHILE can be eliminated. - if lslfuncs.cond(child[0]['value']): + if lslfuncs.cond(child[0].value): # Endless loop which must be kept. # Recurse on the statement. self.FoldTree(child, 1) self.FoldStmt(child, 1) else: - if child[1]['nt'] == '@': + if child[1].nt == '@': # Corner case. The label is in the same scope as this # statement, so it must be preserved just in case it's # jumped to. parent[index] = child[1] else: # Whole statement can be removed. - parent[index] = {'nt':';', 't':None, 'SEF':True} + parent[index] = nr(nt=';', t=None, SEF=True) return else: self.FoldTree(child, 1) @@ -1793,44 +1732,44 @@ class foldconst(object): self.FoldTree(child, 1) self.FoldCond(child, 1) # See if the latest part is a constant. - if child[1]['nt'] == 'CONST': - if not lslfuncs.cond(child[1]['value']): + if child[1].nt == 'CONST': + if not lslfuncs.cond(child[1].value): # Only one go. Replace with the statement(s). parent[index] = child[0] return if nt == 'FOR': - assert child[0]['nt'] == 'EXPRLIST' - assert child[2]['nt'] == 'EXPRLIST' - self.FoldAndRemoveEmptyStmts(child[0]['ch']) + assert child[0].nt == 'EXPRLIST' + assert child[2].nt == 'EXPRLIST' + self.FoldAndRemoveEmptyStmts(child[0].ch) self.ExpandCondition(child, 1) # Condition. self.FoldTree(child, 1) self.FoldCond(child, 1) - if child[1]['nt'] == 'CONST': + if child[1].nt == 'CONST': # FOR is delicate. It can have multiple expressions at start. # And if there is more than one, these expressions will need a # new block, which means new scope, which is dangerous. # They are expressions, no declarations or labels allowed, thus # no new identifiers may be created in the new scope, but it # still feels dodgy. - if lslfuncs.cond(child[1]['value']): + if lslfuncs.cond(child[1].value): # Endless loop. Traverse the loop and the iterator. self.FoldTree(child, 3) self.FoldStmt(child, 3) - self.FoldAndRemoveEmptyStmts(child[2]['ch']) + self.FoldAndRemoveEmptyStmts(child[2].ch) else: # Convert expression list to code block. exprlist = [] - for expr in child[0]['ch']: + for expr in child[0].ch: # Fold into expression statements. - exprlist.append({'nt':'EXPR', 't':expr['t'], 'ch':[expr]}) - if (exprlist or child[2]['ch']) and child[3]['nt'] == '@': + exprlist.append(nr(nt='EXPR', t=expr.t, ch=[expr])) + if (exprlist or child[2].ch) and child[3].nt == '@': # Corner case. We can't optimize this to one single # statement, so we leave it as-is. self.FoldTree(child, 3) self.FoldStmt(child, 3) - self.FoldAndRemoveEmptyStmts(child[2]['ch']) + self.FoldAndRemoveEmptyStmts(child[2].ch) return # returns type None, as FOR does @@ -1838,26 +1777,26 @@ class foldconst(object): # We're in the case where there are expressions. If any # remain, they are not SEF (or they would have been # removed earlier) so don't mark this node as SEF. - parent[index] = {'nt':'{}', 't':None, 'ch':exprlist} + parent[index] = nr(nt='{}', t=None, ch=exprlist) else: - if child[3]['nt'] == '@': + if child[3].nt == '@': # Corner case. The label is in the same scope as # this statement, so it must be preserved. Also, # jumping inside the loop would execute the # iterator, so we fold it. - self.FoldAndRemoveEmptyStmts(child[2]['ch']) - if not child[2]['ch']: + self.FoldAndRemoveEmptyStmts(child[2].ch) + if not child[2].ch: # if there's something in the 2nd list, # preserve the whole statement, otherwise # replace it with the label parent[index] = child[3] else: - parent[index] = {'nt':';', 't':None, 'SEF': True} + parent[index] = nr(nt=';', t=None, SEF=True) return else: self.FoldTree(child, 3) self.FoldStmt(child, 3) - self.FoldAndRemoveEmptyStmts(child[2]['ch']) + self.FoldAndRemoveEmptyStmts(child[2].ch) return if nt == 'RETURN': @@ -1869,25 +1808,26 @@ class foldconst(object): if child: # Check if child is a simple_expr. If it is, then we keep the # original attached to the folded node to use it in the output. - if child[0].pop('Simple', False): + if getattr(child[0], 'Simple', False): orig = self.CopyNode(child[0]) + del orig.Simple # presence of orig in child will be enough self.FoldTree(child, 0) - child[0]['orig'] = orig + child[0].orig = orig else: self.FoldTree(child, 0) # Remove assignment if integer zero. - if node['t'] == 'integer' and child[0]['nt'] == 'CONST' \ - and not child[0]['value']: - del node['ch'] + if node.t == 'integer' and child[0].nt == 'CONST' \ + and not child[0].value: + node.ch = None return else: # Add assignment if vector, rotation or float. - if node['t'] in ('float', 'vector', 'rotation'): - typ = node['t'] - node['ch'] = [{'nt':'CONST', 't':typ, 'SEF': True, - 'value': 0.0 if typ == 'float' else - ZERO_VECTOR if typ == 'vector' else - ZERO_ROTATION}] + if node.t in ('float', 'vector', 'rotation'): + typ = node.t + node.ch = [nr(nt='CONST', t=typ, SEF=True, + value=0.0 if typ == 'float' + else ZERO_VECTOR if typ == 'vector' + else ZERO_ROTATION)] # Declarations always have side effects. return @@ -1901,15 +1841,13 @@ class foldconst(object): issef = True while idx < len(child): self.FoldTree(child, idx) - if 'SEF' not in child[idx]: - issef = False + issef = issef and child[idx].SEF idx += 1 - if issef: - node['SEF'] = True + node.SEF = issef return if nt == ';': - node['SEF'] = True + node.SEF = True return if nt in ('JUMP', '@', 'V++', 'V--', '--V', '++V', 'LAMBDA'): @@ -1923,20 +1861,20 @@ class foldconst(object): def IsValidGlobalIdOrConst(self, node): # nan can't be represented as a simple constant; all others are valid - return not (node['nt'] == 'CONST' and node['t'] == 'float' - and math.isnan(node['value'])) + return not (node.nt == 'CONST' and node.t == 'float' + and math.isnan(node.value)) def IsValidGlobalConstant(self, decl): - if 'ch' not in decl: + if decl.ch is None: return True - expr = decl['ch'][0] - if expr['nt'] in ('CONST', 'IDENT'): + expr = decl.ch[0] + if expr.nt in ('CONST', 'IDENT'): return self.IsValidGlobalIdOrConst(expr) - if expr['nt'] not in ('VECTOR', 'ROTATION', 'LIST'): + if expr.nt not in ('VECTOR', 'ROTATION', 'LIST'): return False - return all(elem['nt'] in ('CONST', 'IDENT') + return all(elem.nt in ('CONST', 'IDENT') and self.IsValidGlobalIdOrConst(elem) - for elem in expr['ch']) + for elem in expr.ch) def FoldScript(self, warningpass = True): """Optimize the symbolic table symtab in place. Requires a table of @@ -1951,7 +1889,7 @@ class foldconst(object): # Constant folding pass. It does some other optimizations along the way. for idx in xrange(len(tree)): - if tree[idx]['nt'] == 'DECL': + if tree[idx].nt == 'DECL': self.globalmode = True self.FoldTree(tree, idx) self.globalmode = False diff --git a/lslopt/lslfuncopt.py b/lslopt/lslfuncopt.py index aa3207e..2f1b2db 100644 --- a/lslopt/lslfuncopt.py +++ b/lslopt/lslfuncopt.py @@ -19,20 +19,21 @@ # This is dependent on the LSL function library. import lslcommon -from lslcommon import Key, Vector, Quaternion +from lslcommon import Key, Vector, Quaternion, nr import lslfuncs def OptimizeArgs(node, sym): """Transform function arguments to shorter equivalents where possible.""" - assert node['nt'] == 'FNCALL' - params = node['ch'] - name = node['name'] + assert node.nt == 'FNCALL' + params = node.ch + name = node.name if name in ('llSensor', 'llSensorRepeat'): # The cutoff value is at a bit less than 3.1275 for some reason, # but we use 3.14159. - if params[4]['nt'] == 'CONST' and params[4]['t'] == 'float' and params[4]['value'] > 3.14159: - params[4]['value'] = 4.0 + if (params[4].nt == 'CONST' and params[4].t == 'float' + and params[4].value > 3.14159): + params[4].value = 4.0 types = sym['ParamTypes'] if name != 'llMessageLinked': @@ -40,10 +41,10 @@ def OptimizeArgs(node, sym): # llMessageLinked is the exception. for i in range(len(types)): if types[i] == 'key': - if params[i]['nt'] == 'CONST': - if not lslfuncs.cond(Key(params[i]['value'])): - params[i]['value'] = u"" - params[i]['type'] = 'string' + if params[i].nt == 'CONST': + if not lslfuncs.cond(Key(params[i].value)): + params[i].value = u"" + params[i].type = 'string' # Type of each entry in llGetObjectDetails. Last: 38 (OBJECT_SIT_COUNT). @@ -118,57 +119,54 @@ def OptimizeFunc(self, parent, index): library function semantics. """ node = parent[index] - assert node['nt'] == 'FNCALL' - name = node['name'] - child = node['ch'] + assert node.nt == 'FNCALL' + name = node.name + child = node.ch if self.optlistlength and name == 'llGetListLength': # Convert llGetListLength(expr) to (expr != []) - node = {'nt':'CONST', 't':'list', 'value':[]} - parent[index] = node = {'nt':'!=', 't':'integer', - 'ch':[child[0], node]} - # SEF if the list is - node['SEF'] = 'SEF' in child[0] + node = nr(nt='CONST', t='list', value=[], SEF=True) + parent[index] = node = nr(nt='!=', t='integer', + ch=[child[0], node], SEF=child[0].SEF) if name == 'llDumpList2String': - if (child[1]['nt'] == 'CONST' - and child[1]['t'] in ('string', 'key') - and child[1]['value'] == u"" + if (child[1].nt == 'CONST' + and child[1].t in ('string', 'key') + and child[1].value == u"" ): # Convert llDumpList2String(expr, "") to (string)(expr) - node['nt'] = 'CAST' + node.nt = 'CAST' del child[1] - del node['name'] + del node.name return if (name in ('llList2String', 'llList2Key', 'llList2Integer', 'llList2Float', 'llList2Vector', 'llList2Rot') - and child[1]['nt'] == 'CONST' + and child[1].nt == 'CONST' ): # 2nd arg to llList2XXXX must be integer - assert child[1]['t'] == 'integer' + assert child[1].t == 'integer' listarg = child[0] - idx = child[1]['value'] + idx = child[1].value value = self.GetListNodeElement(listarg, idx) tvalue = self.TypeFromNodeOrConst(value) const = self.ConstFromNodeOrConst(value) - if const is not False and 'SEF' in node: + if const is not False and node.SEF: # Managed to get a constant from a list, even if the # list wasn't constant. Handle the type conversion. - if (node['t'][0] + tvalue[0]) in listCompat: + if (node.t[0] + tvalue[0]) in listCompat: const = lslfuncs.InternalTypecast(const, - lslcommon.LSLType2Python[node['t']], + lslcommon.LSLType2Python[node.t], InList=True, f32=True) else: const = defaultListVals[name] - parent[index] = {'nt':'CONST', 't':node['t'], - 'value':const, 'SEF':True} + parent[index] = nr(nt='CONST', t=node.t, value=const, SEF=True) return - if listarg['nt'] == 'FNCALL' \ - and listarg['name'] == 'llGetObjectDetails': + if listarg.nt == 'FNCALL' \ + and listarg.name == 'llGetObjectDetails': - listarg = listarg['ch'][1] # make it the list argument of llGetObjectDetails + listarg = listarg.ch[1] # make it the list argument of llGetObjectDetails value = self.GetListNodeElement(listarg, idx) tvalue = self.TypeFromNodeOrConst(value) const = self.ConstFromNodeOrConst(value) @@ -176,9 +174,9 @@ def OptimizeFunc(self, parent, index): # Some of these can be handled with a typecast to string. if name == 'llList2String': # turn the node into a cast of arg 0 to string - node['nt'] = 'CAST' + node.nt = 'CAST' del child[1] - del node['name'] + del node.name return # The other ones that support cast to string then to # the final type in some cases (depending on the @@ -189,12 +187,12 @@ def OptimizeFunc(self, parent, index): and finaltype in ('s', 'i')) # won't work for floats or (name == 'llList2Float' and finaltype in ('s', 'i')) # won't work for floats - ) and (node['t'][0] + finaltype) in listCompat: + ) and (node.t[0] + finaltype) in listCompat: # -> (key)((string)llGetObjectDetails...) # or (integer)((string)llGetObjectDetails...) - node['nt'] = 'CAST' + node.nt = 'CAST' del child[1] - del node['name'] + del node.name child[0] = self.Cast(child[0], 'string') return @@ -202,18 +200,17 @@ def OptimizeFunc(self, parent, index): # and replace node with a constant if that's the case if (value is False or type(const) == int - and (node['t'][0] + objDetailsTypes[const]) + and (node.t[0] + objDetailsTypes[const]) not in listCompat - ) and 'SEF' in node: - parent[index] = {'nt':'CONST', 't':node['t'], - 'value':defaultListVals[name], - 'SEF':True} + ) and node.SEF: + parent[index] = nr(nt='CONST', t=node.t, + value=defaultListVals[name], SEF=True) - elif listarg['nt'] == 'FNCALL' and listarg['name'] in ( + elif listarg.nt == 'FNCALL' and listarg.name in ( 'llGetPrimitiveParams', 'llGetLinkPrimitiveParams'): # We're going to work with the primitive params list. - listarg = listarg['ch'][ - 0 if listarg['name'] == 'llGetPrimitiveParams' + listarg = listarg.ch[ + 0 if listarg.name == 'llGetPrimitiveParams' else 1] length = self.GetListNodeLength(listarg) if length is not False: @@ -249,9 +246,9 @@ def OptimizeFunc(self, parent, index): and idx in (0, -1) ): if name == 'llList2String': - node['nt'] = 'CAST' + node.nt = 'CAST' del child[1] - del node['name'] + del node.name return if ((name == 'llList2Key' or name == 'llList2Integer' @@ -259,16 +256,14 @@ def OptimizeFunc(self, parent, index): or name == 'llList2Float' and returntypes in ('s', 'i') ) - and (node['t'][0] + returntypes) + and (node.t[0] + returntypes) in listCompat ): - node['nt'] = 'CAST' + node.nt = 'CAST' del child[1] - del node['name'] - child[0] = {'nt':'CAST', 't':'string', - 'ch':[child[0]]} - if 'SEF' in child[0]['ch'][0]: - child[0]['SEF'] = True + del node.name + child[0] = nr(nt='CAST', t='string', + ch=[child[0]], SEF=child[0].SEF) return if (returntypes.find('*') == -1 @@ -282,13 +277,11 @@ def OptimizeFunc(self, parent, index): # s[-1:0] doesn't return the last char # so we have to compensate idx += len(returntypes) - if (node['t'][0] + returntypes[idx:idx+1]) \ - not in listCompat \ - and 'SEF' in node: - parent[index] = {'nt':'CONST', - 't':node['t'], - 'value':defaultListVals[name], - 'SEF':True} + if ((node.t[0] + returntypes[idx:idx+1]) + not in listCompat + and node.SEF): + parent[index] = nr(nt='CONST', t=node.t, + value=defaultListVals[name], SEF=True) return del returntypes @@ -302,20 +295,20 @@ def OptimizeFunc(self, parent, index): 0)) if type(button) == unicode and button == u'OK': # remove the element, as 'OK' is the default button in SL - child[2] = {'nt':'CONST','t':'list','value':[],'SEF':True} + child[2] = nr(nt='CONST', t='list', value=[], SEF=True) return if (name == 'llDeleteSubList' - or name == 'llListReplaceList' and child[1]['nt'] == 'CONST' - and not child[1]['value'] + or name == 'llListReplaceList' and child[1].nt == 'CONST' + and not child[1].value ): # llDeleteSubList(x, 0, -1) -> [] if x is SEF # llListReplaceList(x, [], 0, -1) -> [] if x is SEF - if ('SEF' in child[0] - and child[-2]['nt'] == 'CONST' and child[-1]['nt'] == 'CONST' - and child[-2]['value'] == 0 and child[-1]['value'] == -1 + if (child[0].SEF + and child[-2].nt == 'CONST' and child[-1].nt == 'CONST' + and child[-2].value == 0 and child[-1].value == -1 ): - parent[index] = {'nt':'CONST','SEF':True,'t':'list','value':[]} + parent[index] = nr(nt='CONST', t='list', value=[], SEF=True) return def FuncOptSetup(): diff --git a/lslopt/lsllastpass.py b/lslopt/lsllastpass.py index 831b0ee..0a9e9a9 100644 --- a/lslopt/lsllastpass.py +++ b/lslopt/lsllastpass.py @@ -18,6 +18,7 @@ # Optimizations that have a negative effect on other stages. import lslcommon +from lslcommon import nr #from lslcommon import Vector, Quaternion #import lslfuncs #from lslfuncs import ZERO_VECTOR, ZERO_ROTATION @@ -28,14 +29,14 @@ import lslcommon class lastpass(object): def LastPassPreOrder(self, parent, index): node = parent[index] - nt = node['nt'] - child = node['ch'] if 'ch' in node else None + nt = node.nt + child = node.ch if (self.optlistadd and not self.globalmode - and (nt == 'CONST' and node['t'] == 'list' or nt == 'LIST' - or nt == '+' and child[0]['t'] == 'list' and - (child[1]['nt'] == 'CONST' and child[1]['t'] == 'list' - or child[1]['nt'] == 'LIST') + and (nt == 'CONST' and node.t == 'list' or nt == 'LIST' + or nt == '+' and child[0].t == 'list' and + (child[1].nt == 'CONST' and child[1].t == 'list' + or child[1].nt == 'LIST') ) ): # Perform these transformations if the list is SEF: @@ -53,17 +54,16 @@ class lastpass(object): # left = None means the first element of the list must be # turned into a cast within the loop. left = child[0] if nt == '+' else None - if 'SEF' in listnode: - for elem in (listnode['value'] if listnode['nt'] == 'CONST' - else listnode['ch']): - elemnode = {'nt':'CONST', - 't':lslcommon.PythonType2LSL[type(elem)], - 'SEF':True, - 'value':elem - } if listnode['nt'] == 'CONST' else elem + if listnode.SEF: + for elem in (listnode.value if listnode.nt == 'CONST' + else listnode.ch): + elemnode = nr(nt='CONST', + t=lslcommon.PythonType2LSL[type(elem)], + value=elem, SEF=True + ) if listnode.nt == 'CONST' else elem left = (self.Cast(elemnode, 'list') if left is None - else {'nt':'+', 't':'list', 'SEF':True, - 'ch':[left, elemnode]}) + else nr(nt='+', t='list', SEF=True, + ch=[left, elemnode])) del elemnode if left is not None: # it's none for empty lists parent[index] = left @@ -78,7 +78,7 @@ class lastpass(object): # where state changes are considered bad. # BadStCh will be True if at least one state change statement # is found while monitoring state changes. - self.subinfo['StChAreBad'] = 'scope' in node + self.subinfo['StChAreBad'] = hasattr(node, 'scope') self.BadStCh = False return @@ -109,38 +109,38 @@ class lastpass(object): if nt == 'FNCALL': # lslrenamer can benefit from a list of used library functions, # so provide it. - if 'Loc' not in self.symtab[0][node['name']]: + if 'Loc' not in self.symtab[0][node.name]: # system library function - self.usedlibfuncs.add(node['name']) + self.usedlibfuncs.add(node.name) def LastPassPostOrder(self, parent, index): node = parent[index] - nt = node['nt'] - child = node['ch'] if 'ch' in node else None + nt = node.nt + child = node.ch if nt == 'FNDEF': - if 'scope' in node and self.BadStCh: + if hasattr(node, 'scope') and self.BadStCh: # There is at least one bad state change statement in the # function (must be the result of optimization). # Insert dummy IF(1){...} statement covering the whole function # (if it returs a value, insert a return statement too). - child[0] = {'nt':'{}', 't':None, 'ch':[ - {'nt':'IF', 't':None, 'ch':[ - {'nt':'CONST', 't':'integer', 'value':1}, + child[0] = nr(nt='{}', t=None, ch=[ + nr(nt='IF', t=None, ch=[ + nr(nt='CONST', t='integer', value=1, SEF=True), child[0] - ]} - ]} - child = node['ch'] - if node['t'] is not None: + ]) + ]) + child = node.ch + if node.t is not None: # Inserting a state switch in a function that returns a # value must count as one of the dumbest things to do. # We do as best as we can: add a return statement with the # default value for the type. - child[0]['ch'].append({'nt':'RETURN', 't':None, 'ch':[ - {'nt':'CONST', 't':node['t'], - 'value':lslcommon.LSLTypeDefaults[node['t']] - }] - }) + child[0].ch.append(nr(nt='RETURN', t=None, ch=[ + nr(nt='CONST', t=node.t, SEF=True, + value=lslcommon.LSLTypeDefaults[node.t] + )] + )) del self.BadStCh return @@ -150,8 +150,8 @@ class lastpass(object): self.subinfo = subinfo.copy() self.LastPassPreOrder(parent, index) - if 'ch' in parent[index]: - child = parent[index]['ch'] + if parent[index].ch is not None: + child = parent[index].ch idx = 0 while idx < len(child): self.RecursiveLastPass(child, idx) @@ -172,7 +172,7 @@ class lastpass(object): # self.subinfo is subtree-local info. self.subinfo = {} for idx in xrange(len(tree)): - if tree[idx]['nt'] == 'DECL': + if tree[idx].nt == 'DECL': self.globalmode = True self.RecursiveLastPass(tree, idx) self.globalmode = False diff --git a/lslopt/lsloptimizer.py b/lslopt/lsloptimizer.py index 5a611fd..5ede666 100644 --- a/lslopt/lsloptimizer.py +++ b/lslopt/lsloptimizer.py @@ -19,6 +19,7 @@ import lslfuncs +from lslcommon import nr from lslfoldconst import foldconst from lslrenamer import renamer from lsldeadcode import deadcode @@ -41,13 +42,13 @@ class optimizer(foldconst, renamer, deadcode, lastpass): """Return a CAST node if the types are not equal, otherwise the value unchanged. """ - if value['t'] == newtype: + if value.t == newtype: return value - ret = {'nt':'CAST', 't':newtype, 'ch':[value]} - if 'SEF' in value: - ret['SEF'] = True - if 'X' in value: - ret['X'] = value['X'] + ret = nr(nt='CAST', t=newtype, ch=[value], SEF=value.SEF) + if value.SEF: + ret.SEF = True + if hasattr(value, 'X'): + ret.X = value.X return ret def optimize(self, treesymtab, options = ('optimize','constfold','dcr', diff --git a/lslopt/lsloutput.py b/lslopt/lsloutput.py index 08c4a2d..edf428f 100644 --- a/lslopt/lsloutput.py +++ b/lslopt/lsloutput.py @@ -55,7 +55,7 @@ class outscript(object): else: pfx = '((key)' sfx = ')' - if '\t' in value and self.warntabs: + if u'\t' in value and self.warntabs: warning(u"A string contains a tab. Tabs are expanded to four" " spaces by the viewer when copy-pasting the code" " (disable this warning by disabling the 'warntabs'" @@ -178,22 +178,23 @@ class outscript(object): def FindName(self, node, scope = None): if scope is None: # node is a node - if 'scope' in node and 'NewName' in self.symtab[node['scope']][node['name']]: - return self.symtab[node['scope']][node['name']]['NewName'] - if node['nt'] == 'FNCALL' and 'NewName' in self.symtab[0][node['name']]: - return self.symtab[0][node['name']]['NewName'] + if (hasattr(node, 'scope') + and 'NewName' in self.symtab[node.scope][node.name]): + return self.symtab[node.scope][node.name]['NewName'] + if node.nt == 'FNCALL' and 'NewName' in self.symtab[0][node.name]: + return self.symtab[0][node.name]['NewName'] - return node['name'] + return node.name # node is a name if 'NewName' in self.symtab[scope][node]: return self.symtab[scope][node]['NewName'] return node def OutIndented(self, node): - if node['nt'] != '{}': + if node.nt != '{}': self.indentlevel += 1 ret = self.OutCode(node) - if node['nt'] != '{}': + if node.nt != '{}': self.indentlevel -= 1 return ret @@ -210,14 +211,13 @@ class outscript(object): def OutExpr(self, expr): # Handles expression nodes (as opposed to statement nodes) - nt = expr['nt'] - if 'ch' in expr: - child = expr['ch'] + nt = expr.nt + child = expr.ch if nt in self.binary_operands: - lnt = child[0]['nt'] + lnt = child[0].nt lparen = False - rnt = child[1]['nt'] + rnt = child[1].nt rparen = False if nt in self.assignment_ops and nt in self.op_priority: # Assignment is right-associative, so it needs to be dealt with @@ -274,16 +274,17 @@ class outscript(object): return self.FindName(expr) if nt == 'CONST': - if self.foldconst and expr['t'] == 'list' and len(expr['value'])==1 and not self.globalmode: - return '(list)' + self.Value2LSL(expr['value'][0]) - return self.Value2LSL(expr['value']) + if (self.foldconst and expr.t == 'list' and len(expr.value) == 1 + and not self.globalmode): + return '(list)' + self.Value2LSL(expr.value[0]) + return self.Value2LSL(expr.value) if nt == 'CAST' or self.foldconst and nt in ('LIST', 'CONST') and len(child)==1 and not self.globalmode: - ret = '(' + expr['t'] + ')' + ret = '(' + expr.t + ')' expr = child[0] - if expr['nt'] in ('CONST', 'IDENT', 'V++', 'V--', 'VECTOR', + if expr.nt in ('CONST', 'IDENT', 'V++', 'V--', 'VECTOR', 'ROTATION', 'LIST', 'FIELD', 'PRINT', 'FNCALL'): - if expr['nt'] != 'LIST' or len(expr['ch']) != 1: + if expr.nt != 'LIST' or len(expr.ch) != 1: return ret + self.OutExpr(expr) return ret + '(' + self.OutExpr(expr) + ')' @@ -309,7 +310,7 @@ class outscript(object): + self.OutExpr(child[1]) + ',') if nt == 'ROTATION': ret += self.OutExpr(child[2]) + ',' - lnt = child[-1]['nt'] + lnt = child[-1].nt if lnt in self.op_priority \ and self.op_priority[lnt] <= self.op_priority['>']: ret += '(' + self.OutExpr(child[-1]) + ')' @@ -325,22 +326,22 @@ class outscript(object): if nt in self.unary_operands: ret = nt - lnt = child[0]['nt'] + lnt = child[0].nt paren = False if nt == 'NEG': ret = '-' - if (lnt == 'CONST' and child[0]['t'] == 'integer' - and child[0]['value'] < 0 + if (lnt == 'CONST' and child[0].t == 'integer' + and child[0].value < 0 ): # shortcut - ret += str(child[0]['value'] + 4294967296) + ret += str(child[0].value + 4294967296) return ret if lnt in self.op_priority: paren = self.op_priority[lnt] <= self.op_priority['-'] elif (lnt == 'NEG' or lnt == '--V' or lnt == 'CONST' - and child[0]['t'] == 'float' - and child[0]['value'] < 0 + and child[0].t == 'float' + and child[0].value < 0 ): ret += ' ' # don't output "--" as that's a different token else: @@ -354,7 +355,7 @@ class outscript(object): return ret if nt == 'FLD': - return self.OutExpr(child[0]) + '.' + expr['fld'] + return self.OutExpr(child[0]) + '.' + expr.fld if nt in ('V--', 'V++'): return self.OutExpr(child[0]) + ('++' if nt == 'V++' else '--') @@ -375,11 +376,8 @@ class outscript(object): assert False, 'Internal error: expression type "' + nt + '" not handled' # pragma: no cover def OutCode(self, node): - nt = node['nt'] - if 'ch' in node: - child = node['ch'] - else: - child = None + nt = node.nt + child = node.ch if nt == 'IF': ret = self.dent() @@ -390,9 +388,9 @@ class outscript(object): if len(child) == 3: testnode = child[1] # Find last IF in an ELSE IF chain - while testnode['nt'] == 'IF' and len(testnode['ch']) == 3: - testnode = testnode['ch'][2] - if testnode['nt'] == 'IF': + while testnode.nt == 'IF' and len(testnode.ch) == 3: + testnode = testnode.ch[2] + if testnode.nt == 'IF': # hit an IF without ELSE at the end of the chain needs_braces = True if needs_braces: @@ -403,12 +401,12 @@ class outscript(object): ret += self.OutIndented(child[1]) if len(child) < 3: return ret - if child[2]['nt'] != 'IF': + if child[2].nt != 'IF': ret += self.dent() + 'else\n' + self.OutIndented(child[2]) return ret ret += self.dent() + 'else ' node = child[2] - child = node['ch'] + child = node.ch if nt == 'WHILE': ret = self.dent() + 'while (' + self.OutExpr(child[0]) + ')\n' ret += self.OutIndented(child[1]) @@ -436,12 +434,12 @@ class outscript(object): return self.dent() + 'return ' + self.OutExpr(child[0]) + ';\n' return self.dent() + 'return;\n' if nt == 'DECL': - ret = self.dent() + node['t'] + ' ' + self.FindName(node) + ret = self.dent() + node.t + ' ' + self.FindName(node) if child: - if 'orig' in child[0] and (child[0]['orig']['nt'] != 'IDENT' - or child[0]['orig']['name'] - in self.symtab[child[0]['orig']['scope']]): - ret += ' = ' + self.OutExpr(child[0]['orig']) + if hasattr(child[0], 'orig') and (child[0].orig.nt != 'IDENT' + or child[0].orig.name + in self.symtab[child[0].orig.scope]): + ret += ' = ' + self.OutExpr(child[0].orig) else: ret += ' = ' + self.OutExpr(child[0]) return ret + ';\n' @@ -451,26 +449,26 @@ class outscript(object): if nt in ('STDEF', '{}'): ret = '' if nt == 'STDEF': - if node['name'] == 'default': + if node.name == 'default': ret = self.dent() + 'default\n' else: ret = self.dent() + 'state ' + self.FindName(node) + '\n' ret += self.dent() + '{\n' self.indentlevel += 1 - for stmt in node['ch']: + for stmt in node.ch: ret += self.OutCode(stmt) self.indentlevel -= 1 return ret + self.dent() + '}\n' if nt == 'FNDEF': ret = self.dent() - if node['t'] is not None: - ret += node['t'] + ' ' + if node.t is not None: + ret += node.t + ' ' ret += self.FindName(node) + '(' - scope = node['pscope'] + scope = node.pscope ret += ', '.join(typ + ' ' + self.FindName(name, scope) - for typ, name in zip(node['ptypes'], node['pnames'])) + for typ, name in zip(node.ptypes, node.pnames)) return ret + ')\n' + self.OutCode(child[0]) if nt == 'EXPR': @@ -502,7 +500,7 @@ class outscript(object): self.globalmode = False self.listmode = False for node in self.tree: - self.globalmode = node['nt'] == 'DECL' + self.globalmode = node.nt == 'DECL' ret += self.OutCode(node) self.globalmode = False diff --git a/lslopt/lslparse.py b/lslopt/lslparse.py index f2ce253..c6d4040 100644 --- a/lslopt/lslparse.py +++ b/lslopt/lslparse.py @@ -20,7 +20,7 @@ # TODO: Add info to be able to propagate error position to the source. -from lslcommon import Key, Vector, Quaternion, types +from lslcommon import Key, Vector, Quaternion, types, nr import lslcommon, lslfuncs import re @@ -308,13 +308,13 @@ class parser(object): """Check if automatic dynamic cast is possible, and insert it if requested explicitly. """ - tval = value['t'] + tval = value.t if tval == tgttype: return value if tval in ('string', 'key') and tgttype in ('string', 'key') \ or tval == 'integer' and tgttype == 'float': if self.explicitcast: - return {'nt':'CAST', 't':tgttype, 'ch':[value]} + return nr(nt='CAST', t=tgttype, ch=[value]) return value raise EParseTypeMismatch(self) @@ -707,9 +707,9 @@ class parser(object): (a pure combination of ';' and '{}' and '@') """ for node in blk: - if '@' != node['nt'] != ';': - if node['nt'] == '{}': - if self.does_something(node['ch']): + if '@' != node.nt != ';': + if node.nt == '{}': + if self.does_something(node.ch): return True else: return True @@ -773,11 +773,11 @@ class parser(object): ret.append(inequality) return ret # This is basically a copy/paste of the Parse_inequality handler - ltype = inequality['t'] + ltype = inequality.t if ltype not in ('integer', 'float'): raise EParseTypeMismatch(self) rexpr = self.Parse_shift() - rtype = rexpr['t'] + rtype = rexpr.t if rtype not in ('integer', 'float'): raise EParseTypeMismatch(self) if ltype != rtype: @@ -785,7 +785,7 @@ class parser(object): inequality = self.autocastcheck(inequality, rtype) else: rexpr = self.autocastcheck(rexpr, ltype) - inequality = {'nt':op, 't':'integer', 'ch':[inequality, rexpr]} + inequality = nr(nt=op, t='integer', ch=[inequality, rexpr]) # Reaching this means an operator or lower precedence happened, # e.g. <1,1,1,2==2> (that's syntax error in ==) @@ -824,35 +824,36 @@ class parser(object): if self.tok[0] in ('INTEGER_VALUE', 'FLOAT_VALUE'): val = self.tok[1] self.NextToken() - return {'nt':CONST, 't':'integer' if type(val) == int else 'float', 'value':-val} + return nr(nt=CONST, value=-val, + t='integer' if type(val) == int else 'float') if tok0 == 'INTEGER_VALUE': self.NextToken() - return {'nt':CONST, 't':'integer', 'value':val} + return nr(nt=CONST, t='integer', value=val) if tok0 == 'FLOAT_VALUE': self.NextToken() - return {'nt':CONST, 't':'float', 'value':val} + return nr(nt=CONST, t='float', value=val) if tok0 == 'STRING_VALUE': self.NextToken() if self.allowmultistrings: while self.tok[0] == 'STRING_VALUE': val += self.tok[1] self.NextToken() - return {'nt':CONST, 't':'string', 'value':val} + return nr(nt=CONST, t='string', value=val) # Key constants are not currently supported - use string #if tok0 == 'KEY_VALUE': # return [CONST, 'key', val] if tok0 == 'VECTOR_VALUE': self.NextToken() - return {'nt':CONST, 't':'vector', 'value':val} + return nr(nt=CONST, t='vector', value=val) if tok0 == 'ROTATION_VALUE': self.NextToken() - return {'nt':CONST, 't':'rotation', 'value':val} + return nr(nt=CONST, t='rotation', value=val) if tok0 == 'LIST_VALUE': self.NextToken() - return {'nt':CONST, 't':'list', 'value':val} + return nr(nt=CONST, t='list', value=val) if tok0 in ('TRUE', 'FALSE'): self.NextToken() - return {'nt':CONST, 't':'integer', 'value':int(tok0 == 'TRUE')} + return nr(nt=CONST, t='integer', value=1 if tok0 == 'TRUE' else 0) if tok0 == '<': self.NextToken() val = [self.Parse_expression()] @@ -889,29 +890,30 @@ class parser(object): val += self.Parse_vector_rotation_tail() if len(val) == 3: - return {'nt':'VECTOR', 't':'vector', 'ch':val} - return {'nt':'ROTATION', 't':'rotation', 'ch':val} + return nr(nt='VECTOR', t='vector', ch=val) + return nr(nt='ROTATION', t='rotation', ch=val) if tok0 == '[': self.NextToken() val = self.Parse_optional_expression_list(False) self.expect(']') self.NextToken() - return {'nt':'LIST', 't':'list', 'ch':val} + return nr(nt='LIST', t='list', ch=val) if tok0 == 'PRINT': self.NextToken() self.expect('(') self.NextToken() expr = self.Parse_expression() - if expr['t'] not in types: - raise EParseTypeMismatch(self) if expr['t'] is None else EParseUndefined(self) + if expr.t not in types: + raise (EParseTypeMismatch(self) if expr.t is None + else EParseUndefined(self)) self.expect(')') self.NextToken() # Syntactically, print returns the same type as the expression. # However, compilation in Mono throws an exception, and even in # LSO, it throws a bounds check error when the result is a string # or key or list and the returned value is used. - return {'nt':'PRINT', 't':expr['t'], 'ch':[expr]} + return nr(nt='PRINT', t=expr.t, ch=[expr]) if tok0 != 'IDENT': if tok0 == 'EOF': @@ -939,7 +941,7 @@ class parser(object): args = self.Parse_optional_expression_list(sym['ParamTypes']) self.expect(')') self.NextToken() - return {'nt':'FNCALL', 't':sym['Type'], 'name':name, 'ch':args} + return nr(nt='FNCALL', t=sym['Type'], name=name, ch=args) sym = self.FindSymbolFull(val) if sym is None or sym['Kind'] != 'v': @@ -947,7 +949,7 @@ class parser(object): raise EParseUndefined(self) typ = sym['Type'] - lvalue = {'nt':'IDENT', 't':typ, 'name':name, 'scope':sym['Scope']} + lvalue = nr(nt='IDENT', t=typ, name=name, scope=sym['Scope']) # Lazy lists if self.lazylists and tok0 == '[': @@ -958,17 +960,17 @@ class parser(object): self.expect(']') self.NextToken() if self.tok[0] != '=' or not AllowAssignment: - return {'nt':'SUBIDX', 't':None, 'ch':[lvalue] + idxexpr} + return nr(nt='SUBIDX', t=None, ch=[lvalue] + idxexpr) # Lazy list assignment if len(idxexpr) != 1: raise EParseFunctionMismatch(self) - if idxexpr[0]['t'] != 'integer': + if idxexpr[0].t != 'integer': raise EParseTypeMismatch(self) idxexpr = idxexpr[0] self.NextToken() expr = self.Parse_expression() - rtyp = expr['t'] + rtyp = expr.t # Define aux function if it doesn't exist # (leaves users room for writing their own replacement, e.g. # one that fills with something other than zeros) @@ -994,42 +996,145 @@ list lazy_list_set(list L, integer i, list v) return llListReplaceList(L, v, i, i); } ''' - self.tree[self.usedspots] = {'ch': [{'ch': [{'ch': [{'ch': [{'ch': [{'scope': paramscope, 'nt': 'IDENT', 't': 'list', 'name': 'L'}], 'nt': 'FNCALL', 't': 'integer', 'name': 'llGetListLength'}, {'scope': paramscope, 'nt': 'IDENT', 't': 'integer', 'name': 'i'}], 'nt': '<', 't': 'integer'}, {'ch': [{'ch': [{'scope': paramscope, 'nt': 'IDENT', 't': 'list', 'name': 'L'}, {'ch': [{'scope': paramscope, 'nt': 'IDENT', 't': 'list', 'name': 'L'}, {'nt': 'CONST', 't': 'integer', 'value': 0}], 'nt': '+', 't': 'list'}], 'nt': '=', 't': 'list'}], 'nt': 'EXPR', 't': 'list'}], 'nt': 'WHILE', 't': None}, {'ch': [{'ch': [{'scope': paramscope, 'nt': 'IDENT', 't': 'list', 'name': 'L'}, {'scope': paramscope, 'nt': 'IDENT', 't': 'list', 'name': 'v'}, {'scope': paramscope, 'nt': 'IDENT', 't': 'integer', 'name': 'i'}, {'scope': paramscope, 'nt': 'IDENT', 't': 'integer', 'name': 'i'}], 'nt': 'FNCALL', 't': 'list', 'name': 'llListReplaceList'}], 'nt': 'RETURN', 't': None, 'LIR': True}], 'nt': '{}', 't': None, 'LIR': True}], 't': 'list', 'pnames': params[1], 'scope': 0, 'pscope': paramscope, 'nt': 'FNDEF', 'ptypes': params[0], 'name': 'lazy_list_set'} + self.tree[self.usedspots] = nr( + nt='FNDEF' + , t='list' + , name='lazy_list_set' + , ptypes=params[0] + , pnames=params[1] + , scope=0 + , pscope=paramscope + , ch=[ + nr(nt='{}' + , t=None + , LIR=True + , ch=[ + nr(nt='WHILE' + , t=None + , ch=[ + nr(nt='<' + , t='integer' + , ch=[ + nr(nt='FNCALL' + , t='integer' + , name='llGetListLength' + , ch=[ + nr(nt='IDENT' + , t='list' + , name='L' + , scope=paramscope + ) + ] + ), + nr(nt='IDENT' + , t='integer' + , name='i' + , scope=paramscope + ) + ] + ), + nr(nt='EXPR' + , t='list' + , ch=[ + nr(nt='=' + , t='list' + , ch=[ + nr(nt='IDENT' + , t='list' + , name='L' + , scope=paramscope + ), + nr(nt='+' + , t='list' + , ch=[ + nr(nt='IDENT' + , t='list' + , name='L' + , scope=paramscope + ), + nr(nt='CONST' + , t='integer' + , value=0 + ) + ] + ) + ] + ) + ] + ) + ] + ), + nr(nt='RETURN' + , t=None + , LIR=True + , ch=[ + nr(nt='FNCALL' + , t='list' + , name='llListReplaceList' + , ch=[ + nr(nt='IDENT' + , t='list' + , name='L' + , scope=paramscope + ), + nr(nt='IDENT' + , t='list' + , name='v' + , scope=paramscope + ), + nr(nt='IDENT' + , t='integer' + , name='i' + , scope=paramscope + ), + nr(nt='IDENT' + , t='integer' + , name='i' + , scope=paramscope + ) + ] + ) + ] + ) + ] + ) + ] + ) self.usedspots += 1 #self.PopScope() # no locals self.PopScope() - if expr['t'] is None: + if expr.t is None: raise EParseTypeMismatch(self) - if expr['t'] != 'list': - expr = {'nt':'CAST', 't':'list', 'ch':[expr]} + if expr.t != 'list': + expr = nr(nt='CAST', t='list', ch=[expr]) - return {'nt':'=', 't':'list', 'ch':[lvalue, { - 'nt':'FNCALL', 't':'list', 'name':'lazy_list_set', - 'scope':0, - 'ch':[lvalue.copy(), idxexpr, expr] - }]} + return nr(nt='=', t='list', ch=[lvalue, nr( + nt='FNCALL', t='list', name='lazy_list_set', scope=0, + ch=[lvalue.copy(), idxexpr, expr] + )]) if tok0 == '.': self.NextToken() self.expect('IDENT') self.ValidateField(typ, self.tok[1]) - lvalue = {'nt':'FLD', 't':'float', 'ch':[lvalue], 'fld':self.tok[1]} + lvalue = nr(nt='FLD', t='float', ch=[lvalue], fld=self.tok[1]) self.NextToken() tok0 = self.tok[0] typ = 'float' if tok0 in ('++', '--'): self.NextToken() - if lvalue['t'] not in ('integer', 'float'): + if lvalue.t not in ('integer', 'float'): raise EParseTypeMismatch(self) - return {'nt':'V++' if tok0 == '++' else 'V--', 't':lvalue['t'], 'ch':[lvalue]} + return nr(nt='V++' if tok0 == '++' else 'V--', t=lvalue.t, + ch=[lvalue]) if AllowAssignment and (tok0 in self.assignment_toks or self.extendedassignment and tok0 in self.extassignment_toks): self.NextToken() expr = self.Parse_expression() - rtyp = expr['t'] + rtyp = expr.t if rtyp not in types: raise EParseTypeMismatch(self) if typ in ('integer', 'float'): @@ -1045,7 +1150,7 @@ list lazy_list_set(list L, integer i, list v) if tok0 == '=': expr = self.autocastcheck(expr, typ) - return {'nt':'=', 't':typ, 'ch':[lvalue, expr]} + return nr(nt='=', t=typ, ch=[lvalue, expr]) if tok0 == '+=': if typ == 'float': @@ -1055,34 +1160,34 @@ list lazy_list_set(list L, integer i, list v) raise EParseTypeMismatch(self) if self.explicitcast: if typ == 'list' != rtyp: - expr = {'nt':'CAST', 't':typ, 'ch':[expr]} - return {'nt':tok0, 't':typ, 'ch':[lvalue, expr]} + expr = nr(nt='CAST', t=typ, ch=[expr]) + return nr(nt=tok0, t=typ, ch=[lvalue, expr]) if tok0 == '-=': if typ == rtyp in ('integer', 'float', 'vector', 'rotation'): - return {'nt':tok0, 't':typ, 'ch':[lvalue, expr]} + return nr(nt=tok0, t=typ, ch=[lvalue, expr]) raise EParseTypeMismatch(self) if tok0 in ('*=', '/='): # There is a special case that was dealt with before. if tok0 == '*=' and typ == 'integer' and rtyp == 'float': - return {'nt':tok0, 't':typ, 'ch':[lvalue, expr]} + return nr(nt=tok0, t=typ, ch=[lvalue, expr]) if (typ == rtyp or typ == 'vector') and rtyp in ('integer', 'float', 'rotation'): if typ == 'vector' and rtyp == 'integer': expr = self.autocastcheck(expr, 'float') - return {'nt':tok0, 't':typ, 'ch':[lvalue, expr]} + return nr(nt=tok0, t=typ, ch=[lvalue, expr]) raise EParseTypeMismatch(self) if tok0 == '%=': if typ == rtyp in ('integer', 'vector'): - return {'nt':tok0, 't':typ, 'ch':[lvalue, expr]} + return nr(nt=tok0, t=typ, ch=[lvalue, expr]) raise EParseTypeMismatch(self) # Rest take integer operands only if typ == rtyp == 'integer': - return {'nt':tok0, 't':typ, 'ch':[lvalue, expr]} + return nr(nt=tok0, t=typ, ch=[lvalue, expr]) raise EParseTypeMismatch(self) return lvalue @@ -1106,16 +1211,16 @@ list lazy_list_set(list L, integer i, list v) # Unary minus self.NextToken() value = self.Parse_factor() - if value['t'] not in ('integer', 'float', 'vector', 'rotation'): + if value.t not in ('integer', 'float', 'vector', 'rotation'): raise EParseTypeMismatch(self) - return {'nt':'NEG', 't':value['t'], 'ch':[value]} + return nr(nt='NEG', t=value.t, ch=[value]) if tok0 in ('!', '~'): # Unary logic and bitwise NOT - applies to integers only self.NextToken() value = self.Parse_unary_expression() - if value['t'] != 'integer': + if value.t != 'integer': raise EParseTypeMismatch(self) - return {'nt':tok0, 't':'integer', 'ch':[value]} + return nr(nt=tok0, t='integer', ch=[value]) if tok0 in ('++', '--'): # Pre-increment / pre-decrement self.NextToken() @@ -1127,20 +1232,20 @@ list lazy_list_set(list L, integer i, list v) raise EParseUndefined(self) typ = sym['Type'] - ret = {'nt':'IDENT', 't':typ, 'name':name, 'scope':sym['Scope']} + ret = nr(nt='IDENT', t=typ, name=name, scope=sym['Scope']) self.NextToken() if self.tok[0] == '.': self.NextToken() self.expect('IDENT') self.ValidateField(typ, self.tok[1]) - ret = {'nt':'FLD', 't':'float', 'ch':[ret], 'fld':self.tok[1]} + ret = nr(nt='FLD', t='float', ch=[ret], fld=self.tok[1]) self.NextToken() - typ = ret['t'] + typ = ret.t if typ not in ('integer', 'float'): raise EParseTypeMismatch(self) - return {'nt':'++V' if tok0 == '++' else '--V', 't':typ, 'ch':[ret]} + return nr(nt='++V' if tok0 == '++' else '--V', t=typ, ch=[ret]) if tok0 == '(': # Parenthesized expression or typecast @@ -1174,14 +1279,14 @@ list lazy_list_set(list L, integer i, list v) if self.tok[0] == '-': self.NextToken() if self.tok[0] == 'INTEGER_VALUE': - expr = {'nt':'CONST','t':'integer','value':-self.tok[1]} + expr = nr(nt='CONST', t='integer', value=-self.tok[1]) self.NextToken() elif self.tok[0] == 'FLOAT_VALUE': - expr = {'nt':'CONST','t':'float','value':-self.tok[1]} + expr = nr(nt='CONST', t='float', value=-self.tok[1]) self.NextToken() else: expr = self.Parse_unary_expression(AllowAssignment = False) - expr = {'nt':'NEG','t':expr['t'],'ch':[expr]} + expr = nr(nt='NEG', t=expr.t, ch=[expr]) else: expr = self.Parse_unary_expression(AllowAssignment = False) else: @@ -1192,8 +1297,8 @@ list lazy_list_set(list L, integer i, list v) self.NextToken() else: expr = self.Parse_unary_postfix_expression(AllowAssignment = False) - basetype = expr['t'] - if self.lazylists and basetype is None and expr['nt'] == 'SUBIDX': + basetype = expr.t + if self.lazylists and basetype is None and expr.nt == 'SUBIDX': fn = self.TypeToExtractionFunction[typ] sym = self.FindSymbolFull(fn, 0) if sym is None: @@ -1203,11 +1308,11 @@ list lazy_list_set(list L, integer i, list v) # an unknown identifier error would be confusing) raise EParseSyntax(self) fnparamtypes = sym['ParamTypes'] - subparamtypes = [x['t'] for x in expr['ch']] + subparamtypes = [x.t for x in expr.ch] if fnparamtypes != subparamtypes: raise EParseFunctionMismatch(self) - return {'nt':'FNCALL', 't':sym['Type'], 'name':fn, 'scope':0, - 'ch':expr['ch']} + return nr(nt='FNCALL', t=sym['Type'], name=fn, scope=0, + ch=expr.ch) if typ == 'list' and basetype in types \ or basetype in ('integer', 'float') and typ in ('integer', 'float', 'string') \ @@ -1216,7 +1321,7 @@ list lazy_list_set(list L, integer i, list v) or basetype == 'vector' and typ in ('string', 'vector') \ or basetype == 'rotation' and typ in ('string', 'rotation') \ or basetype == 'list' and typ == 'string': - return {'nt':'CAST', 't':typ, 'ch':[expr]} + return nr(nt='CAST', t=typ, ch=[expr]) raise EParseTypeMismatch(self) # Must be a postfix expression. @@ -1231,7 +1336,7 @@ list lazy_list_set(list L, integer i, list v) factor = self.Parse_unary_expression() while self.tok[0] in ('*', '/', '%'): op = self.tok[0] - ltype = factor['t'] + ltype = factor.t # Acceptable types for LHS if op in ('*', '/') and ltype not in ('integer', 'float', 'vector', 'rotation') \ @@ -1239,13 +1344,13 @@ list lazy_list_set(list L, integer i, list v) raise EParseTypeMismatch(self) self.NextToken() rexpr = self.Parse_unary_expression() - rtype = rexpr['t'] + rtype = rexpr.t # Mod is easier to check for if op == '%' and ltype != rtype: raise EParseTypeMismatch(self) if op == '%' or ltype == rtype == 'integer': # Deal with the special cases first (it's easy) - factor = {'nt':op, 't':ltype, 'ch':[factor, rexpr]} + factor = nr(nt=op, t=ltype, ch=[factor, rexpr]) else: # Any integer must be promoted to float now if ltype == 'integer': @@ -1267,7 +1372,7 @@ list lazy_list_set(list L, integer i, list v) resulttype = 'float' else: resulttype = ltype - factor = {'nt':op, 't':resulttype, 'ch':[factor, rexpr]} + factor = nr(nt=op, t=resulttype, ch=[factor, rexpr]) else: raise EParseTypeMismatch(self) @@ -1281,14 +1386,14 @@ list lazy_list_set(list L, integer i, list v) term = self.Parse_factor() while self.tok[0] in ('+', '-'): op = self.tok[0] - ltype = term['t'] + ltype = term.t if op == '+' and ltype not in types \ or op == '-' and ltype not in ('integer', 'float', 'vector', 'rotation'): raise EParseTypeMismatch(self) self.NextToken() rexpr = self.Parse_factor() - rtype = rexpr['t'] + rtype = rexpr.t # This is necessary, but the reason is subtle. # The types must match in principle (except integer/float), so it # doesn't seem necessary to check rtype. But there's the case @@ -1309,16 +1414,16 @@ list lazy_list_set(list L, integer i, list v) # so we don't act on self.explicitcast in this case. if rtype == 'list': ltype = rtype - term = {'nt':op, 't':ltype, 'ch':[term, rexpr]} + term = nr(nt=op, t=ltype, ch=[term, rexpr]) elif self.allowkeyconcat and op == '+' \ and ltype in ('key', 'string') and rtype in ('key', 'string'): # Allow string+key addition (but add explicit cast) if ltype == 'key': - term = {'nt':op, 't':rtype, - 'ch':[{'nt':'CAST', 't':rtype, 'ch':[term]}, rexpr]} + term = nr(nt=op, t=rtype, + ch=[nr(nt='CAST', t=rtype, ch=[term]), rexpr]) else: - term = {'nt':op, 't':ltype, - 'ch':[term, {'nt':'CAST', 't':ltype, 'ch':[rexpr]}]} + term = nr(nt=op, t=ltype, + ch=[term, nr(nt='CAST', t=ltype, ch=[rexpr])]) elif ltype == 'key' or rtype == 'key': # Only list + key or key + list is allowed, otherwise keys can't # be added or subtracted with anything. @@ -1326,10 +1431,12 @@ list lazy_list_set(list L, integer i, list v) else: if ltype == 'float': # Promote rexpr to float - term = {'nt':op, 't':ltype, 'ch':[term, self.autocastcheck(rexpr, ltype)]} + term = nr(nt=op, t=ltype, + ch=[term, self.autocastcheck(rexpr, ltype)]) else: - # Convert LHS to rtype if possible (note no keys arrive here) - term = {'nt':op, 't':rtype, 'ch':[self.autocastcheck(term, rtype), rexpr]} + # Convert LHS to rtype if possible (note no keys get here) + term = nr(nt=op, t=rtype, + ch=[self.autocastcheck(term, rtype), rexpr]) return term @@ -1340,14 +1447,14 @@ list lazy_list_set(list L, integer i, list v) """ shift = self.Parse_term() while self.tok[0] in ('<<', '>>'): - if shift['t'] != 'integer': + if shift.t != 'integer': raise EParseTypeMismatch(self) op = self.tok[0] self.NextToken() rexpr = self.Parse_term() - if rexpr['t'] != 'integer': + if rexpr.t != 'integer': raise EParseTypeMismatch(self) - shift = {'nt':op, 't':'integer', 'ch':[shift , rexpr]} + shift = nr(nt=op, t='integer', ch=[shift , rexpr]) return shift @@ -1360,12 +1467,12 @@ list lazy_list_set(list L, integer i, list v) inequality = self.Parse_shift() while self.tok[0] in ('<', '<=', '>', '>='): op = self.tok[0] - ltype = inequality['t'] + ltype = inequality.t if ltype not in ('integer', 'float'): raise EParseTypeMismatch(self) self.NextToken() rexpr = self.Parse_shift() - rtype = rexpr['t'] + rtype = rexpr.t if rtype not in ('integer', 'float'): raise EParseTypeMismatch(self) if ltype != rtype: @@ -1373,7 +1480,7 @@ list lazy_list_set(list L, integer i, list v) inequality = self.autocastcheck(inequality, rtype) else: rexpr = self.autocastcheck(rexpr, ltype) - inequality = {'nt':op, 't':'integer', 'ch':[inequality, rexpr]} + inequality = nr(nt=op, t='integer', ch=[inequality, rexpr]) return inequality @@ -1386,19 +1493,19 @@ list lazy_list_set(list L, integer i, list v) comparison = self.Parse_inequality() while self.tok[0] in ('==', '!='): op = self.tok[0] - ltype = comparison['t'] + ltype = comparison.t if ltype not in types: raise EParseTypeMismatch(self) self.NextToken() rexpr = self.Parse_inequality() - rtype = rexpr['t'] + rtype = rexpr.t if ltype == 'float': rexpr = self.autocastcheck(rexpr, ltype) else: # For string & key, RHS (rtype) mandates the conversion # (that's room for optimization: always compare strings) comparison = self.autocastcheck(comparison, rtype) - comparison = {'nt':op, 't':'integer', 'ch':[comparison, rexpr]} + comparison = nr(nt=op, t='integer', ch=[comparison, rexpr]) return comparison @@ -1409,14 +1516,14 @@ list lazy_list_set(list L, integer i, list v) """ bitbool_factor = self.Parse_comparison() while self.tok[0] == '&': - if bitbool_factor['t'] != 'integer': + if bitbool_factor.t != 'integer': raise EParseTypeMismatch(self) op = self.tok[0] self.NextToken() rexpr = self.Parse_comparison() - if rexpr['t'] != 'integer': + if rexpr.t != 'integer': raise EParseTypeMismatch(self) - bitbool_factor = {'nt':op, 't':'integer', 'ch':[bitbool_factor, rexpr]} + bitbool_factor = nr(nt=op, t='integer', ch=[bitbool_factor, rexpr]) return bitbool_factor @@ -1427,14 +1534,14 @@ list lazy_list_set(list L, integer i, list v) """ bitxor_term = self.Parse_bitbool_factor() while self.tok[0] == '^': - if bitxor_term['t'] != 'integer': + if bitxor_term.t != 'integer': raise EParseTypeMismatch(self) op = self.tok[0] self.NextToken() rexpr = self.Parse_bitbool_factor() - if rexpr['t'] != 'integer': + if rexpr.t != 'integer': raise EParseTypeMismatch(self) - bitxor_term = {'nt':op, 't':'integer', 'ch':[bitxor_term, rexpr]} + bitxor_term = nr(nt=op, t='integer', ch=[bitxor_term, rexpr]) return bitxor_term @@ -1445,14 +1552,14 @@ list lazy_list_set(list L, integer i, list v) """ bitbool_term = self.Parse_bitxor_term() while self.tok[0] == '|': - if bitbool_term['t'] != 'integer': + if bitbool_term.t != 'integer': raise EParseTypeMismatch(self) op = self.tok[0] self.NextToken() rexpr = self.Parse_bitxor_term() - if rexpr['t'] != 'integer': + if rexpr.t != 'integer': raise EParseTypeMismatch(self) - bitbool_term = {'nt':op, 't':'integer', 'ch':[bitbool_term, rexpr]} + bitbool_term = nr(nt=op, t='integer', ch=[bitbool_term, rexpr]) return bitbool_term @@ -1475,14 +1582,14 @@ list lazy_list_set(list L, integer i, list v) """ expression = self.Parse_bitbool_term() while self.tok[0] in ('&&', '||'): - if expression['t'] != 'integer': + if expression.t != 'integer': raise EParseTypeMismatch(self) op = self.tok[0] self.NextToken() rexpr = self.Parse_bitbool_term() - if rexpr['t'] != 'integer': + if rexpr.t != 'integer': raise EParseTypeMismatch(self) - expression = {'nt':op, 't':'integer', 'ch':[expression, rexpr]} + expression = nr(nt=op, t='integer', ch=[expression, rexpr]) return expression @@ -1514,7 +1621,7 @@ list lazy_list_set(list L, integer i, list v) except EParseTypeMismatch: raise EParseFunctionMismatch(self) elif expected_types is False: # don't accept void expressions - if expr['t'] not in types: + if expr.t not in types: raise EParseTypeMismatch(self) idx += 1 ret.append(expr) @@ -1570,7 +1677,7 @@ list lazy_list_set(list L, integer i, list v) if tok0 == ';': self.NextToken() - return {'nt':';', 't':None} + return nr(nt=';', t=None) if tok0 == '@': self.NextToken() @@ -1602,20 +1709,21 @@ list lazy_list_set(list L, integer i, list v) self.NextToken() self.expect(';') self.NextToken() - return {'nt':'@', 't':None, 'name':name, 'scope':self.scopeindex} + return nr(nt='@', t=None, name=name, scope=self.scopeindex) if tok0 == 'JUMP': self.NextToken() self.expect('IDENT') name = self.tok[1] sym = self.FindSymbolPartial(name, MustBeLabel=True) - jumpnode = {'nt':'JUMP', 't':None, 'name':name} + jumpnode = nr(nt='JUMP', t=None, name=name, scope=None) if not sym or sym['Kind'] != 'l': # It might still be a forward reference, so we add it to the # list of things to look up when done - self.jump_lookups.append((name, self.scopeindex, self.errorpos, jumpnode)) + self.jump_lookups.append((name, self.scopeindex, self.errorpos, + jumpnode)) else: - jumpnode['scope'] = sym['Scope'] + jumpnode.scope = sym['Scope'] self.NextToken() self.expect(';') self.NextToken() @@ -1632,12 +1740,13 @@ list lazy_list_set(list L, integer i, list v) raise EParseSyntax(self) # State Switch only searches for states in the global scope name = self.tok[1] if self.tok[0] == 'IDENT' else 'default' - if name not in self.symtab[0] and (name not in self.globals or self.globals[name]['Kind'] != 's'): + if name not in self.symtab[0] and (name not in self.globals + or self.globals[name]['Kind'] != 's'): raise EParseUndefined(self) self.NextToken() self.expect(';') self.NextToken() - return {'nt':'STSW', 't':None, 'name':name, 'scope':0} + return nr(nt='STSW', t=None, name=name, scope=0) if tok0 == 'RETURN': savepos = self.errorpos @@ -1656,33 +1765,34 @@ list lazy_list_set(list L, integer i, list v) self.errorpos = savepos raise EParseReturnIsEmpty(self) if value is None: - return {'nt':'RETURN', 't':None} + return nr(nt='RETURN', t=None) # Sets LastIsReturn flag too - return {'nt':'RETURN', 't':None, 'LIR':True, - 'ch':[self.autocastcheck(value, ReturnType)]} + return nr(nt='RETURN', t=None, LIR=True, + ch=[self.autocastcheck(value, ReturnType)]) if tok0 == 'IF': - ret = {'nt':'IF', 't':None, 'ch':[]} + ret = nr(nt='IF', t=None, ch=[]) self.NextToken() self.expect('(') self.NextToken() - ret['ch'].append(self.Parse_expression()) + ret.ch.append(self.Parse_expression()) self.expect(')') self.NextToken() saveSuspiciousStSw = self.SuspiciousStSw self.SuspiciousStSw = [] - ret['ch'].append(self.Parse_statement(ReturnType, AllowStSw = None, InsideLoop = InsideLoop)) + ret.ch.append(self.Parse_statement(ReturnType, AllowStSw = None, InsideLoop = InsideLoop)) if self.tok[0] == 'ELSE': if AllowStSw is False and self.SuspiciousStSw: self.errorpos = self.SuspiciousStSw[0] raise EParseCantChangeState(self) - LastIsReturn = 'LIR' in ret['ch'][1] + LastIsReturn = getattr(ret.ch[1], 'LIR', False) self.NextToken() - ret['ch'].append(self.Parse_statement(ReturnType, AllowStSw = AllowStSw, InsideLoop = InsideLoop)) + ret.ch.append(self.Parse_statement(ReturnType, + AllowStSw = AllowStSw, InsideLoop = InsideLoop)) if AllowStSw is None: saveSuspiciousStSw += self.SuspiciousStSw - if LastIsReturn and 'LIR' in ret['ch'][2]: - ret['LIR'] = True + if LastIsReturn and getattr(ret.ch[2], 'LIR', False): + ret.LIR = True self.SuspiciousStSw = saveSuspiciousStSw return ret @@ -1723,18 +1833,20 @@ list lazy_list_set(list L, integer i, list v) else: stmt = self.Parse_statement(ReturnType, AllowStSw = True, InsideLoop = True) - ret = {'nt':'WHILE', 't':None, 'ch':[condition, stmt]} + ret = nr(nt='WHILE', t=None, ch=[condition, stmt]) if self.breakcont: last = self.continuestack.pop() if last[2]: - assert ret['ch'][1]['nt'] == '{}' - ret['ch'][1]['ch'].append({'nt':'@', 't':None, 'name':last[0], 'scope':last[1]}) + assert ret.ch[1].nt == '{}' + ret.ch[1].ch.append(nr(nt='@', t=None, name=last[0], + scope=last[1])) self.AddSymbol('l', last[1], last[0]) last = self.breakstack.pop() if last[2]: - ret = {'nt':'{}', 't':None, 'ch':[ret, {'nt':'@', 't':None, 'name':last[0], 'scope':last[1]}]} + ret = nr(nt='{}', t=None, ch=[ret, nr(nt='@', t=None, + name=last[0], scope=last[1])]) self.AddSymbol('l', last[1], last[0]) self.PopScope() return ret @@ -1761,17 +1873,19 @@ list lazy_list_set(list L, integer i, list v) self.NextToken() self.expect(';') self.NextToken() - ret = {'nt':'DO', 't':None, 'ch':[stmt, condition]} + ret = nr(nt='DO', t=None, ch=[stmt, condition]) if self.breakcont: last = self.continuestack.pop() if last[2]: - assert ret['ch'][0]['nt'] == '{}' - ret['ch'][0]['ch'].append({'nt':'@', 't':None, 'name':last[0], 'scope':last[1]}) + assert ret.ch[0].nt == '{}' + ret.ch[0].ch.append(nr(nt='@', t=None, name=last[0], + scope=last[1])) self.AddSymbol('l', last[1], last[0]) last = self.breakstack.pop() if last[2]: - ret = {'nt':'{}', 't':None, 'ch':[ret, {'nt':'@', 't':None, 'name':last[0], 'scope':last[1]}]} + ret = nr(nt='{}', t=None, ch=[ret, nr(nt='@', t=None, + name=last[0], scope=last[1])]) self.AddSymbol('l', last[1], last[0]) self.PopScope() return ret @@ -1800,21 +1914,24 @@ list lazy_list_set(list L, integer i, list v) self.PushScope() else: stmt = self.Parse_statement(ReturnType, AllowStSw = True, InsideLoop = True) - ret = {'nt':'FOR', 't':None, - 'ch':[{'nt':'EXPRLIST','t':None, 'ch':initializer}, + ret = nr(nt='FOR', t=None, + ch=[nr(nt='EXPRLIST', t=None, ch=initializer), condition, - {'nt':'EXPRLIST','t':None, 'ch':iterator}, - stmt]} + nr(nt='EXPRLIST', t=None, ch=iterator), + stmt + ]) if self.breakcont: last = self.continuestack.pop() if last[2]: - assert ret['ch'][3]['nt'] == '{}' - ret['ch'][3]['ch'].append({'nt':'@', 't':None, 'name':last[0], 'scope':last[1]}) + assert ret.ch[3].nt == '{}' + ret.ch[3].ch.append(nr(nt='@', t=None, name=last[0], + scope=last[1])) self.AddSymbol('l', last[1], last[0]) last = self.breakstack.pop() if last[2]: - ret = {'nt':'{}', 't':None, 'ch':[ret, {'nt':'@', 't':None, 'name':last[0], 'scope':last[1]}]} + ret = nr(nt='{}', t=None, ch=[ret, nr(nt='@', t=None, + name=last[0], scope=last[1])]) self.AddSymbol('l', last[1], last[0]) self.PopScope() return ret @@ -1854,24 +1971,24 @@ list lazy_list_set(list L, integer i, list v) switchcasedefault = None # Since label scope rules prevent us from being able to jump inside # a nested block, only one nesting level is considered. - assert blk['nt'] == '{}' - blk = blk['ch'] # Disregard the '{}' - we'll add it back later + assert blk.nt == '{}' + blk = blk.ch # Disregard the '{}' - we'll add it back later for idx in xrange(len(blk)): - if blk[idx]['nt'] == 'CASE': + if blk[idx].nt == 'CASE': lbl = self.GenerateLabel() - switchcaselist.append((lbl, blk[idx]['ch'][0])) + switchcaselist.append((lbl, blk[idx].ch[0])) self.AddSymbol('l', blkscope, lbl) - blk[idx] = {'nt':'@', 'name':lbl, 'scope':blkscope} - elif blk[idx]['nt'] == 'DEFAULTCASE': + blk[idx] = nr(nt='@', t=None, name=lbl, scope=blkscope) + elif blk[idx].nt == 'DEFAULTCASE': if switchcasedefault is not None: raise EParseManyDefaults(self) lbl = self.GenerateLabel() switchcasedefault = lbl self.AddSymbol('l', blkscope, lbl) - blk[idx] = {'nt':'@', 'name':lbl, 'scope':blkscope} + blk[idx] = nr(nt='@', name=lbl, scope=blkscope) prelude = [] - ltype = expr['t'] + ltype = expr.t for case in switchcaselist: rexpr = case[1] lexpr = expr @@ -1880,11 +1997,11 @@ list lazy_list_set(list L, integer i, list v) else: # For string & key, RHS (rtype) mandates the conversion # (that's room for optimization: always compare strings) - lexpr = self.autocastcheck(lexpr, rexpr['t']) - prelude.append({'nt':'IF', 't':None, 'ch':[ - {'nt':'==', 't':'integer', 'ch':[lexpr, rexpr]}, - {'nt':'JUMP', 't':None, 'name':case[0], 'scope':blkscope} - ]}) + lexpr = self.autocastcheck(lexpr, rexpr.t) + prelude.append(nr(nt='IF', t=None, ch=[ + nr(nt='==', t='integer', ch=[lexpr, rexpr]), + nr(nt='JUMP', t=None, name=case[0], scope=blkscope) + ])) if switchcasedefault is None: if self.errmissingdefault: @@ -1902,8 +2019,8 @@ list lazy_list_set(list L, integer i, list v) # If so, remove the label and don't generate the jump. for i in xrange(len(blk)): node = blk[i] - if (node['nt'] == '@' and node['name'] == switchcasedefault - and node['scope'] == blkscope): + if (node.nt == '@' and node.name == switchcasedefault + and node.scope == blkscope): switchcasedefault = None del blk[i] break @@ -1912,13 +2029,13 @@ list lazy_list_set(list L, integer i, list v) del i, node if switchcasedefault is not None: - prelude.append({'nt':'JUMP', 't':None, 'name':switchcasedefault, - 'scope':blkscope}) + prelude.append(nr(nt='JUMP', t=None, name=switchcasedefault, + scope=blkscope)) last = self.breakstack.pop() if last[2]: - blk.append({'nt':'@', 'name':brk, 'scope':blkscope}) + blk.append(nr(nt='@', name=brk, scope=blkscope)) self.AddSymbol('l', blkscope, brk) - return {'nt':'{}', 't':None, 'ch':prelude + blk} + return nr(nt='{}', t=None, ch=prelude + blk) if tok0 == 'CASE': if not InsideSwitch: @@ -1934,7 +2051,7 @@ list lazy_list_set(list L, integer i, list v) self.NextToken() elif self.tok[0] != '{': raise EParseSyntax(self) - return {'nt':'CASE', 't':None, 'ch':[expr]} + return nr(nt='CASE', t=None, ch=[expr]) if tok0 == 'DEFAULT': if self.enableswitch: @@ -1950,7 +2067,7 @@ list lazy_list_set(list L, integer i, list v) self.NextToken() elif self.tok[0] != '{': raise EParseSyntax(self) - return {'nt':'DEFAULTCASE', 't':None} + return nr(nt='DEFAULTCASE', t=None) # else fall through to eventually fail if tok0 == 'BREAK': @@ -1969,8 +2086,8 @@ list lazy_list_set(list L, integer i, list v) self.breakstack[n][2] = True except IndexError: raise EParseInvalidBrkContArg(self) - return {'nt':'JUMP', 't':None, 'name':self.breakstack[n][0], - 'scope':self.breakstack[n][1]} + return nr(nt='JUMP', t=None, name=self.breakstack[n][0], + scope=self.breakstack[n][1]) if tok0 == 'CONTINUE': if not self.continuestack: @@ -1987,7 +2104,7 @@ list lazy_list_set(list L, integer i, list v) if n == -1 and self.continuestack[-1][1] is None: # We're not inside a block - 'continue' is essentially a nop # e.g. while (cond) continue; is the same as while (cond) ; - return {'nt':';', 't':'None'} + return nr(nt=';', t='None') try: if self.continuestack[n][1] is None: # this can happen with e.g.: @@ -1996,13 +2113,13 @@ list lazy_list_set(list L, integer i, list v) # which is equivalent since there are no {}. n += 1 # e.g. -3 -> -2 self.breakstack[n][2] = True # mark the break as used - return {'nt':'JUMP', 't':None, 'name':self.breakstack[n][0], - 'scope':self.breakstack[n][1]} + return nr(nt='JUMP', t=None, name=self.breakstack[n][0], + scope=self.breakstack[n][1]) except IndexError: raise EParseInvalidBrkContArg(self) self.continuestack[n][2] = True - return {'nt':'JUMP', 't':None, 'name':self.continuestack[n][0], - 'scope':self.continuestack[n][1]} + return nr(nt='JUMP', t=None, name=self.continuestack[n][0], + scope=self.continuestack[n][1]) if tok0 == 'TYPE': if not AllowDecl: @@ -2015,10 +2132,10 @@ list lazy_list_set(list L, integer i, list v) raise EParseAlreadyDefined(self) self.NextToken() value = None - decl = {'nt':'DECL','t':typ, 'name':name, 'scope':self.scopeindex} + decl = nr(nt='DECL', t=typ, name=name, scope=self.scopeindex) if self.tok[0] == '=': self.NextToken() - decl['ch'] = [self.autocastcheck(self.Parse_expression(), typ)] + decl.ch = [self.autocastcheck(self.Parse_expression(), typ)] self.expect(';') self.NextToken() self.AddSymbol('v', self.scopeindex, name, Type=typ) @@ -2028,7 +2145,7 @@ list lazy_list_set(list L, integer i, list v) value = self.Parse_expression() self.expect(';') self.NextToken() - return {'nt':'EXPR', 't':value['t'], 'ch':[value]} + return nr(nt='EXPR', t=value.t, ch=[value]) def Parse_code_block(self, ReturnType, AllowStSw = False, InsideSwitch = False, InsideLoop = False): @@ -2060,7 +2177,7 @@ list lazy_list_set(list L, integer i, list v) stmt = self.Parse_statement(ReturnType, AllowDecl = True, AllowStSw = AllowStSw, InsideSwitch = InsideSwitch, InsideLoop = InsideLoop) - LastIsReturn = 'LIR' in stmt + LastIsReturn = getattr(stmt, 'LIR', False) body.append(stmt) self.PopScope() @@ -2068,9 +2185,9 @@ list lazy_list_set(list L, integer i, list v) self.expect('}') self.NextToken() - node = {'nt':'{}', 't':None, 'ch':body} + node = nr(nt='{}', t=None, ch=body) if LastIsReturn: - node['LIR'] = True + node.LIR = True return node def Parse_simple_expr(self, ForbidList=False): @@ -2089,15 +2206,15 @@ list lazy_list_set(list L, integer i, list v) tok = self.tok self.NextToken() if tok[0] in ('TRUE', 'FALSE'): # TRUE and FALSE don't admit sign in globals - return {'nt':'CONST', 't':'integer', 'value':int(tok[0]=='TRUE')} + return nr(nt='CONST', t='integer', value=int(tok[0]=='TRUE')) if tok[0] in ('STRING_VALUE', 'KEY_VALUE', 'VECTOR_VALUE', 'ROTATION_VALUE', 'LIST_VALUE'): val = tok[1] if tok[0] == 'STRING_VALUE' and self.allowmultistrings: while self.tok[0] == 'STRING_VALUE': val += self.tok[1] self.NextToken() - return {'nt':'CONST', 't':lslcommon.PythonType2LSL[type(val)], - 'value':val} + return nr(nt='CONST', t=lslcommon.PythonType2LSL[type(val)], + value=val) if tok[0] == 'IDENT': sym = self.FindSymbolPartial(tok[1]) if sym is None or sym['Kind'] != 'v': @@ -2108,7 +2225,7 @@ list lazy_list_set(list L, integer i, list v) # var inside a list global definition takes a string value # (SCR-295). typ = 'string' - return {'nt':'IDENT', 't':typ, 'name':tok[1], 'scope':sym['Scope']} + return nr(nt='IDENT', t=typ, name=tok[1], scope=sym['Scope']) if tok[0] == '<': value = [self.Parse_simple_expr()] self.autocastcheck(value[0], 'float') @@ -2122,25 +2239,25 @@ list lazy_list_set(list L, integer i, list v) self.autocastcheck(value[2], 'float') if self.tok[0] == '>': self.NextToken() - return {'nt':'VECTOR', 't':'vector', 'ch':value} + return nr(nt='VECTOR', t='vector', ch=value) self.expect(',') self.NextToken() value.append(self.Parse_simple_expr()) self.autocastcheck(value[3], 'float') self.expect('>') self.NextToken() - return {'nt':'ROTATION', 't':'rotation', 'ch':value} + return nr(nt='ROTATION', t='rotation', ch=value) if tok[0] == '[' and not ForbidList: value = [] if self.tok[0] == ']': self.NextToken() - return {'nt':'LIST','t':'list','ch':value} + return nr(nt='LIST', t='list', ch=value) while True: value.append(self.Parse_simple_expr(ForbidList=True)) if self.tok[0] == ']': self.NextToken() - return {'nt':'LIST','t':'list','ch':value} + return nr(nt='LIST', t='list', ch=value) self.expect(',') self.NextToken() # Integer or Float constant expected @@ -2154,7 +2271,8 @@ list lazy_list_set(list L, integer i, list v) value = tok[1] if neg and (tok[0] != 'INTEGER_VALUE' or value != -2147483648): value = -value - return {'nt':'CONST', 't':'float' if tok[0] == 'FLOAT_VALUE' else 'integer', 'value':value} + return nr(nt='CONST', + t='float' if tok[0] == 'FLOAT_VALUE' else 'integer', value=value) def Parse_optional_param_list(self): """Grammar parsed here: @@ -2217,9 +2335,9 @@ list lazy_list_set(list L, integer i, list v) self.locallabels = set() body = self.Parse_code_block(None) del self.locallabels - ret.append({'nt':'FNDEF', 't':None, 'name':name, # no scope as these are reserved words - 'pscope':self.scopeindex, 'ptypes':params[0], 'pnames':params[1], - 'ch':[body]}) + ret.append(nr(nt='FNDEF', t=None, name=name, # no scope as these are reserved words + pscope=self.scopeindex, ptypes=params[0], pnames=params[1], + ch=[body])) self.PopScope() return ret @@ -2257,7 +2375,7 @@ list lazy_list_set(list L, integer i, list v) report = False # Delete the previous definition. self.tree[self.symtab[0][name]['Loc']] = \ - {'nt':'LAMBDA', 't':None} + nr(nt='LAMBDA', t=None) del self.symtab[0][name] if report: raise EParseAlreadyDefined(self) @@ -2278,7 +2396,7 @@ list lazy_list_set(list L, integer i, list v) try: value = self.Parse_simple_expr() self.expect(';') - value['Simple'] = True # Success - mark it as simple + value.Simple = True # Success - mark it as simple except EParse: # Backtrack self.pos = pos @@ -2296,10 +2414,10 @@ list lazy_list_set(list L, integer i, list v) value = None assert self.scopeindex == 0 - decl = {'nt':'DECL', 't':typ, 'name':name, 'scope':0} + decl = nr(nt='DECL', t=typ, name=name, scope=0) if value is not None: value = self.autocastcheck(value, typ) - decl['ch'] = [value] + decl.ch = [value] self.NextToken() self.AddSymbol('v', 0, name, Loc=len(self.tree), Type=typ) self.tree.append(decl) @@ -2315,14 +2433,14 @@ list lazy_list_set(list L, integer i, list v) self.locallabels = set() body = self.Parse_code_block(typ) del self.locallabels - if typ and 'LIR' not in body: # is LastIsReturn flag set? + if typ and not getattr(body, 'LIR', False): # is LastIsReturn flag set? raise EParseCodePathWithoutRet(self) paramscope = self.scopeindex self.AddSymbol('f', 0, name, Loc=len(self.tree), Type=typ, ParamTypes=params[0], ParamNames=params[1]) - self.tree.append({'nt':'FNDEF', 't':typ, 'name':name, 'scope':0, - 'pscope':paramscope, - 'ptypes':params[0], 'pnames':params[1], 'ch':[body]}) + self.tree.append(nr(nt='FNDEF', t=typ, name=name, scope=0, + pscope=paramscope, + ptypes=params[0], pnames=params[1], ch=[body])) self.PopScope() assert self.scopeindex == 0 else: @@ -2369,7 +2487,8 @@ list lazy_list_set(list L, integer i, list v) del self.localevents self.expect('}') - self.tree.append({'nt':'STDEF', 't':None, 'name':name, 'scope':0, 'ch':events}) + self.tree.append(nr(nt='STDEF', t=None, name=name, scope=0, + ch=events)) self.NextToken() def Parse_script(self): @@ -2392,14 +2511,14 @@ list lazy_list_set(list L, integer i, list v) self.Parse_states() self.expect('EOF') - # Check the pending jump targets + # Check the pending jump targets to assign them the scope of the label. for tgt in self.jump_lookups: self.scopeindex = tgt[1] sym = self.FindSymbolPartial(tgt[0], MustBeLabel = True) if sym is None: self.errorpos = tgt[2] raise EParseUndefined(self) - tgt[3]['scope'] = sym['Scope'] + tgt[3].scope = sym['Scope'] del self.jump_lookups # Finished with it. @@ -2411,7 +2530,7 @@ list lazy_list_set(list L, integer i, list v) script: expression EOF """ value = self.Parse_expression() - self.tree.append({'nt':'EXPR', 't':value['t'], 'ch':[value]}) + self.tree.append(nr(nt='EXPR', t=value.t, ch=[value])) self.expect('EOF') return @@ -2713,7 +2832,7 @@ list lazy_list_set(list L, integer i, list v) self.tok = self.GetToken() # Reserve spots at the beginning for functions we add - self.tree = [{'nt':'LAMBDA','t':None}] + self.tree = [nr(nt='LAMBDA', t=None)] self.usedspots = 0 # Start the parsing proper diff --git a/lslopt/lslrenamer.py b/lslopt/lslrenamer.py index d5ffd8c..960ec02 100644 --- a/lslopt/lslrenamer.py +++ b/lslopt/lslrenamer.py @@ -114,9 +114,9 @@ class renamer(object): name = entry['NewName'] = self.GetNextShortest() stateNames.append(name) # Find also the event names it uses, to add them for reuse. - for node in self.tree[entry['Loc']]['ch']: - assert node['nt'] == 'FNDEF' - event_name = node['name'] + for node in self.tree[entry['Loc']].ch: + assert node.nt == 'FNDEF' + event_name = node.name # These events have their names translated. if event_name == 'on_rez': event_name = 'rez'