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.
This commit is contained in:
Sei Lisa 2018-03-28 00:19:08 +02:00
parent d890f0b5fa
commit 075d3aba0c
9 changed files with 1294 additions and 1201 deletions

View file

@ -19,6 +19,52 @@
import sys 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. # These types just wrap the Python types to make type() work on them.
# There are no ops defined on them or anything. # There are no ops defined on them or anything.
@ -76,24 +122,24 @@ def warning(txt):
sys.stderr.write(u"WARNING: " + txt + u"\n") sys.stderr.write(u"WARNING: " + txt + u"\n")
# Debug function # Debug function
def print_node(node, indent = 0): #def print_node(node, indent = 0):
nt = node['nt'] # nt = node['nt']
write = sys.stdout.write # write = sys.stdout.write
spaces = ' ' * (indent*4+2) # spaces = ' ' * (indent*4+2)
write('%s{ nt:%s\n' % (' '*(indent*4), nt)) # write('%s{ nt:%s\n' % (' '*(indent*4), nt))
if 't' in node: # if 't' in node:
write('%s,t:%s\n' % (spaces, node['t'])) # write('%s,t:%s\n' % (spaces, node['t']))
if 'name' in node: # if 'name' in node:
write('%s,name:%s\n' % (spaces, node['name'])) # write('%s,name:%s\n' % (spaces, node['name']))
if 'value' in node: # if 'value' in node:
write('%s,value:%s\n' % (spaces, repr(node['value']))) # write('%s,value:%s\n' % (spaces, repr(node['value'])))
#
for prop in node: # for prop in node:
if prop not in ('ch', 'nt', 't', 'name', 'value','X','SEF'): # if prop not in ('ch', 'nt', 't', 'name', 'value','X','SEF'):
write('%s,%s:%s\n' % (spaces, prop, repr(node[prop]))) # write('%s,%s:%s\n' % (spaces, prop, repr(node[prop])))
if 'ch' in node: # if 'ch' in node:
write(spaces + ',ch:[\n') # write(spaces + ',ch:[\n')
for subnode in node['ch']: # for subnode in node['ch']:
print_node(subnode, indent+1) # print_node(subnode, indent+1)
write(spaces + ']\n') # write(spaces + ']\n')
write(' '*(indent*4) + '}\n\n') # write(' '*(indent*4) + '}\n\n')

View file

@ -18,6 +18,7 @@
# Dead Code Removal optimization # Dead Code Removal optimization
import lslfuncs import lslfuncs
from lslcommon import nr
class deadcode(object): class deadcode(object):
@ -30,25 +31,25 @@ class deadcode(object):
# The 'X' key, when present, indicates whether a node is executed. # The 'X' key, when present, indicates whether a node is executed.
# Its value means whether this instruction will proceed to the next # Its value means whether this instruction will proceed to the next
# (True: it will; False: it won't). # (True: it will; False: it won't).
if 'X' in node: if hasattr(node, 'X'):
return node['X'] # branch already analyzed return node.X # branch already analyzed
nt = node['nt'] nt = node.nt
child = node['ch'] if 'ch' in node else None child = node.ch
# Control flow statements # Control flow statements
if nt == 'STSW': if nt == 'STSW':
node['X'] = False # Executed but path-breaking. node.X = False # Executed but path-breaking.
sym = self.symtab[0][node['name']] sym = self.symtab[0][node.name]
if 'X' not in self.tree[sym['Loc']]: if not hasattr(self.tree[sym['Loc']], 'X'):
self.MarkReferences(self.tree[sym['Loc']]) self.MarkReferences(self.tree[sym['Loc']])
return False return False
if nt == 'JUMP': if nt == 'JUMP':
node['X'] = False # Executed but path-breaking. node.X = False # Executed but path-breaking.
sym = self.symtab[node['scope']][node['name']] sym = self.symtab[node.scope][node.name]
if 'R' in sym: if 'R' in sym:
sym['R'] += 1 sym['R'] += 1
else: else:
@ -56,87 +57,87 @@ class deadcode(object):
return False return False
if nt == 'RETURN': if nt == 'RETURN':
node['X'] = False # Executed but path-breaking. node.X = False # Executed but path-breaking.
if child: if child:
self.MarkReferences(child[0]) self.MarkReferences(child[0])
return False return False
if nt == 'IF': if nt == 'IF':
# "When you get to a fork in the road, take it." # "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]) self.MarkReferences(child[0])
condnode = child[0] condnode = child[0]
if condnode['nt'] == 'CONST': if condnode.nt == 'CONST':
if lslfuncs.cond(condnode['value']): if lslfuncs.cond(condnode.value):
# TRUE - 'then' branch always executed. # TRUE - 'then' branch always executed.
node['X'] = self.MarkReferences(child[1]) node.X = self.MarkReferences(child[1])
return node['X'] return node.X
elif len(child) == 3: elif len(child) == 3:
# FALSE - 'else' branch always executed. # FALSE - 'else' branch always executed.
node['X'] = self.MarkReferences(child[2]) node.X = self.MarkReferences(child[2])
return node['X'] return node.X
# else fall through # else fall through
else: else:
cont = self.MarkReferences(child[1]) cont = self.MarkReferences(child[1])
if len(child) == 3: if len(child) == 3:
if not cont: if not cont:
cont = self.MarkReferences(child[2]) cont = self.MarkReferences(child[2])
node['X'] = cont node.X = cont
return cont return cont
self.MarkReferences(child[2]) self.MarkReferences(child[2])
node['X'] = True node.X = True
return True return True
if nt == 'WHILE': if nt == 'WHILE':
node['X'] = None # provisional value, refined later node.X = None # provisional value, refined later
self.MarkReferences(child[0]) self.MarkReferences(child[0])
if child[0]['nt'] == 'CONST': if child[0].nt == 'CONST':
if lslfuncs.cond(child[0]['value']): if lslfuncs.cond(child[0].value):
# Infinite loop - unless it returns, it stops # Infinite loop - unless it returns, it stops
# execution. But it is executed itself. # execution. But it is executed itself.
self.MarkReferences(child[1]) self.MarkReferences(child[1])
node['X'] = False node.X = False
return node['X'] return node.X
# else the inside isn't executed at all, so don't mark it # else the inside isn't executed at all, so don't mark it
else: else:
self.MarkReferences(child[1]) self.MarkReferences(child[1])
node['X'] = True node.X = True
return True return True
if nt == 'DO': if nt == 'DO':
node['X'] = None # provisional value, refined later node.X = None # provisional value, refined later
if not self.MarkReferences(child[0]): if not self.MarkReferences(child[0]):
node['X'] = False node.X = False
return False return False
self.MarkReferences(child[1]) self.MarkReferences(child[1])
# It proceeds to the next statement unless it's an infinite loop # 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'])) node.X = not (child[1].nt == 'CONST' and lslfuncs.cond(child[1].value))
return node['X'] return node.X
if nt == 'FOR': if nt == 'FOR':
node['X'] = None # provisional value, refined later node.X = None # provisional value, refined later
self.MarkReferences(child[0]) self.MarkReferences(child[0])
self.MarkReferences(child[1]) self.MarkReferences(child[1])
if child[1]['nt'] == 'CONST': if child[1].nt == 'CONST':
if lslfuncs.cond(child[1]['value']): if lslfuncs.cond(child[1].value):
# Infinite loop - unless it returns, it stops # Infinite loop - unless it returns, it stops
# execution. But it is executed itself. # execution. But it is executed itself.
node['X'] = False node.X = False
self.MarkReferences(child[3]) self.MarkReferences(child[3])
self.MarkReferences(child[2]) # this can't stop execution 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 # else the body and the iterator aren't executed at all, so
# don't mark them # don't mark them
node['X'] = True node.X = True
else: else:
node['X'] = True node.X = True
self.MarkReferences(child[3]) self.MarkReferences(child[3])
self.MarkReferences(child[2]) self.MarkReferences(child[2])
# Mark the EXPRLIST as always executed, but not the subexpressions. # Mark the EXPRLIST as always executed, but not the subexpressions.
# That forces the EXPRLIST (which is a syntactic requirement) to be # That forces the EXPRLIST (which is a syntactic requirement) to be
# kept, while still simplifying the contents properly. # kept, while still simplifying the contents properly.
child[2]['X'] = True child[2].X = True
return True return True
if nt == '{}': if nt == '{}':
@ -146,16 +147,16 @@ class deadcode(object):
# True. # True.
continues = True continues = True
node['X'] = None # provisional node.X = None # provisional
for stmt in child: for stmt in child:
if continues or stmt['nt'] == '@': if continues or stmt.nt == '@':
continues = self.MarkReferences(stmt) continues = self.MarkReferences(stmt)
node['X'] = continues node.X = continues
return continues return continues
if nt == 'FNCALL': if nt == 'FNCALL':
node['X'] = None # provisional node.X = None # provisional
sym = self.symtab[0][node['name']] sym = self.symtab[0][node.name]
fdef = self.tree[sym['Loc']] if 'Loc' in sym else None fdef = self.tree[sym['Loc']] if 'Loc' in sym else None
for idx in xrange(len(child)-1, -1, -1): for idx in xrange(len(child)-1, -1, -1):
@ -164,18 +165,18 @@ class deadcode(object):
# writes to a and b. # writes to a and b.
self.MarkReferences(child[idx]) self.MarkReferences(child[idx])
if fdef is not None: 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: if 'W' in psym:
psym['W'] = False psym['W'] = False
else: else:
psym['W'] = child[idx] psym['W'] = child[idx]
if 'Loc' in sym: 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']]) self.MarkReferences(self.tree[sym['Loc']])
node['X'] = self.tree[sym['Loc']]['X'] node.X = self.tree[sym['Loc']].X
else: else:
node['X'] = 'stop' not in sym node.X = 'stop' not in sym
# Note that JUMP analysis is incomplete. To do it correctly, we # Note that JUMP analysis is incomplete. To do it correctly, we
# should follow the jump right to its destination, in order to know # should follow the jump right to its destination, in order to know
# if that branch leads to a RETURN or completely stops the event. # if that branch leads to a RETURN or completely stops the event.
@ -200,40 +201,40 @@ class deadcode(object):
# fn2() { fn(); x = 1; } # fn2() { fn(); x = 1; }
return node['X'] return node.X
if nt == 'DECL': if nt == 'DECL':
sym = self.symtab[node['scope']][node['name']] sym = self.symtab[node.scope][node.name]
if child is not None: if child is not None:
sym['W'] = child[0] sym['W'] = child[0]
else: else:
sym['W'] = {'nt':'CONST', 't':node['t'], sym['W'] = nr(nt='CONST', t=node.t,
'value':self.DefaultValues[node['t']]} value=self.DefaultValues[node.t])
node['X'] = True node.X = True
if child is not None: if child is not None:
if 'orig' in child[0]: if hasattr(child[0], 'orig'):
orig = child[0]['orig'] orig = child[0].orig
self.MarkReferences(orig) self.MarkReferences(orig)
child[0]['X'] = orig['X'] child[0].X = orig.X
if orig['nt'] == 'LIST': if orig.nt == 'LIST':
# Add fake writes to variables used in list elements in # Add fake writes to variables used in list elements in
# 'orig', so they don't get deleted (Issue #3) # 'orig', so they don't get deleted (Issue #3)
for subnode in orig['ch']: for subnode in orig.ch:
if subnode['nt'] == 'IDENT': if subnode.nt == 'IDENT':
# can only happen in globals # can only happen in globals
assert subnode['scope'] == 0 assert subnode.scope == 0
sym = self.symtab[0][subnode['name']] sym = self.symtab[0][subnode.name]
sym['W'] = False sym['W'] = False
self.tree[sym['Loc']]['X'] = True self.tree[sym['Loc']].X = True
elif subnode['nt'] in ('VECTOR', 'ROTATION'): elif subnode.nt in ('VECTOR', 'ROTATION'):
for sub2node in subnode['ch']: for sub2node in subnode.ch:
if sub2node['nt'] == 'IDENT': if sub2node.nt == 'IDENT':
# can only happen in globals # can only happen in globals
assert sub2node['scope'] == 0 assert sub2node.scope == 0
sym = self.symtab[0][sub2node['name']] sym = self.symtab[0][sub2node.name]
sym['W'] = False sym['W'] = False
self.tree[sym['Loc']]['X'] = True self.tree[sym['Loc']].X = True
else: else:
self.MarkReferences(child[0]) self.MarkReferences(child[0])
return True return True
@ -241,14 +242,14 @@ class deadcode(object):
# ---- Starting here, all node types return through the bottom # ---- Starting here, all node types return through the bottom
# (except '='). # (except '=').
node['X'] = None # provisional node.X = None # provisional
if nt in self.assign_ops or nt in ('--V', '++V', 'V++', 'V--'): if nt in self.assign_ops or nt in ('--V', '++V', 'V++', 'V--'):
ident = node['ch'][0] ident = node.ch[0]
if ident['nt'] == 'FLD': if ident.nt == 'FLD':
ident = ident['ch'][0] ident = ident.ch[0]
assert ident['nt'] == 'IDENT' assert ident.nt == 'IDENT'
sym = self.symtab[ident['scope']][ident['name']] sym = self.symtab[ident.scope][ident.name]
if ident['scope'] == 0: if ident.scope == 0:
# Mark the global first. # Mark the global first.
self.MarkReferences(self.tree[sym['Loc']]) self.MarkReferences(self.tree[sym['Loc']])
# In any case, this is at least the second write, so mark it as such # 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 # Prevent the first node from being mistaken as a read, by
# recursing only on the RHS node. # recursing only on the RHS node.
self.MarkReferences(child[1]) self.MarkReferences(child[1])
node['X'] = True node.X = True
return True return True
elif nt == 'FLD': elif nt == 'FLD':
# Mark this variable as referenced by a Field (recursing will mark # Mark this variable as referenced by a Field (recursing will mark
# the ident as read later) # 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': elif nt == 'IDENT':
sym = self.symtab[node['scope']][node['name']] sym = self.symtab[node.scope][node.name]
# Mark global if it's one. # 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']]) self.MarkReferences(self.tree[sym['Loc']])
# Increase read counter # Increase read counter
if 'R' in sym: if 'R' in sym:
@ -278,7 +279,7 @@ class deadcode(object):
else: else:
sym['R'] = 1 sym['R'] = 1
node['X'] = True node.X = True
if child is not None: if child is not None:
for subnode in child: for subnode in child:
self.MarkReferences(subnode) self.MarkReferences(subnode)
@ -304,7 +305,7 @@ class deadcode(object):
# - Floats are removed if their value has no decimals or if # - Floats are removed if their value has no decimals or if
# used no more than N times (for some N). # used no more than N times (for some N).
# - Strings, keys and integers are just removed. # - 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: if 'R' not in sym:
return True # if not used, it can be removed return True # if not used, it can be removed
@ -316,21 +317,21 @@ class deadcode(object):
if sym['W'] is not False: if sym['W'] is not False:
node = sym['W'] node = sym['W']
nt = node['nt'] nt = node.nt
if nt == 'CONST': if nt == 'CONST':
tcurnode = curnode['t'] tcurnode = curnode.t
if tcurnode in ('integer', 'string', 'key'): if tcurnode in ('integer', 'string', 'key'):
return sym return sym
if tcurnode == 'float': if tcurnode == 'float':
if sym['R'] <= 3 or type(node['value']) == int: if sym['R'] <= 3 or type(node.value) == int:
return sym return sym
elif tcurnode == 'vector' \ elif tcurnode == 'vector' \
or tcurnode == 'list' and len(node['value']) <= 3: or tcurnode == 'list' and len(node.value) <= 3:
if sym['R'] <= 1: if sym['R'] <= 1:
return sym return sym
elif tcurnode == 'rotation' \ elif tcurnode == 'rotation' \
or tcurnode == 'list' and len(node['value']) <= 4: or tcurnode == 'list' and len(node.value) <= 4:
if sym['R'] <= 1: if sym['R'] <= 1:
return sym return sym
return False return False
@ -346,8 +347,8 @@ class deadcode(object):
# the name i is redefined after j is assigned. shrinknames prevents # the name i is redefined after j is assigned. shrinknames prevents
# that. # that.
# FIXME: EMERGENCY FIX: shrinknames is not enough guarantee. See nposerlv.lsl. # FIXME: EMERGENCY FIX: shrinknames is not enough guarantee. See nposerlv.lsl.
#if not self.shrinknames or 'SEF' not in node: #if not self.shrinknames or not node.SEF:
if True or 'SEF' not in node: if True or not node.SEF:
return False return False
if nt not in ('VECTOR', 'ROTATION'): if nt not in ('VECTOR', 'ROTATION'):
@ -371,51 +372,50 @@ class deadcode(object):
"""Recursively checks if the children are used, deleting those that are """Recursively checks if the children are used, deleting those that are
not. not.
""" """
if 'ch' not in curnode or (curnode['nt'] == 'DECL' if curnode.ch is None or (curnode.nt == 'DECL'
and curnode['scope'] == 0): and curnode.scope == 0):
return return
# NOTE: Should not depend on 'Loc', since the nodes that are the # NOTE: Should not depend on 'Loc', since the nodes that are the
# destination of 'Loc' are renumbered as we delete stuff from globals. # 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']): while index < len(curnode.ch):
node = curnode['ch'][index] node = curnode.ch[index]
if 'X' not in node: if not hasattr(node, 'X'):
del curnode['ch'][index] del curnode.ch[index]
continue continue
nt = node['nt'] nt = node.nt
if nt == 'DECL': if nt == 'DECL':
if self.SymbolReplacedOrDeleted(node): if self.SymbolReplacedOrDeleted(node):
if 'ch' not in node or 'SEF' in node['ch'][0]: if not node.ch or node.ch[0].SEF:
del curnode['ch'][index] del curnode.ch[index]
continue continue
node = curnode['ch'][index] = {'nt':'EXPR', 't':node['t'], node = curnode.ch[index] = nr(nt='EXPR', t=node.t,
'ch':[self.Cast(node['ch'][0], node['t'])]} ch=[self.Cast(node.ch[0], node.t)])
elif nt == 'FLD': elif nt == 'FLD':
sym = self.SymbolReplacedOrDeleted(node['ch'][0]) sym = self.SymbolReplacedOrDeleted(node.ch[0])
if sym: if sym:
value = sym['W'] value = sym['W']
# Mark as executed, so it isn't optimized out. # Mark as executed, so it isn't optimized out.
value['X'] = True value.X = True
fieldidx = 'xyzs'.index(node['fld']) fieldidx = 'xyzs'.index(node.fld)
if value['nt'] == 'CONST': if value.nt == 'CONST':
value = value['value'][fieldidx] value = value.value[fieldidx]
value = {'nt':'CONST', 'X':True, 'SEF':True, value = nr(nt='CONST', X=True, SEF=True,
't':self.PythonType2LSL[type(value)], 'value':value} t=self.PythonType2LSL[type(value)], value=value)
value = self.Cast(value, 'float') value = self.Cast(value, 'float')
SEF = True SEF = True
else: # assumed VECTOR or ROTATION per SymbolReplacedOrDeleted else: # assumed VECTOR or ROTATION per SymbolReplacedOrDeleted
SEF = 'SEF' in value SEF = value.SEF
value = self.Cast(value['ch'][fieldidx], 'float') value = self.Cast(value.ch[fieldidx], 'float')
# Replace it # Replace it
node = curnode['ch'][index] = value node = curnode.ch[index] = value
if SEF: node.SEF = SEF
node['SEF'] = True
elif nt == 'IDENT': elif nt == 'IDENT':
sym = self.SymbolReplacedOrDeleted(node) sym = self.SymbolReplacedOrDeleted(node)
@ -425,42 +425,40 @@ class deadcode(object):
# TODO: Needs more analysis to see if it's correct or not. # TODO: Needs more analysis to see if it's correct or not.
# (See constant_anomaly.lsl) # (See constant_anomaly.lsl)
new = sym['W'].copy() new = sym['W'].copy()
if 'orig' in new: if hasattr(new, 'orig'):
del new['orig'] del new.orig
new['X'] = True new.X = True
# this part makes no sense? # this part makes no sense?
#SEF = 'SEF' in sym['W'] #new.SEF = sym['W'].SEF
#if SEF:
# new['SEF'] = True
if new['t'] != node['t']: if new.t != node.t:
new = self.Cast(new, node['t']) new = self.Cast(new, node.t)
curnode['ch'][index] = node = new curnode.ch[index] = node = new
# Delete orig if present, as we've eliminated the original # Delete orig if present, as we've eliminated the original
#if 'orig' in sym['W']: #if hasattr(sym['W'], 'orig'):
# del sym['W']['orig'] # del sym['W'].orig
elif nt in self.assign_ops: elif nt in self.assign_ops:
ident = node['ch'][0] ident = node.ch[0]
if ident['nt'] == 'FLD': if ident.nt == 'FLD':
ident = ident['ch'][0] ident = ident.ch[0]
sym = self.SymbolReplacedOrDeleted(ident) sym = self.SymbolReplacedOrDeleted(ident)
if sym: 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'): elif nt in ('IF', 'WHILE', 'DO', 'FOR'):
# If the mandatory statement is to be removed, replace it # If the mandatory statement is to be removed, replace it
# with a ; to prevent leaving the statement empty. # 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 idx = 3 if nt == 'FOR' else 0 if nt == 'DO' else 1
if 'X' not in child[idx]: if not hasattr(child[idx], 'X'):
child[idx] = {'nt':';', 't':None, 'X':True, 'SEF':True} child[idx] = nr(nt=';', t=None, X=True, SEF=True)
if nt == 'DO' and 'X' not in child[1]: if nt == 'DO' and not hasattr(child[1],'X'):
# Mandatory condition but not executed - replace # Mandatory condition but not executed - replace
child[1] = {'nt':'CONST','X':True,'SEF':True,'t':'integer', child[1] = nr(nt='CONST', X=True, SEF=True, t='integer',
'value':0} value=0)
self.CleanNode(node) self.CleanNode(node)
index += 1 index += 1
@ -495,7 +493,7 @@ class deadcode(object):
return return
statedef = self.tree[self.symtab[0]['default']['Loc']] 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) self.MarkReferences(statedef)
# Track removal of global lines, to reasign locations later. # Track removal of global lines, to reasign locations later.
@ -511,9 +509,9 @@ class deadcode(object):
node = self.tree[idx] node = self.tree[idx]
delete = False delete = False
if 'X' not in node: if not hasattr(node, 'X'):
delete = True delete = True
elif node['nt'] == 'DECL': elif node.nt == 'DECL':
delete = self.SymbolReplacedOrDeleted(node) delete = self.SymbolReplacedOrDeleted(node)
if delete: if delete:
@ -521,8 +519,8 @@ class deadcode(object):
# We can't remove it here because there may be more references # We can't remove it here because there may be more references
# that we will remove in CleanNode later, that hold the # that we will remove in CleanNode later, that hold the
# original value. # original value.
if node['nt'] == 'DECL' or node['nt'] == 'STDEF': if node.nt == 'DECL' or node.nt == 'STDEF':
GlobalDeletions.append(node['name']) GlobalDeletions.append(node.name)
del self.tree[idx] del self.tree[idx]
del LocMap[idx] del LocMap[idx]
else: else:

File diff suppressed because it is too large Load diff

View file

@ -19,20 +19,21 @@
# This is dependent on the LSL function library. # This is dependent on the LSL function library.
import lslcommon import lslcommon
from lslcommon import Key, Vector, Quaternion from lslcommon import Key, Vector, Quaternion, nr
import lslfuncs import lslfuncs
def OptimizeArgs(node, sym): def OptimizeArgs(node, sym):
"""Transform function arguments to shorter equivalents where possible.""" """Transform function arguments to shorter equivalents where possible."""
assert node['nt'] == 'FNCALL' assert node.nt == 'FNCALL'
params = node['ch'] params = node.ch
name = node['name'] name = node.name
if name in ('llSensor', 'llSensorRepeat'): if name in ('llSensor', 'llSensorRepeat'):
# The cutoff value is at a bit less than 3.1275 for some reason, # The cutoff value is at a bit less than 3.1275 for some reason,
# but we use 3.14159. # but we use 3.14159.
if params[4]['nt'] == 'CONST' and params[4]['t'] == 'float' and params[4]['value'] > 3.14159: if (params[4].nt == 'CONST' and params[4].t == 'float'
params[4]['value'] = 4.0 and params[4].value > 3.14159):
params[4].value = 4.0
types = sym['ParamTypes'] types = sym['ParamTypes']
if name != 'llMessageLinked': if name != 'llMessageLinked':
@ -40,10 +41,10 @@ def OptimizeArgs(node, sym):
# llMessageLinked is the exception. # llMessageLinked is the exception.
for i in range(len(types)): for i in range(len(types)):
if types[i] == 'key': if types[i] == 'key':
if params[i]['nt'] == 'CONST': if params[i].nt == 'CONST':
if not lslfuncs.cond(Key(params[i]['value'])): if not lslfuncs.cond(Key(params[i].value)):
params[i]['value'] = u"" params[i].value = u""
params[i]['type'] = 'string' params[i].type = 'string'
# Type of each entry in llGetObjectDetails. Last: 38 (OBJECT_SIT_COUNT). # Type of each entry in llGetObjectDetails. Last: 38 (OBJECT_SIT_COUNT).
@ -118,57 +119,54 @@ def OptimizeFunc(self, parent, index):
library function semantics. library function semantics.
""" """
node = parent[index] node = parent[index]
assert node['nt'] == 'FNCALL' assert node.nt == 'FNCALL'
name = node['name'] name = node.name
child = node['ch'] child = node.ch
if self.optlistlength and name == 'llGetListLength': if self.optlistlength and name == 'llGetListLength':
# Convert llGetListLength(expr) to (expr != []) # Convert llGetListLength(expr) to (expr != [])
node = {'nt':'CONST', 't':'list', 'value':[]} node = nr(nt='CONST', t='list', value=[], SEF=True)
parent[index] = node = {'nt':'!=', 't':'integer', parent[index] = node = nr(nt='!=', t='integer',
'ch':[child[0], node]} ch=[child[0], node], SEF=child[0].SEF)
# SEF if the list is
node['SEF'] = 'SEF' in child[0]
if name == 'llDumpList2String': if name == 'llDumpList2String':
if (child[1]['nt'] == 'CONST' if (child[1].nt == 'CONST'
and child[1]['t'] in ('string', 'key') and child[1].t in ('string', 'key')
and child[1]['value'] == u"" and child[1].value == u""
): ):
# Convert llDumpList2String(expr, "") to (string)(expr) # Convert llDumpList2String(expr, "") to (string)(expr)
node['nt'] = 'CAST' node.nt = 'CAST'
del child[1] del child[1]
del node['name'] del node.name
return return
if (name in ('llList2String', 'llList2Key', 'llList2Integer', if (name in ('llList2String', 'llList2Key', 'llList2Integer',
'llList2Float', 'llList2Vector', 'llList2Rot') 'llList2Float', 'llList2Vector', 'llList2Rot')
and child[1]['nt'] == 'CONST' and child[1].nt == 'CONST'
): ):
# 2nd arg to llList2XXXX must be integer # 2nd arg to llList2XXXX must be integer
assert child[1]['t'] == 'integer' assert child[1].t == 'integer'
listarg = child[0] listarg = child[0]
idx = child[1]['value'] idx = child[1].value
value = self.GetListNodeElement(listarg, idx) value = self.GetListNodeElement(listarg, idx)
tvalue = self.TypeFromNodeOrConst(value) tvalue = self.TypeFromNodeOrConst(value)
const = self.ConstFromNodeOrConst(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 # Managed to get a constant from a list, even if the
# list wasn't constant. Handle the type conversion. # 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, const = lslfuncs.InternalTypecast(const,
lslcommon.LSLType2Python[node['t']], lslcommon.LSLType2Python[node.t],
InList=True, f32=True) InList=True, f32=True)
else: else:
const = defaultListVals[name] const = defaultListVals[name]
parent[index] = {'nt':'CONST', 't':node['t'], parent[index] = nr(nt='CONST', t=node.t, value=const, SEF=True)
'value':const, 'SEF':True}
return return
if listarg['nt'] == 'FNCALL' \ if listarg.nt == 'FNCALL' \
and listarg['name'] == 'llGetObjectDetails': 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) value = self.GetListNodeElement(listarg, idx)
tvalue = self.TypeFromNodeOrConst(value) tvalue = self.TypeFromNodeOrConst(value)
const = self.ConstFromNodeOrConst(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. # Some of these can be handled with a typecast to string.
if name == 'llList2String': if name == 'llList2String':
# turn the node into a cast of arg 0 to string # turn the node into a cast of arg 0 to string
node['nt'] = 'CAST' node.nt = 'CAST'
del child[1] del child[1]
del node['name'] del node.name
return return
# The other ones that support cast to string then to # The other ones that support cast to string then to
# the final type in some cases (depending on the # 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 and finaltype in ('s', 'i')) # won't work for floats
or (name == 'llList2Float' or (name == 'llList2Float'
and finaltype in ('s', 'i')) # won't work for floats 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...) # -> (key)((string)llGetObjectDetails...)
# or (integer)((string)llGetObjectDetails...) # or (integer)((string)llGetObjectDetails...)
node['nt'] = 'CAST' node.nt = 'CAST'
del child[1] del child[1]
del node['name'] del node.name
child[0] = self.Cast(child[0], 'string') child[0] = self.Cast(child[0], 'string')
return return
@ -202,18 +200,17 @@ def OptimizeFunc(self, parent, index):
# and replace node with a constant if that's the case # and replace node with a constant if that's the case
if (value is False if (value is False
or type(const) == int or type(const) == int
and (node['t'][0] + objDetailsTypes[const]) and (node.t[0] + objDetailsTypes[const])
not in listCompat not in listCompat
) and 'SEF' in node: ) and node.SEF:
parent[index] = {'nt':'CONST', 't':node['t'], parent[index] = nr(nt='CONST', t=node.t,
'value':defaultListVals[name], value=defaultListVals[name], SEF=True)
'SEF':True}
elif listarg['nt'] == 'FNCALL' and listarg['name'] in ( elif listarg.nt == 'FNCALL' and listarg.name in (
'llGetPrimitiveParams', 'llGetLinkPrimitiveParams'): 'llGetPrimitiveParams', 'llGetLinkPrimitiveParams'):
# We're going to work with the primitive params list. # We're going to work with the primitive params list.
listarg = listarg['ch'][ listarg = listarg.ch[
0 if listarg['name'] == 'llGetPrimitiveParams' 0 if listarg.name == 'llGetPrimitiveParams'
else 1] else 1]
length = self.GetListNodeLength(listarg) length = self.GetListNodeLength(listarg)
if length is not False: if length is not False:
@ -249,9 +246,9 @@ def OptimizeFunc(self, parent, index):
and idx in (0, -1) and idx in (0, -1)
): ):
if name == 'llList2String': if name == 'llList2String':
node['nt'] = 'CAST' node.nt = 'CAST'
del child[1] del child[1]
del node['name'] del node.name
return return
if ((name == 'llList2Key' if ((name == 'llList2Key'
or name == 'llList2Integer' or name == 'llList2Integer'
@ -259,16 +256,14 @@ def OptimizeFunc(self, parent, index):
or name == 'llList2Float' or name == 'llList2Float'
and returntypes in ('s', 'i') and returntypes in ('s', 'i')
) )
and (node['t'][0] + returntypes) and (node.t[0] + returntypes)
in listCompat in listCompat
): ):
node['nt'] = 'CAST' node.nt = 'CAST'
del child[1] del child[1]
del node['name'] del node.name
child[0] = {'nt':'CAST', 't':'string', child[0] = nr(nt='CAST', t='string',
'ch':[child[0]]} ch=[child[0]], SEF=child[0].SEF)
if 'SEF' in child[0]['ch'][0]:
child[0]['SEF'] = True
return return
if (returntypes.find('*') == -1 if (returntypes.find('*') == -1
@ -282,13 +277,11 @@ def OptimizeFunc(self, parent, index):
# s[-1:0] doesn't return the last char # s[-1:0] doesn't return the last char
# so we have to compensate # so we have to compensate
idx += len(returntypes) idx += len(returntypes)
if (node['t'][0] + returntypes[idx:idx+1]) \ if ((node.t[0] + returntypes[idx:idx+1])
not in listCompat \ not in listCompat
and 'SEF' in node: and node.SEF):
parent[index] = {'nt':'CONST', parent[index] = nr(nt='CONST', t=node.t,
't':node['t'], value=defaultListVals[name], SEF=True)
'value':defaultListVals[name],
'SEF':True}
return return
del returntypes del returntypes
@ -302,20 +295,20 @@ def OptimizeFunc(self, parent, index):
0)) 0))
if type(button) == unicode and button == u'OK': if type(button) == unicode and button == u'OK':
# remove the element, as 'OK' is the default button in SL # 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 return
if (name == 'llDeleteSubList' if (name == 'llDeleteSubList'
or name == 'llListReplaceList' and child[1]['nt'] == 'CONST' or name == 'llListReplaceList' and child[1].nt == 'CONST'
and not child[1]['value'] and not child[1].value
): ):
# llDeleteSubList(x, 0, -1) -> [] if x is SEF # llDeleteSubList(x, 0, -1) -> [] if x is SEF
# llListReplaceList(x, [], 0, -1) -> [] if x is SEF # llListReplaceList(x, [], 0, -1) -> [] if x is SEF
if ('SEF' in child[0] if (child[0].SEF
and child[-2]['nt'] == 'CONST' and child[-1]['nt'] == 'CONST' and child[-2].nt == 'CONST' and child[-1].nt == 'CONST'
and child[-2]['value'] == 0 and child[-1]['value'] == -1 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 return
def FuncOptSetup(): def FuncOptSetup():

View file

@ -18,6 +18,7 @@
# Optimizations that have a negative effect on other stages. # Optimizations that have a negative effect on other stages.
import lslcommon import lslcommon
from lslcommon import nr
#from lslcommon import Vector, Quaternion #from lslcommon import Vector, Quaternion
#import lslfuncs #import lslfuncs
#from lslfuncs import ZERO_VECTOR, ZERO_ROTATION #from lslfuncs import ZERO_VECTOR, ZERO_ROTATION
@ -28,14 +29,14 @@ import lslcommon
class lastpass(object): class lastpass(object):
def LastPassPreOrder(self, parent, index): def LastPassPreOrder(self, parent, index):
node = parent[index] node = parent[index]
nt = node['nt'] nt = node.nt
child = node['ch'] if 'ch' in node else None child = node.ch
if (self.optlistadd and not self.globalmode if (self.optlistadd and not self.globalmode
and (nt == 'CONST' and node['t'] == 'list' or nt == 'LIST' and (nt == 'CONST' and node.t == 'list' or nt == 'LIST'
or nt == '+' and child[0]['t'] == 'list' and or nt == '+' and child[0].t == 'list' and
(child[1]['nt'] == 'CONST' and child[1]['t'] == 'list' (child[1].nt == 'CONST' and child[1].t == 'list'
or child[1]['nt'] == 'LIST') or child[1].nt == 'LIST')
) )
): ):
# Perform these transformations if the list is SEF: # 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 # left = None means the first element of the list must be
# turned into a cast within the loop. # turned into a cast within the loop.
left = child[0] if nt == '+' else None left = child[0] if nt == '+' else None
if 'SEF' in listnode: if listnode.SEF:
for elem in (listnode['value'] if listnode['nt'] == 'CONST' for elem in (listnode.value if listnode.nt == 'CONST'
else listnode['ch']): else listnode.ch):
elemnode = {'nt':'CONST', elemnode = nr(nt='CONST',
't':lslcommon.PythonType2LSL[type(elem)], t=lslcommon.PythonType2LSL[type(elem)],
'SEF':True, value=elem, SEF=True
'value':elem ) if listnode.nt == 'CONST' else elem
} if listnode['nt'] == 'CONST' else elem
left = (self.Cast(elemnode, 'list') if left is None left = (self.Cast(elemnode, 'list') if left is None
else {'nt':'+', 't':'list', 'SEF':True, else nr(nt='+', t='list', SEF=True,
'ch':[left, elemnode]}) ch=[left, elemnode]))
del elemnode del elemnode
if left is not None: # it's none for empty lists if left is not None: # it's none for empty lists
parent[index] = left parent[index] = left
@ -78,7 +78,7 @@ class lastpass(object):
# where state changes are considered bad. # where state changes are considered bad.
# BadStCh will be True if at least one state change statement # BadStCh will be True if at least one state change statement
# is found while monitoring state changes. # is found while monitoring state changes.
self.subinfo['StChAreBad'] = 'scope' in node self.subinfo['StChAreBad'] = hasattr(node, 'scope')
self.BadStCh = False self.BadStCh = False
return return
@ -109,38 +109,38 @@ class lastpass(object):
if nt == 'FNCALL': if nt == 'FNCALL':
# lslrenamer can benefit from a list of used library functions, # lslrenamer can benefit from a list of used library functions,
# so provide it. # so provide it.
if 'Loc' not in self.symtab[0][node['name']]: if 'Loc' not in self.symtab[0][node.name]:
# system library function # system library function
self.usedlibfuncs.add(node['name']) self.usedlibfuncs.add(node.name)
def LastPassPostOrder(self, parent, index): def LastPassPostOrder(self, parent, index):
node = parent[index] node = parent[index]
nt = node['nt'] nt = node.nt
child = node['ch'] if 'ch' in node else None child = node.ch
if nt == 'FNDEF': 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 # There is at least one bad state change statement in the
# function (must be the result of optimization). # function (must be the result of optimization).
# Insert dummy IF(1){...} statement covering the whole function # Insert dummy IF(1){...} statement covering the whole function
# (if it returs a value, insert a return statement too). # (if it returs a value, insert a return statement too).
child[0] = {'nt':'{}', 't':None, 'ch':[ child[0] = nr(nt='{}', t=None, ch=[
{'nt':'IF', 't':None, 'ch':[ nr(nt='IF', t=None, ch=[
{'nt':'CONST', 't':'integer', 'value':1}, nr(nt='CONST', t='integer', value=1, SEF=True),
child[0] child[0]
]} ])
]} ])
child = node['ch'] child = node.ch
if node['t'] is not None: if node.t is not None:
# Inserting a state switch in a function that returns a # Inserting a state switch in a function that returns a
# value must count as one of the dumbest things to do. # value must count as one of the dumbest things to do.
# We do as best as we can: add a return statement with the # We do as best as we can: add a return statement with the
# default value for the type. # default value for the type.
child[0]['ch'].append({'nt':'RETURN', 't':None, 'ch':[ child[0].ch.append(nr(nt='RETURN', t=None, ch=[
{'nt':'CONST', 't':node['t'], nr(nt='CONST', t=node.t, SEF=True,
'value':lslcommon.LSLTypeDefaults[node['t']] value=lslcommon.LSLTypeDefaults[node.t]
}] )]
}) ))
del self.BadStCh del self.BadStCh
return return
@ -150,8 +150,8 @@ class lastpass(object):
self.subinfo = subinfo.copy() self.subinfo = subinfo.copy()
self.LastPassPreOrder(parent, index) self.LastPassPreOrder(parent, index)
if 'ch' in parent[index]: if parent[index].ch is not None:
child = parent[index]['ch'] child = parent[index].ch
idx = 0 idx = 0
while idx < len(child): while idx < len(child):
self.RecursiveLastPass(child, idx) self.RecursiveLastPass(child, idx)
@ -172,7 +172,7 @@ class lastpass(object):
# self.subinfo is subtree-local info. # self.subinfo is subtree-local info.
self.subinfo = {} self.subinfo = {}
for idx in xrange(len(tree)): for idx in xrange(len(tree)):
if tree[idx]['nt'] == 'DECL': if tree[idx].nt == 'DECL':
self.globalmode = True self.globalmode = True
self.RecursiveLastPass(tree, idx) self.RecursiveLastPass(tree, idx)
self.globalmode = False self.globalmode = False

View file

@ -19,6 +19,7 @@
import lslfuncs import lslfuncs
from lslcommon import nr
from lslfoldconst import foldconst from lslfoldconst import foldconst
from lslrenamer import renamer from lslrenamer import renamer
from lsldeadcode import deadcode 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 """Return a CAST node if the types are not equal, otherwise the
value unchanged. value unchanged.
""" """
if value['t'] == newtype: if value.t == newtype:
return value return value
ret = {'nt':'CAST', 't':newtype, 'ch':[value]} ret = nr(nt='CAST', t=newtype, ch=[value], SEF=value.SEF)
if 'SEF' in value: if value.SEF:
ret['SEF'] = True ret.SEF = True
if 'X' in value: if hasattr(value, 'X'):
ret['X'] = value['X'] ret.X = value.X
return ret return ret
def optimize(self, treesymtab, options = ('optimize','constfold','dcr', def optimize(self, treesymtab, options = ('optimize','constfold','dcr',

View file

@ -55,7 +55,7 @@ class outscript(object):
else: else:
pfx = '((key)' pfx = '((key)'
sfx = ')' 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" warning(u"A string contains a tab. Tabs are expanded to four"
" spaces by the viewer when copy-pasting the code" " spaces by the viewer when copy-pasting the code"
" (disable this warning by disabling the 'warntabs'" " (disable this warning by disabling the 'warntabs'"
@ -178,22 +178,23 @@ class outscript(object):
def FindName(self, node, scope = None): def FindName(self, node, scope = None):
if scope is None: if scope is None:
# node is a node # node is a node
if 'scope' in node and 'NewName' in self.symtab[node['scope']][node['name']]: if (hasattr(node, 'scope')
return self.symtab[node['scope']][node['name']]['NewName'] and 'NewName' in self.symtab[node.scope][node.name]):
if node['nt'] == 'FNCALL' and 'NewName' in self.symtab[0][node['name']]: return self.symtab[node.scope][node.name]['NewName']
return self.symtab[0][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 # node is a name
if 'NewName' in self.symtab[scope][node]: if 'NewName' in self.symtab[scope][node]:
return self.symtab[scope][node]['NewName'] return self.symtab[scope][node]['NewName']
return node return node
def OutIndented(self, node): def OutIndented(self, node):
if node['nt'] != '{}': if node.nt != '{}':
self.indentlevel += 1 self.indentlevel += 1
ret = self.OutCode(node) ret = self.OutCode(node)
if node['nt'] != '{}': if node.nt != '{}':
self.indentlevel -= 1 self.indentlevel -= 1
return ret return ret
@ -210,14 +211,13 @@ class outscript(object):
def OutExpr(self, expr): def OutExpr(self, expr):
# Handles expression nodes (as opposed to statement nodes) # Handles expression nodes (as opposed to statement nodes)
nt = expr['nt'] nt = expr.nt
if 'ch' in expr: child = expr.ch
child = expr['ch']
if nt in self.binary_operands: if nt in self.binary_operands:
lnt = child[0]['nt'] lnt = child[0].nt
lparen = False lparen = False
rnt = child[1]['nt'] rnt = child[1].nt
rparen = False rparen = False
if nt in self.assignment_ops and nt in self.op_priority: if nt in self.assignment_ops and nt in self.op_priority:
# Assignment is right-associative, so it needs to be dealt with # Assignment is right-associative, so it needs to be dealt with
@ -274,16 +274,17 @@ class outscript(object):
return self.FindName(expr) return self.FindName(expr)
if nt == 'CONST': if nt == 'CONST':
if self.foldconst and expr['t'] == 'list' and len(expr['value'])==1 and not self.globalmode: if (self.foldconst and expr.t == 'list' and len(expr.value) == 1
return '(list)' + self.Value2LSL(expr['value'][0]) and not self.globalmode):
return self.Value2LSL(expr['value']) 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: 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] 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'): '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)
return ret + '(' + self.OutExpr(expr) + ')' return ret + '(' + self.OutExpr(expr) + ')'
@ -309,7 +310,7 @@ class outscript(object):
+ self.OutExpr(child[1]) + ',') + self.OutExpr(child[1]) + ',')
if nt == 'ROTATION': if nt == 'ROTATION':
ret += self.OutExpr(child[2]) + ',' ret += self.OutExpr(child[2]) + ','
lnt = child[-1]['nt'] lnt = child[-1].nt
if lnt in self.op_priority \ if lnt in self.op_priority \
and self.op_priority[lnt] <= self.op_priority['>']: and self.op_priority[lnt] <= self.op_priority['>']:
ret += '(' + self.OutExpr(child[-1]) + ')' ret += '(' + self.OutExpr(child[-1]) + ')'
@ -325,22 +326,22 @@ class outscript(object):
if nt in self.unary_operands: if nt in self.unary_operands:
ret = nt ret = nt
lnt = child[0]['nt'] lnt = child[0].nt
paren = False paren = False
if nt == 'NEG': if nt == 'NEG':
ret = '-' ret = '-'
if (lnt == 'CONST' and child[0]['t'] == 'integer' if (lnt == 'CONST' and child[0].t == 'integer'
and child[0]['value'] < 0 and child[0].value < 0
): ):
# shortcut # shortcut
ret += str(child[0]['value'] + 4294967296) ret += str(child[0].value + 4294967296)
return ret return ret
if lnt in self.op_priority: if lnt in self.op_priority:
paren = self.op_priority[lnt] <= self.op_priority['-'] paren = self.op_priority[lnt] <= self.op_priority['-']
elif (lnt == 'NEG' or lnt == '--V' elif (lnt == 'NEG' or lnt == '--V'
or lnt == 'CONST' or lnt == 'CONST'
and child[0]['t'] == 'float' and child[0].t == 'float'
and child[0]['value'] < 0 and child[0].value < 0
): ):
ret += ' ' # don't output "--" as that's a different token ret += ' ' # don't output "--" as that's a different token
else: else:
@ -354,7 +355,7 @@ class outscript(object):
return ret return ret
if nt == 'FLD': if nt == 'FLD':
return self.OutExpr(child[0]) + '.' + expr['fld'] return self.OutExpr(child[0]) + '.' + expr.fld
if nt in ('V--', 'V++'): if nt in ('V--', 'V++'):
return self.OutExpr(child[0]) + ('++' if nt == 'V++' else '--') 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 assert False, 'Internal error: expression type "' + nt + '" not handled' # pragma: no cover
def OutCode(self, node): def OutCode(self, node):
nt = node['nt'] nt = node.nt
if 'ch' in node: child = node.ch
child = node['ch']
else:
child = None
if nt == 'IF': if nt == 'IF':
ret = self.dent() ret = self.dent()
@ -390,9 +388,9 @@ class outscript(object):
if len(child) == 3: if len(child) == 3:
testnode = child[1] testnode = child[1]
# Find last IF in an ELSE IF chain # Find last IF in an ELSE IF chain
while testnode['nt'] == 'IF' and len(testnode['ch']) == 3: while testnode.nt == 'IF' and len(testnode.ch) == 3:
testnode = testnode['ch'][2] testnode = testnode.ch[2]
if testnode['nt'] == 'IF': if testnode.nt == 'IF':
# hit an IF without ELSE at the end of the chain # hit an IF without ELSE at the end of the chain
needs_braces = True needs_braces = True
if needs_braces: if needs_braces:
@ -403,12 +401,12 @@ class outscript(object):
ret += self.OutIndented(child[1]) ret += self.OutIndented(child[1])
if len(child) < 3: if len(child) < 3:
return ret return ret
if child[2]['nt'] != 'IF': if child[2].nt != 'IF':
ret += self.dent() + 'else\n' + self.OutIndented(child[2]) ret += self.dent() + 'else\n' + self.OutIndented(child[2])
return ret return ret
ret += self.dent() + 'else ' ret += self.dent() + 'else '
node = child[2] node = child[2]
child = node['ch'] child = node.ch
if nt == 'WHILE': if nt == 'WHILE':
ret = self.dent() + 'while (' + self.OutExpr(child[0]) + ')\n' ret = self.dent() + 'while (' + self.OutExpr(child[0]) + ')\n'
ret += self.OutIndented(child[1]) 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 ' + self.OutExpr(child[0]) + ';\n'
return self.dent() + 'return;\n' return self.dent() + 'return;\n'
if nt == 'DECL': if nt == 'DECL':
ret = self.dent() + node['t'] + ' ' + self.FindName(node) ret = self.dent() + node.t + ' ' + self.FindName(node)
if child: if child:
if 'orig' in child[0] and (child[0]['orig']['nt'] != 'IDENT' if hasattr(child[0], 'orig') and (child[0].orig.nt != 'IDENT'
or child[0]['orig']['name'] or child[0].orig.name
in self.symtab[child[0]['orig']['scope']]): in self.symtab[child[0].orig.scope]):
ret += ' = ' + self.OutExpr(child[0]['orig']) ret += ' = ' + self.OutExpr(child[0].orig)
else: else:
ret += ' = ' + self.OutExpr(child[0]) ret += ' = ' + self.OutExpr(child[0])
return ret + ';\n' return ret + ';\n'
@ -451,26 +449,26 @@ class outscript(object):
if nt in ('STDEF', '{}'): if nt in ('STDEF', '{}'):
ret = '' ret = ''
if nt == 'STDEF': if nt == 'STDEF':
if node['name'] == 'default': if node.name == 'default':
ret = self.dent() + 'default\n' ret = self.dent() + 'default\n'
else: else:
ret = self.dent() + 'state ' + self.FindName(node) + '\n' ret = self.dent() + 'state ' + self.FindName(node) + '\n'
ret += self.dent() + '{\n' ret += self.dent() + '{\n'
self.indentlevel += 1 self.indentlevel += 1
for stmt in node['ch']: for stmt in node.ch:
ret += self.OutCode(stmt) ret += self.OutCode(stmt)
self.indentlevel -= 1 self.indentlevel -= 1
return ret + self.dent() + '}\n' return ret + self.dent() + '}\n'
if nt == 'FNDEF': if nt == 'FNDEF':
ret = self.dent() ret = self.dent()
if node['t'] is not None: if node.t is not None:
ret += node['t'] + ' ' ret += node.t + ' '
ret += self.FindName(node) + '(' ret += self.FindName(node) + '('
scope = node['pscope'] scope = node.pscope
ret += ', '.join(typ + ' ' + self.FindName(name, scope) 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]) return ret + ')\n' + self.OutCode(child[0])
if nt == 'EXPR': if nt == 'EXPR':
@ -502,7 +500,7 @@ class outscript(object):
self.globalmode = False self.globalmode = False
self.listmode = False self.listmode = False
for node in self.tree: for node in self.tree:
self.globalmode = node['nt'] == 'DECL' self.globalmode = node.nt == 'DECL'
ret += self.OutCode(node) ret += self.OutCode(node)
self.globalmode = False self.globalmode = False

File diff suppressed because it is too large Load diff

View file

@ -114,9 +114,9 @@ class renamer(object):
name = entry['NewName'] = self.GetNextShortest() name = entry['NewName'] = self.GetNextShortest()
stateNames.append(name) stateNames.append(name)
# Find also the event names it uses, to add them for reuse. # Find also the event names it uses, to add them for reuse.
for node in self.tree[entry['Loc']]['ch']: for node in self.tree[entry['Loc']].ch:
assert node['nt'] == 'FNDEF' assert node.nt == 'FNDEF'
event_name = node['name'] event_name = node.name
# These events have their names translated. # These events have their names translated.
if event_name == 'on_rez': if event_name == 'on_rez':
event_name = 'rez' event_name = 'rez'