mirror of
https://github.com/Sei-Lisa/LSL-PyOptimizer
synced 2025-07-01 23:58:20 +00:00
Add preprocessor skip option, command line options and help, options for all functions, and enhance parentheses removal.
This commit is contained in:
parent
ae94e80c34
commit
eab0bec84b
4 changed files with 99 additions and 27 deletions
|
@ -31,6 +31,7 @@ class optimizer(object):
|
||||||
"""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.
|
||||||
if code[0] in (CONSTANT, 'IDENT', 'FIELD'):
|
if code[0] in (CONSTANT, 'IDENT', 'FIELD'):
|
||||||
code[:] = [S[';'], None]
|
code[:] = [S[';'], None]
|
||||||
else:
|
else:
|
||||||
|
@ -86,7 +87,10 @@ class optimizer(object):
|
||||||
|
|
||||||
if code0 == '()':
|
if code0 == '()':
|
||||||
self.FoldTree(code[2])
|
self.FoldTree(code[2])
|
||||||
if code[2][0] == CONSTANT:
|
if code[2][0] in (CONSTANT, 'VECTOR', 'ROTATION', 'LIST',
|
||||||
|
'IDENT', 'FIELD', 'V++', 'V--', 'FUNCTION', 'PRINT'):
|
||||||
|
# Child is an unary postfix expression; parentheses can be
|
||||||
|
# removed safely.
|
||||||
code[:] = code[2]
|
code[:] = code[2]
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -154,8 +158,11 @@ class optimizer(object):
|
||||||
code[:] = [S['*'], code[1], code[2], [CONSTANT, 'integer', 1<<(code[3][2] & 31)]]
|
code[:] = [S['*'], code[1], code[2], [CONSTANT, 'integer', 1<<(code[3][2] & 31)]]
|
||||||
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 &
|
||||||
# Note some cases e.g. x*0 can't be optimized away without side-effect analysis.
|
# Note some cases e.g. x*0 can't be optimized away without side-effect analysis.
|
||||||
# But some cases like %1 can be turned into *0 to save bytes.
|
# But some cases like %1 can be turned into *0 to save bytes.
|
||||||
|
# Turn also % (power of 2) into & mask (oops, nope, negative doesn't work)
|
||||||
|
# Maybe turn != -1 into ~ in if()'s.
|
||||||
return
|
return
|
||||||
|
|
||||||
if code0 in self.assign_ops:
|
if code0 in self.assign_ops:
|
||||||
|
@ -345,10 +352,16 @@ class optimizer(object):
|
||||||
|
|
||||||
raise Exception('Internal error: This should not happen, node = ' + code0) # pragma: no cover
|
raise Exception('Internal error: This should not happen, node = ' + code0) # pragma: no cover
|
||||||
|
|
||||||
def optimize(self, symtab, functions):
|
def optimize(self, symtab, functions, 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.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
if 'optimize' not in options:
|
||||||
|
return
|
||||||
|
|
||||||
|
# TODO: Add option to handle local jumps properly.
|
||||||
|
|
||||||
self.functions = functions
|
self.functions = functions
|
||||||
self.symtab = symtab
|
self.symtab = symtab
|
||||||
|
|
||||||
|
|
|
@ -262,12 +262,13 @@ class outscript(object):
|
||||||
first = False
|
first = False
|
||||||
return ret + ')\n' + self.OutCode(code)
|
return ret + ')\n' + self.OutCode(code)
|
||||||
|
|
||||||
def output(self, symtab):
|
def output(self, symtab, options = ('optimizesigns',)):
|
||||||
# Build a sorted list of dict entries
|
# Build a sorted list of dict entries
|
||||||
order = []
|
order = []
|
||||||
self.symtab = symtab
|
self.symtab = symtab
|
||||||
|
|
||||||
self.optsigns = True
|
# Optimize signs
|
||||||
|
self.optsigns = 'optimizesigns' in options
|
||||||
|
|
||||||
for i in symtab:
|
for i in symtab:
|
||||||
item = []
|
item = []
|
||||||
|
@ -284,9 +285,6 @@ class outscript(object):
|
||||||
for name in order[0]:
|
for name in order[0]:
|
||||||
sym = symtab[0][name]
|
sym = symtab[0][name]
|
||||||
|
|
||||||
#DEBUG
|
|
||||||
#print name, repr(sym)
|
|
||||||
|
|
||||||
ret += self.dent()
|
ret += self.dent()
|
||||||
if sym[1] == 'State':
|
if sym[1] == 'State':
|
||||||
if name == 'default':
|
if name == 'default':
|
||||||
|
|
|
@ -249,6 +249,19 @@ class parser(object):
|
||||||
self.pos += 1
|
self.pos += 1
|
||||||
|
|
||||||
# Process comments
|
# Process comments
|
||||||
|
if c == '#' and self.skippreproc:
|
||||||
|
# Preprocessor directives act like single line comments.
|
||||||
|
# Most are not supposed to reach us but cpp also generates
|
||||||
|
# as output lines like: # 123 "file.lsl"
|
||||||
|
self.ceof()
|
||||||
|
while self.script[self.pos] != '\n':
|
||||||
|
self.pos += 1
|
||||||
|
self.ceof() # A single-line comment at EOF is not unexpected EOF.
|
||||||
|
|
||||||
|
self.pos += 1
|
||||||
|
self.ceof()
|
||||||
|
continue
|
||||||
|
|
||||||
if c == '/':
|
if c == '/':
|
||||||
if self.script[self.pos:self.pos+1] == '/':
|
if self.script[self.pos:self.pos+1] == '/':
|
||||||
self.pos += 1
|
self.pos += 1
|
||||||
|
@ -1666,7 +1679,7 @@ class parser(object):
|
||||||
self.NextToken()
|
self.NextToken()
|
||||||
|
|
||||||
|
|
||||||
def parse(self, script, options = frozenset()):
|
def parse(self, script, options = ()):
|
||||||
"""Parse the given stream with the given options.
|
"""Parse the given stream with the given options.
|
||||||
|
|
||||||
This function also builds the temporary globals table.
|
This function also builds the temporary globals table.
|
||||||
|
@ -1693,7 +1706,8 @@ class parser(object):
|
||||||
# Allow C style string composition of strings: "blah" "blah" = "blahblah"
|
# Allow C style string composition of strings: "blah" "blah" = "blahblah"
|
||||||
self.allowmultistrings = 'allowmultistrings' in options
|
self.allowmultistrings = 'allowmultistrings' in options
|
||||||
|
|
||||||
# TODO: Add option to skip preprocessor directives (specifically #line).
|
# Skip preprocessor directives (specifically #line).
|
||||||
|
self.skippreproc = 'skippreproc' in options
|
||||||
|
|
||||||
# TODO: Allow pure C-style string parsing. This is low-priority.
|
# TODO: Allow pure C-style string parsing. This is low-priority.
|
||||||
#self.allowcescapes = 'allowcescapes' in options
|
#self.allowcescapes = 'allowcescapes' in options
|
||||||
|
|
83
main.py
83
main.py
|
@ -6,27 +6,74 @@ from lslopt.lsloptimizer import optimizer
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
if len(sys.argv) > 1:
|
if len(sys.argv) < 2:
|
||||||
p = parser()
|
sys.stderr.write(r'''LSL optimizer v0.1
|
||||||
try:
|
|
||||||
p.parsefile(sys.argv[1])
|
Usage: %s [-o option[,option[,...]]] filename
|
||||||
funcs = p.functions
|
|
||||||
symtab = p.symtab
|
Options (* means not implemented):
|
||||||
except EParse as e:
|
extendedglobalexpr Enables arbitrary expressions in globals (as opposed to
|
||||||
print e.message
|
dull simple expressions allowed by regular LSL). Needs
|
||||||
|
the optimizer to run for the result to be compilable.
|
||||||
|
extendedtypecast Allows extended typecast syntax e.g. (string)(integer)a
|
||||||
|
is valid with this option.
|
||||||
|
extendedassignment Enables &=, |=, ^=, <<=, >>= assignment operators.
|
||||||
|
explicitcast Add explicit casts where they are implicit. This option
|
||||||
|
is useless with 'optimize' and 'optsigns', and is of
|
||||||
|
basically no use in general.
|
||||||
|
allowkeyconcat Allow string + key and key + string (both return string)
|
||||||
|
allowmultistrings Allow C-like string juxtaposition, e.g. "ab" "cd" means
|
||||||
|
"abcd", no concatenation involved. Very useful when used
|
||||||
|
with a preprocessor (although the optimizer would
|
||||||
|
optimize concatenated strings if they are parenthesized
|
||||||
|
correctly, see note at the footer).
|
||||||
|
skippreproc Skip preprocessor directives in the source as if they
|
||||||
|
were comments. Not useful unless the script is output
|
||||||
|
by cpp, which inserts directives like: # 123 "filename"
|
||||||
|
optimize Runs the optimizer.
|
||||||
|
optsigns Optimize signs and float as int.
|
||||||
|
* allowcescapes Enables use of \r, \b, \xNN, \NNN, etc.
|
||||||
|
* enableswitch Enables Firestorm-compatible switch statements
|
||||||
|
(not recommended)
|
||||||
|
* lazylists Enables Firestorm-compatible list syntax, e.g.
|
||||||
|
mylist[3] = 5; v = (float)mylist[2]; (needs to know the
|
||||||
|
type to work). Works better with extendedtypecast.
|
||||||
|
|
||||||
|
Note that the optimizer doesn't reorder expressions to fold constants. This
|
||||||
|
means that e.g. a + 3 + 5 is not optimized to a + 8; however a + (3 + 5) is.
|
||||||
|
''' % sys.argv[0])
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if sys.argv[1] == '-o':
|
||||||
|
if len(sys.argv) < 4:
|
||||||
|
sys.stderr.write('Command line: Not enough parameters\n')
|
||||||
return 1
|
return 1
|
||||||
del p
|
options = sys.argv[2].split(',')
|
||||||
|
fname = sys.argv[3]
|
||||||
|
else:
|
||||||
|
options = ()
|
||||||
|
fname = sys.argv[1]
|
||||||
|
|
||||||
opt = optimizer()
|
p = parser()
|
||||||
opt.optimize(symtab, funcs)
|
try:
|
||||||
del opt
|
p.parsefile(fname, options)
|
||||||
|
funcs = p.functions
|
||||||
|
symtab = p.symtab
|
||||||
|
except EParse as e:
|
||||||
|
print e.message
|
||||||
|
return 1
|
||||||
|
del p
|
||||||
|
|
||||||
outs = outscript()
|
opt = optimizer()
|
||||||
script = outs.output(symtab)
|
opt.optimize(symtab, funcs, options)
|
||||||
del outs
|
del opt
|
||||||
del symtab
|
|
||||||
sys.stdout.write(script)
|
outs = outscript()
|
||||||
return 0
|
script = outs.output(symtab, options)
|
||||||
|
del outs
|
||||||
|
del symtab
|
||||||
|
sys.stdout.write(script)
|
||||||
|
return 0
|
||||||
|
|
||||||
ret = main()
|
ret = main()
|
||||||
if ret:
|
if ret:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue