From 1dea1bd12cc0238a49a0eb6f9998c2f44652e737 Mon Sep 17 00:00:00 2001 From: Sei Lisa Date: Sat, 28 Feb 2015 00:43:26 +0100 Subject: [PATCH] Make parentheses no longer explicit in the AST (Beta). The output module adds parentheses where necessary, depending on the evaluation order in the tree. Or that's the idea. Prone to bugs, let's see how it bodes. --- lslopt/lsldeadcode.py | 15 +------ lslopt/lslfoldconst.py | 95 +++++++----------------------------------- lslopt/lsloptimizer.py | 7 ---- lslopt/lsloutput.py | 66 +++++++++++++++++++++++++---- lslopt/lslparse.py | 5 +-- 5 files changed, 75 insertions(+), 113 deletions(-) diff --git a/lslopt/lsldeadcode.py b/lslopt/lsldeadcode.py index cb0cc8d..fa43597 100644 --- a/lslopt/lsldeadcode.py +++ b/lslopt/lsldeadcode.py @@ -384,12 +384,8 @@ class deadcode(object): else: # assumed VECTOR or ROTATION per SymbolReplacedOrDeleted SEF = 'SEF' in value value = self.Cast(value['ch'][fieldidx], 'float') - # Replace it, in parentheses - if value['nt'] not in ('CONST','()','FLD','IDENT','FNCALL'): - node = curnode['ch'][index] = \ - {'nt':'()', 'X':True, 't':value['t'], 'ch':[value]} - else: - node = curnode['ch'][index] = value + # Replace it + node = curnode['ch'][index] = value if SEF: node['SEF'] = True @@ -400,18 +396,11 @@ class deadcode(object): new = sym['W'] new['X'] = True - if new['nt'] not in ('CONST','()','FLD','IDENT','FNCALL'): - new = {'nt':'()', 'X':True, 't':sym['W']['t'], - 'ch':[new]} SEF = 'SEF' in sym['W'] if SEF: new['SEF'] = True if new['t'] != node['t']: new = self.Cast(new, node['t']) - if new['nt'] not in ('CONST','()','FLD','IDENT','FNCALL'): - new = {'nt':'()', 'X':True, 't':node['t'], 'ch':[new]} - if SEF: - new['SEF'] = True curnode['ch'][index] = node = new # Delete orig if present, as we've eliminated the original #if 'orig' in sym['W']: diff --git a/lslopt/lslfoldconst.py b/lslopt/lslfoldconst.py index 1f3a215..2742d50 100644 --- a/lslopt/lslfoldconst.py +++ b/lslopt/lslfoldconst.py @@ -96,9 +96,6 @@ class foldconst(object): if nt == 'NEG': self.FoldTree(child, 0) - while child[0]['nt'] == '()' and child[0]['ch'][0]['nt'] == 'NEG': - # Remove parentheses: - ( - expr ) --> - - expr - child[0] = child[0]['ch'][0] if child[0]['nt'] == 'NEG': # Double negation: - - expr --> expr # NOTE: Not 100% sure this doesn't need parentheses around expr. @@ -120,12 +117,11 @@ class foldconst(object): track = track['ch'][0]['ch'][0] if const > 2: # -~-~-~expr -> expr+3 - SEF = 'SEF' in track node = {'nt':'CONST', 't':'integer', 'SEF':True, 'value':const} node = {'nt':'+', 't':'integer', 'ch':[node, track]} - parent[index] = node = {'nt':'()', 't':'integer', 'ch':[node]} - if SEF: - node['SEF'] = node['ch'][0]['SEF'] = True + if 'SEF' in track: + node['SEF'] = True + parent[index] = node return @@ -136,8 +132,6 @@ class foldconst(object): subexpr = child[0] if 'SEF' in subexpr: node['SEF'] = True - while subexpr['nt'] == '()' and subexpr['ch'][0]['nt'] in ('()', '~', '!', '++V', '--V'): - subexpr = child[0] = subexpr['ch'][0] # Remove parentheses if subexpr['nt'] == '!' and subexpr['ch'][0]['nt'] == '!': # Simplify !!! to ! subexpr = child[0] = subexpr['ch'][0]['ch'][0] @@ -151,9 +145,6 @@ class foldconst(object): subexpr = child[0] if 'SEF' in subexpr: node['SEF'] = True - while subexpr['nt'] == '()' and subexpr['ch'][0]['nt'] in ('()', - '~', '!', '++V', '--V'): - subexpr = child[0] = subexpr['ch'][0] # Remove parentheses if subexpr['nt'] == '~': # Double negation: ~~expr parent[index] = subexpr['ch'][0] @@ -162,22 +153,6 @@ class foldconst(object): node['value'] = ~node['value'] return - if nt == '()': - self.FoldTree(child, 0) - if 'SEF' in child[0]: - node['SEF'] = True - if child[0]['nt'] in ('()', 'CONST', 'VECTOR', 'ROTATION', 'LIST', - 'IDENT', 'FIELD', 'V++', 'V--', 'FUNCTION', 'PRINT'): - # Child is an unary postfix expression (highest priority); - # parentheses are redundant and can be removed safely. Not - # strictly an optimization but it helps keeping the output - # tidy-ish a bit, and helps the optimizer do its work. It's not - # done in general (e.g. (a * b) + c does not need parentheses - # but these are not eliminated). Only cases like (3) or - # (myvar++) are simplified. - parent[index] = child[0] - return - if nt in self.binary_ops: # RTL evaluation self.FoldTree(child, 1) @@ -272,14 +247,6 @@ class foldconst(object): # Tough one. Remove neutral elements for the diverse types, # and more. - # Remove redundant parentheses - if lnt == '()' and lval['ch'][0]['nt'] in ('+', '-', '*', '/', '%', '~', '!', 'NEG', 'CAST', 'V++', 'V--', '++V', '--V'): - # (a op b) + c -> a op b + c - # (op b) + c -> op b + c - # where op's priority is that of + or greater - lval = child[0] = lval['ch'][0] - lnt = lval['nt'] - # Addition of integers, strings, and lists is associative # Addition of floats, vectors and rotations would be, except # for FP precision. @@ -343,15 +310,6 @@ class foldconst(object): parent[index] = lval return - # Remove parentheses if they enclose a NEG, to unhide their - # operators. Precedence rules allow us. - if lnt == '()' and lval['ch'][0]['nt'] == 'NEG': - # (-expr) + expr -> -expr + expr - lval = child[0] = lval['ch'][0] - if rnt == '()' and rval['ch'][0]['nt'] == 'NEG': - # expr + (-expr) -> expr + -expr - rval = child[1] = rval['ch'][0] - if lnt != 'CONST' != rnt: # Neither is const. Two chances to optimize. # 1. -expr + -expr -> -(expr + expr) (saves 1 byte) @@ -364,9 +322,6 @@ class foldconst(object): if lnt == rnt == 'NEG': node = {'nt':'+', 't':optype, 'ch':[lval['ch'][0], rval['ch'][0]]} SEF = 'SEF' in lval['ch'][0] and 'SEF' in rval['ch'][0] - if SEF: - node['SEF'] = True - node = {'nt':'()', 't':optype, 'ch':[node]} if SEF: node['SEF'] = True node = {'nt':'NEG', 't':optype, 'ch':[node]} @@ -398,9 +353,8 @@ class foldconst(object): lval = child[0] = lval['ch'][0] lnt = lval['nt'] - if rnt == '+' and rval['ch'][0]['nt'] == '()' and rval['ch'][0]['ch'][0]['nt'] == '+' \ - and (rval['ch'][0]['ch'][0]['nt'] == 'CONST' - or rval['ch'][0]['ch'][1]['nt'] == 'CONST'): + if rnt == '+' and (rval['ch'][0]['nt'] == 'CONST' + or rval['ch'][1]['nt'] == 'CONST'): # const + (expr + const) or const + (const + expr) # same as above, join them @@ -416,17 +370,10 @@ class foldconst(object): # output rather than introducing the parens in the tree. if lval['value'] == -1 or lval['value'] == -2: if rnt == 'NEG': # Cancel the NEG - node = {'nt':'()', 't':optype, 'ch':rval['ch']} - if RSEF: - node['SEF'] = True - node = {'nt':'~', 't':optype, 'ch':[node]} + node = {'nt':'~', 't':optype, 'ch':rval['ch']} if RSEF: node['SEF'] = True else: # Add the NEG - #node = {'nt':'()', 't':optype, 'ch':[rval]} - #if RSEF: - # node['SEF'] = True - #node = {'nt':'NEG', 't':optype, 'ch':[node]} node = {'nt':'NEG', 't':optype, 'ch':[rval]} if RSEF: node['SEF'] = True @@ -445,18 +392,11 @@ class foldconst(object): if lval['value'] == 1 or lval['value'] == 2: if rnt == '~': # Cancel the ~ - #node = {'nt':'()', 't':optype, 'ch':rval['ch']} - #if RSEF: - # node['SEF'] = True - #node = {'nt':'NEG', 't':optype, 'ch':[node]} node = {'nt':'NEG', 't':optype, 'ch':rval['ch']} if RSEF: node['SEF'] = True else: - node = {'nt':'()', 't':optype, 'ch':[rval]} - if RSEF: - node['SEF'] = True - node = {'nt':'~', 't':optype, 'ch':[node]} + node = {'nt':'~', 't':optype, 'ch':[rval]} if RSEF: node['SEF'] = True node = {'nt':'NEG', 't':optype, 'ch':[node]} @@ -486,13 +426,6 @@ class foldconst(object): # have e.g. {<< {& x y} 3}; there will be explicit # parentheses here always, so we don't need to worry. - # Operands with priority between * (not included) and << - # (included). - if child[0]['nt'] in ('+', '-', 'NEG', '<<', '>>'): - SEF = 'SEF' in child[0] - child[0] = {'nt':'()', 't':child[0]['t'], 'ch':[child[0]]} - if SEF: - child[0]['SEF'] = True # we have {<<, something, {CONST n}}, transform into {*, something, {CONST n}} node['nt'] = '*' child[1]['value'] = 1 << (child[1]['value'] & 31) @@ -515,13 +448,14 @@ class foldconst(object): if nt != '=': # Replace the node with the expression alone - child[1] = {'nt':'()', 't':child[1]['t'], 'ch':[child[1]]} + # e.g. a += b -> a + b node['nt'] = nt[:-1] - # Linden Craziness: i *= f; is valid (but no other i op= f is). - # It's actually performed as i = (integer)(i + (f)). This breaks - # regular equivalence of x op= y as x = x op (y) so we add - # the type cast here. + # Linden Craziness: int *= float; is valid (but no other + # int op= float is). It's actually performed as + # i = (integer)(i + (f)); + # This breaks equivalence of x op= y as x = x op (y) so we add + # the explicit type cast here. if nt == '*=' and child[0]['t'] == 'integer' and child[1]['t'] == 'float': node['t'] = 'float' # Addition shall return float. node = self.Cast(node, 'integer') @@ -605,8 +539,7 @@ class foldconst(object): elif node['name'] == 'llGetListLength' and child[0]['nt'] == 'IDENT': # Convert llGetListLength(ident) to (ident != []) node = {'nt':'CONST', 't':'list', 'value':[]} - node = {'nt':'!=', 't':'list', 'ch':[child[0], node]} - parent[index] = {'nt':'()', 't':'list', 'ch':[node]} + parent[index] = node = {'nt':'!=', 't':'list', 'ch':[child[0], node]} elif SEFargs and 'SEF' in self.symtab[0][node['name']]: # The function is marked as SEF in the symbol table, and the # arguments are all side-effect-free. The result is SEF. diff --git a/lslopt/lsloptimizer.py b/lslopt/lsloptimizer.py index b346c0b..4587f03 100644 --- a/lslopt/lsloptimizer.py +++ b/lslopt/lsloptimizer.py @@ -31,13 +31,6 @@ class optimizer(foldconst, renamer, deadcode): # value unchanged if value['t'] == newtype: return value - if value not in ('CONST','()','FLD','IDENT','FNCALL','V++','V--', - 'VECTOR','ROTATION','LIST'): - value = {'nt':'()', 't':newtype, 'ch':[value]} - if 'SEF' in value['ch'][0]: - value['SEF'] = True - if 'X' in value['ch'][0]: - value['X'] = value['ch'][0]['X'] ret = {'nt':'CAST', 't':newtype, 'ch':[value]} if 'SEF' in value: ret['SEF'] = True diff --git a/lslopt/lsloutput.py b/lslopt/lsloutput.py index 9d29574..7701ca8 100644 --- a/lslopt/lsloutput.py +++ b/lslopt/lsloutput.py @@ -10,6 +10,11 @@ class outscript(object): )) extended_assignments = frozenset(('&=', '|=', '^=', '<<=', '>>=')) unary_operands = frozenset(('NEG', '!', '~')) + op_priority = {'=':0, '+=':0, '-=':0, '*=':0, '/=':0, '%=':0, '&=':0, + '|=':0, '^=':0, '<<=':0, '>>=':0, + '||':1, '&&':1, '|':2, '^':3, '&':4, '==':5, '!=':5, + '<':6, '<=':6, '>':6, '>=':6, '<<':7, '>>':7, '+':8, '-':8,# 'NEG':8, + '*':9, '/':9, '%':9}#, '!':10, '~':10, '++':10, '--':10, } def Value2LSL(self, value): tvalue = type(value) @@ -174,11 +179,33 @@ class outscript(object): if 'ch' in expr: child = expr['ch'] - if nt == '()': - return '(' + self.OutExpr(child[0]) + ')' - if nt in self.binary_operands: - return self.OutExpr(child[0]) + ' ' + nt + ' ' + self.OutExpr(child[1]) + lnt = child[0]['nt'] + lparen = False + rnt = child[1]['nt'] + rparen = False + if nt in self.op_priority: + base_pri = self.op_priority[nt] + if lnt in self.op_priority: + if self.op_priority[lnt] < base_pri: + lparen = True + elif lnt == 'NEG' and base_pri > self.op_priority['-']: + lparen = True + + if rnt in self.op_priority: + if self.op_priority[rnt] <= base_pri: + rparen = True + + if lparen: + ret = '(' + self.OutExpr(child[0]) + ')' + else: + ret = self.OutExpr(child[0]) + ret += ' ' + nt + ' ' + if rparen: + ret += '(' + self.OutExpr(child[1]) + ')' + else: + ret += self.OutExpr(child[1]) + return ret if nt == 'IDENT': return self.FindName(expr) @@ -190,7 +217,7 @@ class outscript(object): ret = '(' + expr['t'] + ')' expr = child[0] if expr['nt'] in ('CONST', 'IDENT', 'V++', 'V--', 'VECTOR', - 'ROTATION', 'LIST', 'FIELD', 'PRINT', 'FUNCTION', '()'): + 'ROTATION', 'LIST', 'FIELD', 'PRINT', 'FUNCTION'): return ret + self.OutExpr(expr) return ret + '(' + self.OutExpr(expr) + ')' @@ -201,7 +228,17 @@ class outscript(object): return ret if nt in ('VECTOR', 'ROTATION'): - return '<' + self.OutExprList(child) + '>' + ret = ('<' + self.OutExpr(child[0]) + ',' + + self.OutExpr(child[1]) + ',') + if nt == 'ROTATION': + ret += self.OutExpr(child[2]) + ',' + lnt = child[-1]['nt'] + if lnt in self.op_priority \ + and self.op_priority[lnt] <= self.op_priority['>']: + ret += '(' + self.OutExpr(child[-1]) + ')' + else: + ret += self.OutExpr(child[-1]) + return ret + '>' if nt == 'FNCALL': return self.FindName(expr) + '(' + self.OutExprList(child) + ')' @@ -210,9 +247,22 @@ class outscript(object): return 'print(' + self.OutExpr(child[0]) + ')' if nt in self.unary_operands: + ret = nt + lnt = child[0]['nt'] + paren = False if nt == 'NEG': - nt = '- ' - return nt + self.OutExpr(child[0]) + ret = '- ' + if lnt in self.op_priority: + paren = self.op_priority[lnt] <= self.op_priority['-'] + else: + if lnt in self.op_priority: + paren = True + + if paren: + ret += '(' + self.OutExpr(child[0]) + ')' + else: + ret += self.OutExpr(child[0]) + return ret if nt == 'FLD': return self.OutExpr(child[0]) + '.' + expr['fld'] diff --git a/lslopt/lslparse.py b/lslopt/lslparse.py index aa72a3f..e989fca 100644 --- a/lslopt/lslparse.py +++ b/lslopt/lslparse.py @@ -798,8 +798,6 @@ class parser(object): if tok0 == '(': # Parenthesized expression or typecast - # TODO: Don't include parentheses in the tree, defer to the output - # module to add them where necessary. self.NextToken() if self.tok[0] != 'TYPE': @@ -807,7 +805,7 @@ class parser(object): expr = self.Parse_expression() self.expect(')') self.NextToken() - return {'nt':'()', 't':expr['t'], 'ch':[expr]} + return expr # Typecast typ = self.tok[1] @@ -825,7 +823,6 @@ class parser(object): expr = self.Parse_expression() self.expect(')') self.NextToken() - expr = {'nt':'()', 't':expr['t'], 'ch':[expr]} else: expr = self.Parse_unary_postfix_expression(AllowAssignment = False) basetype = expr['t']