From eab0bec84beab69fbfa68c4104dc463b24ff359d Mon Sep 17 00:00:00 2001 From: Sei Lisa Date: Mon, 28 Jul 2014 02:13:08 +0200 Subject: [PATCH] Add preprocessor skip option, command line options and help, options for all functions, and enhance parentheses removal. --- lslopt/lsloptimizer.py | 17 ++++++++- lslopt/lsloutput.py | 8 ++-- lslopt/lslparse.py | 18 ++++++++- main.py | 83 +++++++++++++++++++++++++++++++++--------- 4 files changed, 99 insertions(+), 27 deletions(-) diff --git a/lslopt/lsloptimizer.py b/lslopt/lsloptimizer.py index 6faff03..cb48459 100644 --- a/lslopt/lsloptimizer.py +++ b/lslopt/lsloptimizer.py @@ -31,6 +31,7 @@ class optimizer(object): """If the statement is a constant or an identifier, remove it as it does nothing. """ + # Ideally this should consider side effect analysis of the whole thing. if code[0] in (CONSTANT, 'IDENT', 'FIELD'): code[:] = [S[';'], None] else: @@ -86,7 +87,10 @@ class optimizer(object): if code0 == '()': 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] return @@ -154,8 +158,11 @@ class optimizer(object): code[:] = [S['*'], code[1], code[2], [CONSTANT, 'integer', 1<<(code[3][2] & 31)]] else: 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. # 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 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 - def optimize(self, symtab, functions): + def optimize(self, symtab, functions, options = ('optimize',)): """Optimize the symbolic table symtab in place. Requires a table of predefined functions for folding constants. """ + + if 'optimize' not in options: + return + + # TODO: Add option to handle local jumps properly. + self.functions = functions self.symtab = symtab diff --git a/lslopt/lsloutput.py b/lslopt/lsloutput.py index aff06ec..5e91504 100644 --- a/lslopt/lsloutput.py +++ b/lslopt/lsloutput.py @@ -262,12 +262,13 @@ class outscript(object): first = False return ret + ')\n' + self.OutCode(code) - def output(self, symtab): + def output(self, symtab, options = ('optimizesigns',)): # Build a sorted list of dict entries order = [] self.symtab = symtab - self.optsigns = True + # Optimize signs + self.optsigns = 'optimizesigns' in options for i in symtab: item = [] @@ -284,9 +285,6 @@ class outscript(object): for name in order[0]: sym = symtab[0][name] - #DEBUG - #print name, repr(sym) - ret += self.dent() if sym[1] == 'State': if name == 'default': diff --git a/lslopt/lslparse.py b/lslopt/lslparse.py index 59a627c..a66b71e 100644 --- a/lslopt/lslparse.py +++ b/lslopt/lslparse.py @@ -249,6 +249,19 @@ class parser(object): self.pos += 1 # 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 self.script[self.pos:self.pos+1] == '/': self.pos += 1 @@ -1666,7 +1679,7 @@ class parser(object): self.NextToken() - def parse(self, script, options = frozenset()): + def parse(self, script, options = ()): """Parse the given stream with the given options. 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" 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. #self.allowcescapes = 'allowcescapes' in options diff --git a/main.py b/main.py index 8c5eac6..8cab5a5 100644 --- a/main.py +++ b/main.py @@ -6,27 +6,74 @@ from lslopt.lsloptimizer import optimizer import sys def main(): - if len(sys.argv) > 1: - p = parser() - try: - p.parsefile(sys.argv[1]) - funcs = p.functions - symtab = p.symtab - except EParse as e: - print e.message + if len(sys.argv) < 2: + sys.stderr.write(r'''LSL optimizer v0.1 + +Usage: %s [-o option[,option[,...]]] filename + +Options (* means not implemented): + extendedglobalexpr Enables arbitrary expressions in globals (as opposed to + 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 - del p + options = sys.argv[2].split(',') + fname = sys.argv[3] + else: + options = () + fname = sys.argv[1] - opt = optimizer() - opt.optimize(symtab, funcs) - del opt + p = parser() + try: + p.parsefile(fname, options) + funcs = p.functions + symtab = p.symtab + except EParse as e: + print e.message + return 1 + del p - outs = outscript() - script = outs.output(symtab) - del outs - del symtab - sys.stdout.write(script) - return 0 + opt = optimizer() + opt.optimize(symtab, funcs, options) + del opt + + outs = outscript() + script = outs.output(symtab, options) + del outs + del symtab + sys.stdout.write(script) + return 0 ret = main() if ret: