Turned everything upside down, and fixed a couple bugs.

Bugs fixed:
- %= and the new assignment operators were not emitting error on invalid types.
- List globals referenced in another global were duplicated entirely.
- Properly recognize -option in the command line.

Rest:
- Complete overhaul of the internal data structure.
  - Got rid of the symbol table plus mini-trees, and made everything one big tree plus an auxiliary symbol table.
  - No more special case hacks like using tuples instead of lists...
  - Got rid of the EXPR hack.
  - Dict-based, rather than list-based. Allows adding arbitrary data to any node or symbol entry.
- Added a few coverage tests for the new code.
- Return values can now be chained; the functions parameter requirement is gone. Still not fully convinced, though. My guess is that a parser object should be passed between functions instead. Will do for now.
This commit is contained in:
Sei Lisa 2014-07-30 04:54:16 +02:00
parent 5d4abf967d
commit fb68273eed
5 changed files with 691 additions and 734 deletions

View file

@ -1,11 +1,15 @@
import lslfuncs import lslfuncs
from lslparse import S, warning from lslparse import warning
CONSTANT = S['CONSTANT']
class optimizer(object): class optimizer(object):
# Default values per type when declaring variables
DefaultValues = {'integer': 0, 'float': 0.0, 'string': u'',
'key': lslfuncs.Key(u''), 'vector': lslfuncs.ZERO_VECTOR,
'rotation': lslfuncs.ZERO_ROTATION, 'list': []
}
# explicitly exclude assignments # explicitly exclude assignments
binary_ops = frozenset(('+','-','*','/','%','<<','>>','<','<=','>','>=', binary_ops = frozenset(('+','-','*','/','%','<<','>>','<','<=','>','>=',
'==','!=','|','^','&','||','&&')) '==','!=','|','^','&','||','&&'))
@ -17,91 +21,93 @@ class optimizer(object):
def FoldAndRemoveEmptyStmts(self, lst): def FoldAndRemoveEmptyStmts(self, lst):
"""Utility function for elimination of useless expressions in FOR""" """Utility function for elimination of useless expressions in FOR"""
x = 0 idx = 0
while x < len(lst): while idx < len(lst):
self.FoldTree(lst[x]) self.FoldTree(lst, idx)
self.FoldStmt(lst[x]) self.FoldStmt(lst, idx)
# If eliminated, it must be totally removed. A ';' won't do. # If eliminated, it must be totally removed. A ';' won't do.
if lst[x][0] == ';': if lst[idx]['node'] == ';':
del lst[x] del lst[idx]
else: else:
x += 1 idx += 1
def FoldStmt(self, code): def FoldStmt(self, parent, index):
"""If the statement is a constant or an identifier, remove it as it does """If the statement is a constant or an identifier, remove it as it does
nothing. nothing.
""" """
# Ideally this should consider side effect analysis of the whole thing. # Ideally this should consider side effect analysis of the whole thing.
if code[0] in (CONSTANT, 'IDENT', 'FIELD'): if parent[index]['node'] in ('CONST', 'IDENT', 'FIELD'):
code[:] = [S[';'], None] parent[index] = {'node':';','type':None}
else:
code[:] = code
def FoldTree(self, code): def FoldTree(self, parent, index):
"""Recursively traverse the tree to fold constants, changing it in """Recursively traverse the tree to fold constants, changing it in
place. place.
Also optimizes away IF, WHILE, etc. Also optimizes away IF, WHILE, etc.
""" """
while code[0] == 'EXPR': code = parent[index]
if type(code) == tuple: if code is None: return # Deleted statement
# just enter node = code['node']
code = code[2] child = code['br'] if 'br' in code else None
else:
# unfold
code[:] = code[2]
code0 = code[0] if node == 'CONST':
if code0 == CONSTANT:
# Job already done # Job already done
return return
if code0 == 'CAST': if node == 'CAST':
self.FoldTree(code[2]) self.FoldTree(child, 0)
if code[2][0] == CONSTANT: if child[0]['node'] == 'CONST':
# Enable key constants. We'll typecast them back on output, but # Enable key constants. We'll typecast them back on output, but
# this enables some optimizations. # this enables some optimizations.
#if code[1] != 'key': # key constants not possible #if code['type'] != 'key': # key constants not possible
code[:] = [CONSTANT, code[1], lslfuncs.typecast(code[2][2], self.LSL2PythonType[code[1]])] parent[index] = {'node':'CONST', 'type':code['type'],
'value':lslfuncs.typecast(
child[0]['value'], self.LSL2PythonType[code['type']])}
return return
if code0 == 'NEG': if node == 'NEG':
self.FoldTree(code[2]) self.FoldTree(child, 0)
if code[2][0] == CONSTANT: if child[0]['node'] == 'CONST':
code[:] = [CONSTANT, code[1], lslfuncs.neg(code[2][2])] code = parent[index] = child[0]
code['value'] = lslfuncs.neg(code['value'])
return return
if code0 == '!': if node == '!':
self.FoldTree(code[2]) self.FoldTree(child, 0)
if code[2][0] == CONSTANT: if child[0]['node'] == 'CONST':
code[:] = [CONSTANT, code[1], int(not code[2][2])] code = parent[index] = child[0]
code['value'] = int(not code['value'])
return return
if code0 == '~': if node == '~':
self.FoldTree(code[2]) self.FoldTree(child, 0)
if code[2][0] == CONSTANT: if child[0]['node'] == 'CONST':
code[:] = [CONSTANT, code[1], ~code[2][2]] code = parent[index] = child[0]
code['value'] = ~code['value']
return return
if code0 == '()': if node == '()':
self.FoldTree(code[2]) self.FoldTree(child, 0)
if code[2][0] in (CONSTANT, 'VECTOR', 'ROTATION', 'LIST', if child[0]['node'] in ('CONST', 'VECTOR', 'ROTATION', 'LIST',
'IDENT', 'FIELD', 'V++', 'V--', 'FUNCTION', 'PRINT'): 'IDENT', 'FIELD', 'V++', 'V--', 'FUNCTION', 'PRINT'):
# Child is an unary postfix expression; parentheses can be # Child is an unary postfix expression; parentheses are
# removed safely. # redundant and can be removed safely. Not strictly an
code[:] = code[2] # optimization but it helps keep the output tidy-ish a bit.
# It's not done in general (e.g. (a * b) + c does not need
# parentheses but these are not eliminated). Only the cases
# like (myvar) are simplified.
parent[index] = child[0]
return return
if code0 in self.binary_ops: if node in self.binary_ops:
# RTL evaluation # RTL evaluation
self.FoldTree(code[3]) self.FoldTree(child, 1)
self.FoldTree(code[2]) self.FoldTree(child, 0)
if code[2][0] == code[3][0] == CONSTANT: if child[0]['node'] == child[1]['node'] == 'CONST':
op = code0 op = node
op1 = code[2][2] op1 = child[0]['value']
op2 = code[3][2] op2 = child[1]['value']
if op == '+': if op == '+':
result = lslfuncs.add(op1, op2) result = lslfuncs.add(op1, op2)
elif op == '-': elif op == '-':
@ -117,7 +123,7 @@ class optimizer(object):
elif op == '>>': elif op == '>>':
result = lslfuncs.S32(op1 >> (op2 & 31)) result = lslfuncs.S32(op1 >> (op2 & 31))
elif op == '==' or op == '!=': elif op == '==' or op == '!=':
result = lslfuncs.compare(op1, op2, op == '==') result = lslfuncs.compare(op1, op2, Eq = (op == '=='))
elif op in ('<', '<=', '>', '>='): elif op in ('<', '<=', '>', '>='):
if op in ('>', '<='): if op in ('>', '<='):
result = lslfuncs.less(op2, op1) result = lslfuncs.less(op2, op1)
@ -137,25 +143,33 @@ class optimizer(object):
result = int(op1 and op2) result = int(op1 and op2)
else: else:
raise Exception(u'Internal error: Operator not found: ' + op.decode('utf8')) # pragma: no cover raise Exception(u'Internal error: Operator not found: ' + op.decode('utf8')) # pragma: no cover
code[:] = [CONSTANT, code[1], result] parent[index] = {'node':'CONST', 'type':code['type'], 'value':result}
elif code[0] == '-' and code[2][1] in ('integer', 'float') and code[3][1] in ('integer', 'float'): elif node == '-' and child[0]['type'] in ('integer', 'float') \
and child[1]['type'] in ('integer', 'float'):
# Change - to + - for int/float # Change - to + - for int/float
if code[3][0] == CONSTANT: if child[1]['node'] == 'CONST':
if code[3][2] == 0: if child[1]['value'] == 0:
code[:] = code[2] parent[index] = child[0]
else: else:
code[0] = S['+'] code['node'] = '+'
code[3][2] = lslfuncs.neg(code[3][2]) child[1]['value'] = lslfuncs.neg(child[1]['value'])
#TODO: Implement to transform 0-x into -x: elif child[0]['node'] == 'CONST':
else: else:
code[:] = [S['+'], code[1], code[2], [S['NEG'], code[3][1], code[3]]] code['node'] = '+'
elif code[0] == '<<' and code[3][0] == CONSTANT: child[1] = {'node':'NEG', 'type':child[1]['type'], 'br':[child[1]]}
elif node == '<<' and child[1]['node'] == 'CONST':
# Transforming << into multiply saves some bytes. # Transforming << into multiply saves some bytes.
if code[2][0] in ('+', '-', 'NEG'): # operands with priority between * and << if child[1]['value'] & 31:
code[2] = [S['()'], code[2][1], code[2]] # x << 3 --> x * 8
if not (code[3][2] & 31): # Do we need parentheses for *? It depends on x
code[:] = code[2] # e.g. x+3<<3 needs parentheses when converted to (x+3)*8
else: if child[0]['node'] in ('+', '-', 'NEG'): # operands with priority between * and << #TODO: CHECK
code[:] = [S['*'], code[1], code[2], [CONSTANT, 'integer', 1<<(code[3][2] & 31)]] child[0] = {'node':'()', 'type':child[0]['type'], 'br':[child[0]]}
# we have {<<, something, {CONST n}}, transform into {*, something, {CONST n}}
code['node'] = '*'
child[1]['value'] = 1<<(child[1]['value'] & 31)
else: # x << 0 --> x
parent[index] = child[0]
else: else:
pass # TODO: Eliminate redundancy (x+0, x*1, x*-1, v+ZERO_VECTOR, perhaps x-1=~-x, etc.) pass # TODO: Eliminate redundancy (x+0, x*1, x*-1, v+ZERO_VECTOR, perhaps x-1=~-x, etc.)
# Include != to ^ and || to | and maybe && to & # Include != to ^ and || to | and maybe && to &
@ -165,205 +179,195 @@ class optimizer(object):
# Maybe turn != -1 into ~ in if()'s. # Maybe turn != -1 into ~ in if()'s.
return return
if code0 in self.assign_ops: if node in self.assign_ops:
# TODO: Eliminate redundant operations, e.g. a += 0; etc. # TODO: Eliminate redundant operations, e.g. a += 0; etc.
# Consider also e.g. x -= 1 or x -= a transforming it into +=. # Consider also e.g. x -= 1 or x -= a transforming it into +=.
self.FoldTree(code[3]) # Actually just consider transforming the whole thing into a
# regular assignment, as there are no gains and it simplifies the
# optimization.
self.FoldTree(child, 1)
return return
if code0 == 'IDENT': if node == 'IDENT' or node == 'FLD':
if self.globalmode: if self.globalmode:
val = self.symtab[code[3]][code[2]][2] ident = code if node == 'IDENT' else child[0]
if val is not None: # Resolve constant values so they can be optimized
if type(val) == tuple: sym = self.symtab[ident['scope']][ident['name']]
# Infinite recursion is prevented at the parser level, by
# not allowing forward globals in global var definitions. defn = self.tree[sym['Loc']]
self.FoldTree(val) assert defn['name'] == ident['name']
if val[0] != 'EXPR' or val[2][0] != CONSTANT:
return # Assume we already were there
val = val[2][2] if 'br' in defn:
if code[1] != 'key' and val is not None: val = defn['br'][0]
code[:] = [CONSTANT, code[1], val] if val['node'] != 'CONST' or ident['type'] in ('list', 'key'):
return
else:
val = {'node':'CONST', 'type':defn['type'],
'value':self.DefaultValues[defn['type']]}
if node == 'FLD':
val = {'node':'CONST', 'type':'float',
'value':val['value']['xyzs'.index(code['fld'])]}
parent[index] = val
return return
if code0 == 'FUNCTION': if node == 'FNCALL':
for x in code[3][::-1]: for idx in xrange(len(child)-1, -1, -1):
self.FoldTree(x) self.FoldTree(child, idx)
if code[2] in self.functions and self.functions[code[2]][2] is not None: if code['name'] in self.symtab[0]:
for x in code[3]: fn = self.symtab[0][code['name']]['Loc']
if x[0] != CONSTANT: if fn is not None and type(fn) != int and all(arg['node'] == 'CONST' for arg in child):
break
else:
# Call it # Call it
val = self.functions[code[2]][2](*tuple(x[2] for x in code[3])) value = fn(*tuple(arg['value'] for arg in child))
if not self.foldtabs and isinstance(val, unicode) and '\t' in val: if not self.foldtabs and isinstance(value, unicode) and '\t' in value:
warning('WARNING: Tab in function result and foldtabs option not used.') warning('WARNING: Tab in function result and foldtabs option not used.')
return return
code[:] = [CONSTANT, code[1], val] parent[index] = {'node':'CONST', 'type':code['type'], 'value':value}
return return
if code0 == 'PRINT': if node == 'PRINT':
# useless but who knows # useless but who knows
self.FoldTree(code[2]) self.FoldTree(child, 0)
return return
if code0 in ('VECTOR', 'ROTATION', 'LIST'): if node in ('VECTOR', 'ROTATION', 'LIST'):
isconst = True isconst = True
for x in code[:1:-1]: for idx in xrange(len(child)-1, -1, -1):
self.FoldTree(x) self.FoldTree(child, idx)
if x[0] != CONSTANT: if child[idx]['node'] != 'CONST':
isconst = False isconst = False
if isconst: if isconst:
value = [x[2] for x in code[2:]] value = [elem['value'] for elem in child]
if code0 == 'VECTOR': if node == 'VECTOR':
value = lslfuncs.Vector([lslfuncs.ff(x) for x in value]) value = lslfuncs.Vector([lslfuncs.ff(x) for x in value])
elif code0 == 'ROTATION': elif node == 'ROTATION':
value = lslfuncs.Quaternion([lslfuncs.ff(x) for x in value]) value = lslfuncs.Quaternion([lslfuncs.ff(x) for x in value])
code[:] = [CONSTANT, code[1], value] parent[index] = {'node':'CONST', 'type':code['type'], 'value':value}
return return
if code0 == 'FIELD': if node in ('{}', 'FNDEF', 'STATEDEF'):
if self.globalmode: for idx in xrange(len(child)):
# We can fold a global vector or rotation field as they are self.FoldTree(child, idx)
# constant, but that involves resolving the symbols that aren't self.FoldStmt(child, idx)
# already.
assert code[2][0] == 'IDENT' # that should be granted
glob = self.symtab[code[2][3]][code[2][2]]
origin = glob[2]
if type(origin) == tuple:
# We have to do this due to not processing globals in order.
self.FoldTree(origin)
# Unfold constant expression
if origin[0] != 'EXPR' or origin[2][0] != CONSTANT:
return
origin = origin[2][2]
self.symtab[code[2][3]][code[2][2]] = glob[:2] + (origin,) + glob[3:]
if type(origin) not in (lslfuncs.Vector, lslfuncs.Quaternion):
# Precondition not met
return # pragma: no cover
code[:] = [CONSTANT, 'float', lslfuncs.ff(origin['xyzs'.index(code[3])])]
return return
if code0 == '{}': if node == 'IF':
for x in code[2:]: self.FoldTree(child, 0)
self.FoldTree(x) if child[0]['node'] == 'CONST':
self.FoldStmt(x)
return
if code0 == 'IF':
self.FoldTree(code[2])
if code[2][0] == CONSTANT:
# We can remove one of the branches safely. # We can remove one of the branches safely.
if lslfuncs.cond(code[2][2]): if lslfuncs.cond(child[0]['value']):
self.FoldTree(code[3]) self.FoldTree(child, 1)
code[:] = code[3] parent[index] = child[1]
self.FoldStmt(code) self.FoldStmt(child, 1)
elif len(code) > 4: elif len(child) > 2:
self.FoldTree(code[4]) self.FoldTree(child, 2)
code[:] = code[4] parent[index] = child[2]
self.FoldStmt(code) self.FoldStmt(child, 2)
else: else:
# No ELSE branch, replace the statement with an empty one. # No ELSE branch, replace the statement with an empty one.
code[:] = [S[';'], None] parent[index] = {'node':';', 'type':None}
else: else:
self.FoldTree(code[3]) self.FoldTree(child, 1)
self.FoldStmt(code[3]) self.FoldStmt(child, 1)
if len(code) > 4: if len(child) > 2:
self.FoldTree(code[4]) self.FoldTree(child, 2)
self.FoldStmt(code[4]) self.FoldStmt(child, 2)
return return
if code0 == 'WHILE': if node == 'WHILE':
self.FoldTree(code[2]) self.FoldTree(child, 0)
if code[2][0] == CONSTANT: if child[0]['node'] == 'CONST':
# See if the whole WHILE can be eliminated. # See if the whole WHILE can be eliminated.
if lslfuncs.cond(code[2][2]): if lslfuncs.cond(child[0]['value']):
# Endless loop which must be kept. # Endless loop which must be kept.
# First, replace the constant. # First, replace the constant.
code[2][1:2] = [S['integer'], 1] child[0].update({'type':'integer', 'value':1})
# Recurse on the statement. # Recurse on the statement.
self.FoldTree(code[3]) self.FoldTree(child, 1)
self.FoldStmt(code[3]) self.FoldStmt(child, 1)
else: else:
# Can be removed. # Can be removed.
code[:] = [S[';'], None] parent[index] = {'node':';', 'type':None}
else: else:
self.FoldTree(code[3]) self.FoldTree(child, 1)
self.FoldStmt(code[3]) self.FoldStmt(child, 1)
return return
if code0 == 'DO': if node == 'DO':
self.FoldTree(code[2]) # This one is always executed. self.FoldTree(child, 0) # This one is always executed.
self.FoldStmt(code[2]) self.FoldStmt(child, 0)
self.FoldTree(code[3]) self.FoldTree(child, 1)
# See if the latest part is a constant. # See if the latest part is a constant.
if code[3][0] == CONSTANT: if child[1]['node'] == 'CONST':
if lslfuncs.cond(code[3][2]): if lslfuncs.cond(child[1]['value']):
# Endless loop. Replace the constant. # Endless loop. Replace the constant.
code[3][1:2] = [S['integer'], 1] child[1].update({'type':'integer', 'value':1})
else: else:
# Only one go. Replace with the statement(s). # Only one go. Replace with the statement(s).
code[:] = code[2] parent[index] = child[0]
return return
if code0 == 'FOR': if node == 'FOR':
self.FoldAndRemoveEmptyStmts(code[2]) assert child[0]['node'] == 'EXPRLIST'
assert child[2]['node'] == 'EXPRLIST'
self.FoldAndRemoveEmptyStmts(child[0]['br'])
self.FoldTree(code[3]) # Condition. self.FoldTree(child, 1) # Condition.
if code[3][0] == CONSTANT: if child[1]['node'] == 'CONST':
# FOR is delicate. It can have multiple expressions at start. # FOR is delicate. It can have multiple expressions at start.
# And if there is more than one, these expressions will need a # And if there is more than one, these expressions will need a
# new block, which means new scope, which is dangerous. # new block, which means new scope, which is dangerous.
# They are expressions, no declarations or labels allowed, but # They are expressions, no declarations or labels allowed, but
# it feels creepy. # it feels creepy.
if lslfuncs.cond(code[3][2]): if lslfuncs.cond(child[1]['value']):
# Endless loop. Just replace the constant and traverse the rest. # Endless loop. Just replace the constant and traverse the rest.
code[3][1:2] = [S['integer'], 1] child[1].update({'type':'integer', 'value':1})
self.FoldAndRemoveEmptyStmts(code[4]) self.FoldAndRemoveEmptyStmts(child[2]['br'])
self.FoldTree(code[5]) self.FoldTree(child, 3)
self.FoldStmt(code[5]) self.FoldStmt(child, 3)
elif len(code[2]) > 1: elif len(child[0]['br']) > 1:
code[:] = [S['{}'], None] + code[2] parent[index] = {'node':'{}', 'type':None, 'br':child[0]['br']}
elif code[2]: elif child[0]['br']:
code[:] = code[2][0] parent[index] = child[0]['br'][0]
else: else:
code[:] = [S[';'], None] parent[index] = {'node':';', 'type':None}
else: else:
self.FoldAndRemoveEmptyStmts(code[4]) self.FoldAndRemoveEmptyStmts(child[2]['br'])
self.FoldTree(code[5]) self.FoldTree(child, 3)
self.FoldStmt(code[5]) self.FoldStmt(child, 3)
return return
if code0 == 'RETURN': if node == 'RETURN':
if code[2] is not None: if child:
self.FoldTree(code[2]) self.FoldTree(child, 0)
return return
if code0 == 'DECL': if node == 'DECL':
# The expression code is elsewhere. # The expression code is elsewhere.
expr = self.symtab[code[3]][code[2]][2] if child:
# Irrelevant if list or string or key. self.FoldTree(child, 0)
if expr is not None:
self.FoldTree(expr)
# TODO: Remove assignment if integer zero. # TODO: Remove assignment if integer zero.
else: else:
# TODO: Add assignment if vector, rotation or float. # TODO: Add assignment if vector, rotation or float.
pass pass
return return
if code0 in self.ignored_stmts: if node in self.ignored_stmts:
return return
raise Exception('Internal error: This should not happen, node = ' + code0) # pragma: no cover raise Exception('Internal error: This should not happen, node = ' + node) # pragma: no cover
def IsValidGlobalConstant(self, value): def IsValidGlobalConstant(self, decl):
if value[0] == 'EXPR': if 'br' not in decl:
value = value[2] return True
if value[0] not in ('VECTOR', 'ROTATION', 'LIST'): expr = decl['br'][0]
if expr['node'] in ('CONST', 'IDENT'):
return True
if expr['node'] not in ('VECTOR', 'ROTATION', 'LIST'):
return False return False
return all(x[0] in (CONSTANT, 'IDENT') for x in value[2:]) return all(elem['node'] in ('CONST', 'IDENT') for elem in expr['br'])
def optimize(self, symtab, functions, options = ('optimize',)): def optimize(self, treesymtab, options = ('optimize',)):
"""Optimize the symbolic table symtab in place. Requires a table of """Optimize the symbolic table symtab in place. Requires a table of
predefined functions for folding constants. predefined functions for folding constants.
""" """
@ -375,27 +379,22 @@ class optimizer(object):
# TODO: Add option to handle local jumps properly. # TODO: Add option to handle local jumps properly.
self.functions = functions tree, symtab = self.tree, self.symtab = treesymtab
self.symtab = symtab
self.globalmode = False self.globalmode = False
# Fold constants etc. # Constant folding pass. It does some other optimizations along the way.
for name in symtab[0]: for idx in xrange(len(tree)):
if name == -1: if tree[idx]['node'] == 'DECL':
continue self.globalmode = True
entry = symtab[0][name] self.FoldTree(tree, idx)
if entry[1] == 'State':
for event in entry[2]:
self.FoldTree(entry[2][event][2])
elif type(entry[2]) == tuple:
self.globalmode = len(entry) == 3
self.FoldTree(entry[2]) # global
if self.globalmode:
val = entry[2]
# Unfold constant
if val[0] == 'EXPR' and val[2][0] == CONSTANT:
symtab[0][name] = entry[:2] + (val[2][2],) + entry[3:]
elif not self.IsValidGlobalConstant(val):
warning('WARNING: Expression does not collapse to a single constant.')
self.globalmode = False self.globalmode = False
if not self.IsValidGlobalConstant(tree[idx]):
warning('WARNING: Expression does not collapse to a single constant.')
else:
self.FoldTree(tree, idx)
treesymtab = (self.tree, self.symtab)
del self.tree
del self.symtab
return treesymtab

View file

@ -77,7 +77,7 @@ class outscript(object):
neg = '-' neg = '-'
# Try harder # Try harder
point = news.index('.') + 1 - len(news) # Remove point point = news.index('.') + 1 - len(news) # Remove point
news = str(int(news[:point-1] + news[point:]) + 1) # Increment news = str(int(news[:point-1] + news[point:]) + 1).zfill(len(news)-1) # Increment
news = news[:point + len(news)] + '.' + news[point + len(news):] # Reinsert point news = news[:point + len(news)] + '.' + news[point + len(news):] # Reinsert point
# Repeat the operation with the incremented number # Repeat the operation with the incremented number
while news[-1] != '.' and lslfuncs.F32(float(neg+news[:-1]+exp)) == value: while news[-1] != '.' and lslfuncs.F32(float(neg+news[:-1]+exp)) == value:
@ -129,230 +129,190 @@ class outscript(object):
self.indentlevel -= 1 self.indentlevel -= 1
return ret + self.dent() + self.indent + ']' return ret + self.dent() + self.indent + ']'
if tvalue == tuple and value[0] == 'IDENT': # HACK
return value[2]
assert False, u'Value of unknown type in Value2LSL: ' + repr(value) assert False, u'Value of unknown type in Value2LSL: ' + repr(value)
def dent(self): def dent(self):
return self.indent * self.indentlevel return self.indent * self.indentlevel
def OutIndented(self, code): def OutIndented(self, code):
if code[0] != '{}': if code['node'] != '{}':
self.indentlevel += 1 self.indentlevel += 1
ret = self.OutCode(code) ret = self.OutCode(code)
if code[0] != '{}': if code['node'] != '{}':
self.indentlevel -= 1 self.indentlevel -= 1
return ret return ret
def OutExprList(self, L): def OutExprList(self, L):
ret = '' ret = ''
if L: if L:
First = True
for item in L: for item in L:
if ret != '': if not First:
ret += ', ' ret += ', '
ret += self.OutExpr(item) ret += self.OutExpr(item)
First = False
return ret return ret
def OutExpr(self, expr): def OutExpr(self, expr):
# Save some recursion by unwrapping the expression # Handles expression nodes (as opposed to statement nodes)
while expr[0] == 'EXPR': node = expr['node']
expr = expr[2] if 'br' in expr:
node = expr[0] child = expr['br']
if node == '()': if node == '()':
return '(' + self.OutExpr(expr[2]) + ')' return '(' + self.OutExpr(child[0]) + ')'
if node in self.binary_operands: if node in self.binary_operands:
return self.OutExpr(expr[2]) + ' ' + node + ' ' + self.OutExpr(expr[3]) return self.OutExpr(child[0]) + ' ' + node + ' ' + self.OutExpr(child[1])
if node == 'IDENT': if node == 'IDENT':
return expr[2] return expr['name']
if node == 'CONSTANT':
return self.Value2LSL(expr[2]) if node == 'CONST':
return self.Value2LSL(expr['value'])
if node == 'CAST': if node == 'CAST':
ret = '(' + expr[1] + ')' ret = '(' + expr['type'] + ')'
expr = expr[2] expr = child[0]
if expr[0] == 'EXPR': if expr['node'] in ('CONST', 'IDENT', 'V++', 'V--', 'VECTOR',
expr = expr[2] 'ROTATION', 'LIST', 'FIELD', 'PRINT', 'FUNCTION', '()'):
if expr[0] in ('CONSTANT', 'IDENT', 'V++', 'V--', 'VECTOR', return ret + self.OutExpr(expr)
'ROTATION', 'LIST', 'FIELD', 'PRINT', 'FUNCTION', '()'): return ret + '(' + self.OutExpr(expr) + ')'
ret += self.OutExpr(expr)
else:
ret += '(' + self.OutExpr(expr) + ')'
return ret
if node == 'LIST': if node == 'LIST':
if len(expr) == 2: self.listmode = True
return '[]' ret = '[' + self.OutExprList(child) + ']'
return '[' + self.OutExprList(expr[2:]) + ']' self.listmode = False
if node == 'VECTOR': return ret
return '<' + self.OutExpr(expr[2]) + ', ' + self.OutExpr(expr[3]) \
+ ', ' + self.OutExpr(expr[4]) + '>' if node in ('VECTOR', 'ROTATION'):
if node == 'ROTATION': return '<' + self.OutExprList(child) + '>'
return '<' + self.OutExpr(expr[2]) + ', ' + self.OutExpr(expr[3]) \
+ ', ' + self.OutExpr(expr[4]) + ', ' + self.OutExpr(expr[5]) + '>' if node == 'FNCALL':
if node == 'FUNCTION': return expr['name'] + '(' + self.OutExprList(child) + ')'
return expr[2] + '(' + self.OutExprList(expr[3]) + ')'
if node == 'PRINT': if node == 'PRINT':
return 'print(' + self.OutExpr(expr[2]) + ')' return 'print(' + self.OutExpr(child[0]) + ')'
if node in self.unary_operands: if node in self.unary_operands:
if node == 'NEG': if node == 'NEG':
node = '- ' node = '- '
return node + self.OutExpr(expr[2]) return node + self.OutExpr(child[0])
if node == 'FIELD': if node == 'FLD':
return self.OutExpr(expr[2]) + '.' + expr[3] return self.OutExpr(child[0]) + '.' + expr['fld']
if node in ('V--', 'V++'): if node in ('V--', 'V++'):
return self.OutExpr(expr[2]) + node[1:] return self.OutExpr(child[0]) + ('++' if node == 'V++' else '--')
if node in ('--V', '++V'): if node in ('--V', '++V'):
return node[:-1] + self.OutExpr(expr[2]) return ('++' if node == '++V' else '--') + self.OutExpr(child[0])
if node in self.extended_assignments: if node in self.extended_assignments:
op = self.OutExpr(expr[2]) lvalue = self.OutExpr(child[0])
return op + ' = ' + op + ' ' + node[:-1] + ' (' + self.OutExpr(expr[3]) + ')' return lvalue + ' = ' + lvalue + ' ' + node[:-1] + ' (' + self.OutExpr(child[1]) + ')'
if node == 'EXPRLIST':
return self.OutExprList(child)
assert False, 'Internal error: expression type "' + node + '" not handled' # pragma: no cover assert False, 'Internal error: expression type "' + node + '" not handled' # pragma: no cover
def OutCode(self, code): def OutCode(self, code):
#return self.dent() + '{\n' + self.dent() + '}\n' node = code['node']
node = code[0] if 'br' in code:
if node == '{}': child = code['br']
ret = self.dent() + '{\n' else:
self.indentlevel += 1 child = None
for stmt in code[2:]:
ret += self.OutCode(stmt)
self.indentlevel -= 1
return ret + self.dent() + '}\n'
if node == 'IF': if node == 'IF':
ret = self.dent() ret = self.dent()
while True: while True:
ret += 'if (' + self.OutExpr(code[2]) + ')\n' + self.OutIndented(code[3]) ret += 'if (' + self.OutExpr(child[0]) + ')\n' + self.OutIndented(child[1])
if len(code) < 5: if len(child) < 3:
return ret return ret
if code[4][0] != 'IF': if child[2]['node'] != 'IF':
ret += self.dent() + 'else\n' + self.OutIndented(code[4]) ret += self.dent() + 'else\n' + self.OutIndented(child[2])
return ret return ret
ret += self.dent() + 'else ' ret += self.dent() + 'else '
code = code[4] code = child[2]
child = code['br']
if node == 'WHILE': if node == 'WHILE':
ret = self.dent() + 'while (' + self.OutExpr(code[2]) + ')\n' ret = self.dent() + 'while (' + self.OutExpr(child[0]) + ')\n'
ret += self.OutIndented(code[3]) ret += self.OutIndented(child[1])
return ret return ret
if node == 'DO': if node == 'DO':
ret = self.dent() + 'do\n' ret = self.dent() + 'do\n'
ret += self.OutIndented(code[2]) ret += self.OutIndented(child[0])
return ret + self.dent() + 'while (' + self.OutExpr(code[3]) + ');\n' return ret + self.dent() + 'while (' + self.OutExpr(child[1]) + ');\n'
if node == 'FOR': if node == 'FOR':
ret = self.dent() + 'for (' ret = self.dent() + 'for ('
if code[2]: ret += self.OutExpr(child[0])
ret += self.OutExpr(code[2][0]) ret += '; ' + self.OutExpr(child[1]) + '; '
if len(code[2]) > 1: ret += self.OutExpr(child[2])
for expr in code[2][1:]:
ret += ', ' + self.OutExpr(expr)
ret += '; ' + self.OutExpr(code[3]) + '; '
if code[4]:
ret += self.OutExpr(code[4][0])
if len(code[4]) > 1:
for expr in code[4][1:]:
ret += ', ' + self.OutExpr(expr)
ret += ')\n' ret += ')\n'
ret += self.OutIndented(code[5]) ret += self.OutIndented(child[3])
return ret return ret
if node == '@': if node == '@':
return self.dent() + '@' + code[2] + ';\n' return self.dent() + '@' + code['name'] + ';\n'
if node == 'JUMP': if node == 'JUMP':
assert code[2][0:2] == ['IDENT', 'Label'] return self.dent() + 'jump ' + code['name'] + ';\n'
return self.dent() + 'jump ' + code[2][2] + ';\n'
if node == 'STATE': if node == 'STATE':
name = 'default' return self.dent() + 'state ' + code['name'] + ';\n'
if code[2] != 'DEFAULT':
assert code[2][0:2] == ['IDENT', 'State']
name = code[2][2]
return self.dent() + 'state ' + name + ';\n'
if node == 'RETURN': if node == 'RETURN':
if code[2] is None: if child:
return self.dent() + 'return;\n' return self.dent() + 'return ' + self.OutExpr(child[0]) + ';\n'
return self.dent() + 'return ' + self.OutExpr(code[2]) + ';\n' return self.dent() + 'return;\n'
if node == 'DECL': if node == 'DECL':
sym = self.symtab[code[3]][code[2]] ret = self.dent() + code['type'] + ' ' + code['name']
ret = self.dent() + sym[1] + ' ' + code[2] if child:
if sym[2] is not None: ret += ' = ' + self.OutExpr(child[0])
ret += ' = ' + self.OutExpr(sym[2])
return ret + ';\n' return ret + ';\n'
if node == ';': if node == ';':
return self.dent() + ';\n' return self.dent() + ';\n'
if node in ('STATEDEF', '{}'):
ret = ''
if node == 'STATEDEF':
if code['name'] == 'default':
ret = self.dent() + 'default\n'
else:
ret = self.dent() + 'state ' + code['name'] + '\n'
ret += self.dent() + '{\n'
self.indentlevel += 1
for stmt in code['br']:
ret += self.OutCode(stmt)
self.indentlevel -= 1
return ret + self.dent() + '}\n'
if node == 'FNDEF':
ret = self.dent()
if code['type'] is not None:
ret += code['type'] + ' '
ret += code['name'] + '('
ret += ', '.join(typ + ' ' + name for typ, name in zip(code['ptypes'], code['pnames']))
return ret + ')\n' + self.OutCode(child[0])
return self.dent() + self.OutExpr(code) + ';\n' return self.dent() + self.OutExpr(code) + ';\n'
def OutFunc(self, typ, name, paramlist, paramsymtab, code): def output(self, treesymtab, options = ('optsigns',)):
ret = self.dent()
if typ is not None:
ret += typ + ' '
ret += name + '('
first = True
if paramlist:
for name in paramlist:
if not first:
ret += ', '
ret += paramsymtab[name][1] + ' ' + name
first = False
return ret + ')\n' + self.OutCode(code)
def output(self, symtab, options = ('optimizesigns',)):
# Build a sorted list of dict entries # Build a sorted list of dict entries
order = [] self.tree, self.symtab = treesymtab
self.symtab = symtab
# Optimize signs # Optimize signs
self.optsigns = 'optimizesigns' in options self.optsigns = 'optsigns' in options
for i in symtab:
item = []
for j in sorted(i.items(), key=lambda k: -1 if k[0]==-1 else k[1][0]):
if j[0] != -1:
item.append(j[0])
order.append(item)
ret = '' ret = ''
self.indent = ' ' self.indent = ' '
self.indentlevel = 0 self.indentlevel = 0
self.globalmode = False self.globalmode = False
self.listmode = False self.listmode = False
for name in order[0]: for code in self.tree:
sym = symtab[0][name] if code['node'] == 'DECL':
ret += self.dent()
if sym[1] == 'State':
if name == 'default':
ret += 'default\n{\n'
else:
ret += 'state ' + name + '\n{\n'
self.indentlevel += 1
eventorder = []
for event in sorted(sym[2].items(), key=lambda k: k[1][0]):
eventorder.append(event[0])
for name in eventorder:
eventdef = sym[2][name]
ret += self.OutFunc(eventdef[1], name, eventdef[3], symtab[eventdef[4]], eventdef[2])
self.indentlevel -= 1
ret += self.dent() + '}\n'
elif len(sym) > 3: # function definition
ret += self.OutFunc(sym[1], name, sym[3], symtab[sym[4]], sym[2])
else: # global var
self.globalmode = True self.globalmode = True
ret += sym[1] + ' ' + name ret += self.OutCode(code)
if sym[2] is not None:
ret += ' = '
if type(sym[2]) == tuple:
ret += self.OutExpr(sym[2])
else:
ret += self.Value2LSL(sym[2])
ret += ';\n'
self.globalmode = False self.globalmode = False
else:
ret += self.OutCode(code)
return ret return ret

File diff suppressed because it is too large Load diff

14
main.py
View file

@ -64,7 +64,7 @@ means that e.g. a + 3 + 5 is not optimized to a + 8; however a + (3 + 5) is.
return 1 return 1
optchanges = sys.argv[2].split(',') optchanges = sys.argv[2].split(',')
for chg in optchanges: for chg in optchanges:
if chg[0:1] != '+': if chg[0:1] not in ('+', '-'):
chg = '+' + chg chg = '+' + chg
if chg[0] == '-': if chg[0] == '-':
options.discard(chg[1:]) options.discard(chg[1:])
@ -78,24 +78,22 @@ means that e.g. a + 3 + 5 is not optimized to a + 8; however a + (3 + 5) is.
try: try:
if fname == '-': if fname == '-':
script = sys.stdin.read() script = sys.stdin.read()
p.parse(script, options) ts = p.parse(script, options)
else: else:
p.parsefile(fname, options) ts = p.parsefile(fname, options)
funcs = p.functions
symtab = p.symtab
except EParse as e: except EParse as e:
print e.message print e.message
return 1 return 1
del p del p
opt = optimizer() opt = optimizer()
opt.optimize(symtab, funcs, options) ts = opt.optimize(ts, options)
del opt del opt
outs = outscript() outs = outscript()
script = outs.output(symtab, options) script = outs.output(ts, options)
del outs del outs
del symtab del ts
sys.stdout.write(script) sys.stdout.write(script)
return 0 return 0

View file

@ -1,6 +1,6 @@
from lslopt.lslparse import parser,EParseSyntax,EParseUEOF,EParseAlreadyDefined,\ from lslopt.lslparse import parser,EParseSyntax,EParseUEOF,EParseAlreadyDefined,\
EParseUndefined,EParseTypeMismatch,EParseReturnShouldBeEmpty,EParseReturnIsEmpty,\ EParseUndefined,EParseTypeMismatch,EParseReturnShouldBeEmpty,EParseReturnIsEmpty,\
EParseInvalidField,EParseFunctionMismatch,EParseDeclarationScope,EParseUnexpected,\ EParseInvalidField,EParseFunctionMismatch,EParseDeclarationScope,\
fieldpos fieldpos
from lslopt.lsloutput import outscript from lslopt.lsloutput import outscript
from lslopt.lsloptimizer import optimizer from lslopt.lsloptimizer import optimizer
@ -69,8 +69,8 @@ class Test02_Compiler(UnitTestCase):
float f; float f;
float ff = f; float ff = f;
list L = []; list L = [];
list L2 = [2,3,4,5,6]; list L2 = [2,3,4,5,-6];
list L3 = [2,3,f,5,6]; list L3 = [2,3,f,5,-6.0];
rotation QQ = <f,f,f,f>; rotation QQ = <f,f,f,f>;
integer fn(integer x){ integer fn(integer x){
if (1) for (f=3,f=4,f=5;3;f++,f++) do while(0); while(0); else if (2) return 2; else; if (1) for (f=3,f=4,f=5;3;f++,f++) do while(0); while(0); else if (2) return 2; else;
@ -88,7 +88,7 @@ class Test02_Compiler(UnitTestCase):
1e37;1.1e22;1.; 1e37;1.1e22;1.;
print(V *= 3); print(V *= 3);
fwd("","",""); fwd("","","");
L"\n\t\rxxxx"; L"\n\t\rxxxx";@lbl;jump lbl;
{f;} {f;}
[1,2,3]; [1,2,3];
} }
@ -159,6 +159,8 @@ class Test02_Compiler(UnitTestCase):
self.assertRaises(EParseTypeMismatch, self.parser.parse, '''f(){""%4;}''') self.assertRaises(EParseTypeMismatch, self.parser.parse, '''f(){""%4;}''')
self.assertRaises(EParseTypeMismatch, self.parser.parse, '''f(){3%<2,3,4>;}''') self.assertRaises(EParseTypeMismatch, self.parser.parse, '''f(){3%<2,3,4>;}''')
self.assertRaises(EParseTypeMismatch, self.parser.parse, '''f(){""%4;}''') self.assertRaises(EParseTypeMismatch, self.parser.parse, '''f(){""%4;}''')
self.assertRaises(EParseTypeMismatch, self.parser.parse, '''f(){float i;i%=2;}''')
self.assertRaises(EParseTypeMismatch, self.parser.parse, '''f(){float i;i&=2;}''', ['extendedassignment'])
self.assertRaises(EParseTypeMismatch, self.parser.parse, '''f(){(vector)4;}''') self.assertRaises(EParseTypeMismatch, self.parser.parse, '''f(){(vector)4;}''')
self.assertRaises(EParseTypeMismatch, self.parser.parse, '''f(){key k;k+=k;}''') self.assertRaises(EParseTypeMismatch, self.parser.parse, '''f(){key k;k+=k;}''')
self.assertRaises(EParseTypeMismatch, self.parser.parse, '''f(){string i;i++;}''') self.assertRaises(EParseTypeMismatch, self.parser.parse, '''f(){string i;i++;}''')
@ -213,7 +215,7 @@ class Test02_Compiler(UnitTestCase):
'skippreproc'] 'skippreproc']
)) ))
print self.parser.scopeindex print self.parser.scopeindex
self.assertRaises(EParseUnexpected, self.parser.PopScope) #self.assertRaises(EParseUnexpected, self.parser.PopScope)
self.assertEqual(fieldpos("a,b",",",3),-1) self.assertEqual(fieldpos("a,b",",",3),-1)
self.assertEqual(self.outscript.Value2LSL(lslfuncs.Key(u'')), '((key)"")') self.assertEqual(self.outscript.Value2LSL(lslfuncs.Key(u'')), '((key)"")')
@ -235,8 +237,11 @@ class Test03_Optimizer(UnitTestCase):
float g = f; float g = f;
string s = "1" "2"; string s = "1" "2";
list L = [(key)""]; list L = [(key)""];
list L1 = L;
list L2 = [1,2,3,4,5,6.0];
list L3 = [];
vector v=<1,2,f>; vector v=<1,2,f>;
float ffff2 = v.x; // This needs a bit of luck for coverage, as it's order-dependent. float ffff2 = v.x;
vector vvvv = <1,2,llGetNumberOfSides()>; vector vvvv = <1,2,llGetNumberOfSides()>;
float ffff=vvvv.x; float ffff=vvvv.x;
vector vvvv2=vvvv; vector vvvv2=vvvv;
@ -269,14 +274,14 @@ class Test03_Optimizer(UnitTestCase):
['explicitcast','extendedtypecast','extendedassignment', ['explicitcast','extendedtypecast','extendedassignment',
'extendedglobalexpr', 'allowmultistrings', 'allowkeyconcat'] 'extendedglobalexpr', 'allowmultistrings', 'allowkeyconcat']
) )
self.opt.optimize(p, self.parser.functions) self.opt.optimize(p)
self.opt.optimize(p, self.parser.functions, ()) self.opt.optimize(p, ())
print self.outscript.output(p) print self.outscript.output(p)
p = self.parser.parse('''string s = llUnescapeURL("%09");default{timer(){float f=llSqrt(-1);}}''', p = self.parser.parse('''string s = llUnescapeURL("%09");default{timer(){float f=llSqrt(-1);}}''',
['explicitcast','extendedtypecast','extendedassignment', ['explicitcast','extendedtypecast','extendedassignment',
'extendedglobalexpr', 'allowmultistrings', 'allowkeyconcat'] 'extendedglobalexpr', 'allowmultistrings', 'allowkeyconcat']
) )
self.opt.optimize(p, self.parser.functions, ['optimize','foldtabs']) self.opt.optimize(p, ['optimize','foldtabs'])
print self.outscript.output(p) print self.outscript.output(p)
def test_regression(self): def test_regression(self):
p = self.parser.parse(''' p = self.parser.parse('''
@ -284,7 +289,7 @@ class Test03_Optimizer(UnitTestCase):
x() { if (1) { string s = "x"; s = s + (string)a; } } x() { if (1) { string s = "x"; s = s + (string)a; } }
default { timer() { } } default { timer() { } }
''', ['extendedassignment']) ''', ['extendedassignment'])
self.opt.optimize(p, self.parser.functions) self.opt.optimize(p)
self.outscript.output(p) self.outscript.output(p)
p = self.parser.parse(''' p = self.parser.parse('''
key k = "blah"; key k = "blah";
@ -294,10 +299,11 @@ class Test03_Optimizer(UnitTestCase):
default{timer(){}} default{timer(){}}
''', ['extendedassignment']) ''', ['extendedassignment'])
self.opt.optimize(p, self.parser.functions) self.opt.optimize(p)
out = self.outscript.output(p) out = self.outscript.output(p)
print out
self.assertEqual(out, 'key k = "blah";\nlist L = [k, "xxxx", 1.];\n' self.assertEqual(out, 'key k = "blah";\nlist L = [k, "xxxx", 1.];\n'
'float f;\nvector v = <f, 3, 4>;\ndefault\n{\n timer()\n' 'float f;\nvector v = <0, 3, 4>;\ndefault\n{\n timer()\n'
' {\n }\n}\n') ' {\n }\n}\n')
def tearDown(self): def tearDown(self):