From 075d3aba0cffd7f8caed49bd6068e9b6f3b30b8d Mon Sep 17 00:00:00 2001 From: Sei Lisa Date: Wed, 28 Mar 2018 00:19:08 +0200 Subject: [PATCH] Change the AST node type from dict to object That was long overdue. Obviously, this is a large commit. The new nr (node record) class has built-in dump capabilities, rather than using print_node(). SEF always exists now, and is a boolean, rather than using the existence of SEF as the flag. This was changed for sanity. However, other flags like 'X' are still possibly absent, and in some cases the absence itself has meaning (in the case of 'X', its absence means that the node has not yet been analyzed). Similarly, an event is distinguished from a UDF by checking for the existence of the 'scope' attribute. This trick works because events are not in the symbol table therefore they have no scope. But this should probably be changed in future to something more rational and faster. A few minor bugfixes were applied while going through the code. - Some tabs used as Unicode were written as byte strings. Add the u'\t' prefix. - After simplifying a%1 -> a&0, fold again the node and return. It's not clear why it didn't return, and whether it depended on subsequent passes (e.g. after DCR) for possibly optimizing out the result. Now we're sure. - A few places lacked a SEF declaration. - Formatting changes to split lines that spilled the margin. - Some comment changes. - Expanded lazy_list_set definition while adapting it to object format. The plan was to re-compress it after done, but decided to leave it in expanded form. - Added a few TODOs & FIXMEs, resisting the temptation to fix them in the same commit: - TODO: ~-~-~-expr -> expr + -3. - FIXME: Now that we have CompareTrees, we can easily check if expr + -expr cancels out and remove a TODO. Low-hanging fruit. - TODO: Check what we can do when comparing non-SEF and non-CONST values in '>' (current code relies on converting '>' to '<' for applying more optimizations, but that may miss some opportunities). - FIXME: Could remove one comparison in nt == '&&' or nt == '||'. Low-hanging fruit. --- lslopt/lslcommon.py | 88 ++- lslopt/lsldeadcode.py | 270 ++++----- lslopt/lslfoldconst.py | 1310 +++++++++++++++++++--------------------- lslopt/lslfuncopt.py | 133 ++-- lslopt/lsllastpass.py | 74 +-- lslopt/lsloptimizer.py | 13 +- lslopt/lsloutput.py | 96 ++- lslopt/lslparse.py | 505 ++++++++++------ lslopt/lslrenamer.py | 6 +- 9 files changed, 1294 insertions(+), 1201 deletions(-) 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'