mirror of
https://github.com/Sei-Lisa/LSL-PyOptimizer
synced 2025-07-02 08:08:20 +00:00
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.
318 lines
12 KiB
Python
318 lines
12 KiB
Python
# Convert a symbol table (with parse tree) back to a script.
|
|
import lslfuncs
|
|
from lslcommon import Key, Vector, Quaternion
|
|
from lslparse import warning
|
|
|
|
class outscript(object):
|
|
|
|
binary_operands = frozenset(('||','&&','^','|','&','==','!=','<','<=','>',
|
|
'>=','<<','>>','+','-','*','/','%', '=', '+=', '-=', '*=', '/=','%=',
|
|
))
|
|
extended_assignments = frozenset(('&=', '|=', '^=', '<<=', '>>='))
|
|
unary_operands = frozenset(('NEG', '!', '~'))
|
|
|
|
def Value2LSL(self, value):
|
|
tvalue = type(value)
|
|
if tvalue in (Key, unicode):
|
|
pfx = sfx = ''
|
|
if type(value) == Key:
|
|
# Constants of type key can not be represented
|
|
#raise lslfuncs.ELSLTypeMismatch
|
|
# Actually they can be the result of folding.
|
|
# On second thought, if we report the error, the location info
|
|
# is lost. So we emit a warning instead, letting the compiler
|
|
# report the error in the generated source.
|
|
if self.globalmode and self.listmode:
|
|
warning('WARNING: Illegal combo: Key type inside a global list')
|
|
if self.listmode or not self.globalmode:
|
|
if self.globalmode:
|
|
pfx = '(key)'
|
|
else:
|
|
pfx = '((key)'
|
|
sfx = ')'
|
|
if '\t' in value:
|
|
warning('WARNING: A string contains a tab. Tabs are expanded to four'
|
|
' spaces by the viewer when copy-pasting the code.')
|
|
return pfx + '"' + value.encode('utf8').replace('\\','\\\\') \
|
|
.replace('"','\\"').replace('\n','\\n') + '"' + sfx
|
|
if tvalue == int:
|
|
if value < 0 and not self.globalmode and self.optsigns:
|
|
#return '0x%X' % (value + 4294967296)
|
|
return '((integer)' + str(value) + ')'
|
|
return str(value)
|
|
if tvalue == float:
|
|
if self.optsigns and value.is_integer() and -2147483648.0 <= value < 2147483648.0:
|
|
if self.globalmode and not self.listmode:
|
|
return str(int(value))
|
|
elif not self.globalmode:
|
|
# Important inside lists!!
|
|
return '((float)' + str(int(value)) + ')'
|
|
s = str(value)
|
|
if s in ('inf', '-inf', 'nan'):
|
|
return '((float)"' + s + '")' # this shouldn't appear in globals
|
|
# Try to remove as many decimals as possible but keeping the F32 value intact
|
|
exp = s.find('e')
|
|
if ~exp:
|
|
s, exp = s[:exp], s[exp:]
|
|
if exp[1] == '+':
|
|
exp = exp[:1] + exp[2:]
|
|
if '.' not in s:
|
|
# I couldn't produce one but it's assumed that if it happens,
|
|
# this code deals with it correctly
|
|
s += '.' # pragma: no cover
|
|
else:
|
|
if '.' not in s:
|
|
# This should never happen (Python should always return a point or exponent)
|
|
return s + '.' # pragma: no cover
|
|
exp = ''
|
|
|
|
# Shorten the float as much as possible.
|
|
while s[-1] != '.' and lslfuncs.F32(float(s[:-1]+exp)) == value:
|
|
s = s[:-1]
|
|
if s[-1] != '.':
|
|
news = s
|
|
neg = ''
|
|
if s[0] == '-':
|
|
news = s[1:]
|
|
neg = '-'
|
|
# Try harder
|
|
point = news.index('.') + 1 - len(news) # Remove point
|
|
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
|
|
# Repeat the operation with the incremented number
|
|
while news[-1] != '.' and lslfuncs.F32(float(neg+news[:-1]+exp)) == value:
|
|
news = news[:-1]
|
|
if len(neg+news) < len(s) and lslfuncs.F32(float(neg+news[:-1]+exp)) == value:
|
|
# Success! But we try even harder.
|
|
if exp != '':
|
|
if news[2:3] == '.': # we converted 9.9... into 10.
|
|
newexp = 'e' + str(int(exp[1:])+1) # increase exponent
|
|
news2 = news[0] + '.' + news[1] + news[3:] # move dot to the left
|
|
while news2[-1] == '0': # remove trailing zeros
|
|
news2 = news2[:-1]
|
|
if len(neg+news2) < len(s) and lslfuncs.F32(float(neg+news2[:-1]+newexp)) == value:
|
|
news = news2
|
|
exp = newexp
|
|
s = neg+news
|
|
if value >= 0 or self.globalmode or not self.optsigns:
|
|
return s + exp
|
|
return '((float)' + s + exp + ')'
|
|
if tvalue == Vector:
|
|
return '<' + self.Value2LSL(value[0]) + ', ' + self.Value2LSL(value[1]) \
|
|
+ ', ' + self.Value2LSL(value[2]) + '>'
|
|
if tvalue == Quaternion:
|
|
return '<' + self.Value2LSL(value[0]) + ', ' + self.Value2LSL(value[1]) \
|
|
+ ', ' + self.Value2LSL(value[2]) + ', ' + self.Value2LSL(value[3]) + '>'
|
|
if tvalue == list:
|
|
if value == []:
|
|
return '[]'
|
|
if len(value) < 5:
|
|
self.listmode = True
|
|
ret = '[' + self.Value2LSL(value[0])
|
|
for elem in value[1:]:
|
|
ret += ', ' + self.Value2LSL(elem)
|
|
ret += ']'
|
|
self.listmode = False
|
|
return ret
|
|
ret = '\n'
|
|
first = True
|
|
self.indentlevel += 1
|
|
for entry in value:
|
|
if not first:
|
|
ret += self.dent() + ', '
|
|
else:
|
|
ret += self.dent() + '[ '
|
|
self.listmode = True
|
|
ret += self.Value2LSL(entry) + '\n'
|
|
self.listmode = False
|
|
first = False
|
|
self.indentlevel -= 1
|
|
return ret + self.dent() + self.indent + ']'
|
|
|
|
assert False, u'Value of unknown type in Value2LSL: ' + repr(value)
|
|
|
|
def dent(self):
|
|
return self.indent * self.indentlevel
|
|
|
|
def OutIndented(self, code):
|
|
if code['node'] != '{}':
|
|
self.indentlevel += 1
|
|
ret = self.OutCode(code)
|
|
if code['node'] != '{}':
|
|
self.indentlevel -= 1
|
|
return ret
|
|
|
|
def OutExprList(self, L):
|
|
ret = ''
|
|
if L:
|
|
First = True
|
|
for item in L:
|
|
if not First:
|
|
ret += ', '
|
|
ret += self.OutExpr(item)
|
|
First = False
|
|
return ret
|
|
|
|
def OutExpr(self, expr):
|
|
# Handles expression nodes (as opposed to statement nodes)
|
|
node = expr['node']
|
|
if 'br' in expr:
|
|
child = expr['br']
|
|
|
|
if node == '()':
|
|
return '(' + self.OutExpr(child[0]) + ')'
|
|
|
|
if node in self.binary_operands:
|
|
return self.OutExpr(child[0]) + ' ' + node + ' ' + self.OutExpr(child[1])
|
|
|
|
if node == 'IDENT':
|
|
return expr['name']
|
|
|
|
if node == 'CONST':
|
|
return self.Value2LSL(expr['value'])
|
|
|
|
if node == 'CAST':
|
|
ret = '(' + expr['type'] + ')'
|
|
expr = child[0]
|
|
if expr['node'] in ('CONST', 'IDENT', 'V++', 'V--', 'VECTOR',
|
|
'ROTATION', 'LIST', 'FIELD', 'PRINT', 'FUNCTION', '()'):
|
|
return ret + self.OutExpr(expr)
|
|
return ret + '(' + self.OutExpr(expr) + ')'
|
|
|
|
if node == 'LIST':
|
|
self.listmode = True
|
|
ret = '[' + self.OutExprList(child) + ']'
|
|
self.listmode = False
|
|
return ret
|
|
|
|
if node in ('VECTOR', 'ROTATION'):
|
|
return '<' + self.OutExprList(child) + '>'
|
|
|
|
if node == 'FNCALL':
|
|
return expr['name'] + '(' + self.OutExprList(child) + ')'
|
|
|
|
if node == 'PRINT':
|
|
return 'print(' + self.OutExpr(child[0]) + ')'
|
|
|
|
if node in self.unary_operands:
|
|
if node == 'NEG':
|
|
node = '- '
|
|
return node + self.OutExpr(child[0])
|
|
|
|
if node == 'FLD':
|
|
return self.OutExpr(child[0]) + '.' + expr['fld']
|
|
|
|
if node in ('V--', 'V++'):
|
|
return self.OutExpr(child[0]) + ('++' if node == 'V++' else '--')
|
|
|
|
if node in ('--V', '++V'):
|
|
return ('++' if node == '++V' else '--') + self.OutExpr(child[0])
|
|
|
|
if node in self.extended_assignments:
|
|
lvalue = self.OutExpr(child[0])
|
|
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
|
|
|
|
def OutCode(self, code):
|
|
node = code['node']
|
|
if 'br' in code:
|
|
child = code['br']
|
|
else:
|
|
child = None
|
|
|
|
if node == 'IF':
|
|
ret = self.dent()
|
|
while True:
|
|
ret += 'if (' + self.OutExpr(child[0]) + ')\n' + self.OutIndented(child[1])
|
|
if len(child) < 3:
|
|
return ret
|
|
if child[2]['node'] != 'IF':
|
|
ret += self.dent() + 'else\n' + self.OutIndented(child[2])
|
|
return ret
|
|
ret += self.dent() + 'else '
|
|
code = child[2]
|
|
child = code['br']
|
|
if node == 'WHILE':
|
|
ret = self.dent() + 'while (' + self.OutExpr(child[0]) + ')\n'
|
|
ret += self.OutIndented(child[1])
|
|
return ret
|
|
if node == 'DO':
|
|
ret = self.dent() + 'do\n'
|
|
ret += self.OutIndented(child[0])
|
|
return ret + self.dent() + 'while (' + self.OutExpr(child[1]) + ');\n'
|
|
if node == 'FOR':
|
|
ret = self.dent() + 'for ('
|
|
ret += self.OutExpr(child[0])
|
|
ret += '; ' + self.OutExpr(child[1]) + '; '
|
|
ret += self.OutExpr(child[2])
|
|
ret += ')\n'
|
|
ret += self.OutIndented(child[3])
|
|
return ret
|
|
if node == '@':
|
|
return self.dent() + '@' + code['name'] + ';\n'
|
|
if node == 'JUMP':
|
|
return self.dent() + 'jump ' + code['name'] + ';\n'
|
|
if node == 'STATE':
|
|
return self.dent() + 'state ' + code['name'] + ';\n'
|
|
if node == 'RETURN':
|
|
if child:
|
|
return self.dent() + 'return ' + self.OutExpr(child[0]) + ';\n'
|
|
return self.dent() + 'return;\n'
|
|
if node == 'DECL':
|
|
ret = self.dent() + code['type'] + ' ' + code['name']
|
|
if child:
|
|
ret += ' = ' + self.OutExpr(child[0])
|
|
return ret + ';\n'
|
|
if node == ';':
|
|
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'
|
|
|
|
def output(self, treesymtab, options = ('optsigns',)):
|
|
# Build a sorted list of dict entries
|
|
self.tree, self.symtab = treesymtab
|
|
|
|
# Optimize signs
|
|
self.optsigns = 'optsigns' in options
|
|
|
|
ret = ''
|
|
self.indent = ' '
|
|
self.indentlevel = 0
|
|
self.globalmode = False
|
|
self.listmode = False
|
|
for code in self.tree:
|
|
if code['node'] == 'DECL':
|
|
self.globalmode = True
|
|
ret += self.OutCode(code)
|
|
self.globalmode = False
|
|
else:
|
|
ret += self.OutCode(code)
|
|
|
|
return ret
|