diff --git a/lslopt/lsloptimizer.py b/lslopt/lsloptimizer.py index 5c18b9f..26f3b72 100644 --- a/lslopt/lsloptimizer.py +++ b/lslopt/lsloptimizer.py @@ -1,11 +1,15 @@ import lslfuncs -from lslparse import S, warning - -CONSTANT = S['CONSTANT'] +from lslparse import warning 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 binary_ops = frozenset(('+','-','*','/','%','<<','>>','<','<=','>','>=', '==','!=','|','^','&','||','&&')) @@ -17,91 +21,93 @@ class optimizer(object): def FoldAndRemoveEmptyStmts(self, lst): """Utility function for elimination of useless expressions in FOR""" - x = 0 - while x < len(lst): - self.FoldTree(lst[x]) - self.FoldStmt(lst[x]) + idx = 0 + while idx < len(lst): + self.FoldTree(lst, idx) + self.FoldStmt(lst, idx) # If eliminated, it must be totally removed. A ';' won't do. - if lst[x][0] == ';': - del lst[x] + if lst[idx]['node'] == ';': + del lst[idx] 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 nothing. """ # Ideally this should consider side effect analysis of the whole thing. - if code[0] in (CONSTANT, 'IDENT', 'FIELD'): - code[:] = [S[';'], None] - else: - code[:] = code + if parent[index]['node'] in ('CONST', 'IDENT', 'FIELD'): + parent[index] = {'node':';','type':None} - def FoldTree(self, code): + def FoldTree(self, parent, index): """Recursively traverse the tree to fold constants, changing it in place. Also optimizes away IF, WHILE, etc. """ - while code[0] == 'EXPR': - if type(code) == tuple: - # just enter - code = code[2] - else: - # unfold - code[:] = code[2] + code = parent[index] + if code is None: return # Deleted statement + node = code['node'] + child = code['br'] if 'br' in code else None - code0 = code[0] - - if code0 == CONSTANT: + if node == 'CONST': # Job already done return - if code0 == 'CAST': - self.FoldTree(code[2]) - if code[2][0] == CONSTANT: + if node == 'CAST': + self.FoldTree(child, 0) + if child[0]['node'] == 'CONST': # Enable key constants. We'll typecast them back on output, but # 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 - if code0 == 'NEG': - self.FoldTree(code[2]) - if code[2][0] == CONSTANT: - code[:] = [CONSTANT, code[1], lslfuncs.neg(code[2][2])] + if node == 'NEG': + self.FoldTree(child, 0) + if child[0]['node'] == 'CONST': + code = parent[index] = child[0] + code['value'] = lslfuncs.neg(code['value']) return - if code0 == '!': - self.FoldTree(code[2]) - if code[2][0] == CONSTANT: - code[:] = [CONSTANT, code[1], int(not code[2][2])] + if node == '!': + self.FoldTree(child, 0) + if child[0]['node'] == 'CONST': + code = parent[index] = child[0] + code['value'] = int(not code['value']) return - if code0 == '~': - self.FoldTree(code[2]) - if code[2][0] == CONSTANT: - code[:] = [CONSTANT, code[1], ~code[2][2]] + if node == '~': + self.FoldTree(child, 0) + if child[0]['node'] == 'CONST': + code = parent[index] = child[0] + code['value'] = ~code['value'] return - if code0 == '()': - self.FoldTree(code[2]) - if code[2][0] in (CONSTANT, 'VECTOR', 'ROTATION', 'LIST', + if node == '()': + self.FoldTree(child, 0) + if child[0]['node'] in ('CONST', 'VECTOR', 'ROTATION', 'LIST', 'IDENT', 'FIELD', 'V++', 'V--', 'FUNCTION', 'PRINT'): - # Child is an unary postfix expression; parentheses can be - # removed safely. - code[:] = code[2] + # Child is an unary postfix expression; parentheses are + # redundant and can be removed safely. Not strictly an + # 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 - if code0 in self.binary_ops: + if node in self.binary_ops: # RTL evaluation - self.FoldTree(code[3]) - self.FoldTree(code[2]) - if code[2][0] == code[3][0] == CONSTANT: - op = code0 - op1 = code[2][2] - op2 = code[3][2] + self.FoldTree(child, 1) + self.FoldTree(child, 0) + if child[0]['node'] == child[1]['node'] == 'CONST': + op = node + op1 = child[0]['value'] + op2 = child[1]['value'] if op == '+': result = lslfuncs.add(op1, op2) elif op == '-': @@ -117,7 +123,7 @@ class optimizer(object): elif op == '>>': result = lslfuncs.S32(op1 >> (op2 & 31)) elif op == '==' or op == '!=': - result = lslfuncs.compare(op1, op2, op == '==') + result = lslfuncs.compare(op1, op2, Eq = (op == '==')) elif op in ('<', '<=', '>', '>='): if op in ('>', '<='): result = lslfuncs.less(op2, op1) @@ -137,25 +143,33 @@ class optimizer(object): result = int(op1 and op2) else: raise Exception(u'Internal error: Operator not found: ' + op.decode('utf8')) # pragma: no cover - code[:] = [CONSTANT, code[1], result] - elif code[0] == '-' and code[2][1] in ('integer', 'float') and code[3][1] in ('integer', 'float'): + parent[index] = {'node':'CONST', 'type':code['type'], 'value':result} + elif node == '-' and child[0]['type'] in ('integer', 'float') \ + and child[1]['type'] in ('integer', 'float'): # Change - to + - for int/float - if code[3][0] == CONSTANT: - if code[3][2] == 0: - code[:] = code[2] + if child[1]['node'] == 'CONST': + if child[1]['value'] == 0: + parent[index] = child[0] else: - code[0] = S['+'] - code[3][2] = lslfuncs.neg(code[3][2]) + code['node'] = '+' + child[1]['value'] = lslfuncs.neg(child[1]['value']) + #TODO: Implement to transform 0-x into -x: elif child[0]['node'] == 'CONST': else: - code[:] = [S['+'], code[1], code[2], [S['NEG'], code[3][1], code[3]]] - elif code[0] == '<<' and code[3][0] == CONSTANT: + code['node'] = '+' + child[1] = {'node':'NEG', 'type':child[1]['type'], 'br':[child[1]]} + elif node == '<<' and child[1]['node'] == 'CONST': # Transforming << into multiply saves some bytes. - if code[2][0] in ('+', '-', 'NEG'): # operands with priority between * and << - code[2] = [S['()'], code[2][1], code[2]] - if not (code[3][2] & 31): - code[:] = code[2] - else: - code[:] = [S['*'], code[1], code[2], [CONSTANT, 'integer', 1<<(code[3][2] & 31)]] + if child[1]['value'] & 31: + # x << 3 --> x * 8 + # Do we need parentheses for *? It depends on x + # e.g. x+3<<3 needs parentheses when converted to (x+3)*8 + if child[0]['node'] in ('+', '-', 'NEG'): # operands with priority between * and << #TODO: CHECK + 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: 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 & @@ -165,205 +179,195 @@ class optimizer(object): # Maybe turn != -1 into ~ in if()'s. return - if code0 in self.assign_ops: + if node in self.assign_ops: # TODO: Eliminate redundant operations, e.g. a += 0; etc. # 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 - if code0 == 'IDENT': + if node == 'IDENT' or node == 'FLD': if self.globalmode: - val = self.symtab[code[3]][code[2]][2] - if val is not None: - if type(val) == tuple: - # Infinite recursion is prevented at the parser level, by - # not allowing forward globals in global var definitions. - self.FoldTree(val) - if val[0] != 'EXPR' or val[2][0] != CONSTANT: - return - val = val[2][2] - if code[1] != 'key' and val is not None: - code[:] = [CONSTANT, code[1], val] + ident = code if node == 'IDENT' else child[0] + # Resolve constant values so they can be optimized + sym = self.symtab[ident['scope']][ident['name']] + + defn = self.tree[sym['Loc']] + assert defn['name'] == ident['name'] + + # Assume we already were there + if 'br' in defn: + val = defn['br'][0] + 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 - if code0 == 'FUNCTION': - for x in code[3][::-1]: - self.FoldTree(x) - if code[2] in self.functions and self.functions[code[2]][2] is not None: - for x in code[3]: - if x[0] != CONSTANT: - break - else: + if node == 'FNCALL': + for idx in xrange(len(child)-1, -1, -1): + self.FoldTree(child, idx) + if code['name'] in self.symtab[0]: + fn = self.symtab[0][code['name']]['Loc'] + if fn is not None and type(fn) != int and all(arg['node'] == 'CONST' for arg in child): # Call it - val = self.functions[code[2]][2](*tuple(x[2] for x in code[3])) - if not self.foldtabs and isinstance(val, unicode) and '\t' in val: + value = fn(*tuple(arg['value'] for arg in child)) + if not self.foldtabs and isinstance(value, unicode) and '\t' in value: warning('WARNING: Tab in function result and foldtabs option not used.') return - code[:] = [CONSTANT, code[1], val] + parent[index] = {'node':'CONST', 'type':code['type'], 'value':value} return - if code0 == 'PRINT': + if node == 'PRINT': # useless but who knows - self.FoldTree(code[2]) + self.FoldTree(child, 0) return - if code0 in ('VECTOR', 'ROTATION', 'LIST'): + if node in ('VECTOR', 'ROTATION', 'LIST'): isconst = True - for x in code[:1:-1]: - self.FoldTree(x) - if x[0] != CONSTANT: + for idx in xrange(len(child)-1, -1, -1): + self.FoldTree(child, idx) + if child[idx]['node'] != 'CONST': isconst = False if isconst: - value = [x[2] for x in code[2:]] - if code0 == 'VECTOR': + value = [elem['value'] for elem in child] + if node == 'VECTOR': 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]) - code[:] = [CONSTANT, code[1], value] + parent[index] = {'node':'CONST', 'type':code['type'], 'value':value} return - if code0 == 'FIELD': - if self.globalmode: - # We can fold a global vector or rotation field as they are - # constant, but that involves resolving the symbols that aren't - # 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])])] + if node in ('{}', 'FNDEF', 'STATEDEF'): + for idx in xrange(len(child)): + self.FoldTree(child, idx) + self.FoldStmt(child, idx) return - if code0 == '{}': - for x in code[2:]: - self.FoldTree(x) - self.FoldStmt(x) - return - - if code0 == 'IF': - self.FoldTree(code[2]) - if code[2][0] == CONSTANT: + if node == 'IF': + self.FoldTree(child, 0) + if child[0]['node'] == 'CONST': # We can remove one of the branches safely. - if lslfuncs.cond(code[2][2]): - self.FoldTree(code[3]) - code[:] = code[3] - self.FoldStmt(code) - elif len(code) > 4: - self.FoldTree(code[4]) - code[:] = code[4] - self.FoldStmt(code) + if lslfuncs.cond(child[0]['value']): + self.FoldTree(child, 1) + parent[index] = child[1] + self.FoldStmt(child, 1) + elif len(child) > 2: + self.FoldTree(child, 2) + parent[index] = child[2] + self.FoldStmt(child, 2) else: # No ELSE branch, replace the statement with an empty one. - code[:] = [S[';'], None] + parent[index] = {'node':';', 'type':None} else: - self.FoldTree(code[3]) - self.FoldStmt(code[3]) - if len(code) > 4: - self.FoldTree(code[4]) - self.FoldStmt(code[4]) + self.FoldTree(child, 1) + self.FoldStmt(child, 1) + if len(child) > 2: + self.FoldTree(child, 2) + self.FoldStmt(child, 2) return - if code0 == 'WHILE': - self.FoldTree(code[2]) - if code[2][0] == CONSTANT: + if node == 'WHILE': + self.FoldTree(child, 0) + if child[0]['node'] == 'CONST': # 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. # First, replace the constant. - code[2][1:2] = [S['integer'], 1] + child[0].update({'type':'integer', 'value':1}) # Recurse on the statement. - self.FoldTree(code[3]) - self.FoldStmt(code[3]) + self.FoldTree(child, 1) + self.FoldStmt(child, 1) else: # Can be removed. - code[:] = [S[';'], None] + parent[index] = {'node':';', 'type':None} else: - self.FoldTree(code[3]) - self.FoldStmt(code[3]) + self.FoldTree(child, 1) + self.FoldStmt(child, 1) return - if code0 == 'DO': - self.FoldTree(code[2]) # This one is always executed. - self.FoldStmt(code[2]) - self.FoldTree(code[3]) + if node == 'DO': + self.FoldTree(child, 0) # This one is always executed. + self.FoldStmt(child, 0) + self.FoldTree(child, 1) # See if the latest part is a constant. - if code[3][0] == CONSTANT: - if lslfuncs.cond(code[3][2]): + if child[1]['node'] == 'CONST': + if lslfuncs.cond(child[1]['value']): # Endless loop. Replace the constant. - code[3][1:2] = [S['integer'], 1] + child[1].update({'type':'integer', 'value':1}) else: # Only one go. Replace with the statement(s). - code[:] = code[2] + parent[index] = child[0] return - if code0 == 'FOR': - self.FoldAndRemoveEmptyStmts(code[2]) + if node == 'FOR': + assert child[0]['node'] == 'EXPRLIST' + assert child[2]['node'] == 'EXPRLIST' + self.FoldAndRemoveEmptyStmts(child[0]['br']) - self.FoldTree(code[3]) # Condition. - if code[3][0] == CONSTANT: + self.FoldTree(child, 1) # Condition. + if child[1]['node'] == 'CONST': # FOR is delicate. It can have multiple expressions at start. # And if there is more than one, these expressions will need a # new block, which means new scope, which is dangerous. # They are expressions, no declarations or labels allowed, but # 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. - code[3][1:2] = [S['integer'], 1] - self.FoldAndRemoveEmptyStmts(code[4]) - self.FoldTree(code[5]) - self.FoldStmt(code[5]) - elif len(code[2]) > 1: - code[:] = [S['{}'], None] + code[2] - elif code[2]: - code[:] = code[2][0] + child[1].update({'type':'integer', 'value':1}) + self.FoldAndRemoveEmptyStmts(child[2]['br']) + self.FoldTree(child, 3) + self.FoldStmt(child, 3) + elif len(child[0]['br']) > 1: + parent[index] = {'node':'{}', 'type':None, 'br':child[0]['br']} + elif child[0]['br']: + parent[index] = child[0]['br'][0] else: - code[:] = [S[';'], None] + parent[index] = {'node':';', 'type':None} else: - self.FoldAndRemoveEmptyStmts(code[4]) - self.FoldTree(code[5]) - self.FoldStmt(code[5]) + self.FoldAndRemoveEmptyStmts(child[2]['br']) + self.FoldTree(child, 3) + self.FoldStmt(child, 3) return - if code0 == 'RETURN': - if code[2] is not None: - self.FoldTree(code[2]) + if node == 'RETURN': + if child: + self.FoldTree(child, 0) return - if code0 == 'DECL': + if node == 'DECL': # The expression code is elsewhere. - expr = self.symtab[code[3]][code[2]][2] - # Irrelevant if list or string or key. - if expr is not None: - self.FoldTree(expr) + if child: + self.FoldTree(child, 0) # TODO: Remove assignment if integer zero. else: # TODO: Add assignment if vector, rotation or float. pass return - if code0 in self.ignored_stmts: + if node in self.ignored_stmts: 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): - if value[0] == 'EXPR': - value = value[2] - if value[0] not in ('VECTOR', 'ROTATION', 'LIST'): + def IsValidGlobalConstant(self, decl): + if 'br' not in decl: + return True + expr = decl['br'][0] + if expr['node'] in ('CONST', 'IDENT'): + return True + if expr['node'] not in ('VECTOR', 'ROTATION', 'LIST'): 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 predefined functions for folding constants. """ @@ -375,27 +379,22 @@ class optimizer(object): # TODO: Add option to handle local jumps properly. - self.functions = functions - self.symtab = symtab + tree, symtab = self.tree, self.symtab = treesymtab self.globalmode = False - # Fold constants etc. - for name in symtab[0]: - if name == -1: - continue - entry = symtab[0][name] - 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.') + # Constant folding pass. It does some other optimizations along the way. + for idx in xrange(len(tree)): + if tree[idx]['node'] == 'DECL': + self.globalmode = True + self.FoldTree(tree, idx) 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 diff --git a/lslopt/lsloutput.py b/lslopt/lsloutput.py index 158a14b..ead3703 100644 --- a/lslopt/lsloutput.py +++ b/lslopt/lsloutput.py @@ -77,7 +77,7 @@ class outscript(object): neg = '-' # Try harder 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 # Repeat the operation with the incremented number while news[-1] != '.' and lslfuncs.F32(float(neg+news[:-1]+exp)) == value: @@ -129,230 +129,190 @@ class outscript(object): self.indentlevel -= 1 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) def dent(self): return self.indent * self.indentlevel def OutIndented(self, code): - if code[0] != '{}': + if code['node'] != '{}': self.indentlevel += 1 ret = self.OutCode(code) - if code[0] != '{}': + if code['node'] != '{}': self.indentlevel -= 1 return ret def OutExprList(self, L): ret = '' if L: + First = True for item in L: - if ret != '': + if not First: ret += ', ' ret += self.OutExpr(item) + First = False return ret def OutExpr(self, expr): - # Save some recursion by unwrapping the expression - while expr[0] == 'EXPR': - expr = expr[2] - node = expr[0] + # Handles expression nodes (as opposed to statement nodes) + node = expr['node'] + if 'br' in expr: + child = expr['br'] if node == '()': - return '(' + self.OutExpr(expr[2]) + ')' + return '(' + self.OutExpr(child[0]) + ')' + 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': - return expr[2] - if node == 'CONSTANT': - return self.Value2LSL(expr[2]) + return expr['name'] + + if node == 'CONST': + return self.Value2LSL(expr['value']) + if node == 'CAST': - ret = '(' + expr[1] + ')' - expr = expr[2] - if expr[0] == 'EXPR': - expr = expr[2] - if expr[0] in ('CONSTANT', 'IDENT', 'V++', 'V--', 'VECTOR', - 'ROTATION', 'LIST', 'FIELD', 'PRINT', 'FUNCTION', '()'): - ret += self.OutExpr(expr) - else: - ret += '(' + self.OutExpr(expr) + ')' - return ret + 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': - if len(expr) == 2: - return '[]' - return '[' + self.OutExprList(expr[2:]) + ']' - if node == 'VECTOR': - return '<' + self.OutExpr(expr[2]) + ', ' + self.OutExpr(expr[3]) \ - + ', ' + self.OutExpr(expr[4]) + '>' - if node == 'ROTATION': - return '<' + self.OutExpr(expr[2]) + ', ' + self.OutExpr(expr[3]) \ - + ', ' + self.OutExpr(expr[4]) + ', ' + self.OutExpr(expr[5]) + '>' - if node == 'FUNCTION': - return expr[2] + '(' + self.OutExprList(expr[3]) + ')' + 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(expr[2]) + ')' + return 'print(' + self.OutExpr(child[0]) + ')' if node in self.unary_operands: if node == 'NEG': node = '- ' - return node + self.OutExpr(expr[2]) + return node + self.OutExpr(child[0]) - if node == 'FIELD': - return self.OutExpr(expr[2]) + '.' + expr[3] + if node == 'FLD': + return self.OutExpr(child[0]) + '.' + expr['fld'] 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'): - return node[:-1] + self.OutExpr(expr[2]) + return ('++' if node == '++V' else '--') + self.OutExpr(child[0]) if node in self.extended_assignments: - op = self.OutExpr(expr[2]) - return op + ' = ' + op + ' ' + node[:-1] + ' (' + self.OutExpr(expr[3]) + ')' + 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): - #return self.dent() + '{\n' + self.dent() + '}\n' - node = code[0] - if node == '{}': - ret = self.dent() + '{\n' - self.indentlevel += 1 - for stmt in code[2:]: - ret += self.OutCode(stmt) - self.indentlevel -= 1 - return ret + self.dent() + '}\n' + 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(code[2]) + ')\n' + self.OutIndented(code[3]) - if len(code) < 5: + ret += 'if (' + self.OutExpr(child[0]) + ')\n' + self.OutIndented(child[1]) + if len(child) < 3: return ret - if code[4][0] != 'IF': - ret += self.dent() + 'else\n' + self.OutIndented(code[4]) + if child[2]['node'] != 'IF': + ret += self.dent() + 'else\n' + self.OutIndented(child[2]) return ret ret += self.dent() + 'else ' - code = code[4] + code = child[2] + child = code['br'] if node == 'WHILE': - ret = self.dent() + 'while (' + self.OutExpr(code[2]) + ')\n' - ret += self.OutIndented(code[3]) + 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(code[2]) - return ret + self.dent() + 'while (' + self.OutExpr(code[3]) + ');\n' + ret += self.OutIndented(child[0]) + return ret + self.dent() + 'while (' + self.OutExpr(child[1]) + ');\n' if node == 'FOR': ret = self.dent() + 'for (' - if code[2]: - ret += self.OutExpr(code[2][0]) - if len(code[2]) > 1: - 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 += self.OutExpr(child[0]) + ret += '; ' + self.OutExpr(child[1]) + '; ' + ret += self.OutExpr(child[2]) ret += ')\n' - ret += self.OutIndented(code[5]) + ret += self.OutIndented(child[3]) return ret if node == '@': - return self.dent() + '@' + code[2] + ';\n' + return self.dent() + '@' + code['name'] + ';\n' if node == 'JUMP': - assert code[2][0:2] == ['IDENT', 'Label'] - return self.dent() + 'jump ' + code[2][2] + ';\n' + return self.dent() + 'jump ' + code['name'] + ';\n' if node == 'STATE': - name = 'default' - if code[2] != 'DEFAULT': - assert code[2][0:2] == ['IDENT', 'State'] - name = code[2][2] - return self.dent() + 'state ' + name + ';\n' + return self.dent() + 'state ' + code['name'] + ';\n' if node == 'RETURN': - if code[2] is None: - return self.dent() + 'return;\n' - return self.dent() + 'return ' + self.OutExpr(code[2]) + ';\n' + if child: + return self.dent() + 'return ' + self.OutExpr(child[0]) + ';\n' + return self.dent() + 'return;\n' if node == 'DECL': - sym = self.symtab[code[3]][code[2]] - ret = self.dent() + sym[1] + ' ' + code[2] - if sym[2] is not None: - ret += ' = ' + self.OutExpr(sym[2]) + 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 OutFunc(self, typ, name, paramlist, paramsymtab, code): - 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',)): + def output(self, treesymtab, options = ('optsigns',)): # Build a sorted list of dict entries - order = [] - self.symtab = symtab + self.tree, self.symtab = treesymtab # Optimize signs - self.optsigns = 'optimizesigns' 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) + self.optsigns = 'optsigns' in options ret = '' self.indent = ' ' self.indentlevel = 0 self.globalmode = False self.listmode = False - for name in order[0]: - sym = symtab[0][name] - - 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 - + for code in self.tree: + if code['node'] == 'DECL': self.globalmode = True - ret += sym[1] + ' ' + name - 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' + ret += self.OutCode(code) self.globalmode = False + else: + ret += self.OutCode(code) return ret diff --git a/lslopt/lslparse.py b/lslopt/lslparse.py index 4b5d0db..423498c 100644 --- a/lslopt/lslparse.py +++ b/lslopt/lslparse.py @@ -57,10 +57,6 @@ class EParseUndefined(EParse): def __init__(self, parser): super(self.__class__, self).__init__(parser, u"Name not defined within scope") -class EParseUnexpected(EParse): - def __init__(self, parser): - super(self.__class__, self).__init__(parser, u"Unexpected internal error") - class EParseTypeMismatch(EParse): def __init__(self, parser): super(self.__class__, self).__init__(parser, u"Type mismatch") @@ -93,18 +89,6 @@ class EInternal(Exception): """ pass -# This table is to save memory in the parse tree in interpreters that don't -# intern strings. -S = ('integer','float','string','key','vector','rotation','quaternion','list', - 'IDENT','x','y','z','s','CAST','<','<=','>=','>','CONSTANT','VECTOR', - 'ROTATION','LIST','PRINT','FUNCTION','FIELD','EXPR','V++','V--','=', - '+=','-=','*=','/=','%=','&=','|=','^=','<<=','>>=','NEG','!','~','++V', - '--V','()','*','/','%','+','-','<<','>>','==','!=','&','^','|','&&','||', - '@','JUMP','STATE','RETURN','IF','WHILE','DO','FOR','DECL','{}',';', - 'Label','State','TRUE','FALSE','default','DEFAULT' - ) -S = {i:i for i in S} - class parser(object): assignment_ops = frozenset(('=', '+=', '-=', '*=', '/=', '%=')) extassignment_ops = frozenset(('|=', '&=', '^=', '<<=', '>>=')) @@ -115,19 +99,13 @@ class parser(object): # These are hardcoded because additions or modifications imply # important changes to the code anyway. - keywords = frozenset((S['default'], 'state', 'event', 'jump', 'return', 'if', - 'else', 'for', 'do', 'while', 'print', S['TRUE'], S['FALSE'])) - types = frozenset((S['integer'],S['float'],S['string'],S['key'],S['vector'], - S['quaternion'],S['rotation'],S['list'])) - # Default values per type when declaring variables - DefaultValues = {S['integer']: 0, S['float']: 0.0, S['string']: u'', - S['key']: Key(u''), S['vector']: lslfuncs.ZERO_VECTOR, - S['rotation']: lslfuncs.ZERO_ROTATION, S['list']: [] - } - - PythonType2LSL = {int: S['integer'], float: S['float'], - unicode: S['string'], Key: S['key'], Vector: S['vector'], - Quaternion: S['rotation'], list: S['list']} + keywords = frozenset(('default', 'state', 'event', 'jump', 'return', 'if', + 'else', 'for', 'do', 'while', 'print', 'TRUE', 'FALSE')) + types = frozenset(('integer','float','string','key','vector', + 'quaternion','rotation','list')) + PythonType2LSL = {int: 'integer', float: 'float', + unicode: 'string', Key: 'key', Vector: 'vector', + Quaternion: 'rotation', list: 'list'} PythonType2LSLToken = {int:'INTEGER_VALUE', float:'FLOAT_VALUE', unicode:'STRING_VALUE', Key:'KEY_VALUE', Vector:'VECTOR_VALUE', @@ -136,26 +114,32 @@ class parser(object): def PushScope(self): """Create a new symbol table / scope level""" - self.symtab.append({-1: self.scopeindex}) + self.symtab.append({-1:self.scopeindex}) # Add parent pointer self.scopeindex = len(self.symtab)-1 def PopScope(self): """Return to the previous scope level""" - self.scopeindex = self.symtab[self.scopeindex][-1] - if self.scopeindex is None: - raise EParseUnexpected(self) + self.scopeindex = self.symtab[self.scopeindex][-1] # -1 is a dict key, not an index + assert self.scopeindex is not None, 'Unexpected internal error' + + def AddSymbol(self, kind, scope, name, **values): + values['Kind'] = kind + if kind in 'vl': + values['Scope'] = scope + self.symtab[scope][name] = values def FindSymbolPartial(self, symbol, MustBeLabel = False): - """Find a symbol in all visible scopes in order. + """Find a symbol in all visible scopes in order, but not in the full + globals table (only globals seen so far are visible). Labels have special scope rules: other identifiers with the same name that are not labels are invisible to JUMP statements. Example: - default{timer(){ @x; {integer x; jump x;} }} + default{timer(){ @x; {integer x; jump x;} }} finds the label at the outer block. However: - default{timer(){ @x; integer x; }} + default{timer(){ @x; integer x; }} gives an identifier already defined error. On the other hand, labels hide other types (but that's dealt with in the caller to this function): @@ -164,45 +148,33 @@ class parser(object): gives an Name Not Defined error. """ - scope = self.scopeindex - while scope is not None: - symtab = self.symtab[scope] - if symbol in symtab and (not MustBeLabel or symtab[symbol][1] == 'Label'): + scopelevel = self.scopeindex + while scopelevel is not None: + symtab = self.symtab[scopelevel] + if symbol in symtab and (not MustBeLabel or symtab[symbol]['Kind'] == 'l'): return symtab[symbol] - scope = symtab[-1] # it's a dict, not a list; -1 is a key + scopelevel = symtab[-1] # -1 is a dict key, not an index return None # No labels or states allowed here (but functions are) def FindSymbolFull(self, symbol): - """Returns either a string with the LSL type, or a tuple if it's a - function. - """ - scope = self.scopeindex - while scope: - symtab = self.symtab[scope] + """Returns the symbol table entry for the given symbol.""" + scopelevel = self.scopeindex + while scopelevel: # Loop over all local scopes + symtab = self.symtab[scopelevel] if symbol in symtab: # This can't happen, as functions can't be local #if len(symtab[symbol]) > 3: # return (symtab[symbol][1], symtab[symbol][3]) - return symtab[symbol][1] - scope = symtab[-1] - if self.globalmode and symbol not in self.symtab[0] and symbol not in self.functions: - return None # Disallow forwards in global var mode - if symbol not in self.globals: - return None - return self.globals[symbol] - - def FindScopeIndex(self, symbol, MustBeLabel = False): - """Same as FindSymbolPartial, but stops at globals, and returns scope - level instead of symbol table entry. - """ - scope = self.scopeindex - while scope: - symtab = self.symtab[scope] - if symbol in symtab and (not MustBeLabel or symtab[symbol][1] == 'Label'): - return scope - scope = symtab[-1] - return scope + return symtab[symbol] + scopelevel = symtab[-1] + try: + return self.symtab[0][symbol] # Quick guess + except KeyError: + if self.globalmode and symbol not in self.symtab[0] and symbol not in self.functions \ + or symbol not in self.globals: + return None # Disallow forwards in global var mode + return self.globals[symbol] def ValidateField(self, typ, field): if typ == 'vector' and field in ('x', 'y', 'z') \ @@ -210,20 +182,17 @@ class parser(object): return raise EParseInvalidField(self) - def order(self): - self.dictorder += 1 - return self.dictorder - - def autocastcheck(self, value, typ): + def autocastcheck(self, value, tgttype): """Check if automatic dynamic cast is possible, and insert it if requested explicitly. """ - if value[1] == typ: + tval = value['type'] + if tval == tgttype: return value - if value[1] in ('string', 'key') and typ in ('string', 'key') \ - or value[1] == 'integer' and typ == 'float': + if tval in ('string', 'key') and tgttype in ('string', 'key') \ + or tval == 'integer' and tgttype == 'float': if self.explicitcast: - return [S['CAST'], S[typ], value] + return {'node':'CAST', 'type':tgttype, 'br':[value]} return value raise EParseTypeMismatch(self) @@ -466,8 +435,7 @@ class parser(object): But first, a quaternion _may_ have a full expression at the third component, so we tentatively parse this position as an expression, and - backtrack if it causes an error. This is the only point where this - parser backtracks. + backtrack if it causes an error. """ ret = [] pos = self.pos @@ -513,19 +481,19 @@ class parser(object): ret.append(inequality) return ret # This is basically a copy/paste of the Parse_inequality handler - type1 = inequality[1] - if type1 not in ('integer', 'float'): + ltype = inequality['type'] + if ltype not in ('integer', 'float'): raise EParseTypeMismatch(self) - value = self.Parse_shift() - type2 = value[1] - if type2 not in ('integer', 'float'): + rexpr = self.Parse_shift() + rtype = rexpr['type'] + if rtype not in ('integer', 'float'): raise EParseTypeMismatch(self) - if type1 != type2: - if type2 == 'float': - inequality = self.autocastcheck(inequality, type2) + if ltype != rtype: + if rtype == 'float': + inequality = self.autocastcheck(inequality, rtype) else: - value = self.autocastcheck(value, type1) - inequality = [S[op], S['integer'], inequality, value] + rexpr = self.autocastcheck(rexpr, ltype) + inequality = {'node':op, 'type':'integer', 'br':[inequality, rexpr]} # Reaching this means an operator or lower precedence happened, # e.g. <1,1,1,2==2> (that's syntax error in ==) @@ -556,33 +524,33 @@ class parser(object): tok0 = self.tok[0] val = self.tok[1] if len(self.tok) > 1 else None self.NextToken() - CONSTANT = S['CONSTANT'] + CONST = 'CONST' if tok0 == '-' and self.tok[0] in ('INTEGER_VALUE', 'FLOAT_VALUE'): tok0 = self.tok[0] val = self.tok[1] self.NextToken() - return [CONSTANT, S['integer' if type(val) == int else 'float'], -val] + return {'node':CONST, 'type':'integer' if type(val) == int else 'float', 'value':-val} if tok0 == 'INTEGER_VALUE': - return [CONSTANT, S['integer'], val] + return {'node':CONST, 'type':'integer', 'value':val} if tok0 == 'FLOAT_VALUE': - return [CONSTANT, S['float'], val] + return {'node':CONST, 'type':'float', 'value':val} if tok0 == 'STRING_VALUE': if self.allowmultistrings: while self.tok[0] == 'STRING_VALUE': val += self.tok[1] self.NextToken() - return [CONSTANT, S['string'], val] + return {'node':CONST, 'type':'string', 'value':val} # Key constants are not currently supported - use string #if tok0 == 'KEY_VALUE': - # return [CONSTANT, S['key'], val] + # return [CONST, 'key', val] if tok0 == 'VECTOR_VALUE': - return [CONSTANT, S['vector'], val] + return {'node':CONST, 'type':'vector', 'value':val} if tok0 == 'ROTATION_VALUE': - return [CONSTANT, S['rotation'], val] + return {'node':CONST, 'type':'rotation', 'value':val} if tok0 == 'LIST_VALUE': - return [CONSTANT, S['list'], val] + return {'node':CONST, 'type':'list', 'value':val} if tok0 in ('TRUE', 'FALSE'): - return [CONSTANT, S['integer'], 1 if tok0 == 'TRUE' else 0] + return {'node':CONST, 'type':'integer', 'value':int(tok0 == 'TRUE')} if tok0 == '<': val = [self.Parse_expression()] self.expect(',') @@ -595,18 +563,18 @@ class parser(object): #val.append(self.Parse_expression()) #if self.tok[0] == '>': # self.NextToken() - # return [S['VECTOR'], S['vector']] + val + # return ['VECTOR', 'vector'] + val #self.expect(',') #self.NextToken() #val.append(self.Parse_inequality()) #self.expect('>') #self.NextToken() - #return [S['ROTATION'], S['rotation']] + val + #return ['ROTATION', 'rotation'] + val # Alas, it isn't. The closing angle bracket of a vector '>' # conflicts with the inequality operator '>' in unexpected ways. - # Example: <2,2,2> * 2 will trigger the problem: - # the expression parser tries to parse the inequality 2 > *2, + # Example: <2,2,2> * 2 would trigger the problem with that code: + # the expression parser would try to parse the inequality 2 > *2, # choking at the *. To make things worse, LSL admits things such as # <2,2,2 > 2> (but not things like <2,2,2 == 2> because the == has # lower precedence than the '>' and thus it forces termination of @@ -618,33 +586,31 @@ class parser(object): val += self.Parse_vector_rotation_tail() if len(val) == 3: - return [S['VECTOR'], S['vector']] + val - return [S['ROTATION'], S['rotation']] + val + return {'node':'VECTOR', 'type':'vector', 'br':val} + return {'node':'ROTATION', 'type':'rotation', 'br':val} if tok0 == '[': val = self.Parse_optional_expression_list() self.expect(']') self.NextToken() - return [S['LIST'], S['list']] + val + return {'node':'LIST', 'type':'list', 'br':val} if tok0 == 'PRINT': self.expect('(') self.NextToken() - val = self.Parse_expression() - if val[1] not in self.types: - raise EParseTypeMismatch(self) if val[1] is None else EParseUndefined(self) + expr = self.Parse_expression() + if expr['type'] not in self.types: + raise EParseTypeMismatch(self) if expr['type'] is None else EParseUndefined(self) self.expect(')') self.NextToken() - return [S['PRINT'], None, val] + return {'node':'PRINT', 'type':None, 'br':[expr]} if tok0 != 'IDENT': if tok0 == 'EOF': raise EParseUEOF(self) raise EParseSyntax(self) - typ = self.FindSymbolFull(val) - if typ is None: + sym = self.FindSymbolFull(val) + if sym is None: raise EParseUndefined(self) - # Note this may fail to do interning of the string from the symbol table. - # Doing so with a dictionary key may affect performance. name = val # Course of action decided here. @@ -652,34 +618,35 @@ class parser(object): if tok0 == '(': # Function call self.NextToken() - if type(typ) != tuple: + if sym['Kind'] != 'f': raise EParseUndefined(self) - args = self.Parse_optional_expression_list(typ[1]) + args = self.Parse_optional_expression_list(sym['ParamTypes']) self.expect(')') self.NextToken() - return [S['FUNCTION'], None if typ[0] is None else S[typ[0]], name, args, self.scopeindex] - if typ not in self.types: + return {'node':'FNCALL', 'type':sym['Type'], 'name':name, + 'scope':self.scopeindex, 'br':args} + if sym['Kind'] != 'v': raise EParseTypeMismatch(self) - typ = S[typ] - lvalue = [S['IDENT'], typ, name, self.FindScopeIndex(name)] + typ = sym['Type'] + lvalue = {'node':'IDENT', 'type':typ, 'name':name, 'scope':sym['Scope']} if tok0 == '.': self.NextToken() self.expect('IDENT') self.ValidateField(typ, self.tok[1]) - lvalue = [S['FIELD'], S['float'], lvalue, S[self.tok[1]]] + lvalue = {'node':'FLD', 'type':'float', 'br':[lvalue], 'fld':self.tok[1]} self.NextToken() tok0 = self.tok[0] if tok0 in ('++', '--'): self.NextToken() - if lvalue[1] not in ('integer', 'float'): + if lvalue['type'] not in ('integer', 'float'): raise EParseTypeMismatch(self) - return [S['V'+tok0], lvalue[1], lvalue] + return {'node':'V++' if tok0 == '++' else 'V--', 'type':lvalue['type'], 'br':[lvalue]} if AllowAssignment and (tok0 in self.assignment_ops or self.extendedassignment and tok0 in self.extassignment_ops): self.NextToken() expr = self.Parse_expression() - rtyp = expr[1] + rtyp = expr['type'] if rtyp not in self.types: raise EParseTypeMismatch(self) if typ in ('integer', 'float'): @@ -694,11 +661,11 @@ class parser(object): if tok0 == '=': if typ == 'list' != rtyp: if self.explicitcast: - expr = [S['CAST'], typ, expr] + expr = {'node':'CAST', 'type':typ, 'br':[expr]} else: expr = self.autocastcheck(expr, typ) - return [S['='], typ, lvalue, expr] + return {'node':'=', 'type':typ, 'br':[lvalue, expr]} if tok0 == '+=': if typ == 'float': @@ -708,33 +675,35 @@ class parser(object): raise EParseTypeMismatch(self) if self.explicitcast: if typ == 'list' != rtyp: - expr = [S['CAST'], S[typ], expr] - return [S[tok0], typ, lvalue, expr] + expr = {'node':'CAST', 'type':typ, 'br':[expr]} + return {'node':tok0, 'type':typ, 'br':[lvalue, expr]} if tok0 == '-=': if typ == rtyp in ('integer', 'float', 'vector', 'rotation'): - return [S[tok0], typ, lvalue, expr] + return {'node':tok0, 'type':typ, 'br':[lvalue, expr]} raise EParseTypeMismatch(self) if tok0 in ('*=', '/='): # There is a special case dealt with in advance. if tok0 == '*=' and typ == 'integer' and rtyp == 'float': - return [S[tok0], typ, lvalue, expr] + return {'node':tok0, 'type':typ, 'br':[lvalue, expr]} if (typ == rtyp or typ == 'vector') and rtyp in ('integer', 'float', 'rotation'): if typ == 'vector' and rtyp == 'integer': expr = self.autocastcheck(expr, 'float') - return [S[tok0], typ, lvalue, expr] + return {'node':tok0, 'type':typ, 'br':[lvalue, expr]} raise EParseTypeMismatch(self) if tok0 == '%=': if typ == rtyp in ('integer', 'vector'): - return [S[tok0], typ, lvalue, expr] + return {'node':tok0, 'type':typ, 'br':[lvalue, expr]} + raise EParseTypeMismatch(self) # Rest take integer operands only if typ == rtyp == 'integer': - return [S[tok0], typ, lvalue, expr] + return {'node':tok0, 'type':typ, 'br':[lvalue, expr]} + raise EParseTypeMismatch(self) return lvalue @@ -757,41 +726,41 @@ class parser(object): # Unary minus self.NextToken() value = self.Parse_factor() - if value[1] not in ('integer', 'float', 'vector', 'rotation'): + if value['type'] not in ('integer', 'float', 'vector', 'rotation'): raise EParseTypeMismatch(self) - return [S['NEG'], value[1], value] + return {'node':'NEG', 'type':value['type'], 'br':[value]} if tok0 in ('!', '~'): # Unary logic and bitwise NOT - applies to integers only self.NextToken() value = self.Parse_unary_expression() - if value[1] != 'integer': + if value['type'] != 'integer': raise EParseTypeMismatch(self) - return [S[tok0], S['integer'], value] + return {'node':tok0, 'type':'integer', 'br':[value]} if tok0 in ('++', '--'): # Pre-increment / pre-decrement self.NextToken() self.expect('IDENT') name = self.tok[1] - typ = self.FindSymbolFull(name) - if typ not in self.types: + sym = self.FindSymbolFull(name) + if sym is None or sym['Kind'] != 'v': # Pretend it doesn't exist raise EParseUndefined(self) - typ = S[typ] + typ = sym['Type'] - ret = [S['IDENT'], typ, name, self.FindScopeIndex(name)] + ret = {'node':'IDENT', 'type':typ, 'name':name, 'scope':sym['Scope']} self.NextToken() if self.tok[0] == '.': self.NextToken() self.expect('IDENT') self.ValidateField(typ, self.tok[1]) - ret = [S['FIELD'], S['float'], ret, S[self.tok[1]]] + ret = {'node':'FLD', 'type':'float', 'br':[ret], 'fld':self.tok[1]} self.NextToken() - typ = ret[1] + typ = ret['type'] if typ not in ('integer', 'float'): raise EParseTypeMismatch(self) - return [S[tok0+'V'], typ, ret] + return {'node':'++V' if tok0 == '++' else '--V', 'type':typ, 'br':[ret]} if tok0 == '(': # Parenthesized expression or typecast @@ -802,10 +771,10 @@ class parser(object): expr = self.Parse_expression() self.expect(')') self.NextToken() - return [S['()'], expr[1], expr] + return {'node':'()', 'type':expr['type'], 'br':[expr]} # Typecast - typ = S[self.tok[1]] + typ = self.tok[1] self.NextToken() self.expect(')') self.NextToken() @@ -820,10 +789,10 @@ class parser(object): expr = self.Parse_expression() self.expect(')') self.NextToken() - expr = [S['()'], expr[1], expr] + expr = {'node':'()', 'type':expr['type'], 'br':[expr]} else: expr = self.Parse_unary_postfix_expression(AllowAssignment = False) - basetype = expr[1] + basetype = expr['type'] if typ == 'list' and basetype in self.types \ or basetype in ('integer', 'float') and typ in ('integer', 'float', 'string') \ or basetype == 'string' and typ in self.types \ @@ -831,7 +800,7 @@ class parser(object): or basetype == 'vector' and typ in ('string', 'vector') \ or basetype == 'rotation' and typ in ('string', 'rotation') \ or basetype == 'list' and typ == 'string': - return [S['CAST'], typ, expr] + return {'node':'CAST', 'type':typ, 'br':[expr]} raise EParseTypeMismatch(self) # Must be a postfix expression. @@ -846,43 +815,43 @@ class parser(object): factor = self.Parse_unary_expression() while self.tok[0] in ('*', '/', '%'): op = self.tok[0] - type1 = factor[1] + ltype = factor['type'] # Acceptable types for LHS - if op in ('*', '/') and type1 not in ('integer', 'float', + if op in ('*', '/') and ltype not in ('integer', 'float', 'vector', 'rotation') \ - or op == '%' and type1 not in ('integer', 'vector'): + or op == '%' and ltype not in ('integer', 'vector'): raise EParseTypeMismatch(self) self.NextToken() - value = self.Parse_unary_expression() - type2 = value[1] + rexpr = self.Parse_unary_expression() + rtype = rexpr['type'] # Mod is easier to check for - if op == '%' and type1 != type2: + if op == '%' and ltype != rtype: raise EParseTypeMismatch(self) - if op == '%' or type1 == type2 == 'integer': + if op == '%' or ltype == rtype == 'integer': # Deal with the special cases first (it's easy) - factor = [S[op], S[type1], factor, value] + factor = {'node':op, 'type':ltype, 'br':[factor, rexpr]} else: # Any integer must be promoted to float now - if type1 == 'integer': - type1 = 'float' - factor = self.autocastcheck(factor, type1) - if type2 == 'integer': - type2 = 'float' - value = self.autocastcheck(value, type2) - if type1 == 'float' and type2 in ('float', 'vector') \ - or type1 == 'vector' and type2 in ('float', 'vector', 'rotation') \ - or type1 == type2 == 'rotation': - if op == '/' and type2 == 'vector': + if ltype == 'integer': + ltype = 'float' + factor = self.autocastcheck(factor, ltype) + if rtype == 'integer': + rtype = 'float' + rexpr = self.autocastcheck(rexpr, rtype) + if ltype == 'float' and rtype in ('float', 'vector') \ + or ltype == 'vector' and rtype in ('float', 'vector', 'rotation') \ + or ltype == rtype == 'rotation': + if op == '/' and rtype == 'vector': # Division by vector isn't valid raise EParseTypeMismatch(self) # The rest are valid - if type1 == 'float' and type2 == 'vector': - resulttype = type2 - elif type1 == type2 == 'vector': + if ltype == 'float' and rtype == 'vector': + resulttype = rtype + elif ltype == rtype == 'vector': resulttype = 'float' else: - resulttype = type1 - factor = [S[op], S[resulttype], factor, value] + resulttype = ltype + factor = {'node':op, 'type':resulttype, 'br':[factor, rexpr]} else: raise EParseTypeMismatch(self) @@ -896,58 +865,60 @@ class parser(object): term = self.Parse_factor() while self.tok[0] in ('+', '-'): op = self.tok[0] - type1 = term[1] - if op == '+' and type1 not in self.types \ - or op == '-' and type1 not in ('integer', 'float', + ltype = term['type'] + if op == '+' and ltype not in self.types \ + or op == '-' and ltype not in ('integer', 'float', 'vector', 'rotation'): raise EParseTypeMismatch(self) self.NextToken() - value = self.Parse_factor() - type2 = value[1] + rexpr = self.Parse_factor() + rtype = rexpr['type'] # This is necessary, but the reason is subtle. # The types must match in principle (except integer/float), so it - # doesn't seem necessary to check type2. But there's the case + # doesn't seem necessary to check rtype. But there's the case # where the first element is a list, where the types don't need to # match but the second type must make sense. - if op == '+' and type2 not in self.types: - #or op == '-' and type2 not in ('integer', 'float', + if op == '+' and rtype not in self.types: + #or op == '-' and rtype not in ('integer', 'float', # 'vector', 'rotation'): raise EParseTypeMismatch(self) # Isolate the additions where the types match to make our life easier later - if op == '+' and (type1 == type2 or type1 == 'list' or type2 == 'list'): - if type1 == type2 == 'key': + if op == '+' and (ltype == rtype or ltype == 'list' or rtype == 'list'): + if ltype == rtype == 'key': # key + key is the only disallowed combo of equals raise EParseTypeMismatch(self) if self.explicitcast: - if type1 == 'list' != type2: - value = [S['CAST'], S[type1], value] - #type2 = type1 # unused - elif type2 == 'list' != type1: - term = [S['CAST'], S[type2], term] - type1 = type2 - term = [S[op], S[type1], term, value] + if ltype == 'list' != rtype: + rexpr = {'node':'CAST', 'type':ltype, 'br':[rexpr]} + #rtype = ltype # unused + elif rtype == 'list' != ltype: + term = {'node':'CAST', 'type':rtype, 'br':[term]} + ltype = rtype + term = {'node':op, 'type':ltype, 'br':[term, rexpr]} # Note that although list + nonlist is semantically the same as # list + (list)nonlist and same goes for nonlist + list, they # don't compile to the same thing, but the optimizer should deal # with typecast removal anyway. elif self.allowkeyconcat and op == '+' \ - and type1 in ('key', 'string') and type2 in ('key', 'string'): + and ltype in ('key', 'string') and rtype in ('key', 'string'): # Allow string+key addition (but add explicit cast) - if type1 == 'key': - term = [S[op], S[type2], [S['CAST'], S[type2], term], value] + if ltype == 'key': + term = {'node':op, 'type':rtype, + 'br':[{'node':'CAST', 'type':rtype, 'br':[term]}, rexpr]} else: - term = [S[op], S[type1], term, [S['CAST'], S[type1], value]] - elif type1 == 'key' or type2 == 'key': + term = {'node':op, 'type':ltype, + 'br':[term, {'node':'CAST', 'type':ltype, 'br':[rexpr]}]} + elif ltype == 'key' or rtype == 'key': # Only list + key or key + list is allowed, otherwise keys can't # be added or subtracted with anything. raise EParseTypeMismatch(self) else: - if type1 == 'float': - # Promote value to float - term = [S[op], S[type1], term, self.autocastcheck(value, type1)] + if ltype == 'float': + # Promote rexpr to float + term = {'node':op, 'type':ltype, 'br':[term, self.autocastcheck(rexpr, ltype)]} else: - # Convert LHS to type2 if possible (note no keys arrive here) - term = [S[op], S[type2], self.autocastcheck(term, type2), value] + # Convert LHS to rtype if possible (note no keys arrive here) + term = {'node':op, 'type':rtype, 'br':[self.autocastcheck(term, rtype), rexpr]} return term @@ -958,13 +929,14 @@ class parser(object): """ shift = self.Parse_term() while self.tok[0] in ('<<', '>>'): - if shift[1] != 'integer': + if shift['type'] != 'integer': raise EParseTypeMismatch(self) op = self.tok[0] self.NextToken() - shift = [S[op], S['integer'], shift , self.Parse_term()] - if shift[3][1] != 'integer': + rexpr = self.Parse_term() + if rexpr['type'] != 'integer': raise EParseTypeMismatch(self) + shift = {'node':op, 'type':'integer', 'br':[shift , rexpr]} return shift @@ -977,20 +949,20 @@ class parser(object): inequality = self.Parse_shift() while self.tok[0] in ('<', '<=', '>', '>='): op = self.tok[0] - type1 = inequality[1] - if type1 not in ('integer', 'float'): + ltype = inequality['type'] + if ltype not in ('integer', 'float'): raise EParseTypeMismatch(self) self.NextToken() - value = self.Parse_shift() - type2 = value[1] - if type2 not in ('integer', 'float'): + rexpr = self.Parse_shift() + rtype = rexpr['type'] + if rtype not in ('integer', 'float'): raise EParseTypeMismatch(self) - if type1 != type2: - if type2 == 'float': - inequality = self.autocastcheck(inequality, type2) + if ltype != rtype: + if rtype == 'float': + inequality = self.autocastcheck(inequality, rtype) else: - value = self.autocastcheck(value, type1) - inequality = [S[op], S['integer'], inequality, value] + rexpr = self.autocastcheck(rexpr, ltype) + inequality = {'node':op, 'type':'integer', 'br':[inequality, rexpr]} return inequality @@ -1003,19 +975,19 @@ class parser(object): comparison = self.Parse_inequality() while self.tok[0] in ('==', '!='): op = self.tok[0] - type1 = comparison[1] - if type1 not in self.types: + ltype = comparison['type'] + if ltype not in self.types: raise EParseTypeMismatch(self) self.NextToken() - value = self.Parse_inequality() - type2 = value[1] - if type1 == 'float': - value = self.autocastcheck(value, type1) + rexpr = self.Parse_inequality() + rtype = rexpr['type'] + if ltype == 'float': + rexpr = self.autocastcheck(rexpr, ltype) else: - # For string & key, RHS (type2) mandates the conversion + # For string & key, RHS (rtype) mandates the conversion # (that's room for optimization: always compare strings) - comparison = self.autocastcheck(comparison, type2) - comparison = [S[op], S['integer'], comparison, value] + comparison = self.autocastcheck(comparison, rtype) + comparison = {'node':op, 'type':'integer', 'br':[comparison, rexpr]} return comparison @@ -1026,13 +998,14 @@ class parser(object): """ bitbool_factor = self.Parse_comparison() while self.tok[0] == '&': - if bitbool_factor[1] != 'integer': + if bitbool_factor['type'] != 'integer': raise EParseTypeMismatch(self) op = self.tok[0] self.NextToken() - bitbool_factor = [S[op], S['integer'], bitbool_factor, self.Parse_comparison()] - if bitbool_factor[3][1] != 'integer': + rexpr = self.Parse_comparison() + if rexpr['type'] != 'integer': raise EParseTypeMismatch(self) + bitbool_factor = {'node':op, 'type':'integer', 'br':[bitbool_factor, rexpr]} return bitbool_factor @@ -1043,13 +1016,14 @@ class parser(object): """ bitxor_term = self.Parse_bitbool_factor() while self.tok[0] == '^': - if bitxor_term[1] != 'integer': + if bitxor_term['type'] != 'integer': raise EParseTypeMismatch(self) op = self.tok[0] self.NextToken() - bitxor_term = [S[op], S['integer'], bitxor_term, self.Parse_bitbool_factor()] - if bitxor_term[3][1] != 'integer': + rexpr = self.Parse_bitbool_factor() + if rexpr['type'] != 'integer': raise EParseTypeMismatch(self) + bitxor_term = {'node':op, 'type':'integer', 'br':[bitxor_term, rexpr]} return bitxor_term @@ -1060,13 +1034,14 @@ class parser(object): """ bitbool_term = self.Parse_bitxor_term() while self.tok[0] == '|': - if bitbool_term[1] != 'integer': + if bitbool_term['type'] != 'integer': raise EParseTypeMismatch(self) op = self.tok[0] self.NextToken() - bitbool_term = [S[op], S['integer'], bitbool_term, self.Parse_bitxor_term()] - if bitbool_term[3][1] != 'integer': + rexpr = self.Parse_bitxor_term() + if rexpr['type'] != 'integer': raise EParseTypeMismatch(self) + bitbool_term = {'node':op, 'type':'integer', 'br':[bitbool_term, rexpr]} return bitbool_term @@ -1089,15 +1064,16 @@ class parser(object): """ expression = self.Parse_bitbool_term() while self.tok[0] in ('&&', '||'): - if expression[1] != 'integer': + if expression['type'] != 'integer': raise EParseTypeMismatch(self) op = self.tok[0] self.NextToken() - expression = [S[op], S['integer'], expression, self.Parse_bitbool_term()] - if expression[3][1] != 'integer': + rexpr = self.Parse_bitbool_term() + if rexpr['type'] != 'integer': raise EParseTypeMismatch(self) + expression = {'node':op, 'type':'integer', 'br':[expression, rexpr]} - return [S['EXPR'], expression[1], expression] + return expression def Parse_optional_expression_list(self, expected_types = None): """Grammar parsed here: @@ -1123,7 +1099,7 @@ class parser(object): except EParseTypeMismatch: raise EParseFunctionMismatch(self) else: - if val[1] not in self.types: + if val['type'] not in self.types: raise EParseTypeMismatch(self) idx += 1 ret.append(val) @@ -1160,44 +1136,46 @@ class parser(object): return self.Parse_code_block(ReturnType) if tok0 == ';': self.NextToken() - return [';', None] + return {'node':';', 'type':None} if tok0 == '@': self.NextToken() self.expect('IDENT') name = self.tok[1] if name in self.symtab[self.scopeindex]: raise EParseAlreadyDefined(self) - self.symtab[self.scopeindex][name] = (self.order(), S['Label']) + self.AddSymbol('l', self.scopeindex, name) self.NextToken() self.expect(';') self.NextToken() - return [S['@'], None, name] + return {'node':'@', 'type':None, 'name':name} if tok0 == 'JUMP': self.NextToken() self.expect('IDENT') name = self.tok[1] - tmp = self.FindSymbolPartial(name, MustBeLabel=True) - if not tmp or tmp[1] != 'Label': + sym = self.FindSymbolPartial(name, MustBeLabel=True) + jumpnode = {'node':'JUMP', 'type':None, 'name':name} + if not sym or sym['Kind'] != 'l': # It might still be a forward reference, so we add it to the # list of things to look up when done - self.jump_lookups.append((name, self.scopeindex, self.errorpos)) + self.jump_lookups.append((name, self.scopeindex, self.errorpos, jumpnode)) self.NextToken() self.expect(';') self.NextToken() - return [S['JUMP'], None, ['IDENT', S['Label'], name, self.FindScopeIndex(name, MustBeLabel=True)]] + if sym is not None: + jumpnode['scope'] = sym['Scope'] + return jumpnode if tok0 == 'STATE': self.NextToken() if self.tok[0] not in ('DEFAULT', 'IDENT'): raise EParseSyntax(self) # States are only searched in the global scope name = self.tok[1] if self.tok[0] == 'IDENT' else 'default' - if name not in self.symtab[0] and (name not in self.globals or self.globals[name] != 'State'): + if name not in self.symtab[0] and (name not in self.globals or self.globals[name]['Kind'] != 's'): raise EParseUndefined(self) self.NextToken() self.expect(';') self.NextToken() - return [S['STATE'], None, - [S['IDENT'], S['State'], name, 0] if name != 'default' else S['DEFAULT']] + return {'node':'STATE', 'type':None, 'name':name} if tok0 == 'RETURN': self.NextToken() if self.tok[0] == ';': @@ -1211,8 +1189,8 @@ class parser(object): if ReturnType is not None and value is None: raise EParseReturnIsEmpty(self) if value is None: - return [S['RETURN'], None, None] - return [S['RETURN'], None, self.autocastcheck(value, ReturnType)] + return {'node':'RETURN', 'type':None} + return {'node':'RETURN', 'type':None, 'br':[self.autocastcheck(value, ReturnType)]} if tok0 == 'IF': self.NextToken() self.expect('(') @@ -1225,7 +1203,10 @@ class parser(object): if self.tok[0] == 'ELSE': self.NextToken() else_branch = self.Parse_statement(ReturnType) - return [S['IF'], None, condition, then_branch] + ([else_branch] if else_branch is not None else []) + if else_branch is not None: + return {'node':'IF', 'type':None, 'br':[condition, then_branch, else_branch]} + return {'node':'IF', 'type':None, 'br':[condition, then_branch]} + if tok0 == 'WHILE': self.NextToken() self.expect('(') @@ -1233,7 +1214,7 @@ class parser(object): condition = self.Parse_expression() self.expect(')') self.NextToken() - return [S['WHILE'], None, condition, self.Parse_statement(ReturnType)] + return {'node':'WHILE', 'type':None, 'br':[condition, self.Parse_statement(ReturnType)]} if tok0 == 'DO': self.NextToken() stmt = self.Parse_statement(ReturnType) @@ -1246,7 +1227,7 @@ class parser(object): self.NextToken() self.expect(';') self.NextToken() - return [S['DO'], None, stmt, condition] + return {'node':'DO', 'type':None, 'br':[stmt, condition]} if tok0 == 'FOR': self.NextToken() self.expect('(') @@ -1261,11 +1242,15 @@ class parser(object): self.expect(')') self.NextToken() stmt = self.Parse_statement(ReturnType) - return [S['FOR'], None, initializer, condition, iterator, stmt] + return {'node':'FOR', 'type':None, + 'br':[{'node':'EXPRLIST','type':None, 'br':initializer}, + condition, + {'node':'EXPRLIST','type':None, 'br':iterator}, + stmt]} if tok0 == 'TYPE': if not AllowDecl: raise EParseDeclarationScope(self) - typ = S[self.tok[1]] + typ = self.tok[1] self.NextToken() self.expect('IDENT') name = self.tok[1] @@ -1273,13 +1258,14 @@ class parser(object): raise EParseAlreadyDefined(self) self.NextToken() value = None + decl = {'node':'DECL','type':typ, 'name':name, 'scope':self.scopeindex} if self.tok[0] == '=': self.NextToken() - value = self.Parse_expression() + decl['br'] = [self.Parse_expression()] self.expect(';') self.NextToken() - self.symtab[self.scopeindex][name] = (self.order(), typ, value) - return [S['DECL'], None, name, self.scopeindex] + self.AddSymbol('v', self.scopeindex, name, Type=typ) + return decl # If none of the above, it must be an expression. value = self.Parse_expression() @@ -1300,20 +1286,20 @@ class parser(object): self.PushScope() - ret = [S['{}'], None] + body = [] while True: if self.tok[0] == '}': break - ret.append(self.Parse_statement(ReturnType, AllowDecl = True)) + body.append(self.Parse_statement(ReturnType, AllowDecl = True)) self.PopScope() self.expect('}') self.NextToken() - return ret + return {'node':'{}', 'type':None, 'br':body} - def Parse_simple_expr(self, List=False): + def Parse_simple_expr(self, ForbidList=False): """Grammar parsed here: simple_expr: simple_expr_except_list | list_simple_expr @@ -1328,73 +1314,55 @@ class parser(object): """ tok = self.tok self.NextToken() - if tok[0] == 'TRUE': # TRUE and FALSE don't admit sign in globals - return 1 - if tok[0] == 'FALSE': - return 0 + if tok[0] in ('TRUE', 'FALSE'): # TRUE and FALSE don't admit sign in globals + return {'node':'CONST', 'type':'integer', 'value':int(tok[0]=='TRUE')} if tok[0] in ('STRING_VALUE', 'KEY_VALUE', 'VECTOR_VALUE', 'ROTATION_VALUE', 'LIST_VALUE'): val = tok[1] if tok[0] == 'STRING_VALUE' and self.allowmultistrings: while self.tok[0] == 'STRING_VALUE': val += self.tok[1] self.NextToken() - return val + return {'node':'CONST', 'type':self.PythonType2LSL[type(val)], 'value':val} if tok[0] == 'IDENT': - tmp = self.FindSymbolPartial(tok[1]) - if tmp is None or len(tmp) > 3 or tmp[1] not in self.types: + sym = self.FindSymbolPartial(tok[1]) + if sym is None or sym['Kind'] != 'v': raise EParseUndefined(self) - #return tmp[2] - return (S['IDENT'], S[tmp[1]], tok[1], self.FindScopeIndex(tok[1])) + return {'node':'IDENT', 'type':sym['Type'], 'name':tok[1], 'scope':sym['Scope']} if tok[0] == '<': value = [self.Parse_simple_expr()] - if type(value[0]) == tuple: - typnode = value[0] - else: - typnode = (0, self.PythonType2LSL[type(value[0])]) - self.autocastcheck(typnode, 'float') + self.autocastcheck(value[0], 'float') self.expect(',') self.NextToken() value.append(self.Parse_simple_expr()) - if type(value[1]) == tuple: - typnode = value[1] - else: - typnode = (0, self.PythonType2LSL[type(value[1])]) - self.autocastcheck(typnode, 'float') + self.autocastcheck(value[1], 'float') self.expect(',') self.NextToken() value.append(self.Parse_simple_expr()) - if type(value[2]) == tuple: - typnode = value[2] - else: - typnode = (0, self.PythonType2LSL[type(value[2])]) - self.autocastcheck(typnode, 'float') + self.autocastcheck(value[2], 'float') if self.tok[0] == '>': self.NextToken() - return Vector(value) + return {'node':'VECTOR', 'type':'vector', 'br':value} self.expect(',') self.NextToken() value.append(self.Parse_simple_expr()) - if type(value[3]) == tuple: - typnode = value[3] - else: - typnode = (0, self.PythonType2LSL[type(value[3])]) - self.autocastcheck(typnode, 'float') + self.autocastcheck(value[3], 'float') self.expect('>') self.NextToken() - return Quaternion(value) + return {'node':'ROTATION', 'type':'rotation', 'br':value} - if tok[0] == '[' and not List: + if tok[0] == '[' and not ForbidList: value = [] if self.tok[0] == ']': self.NextToken() - return value + return {'node':'LIST','type':'list','br':value} while True: - value.append(self.Parse_simple_expr(List=True)) + value.append(self.Parse_simple_expr(ForbidList=True)) if self.tok[0] == ']': self.NextToken() - return value + return {'node':'LIST','type':'list','br':value} self.expect(',') self.NextToken() + # Integer or Float constant expected neg = False if tok[0] == '-': neg = True @@ -1402,12 +1370,10 @@ class parser(object): self.NextToken() if tok[0] not in ('INTEGER_VALUE', 'FLOAT_VALUE'): raise EParseSyntax(self) - if neg: - if tok[0] == 'INTEGER_VALUE': - if tok[1] == -2147483648: - return -2147483648 - return -tok[1] - return tok[1] + value = tok[1] + if neg and (tok[0] != 'INTEGER_VALUE' or value == -2147483648): + value = -value + return {'node':'CONST', 'type':'float' if tok[0] == 'FLOAT_VALUE' else 'integer', 'value':value} def Parse_optional_param_list(self): """Grammar parsed here: @@ -1415,27 +1381,30 @@ class parser(object): optional_param_list: LAMBDA | param_list param_list: TYPE IDENT | param_list ',' TYPE IDENT """ - ret = [] + types = [] + names = [] if self.tok[0] == 'TYPE': while True: - typ = S[self.tok[1]] + typ = self.tok[1] self.NextToken() self.expect('IDENT') name = self.tok[1] - ret.append(name) if name in self.symtab[self.scopeindex]: raise EParseAlreadyDefined(self) - self.symtab[self.scopeindex][name] = (self.order(), typ, None) # Value is not predefined + types.append(typ) + names.append(name) + + self.AddSymbol('v', self.scopeindex, name, Type=typ, Param=True) self.NextToken() if self.tok[0] != ',': break self.NextToken() self.expect('TYPE') - return tuple(ret) + return (types, names) def Parse_events(self): """Grammar parsed here: @@ -1445,7 +1414,7 @@ class parser(object): """ self.expect('EVENT_NAME') # mandatory - ret = {} + ret = [] while self.tok[0] == 'EVENT_NAME': name = self.tok[1] @@ -1457,12 +1426,14 @@ class parser(object): params = self.Parse_optional_param_list() # NOTE: Parse_events: This is a bit crude, as the error is given at the end of the param list. # To do it correctly, we can pass the parameter list to Parse_optional_param_list(). - if tuple(self.symtab[self.scopeindex][x][1] for x in params) != self.events[name]: + if tuple(params[0]) != self.events[name]: raise EParseSyntax(self) self.expect(')') self.NextToken() - value = tuple(self.Parse_code_block(None)) - ret[name] = (self.order(), None, value, params, self.scopeindex) + body = self.Parse_code_block(None) + ret.append({'node':'FNDEF', 'type':None, 'name':name, + 'pscope':self.scopeindex, 'ptypes':params[0], 'pnames':params[1], + 'br':[body]}) self.PopScope() return ret @@ -1478,7 +1449,7 @@ class parser(object): while self.tok[0] in ('TYPE','IDENT'): typ = None if self.tok[0] == 'TYPE': - typ = S[self.tok[1]] + typ = self.tok[1] self.NextToken() self.expect('IDENT') @@ -1495,8 +1466,8 @@ class parser(object): if self.tok[0] == '=': self.NextToken() if self.extendedglobalexpr: - self.globalmode = True # Var def. Disallow forward globals. - value = tuple(self.Parse_expression()) # Use advanced expression evaluation. + self.globalmode = True # Disallow forward globals. + value = self.Parse_expression() # Use advanced expression evaluation. self.globalmode = False # Allow forward globals again. else: value = self.Parse_simple_expr() # Use LSL's dull global expression. @@ -1506,24 +1477,30 @@ class parser(object): self.NextToken() value = None + assert self.scopeindex == 0 + decl = {'node':'DECL', 'type':typ, 'name':name, 'scope':0} if value is not None: - if type(value) != tuple and not self.extendedglobalexpr: - self.autocastcheck((0, self.PythonType2LSL[type(value)]), typ) - else: - self.autocastcheck(value, typ) - self.symtab[self.scopeindex][name] = (self.order(), typ, value) + value = self.autocastcheck(value, typ) + decl['br'] = [value] + self.AddSymbol('v', 0, name, Loc=len(self.tree), Type=typ) + self.tree.append(decl) elif self.tok[0] == '(': # This is a function definition self.NextToken() - self.PushScope() # Parameter names don't conflict with globals. + self.PushScope() params = self.Parse_optional_param_list() self.expect(')') self.NextToken() - value = tuple(self.Parse_code_block(typ)) + body = self.Parse_code_block(typ) paramscope = self.scopeindex + self.AddSymbol('f', 0, name, Loc=len(self.tree), Type=typ, + ParamTypes=params[0], ParamNames=params[1]) + self.tree.append({'node':'FNDEF', 'type':typ, 'name':name, + 'pscope':paramscope, + 'ptypes':params[0], 'pnames':params[1], 'br':[body]}) self.PopScope() - self.symtab[self.scopeindex][name] = (self.order(), typ, value, params, paramscope) + assert self.scopeindex == 0 else: raise EParseSyntax(self) pass @@ -1546,7 +1523,7 @@ class parser(object): return if self.tok[0] == 'DEFAULT': - name = S['default'] + name = 'default' else: self.NextToken() if self.tok[0] != 'IDENT': @@ -1556,7 +1533,8 @@ class parser(object): if name in self.symtab[self.scopeindex]: raise EParseAlreadyDefined(self) - self.symtab[self.scopeindex][name] = (self.order(), S['State']) # to expand later + assert self.scopeindex == 0 + self.AddSymbol('s', 0, name, Loc=len(self.tree)) self.NextToken() self.expect('{') @@ -1565,7 +1543,7 @@ class parser(object): events = self.Parse_events() self.expect('}') - self.symtab[self.scopeindex][name] += (events,) + self.tree.append({'node':'STATEDEF', 'type':None, 'name':name, 'br':events}) self.NextToken() def Parse_script(self): @@ -1576,6 +1554,11 @@ class parser(object): script: globals states EOF """ + # We need a table of undefined jump references, to check later, + # as jumps are local, not global, and allow forward definitions. + # This avoids making one more pass, or making the first pass more + # detailed unnecessarily. + self.jump_lookups = [] self.Parse_globals() self.Parse_states() @@ -1587,7 +1570,9 @@ class parser(object): if self.FindSymbolPartial(tgt[0], MustBeLabel = True) is None: self.errorpos = tgt[2] raise EParseUndefined(self) + tgt[3]['scope'] = tgt[1] + del self.jump_lookups # Finished with it. def BuildTempGlobalsTable(self): """Build an approximate globals table. @@ -1595,8 +1580,7 @@ class parser(object): If the script syntax is correct, the globals table will be accurate. If it is not, it may contain too many or too few symbols (normally the latter). This globals table is not the normal globals in the symbol - table; it's just needed to resolve which names are declared at all as - globals and their type. It's temporary. + table; it's just needed to resolve forward references. It's temporary. The grammar is approximately: script: globals states @@ -1621,7 +1605,7 @@ class parser(object): while self.tok[0] not in ('DEFAULT', 'EOF'): typ = None if self.tok[0] == 'TYPE': - typ = S[self.tok[1]] + typ = self.tok[1] self.NextToken() if self.tok[0] != 'IDENT': return ret @@ -1635,7 +1619,7 @@ class parser(object): while True: if self.tok[0] != 'TYPE': return ret - params.append(S[self.tok[1]]) + params.append(self.tok[1]) self.NextToken() self.NextToken() # not interested in parameter names if self.tok[0] != ',': @@ -1653,12 +1637,14 @@ class parser(object): elif self.tok[0] == '}': bracelevel -= 1 self.NextToken() - ret[name] = (typ, tuple(params)) + ret[name] = {'Kind':'f','Type':typ,'ParamTypes':params} elif typ is None: return ret # A variable needs a type else: - ret[name] = typ + # No location info but none is necessary for forward + # declarations. + ret[name] = {'Kind':'v','Type':typ,'Scope':0} while self.tok[0] != ';': # Don't stop to analyze what's before the ending ';' self.NextToken() @@ -1677,9 +1663,10 @@ class parser(object): return ret name = self.tok[1] else: - name = S['default'] + name = 'default' - ret[name] = S['State'] + # No location info but none is necessary for forward declarations. + ret[name] = {'Kind':'s'} self.NextToken() if self.tok[0] != '{': @@ -1734,20 +1721,22 @@ class parser(object): # TODO: Enable brackets for list elements e.g. (float)mylist[3], or mylist[5]=4 #self.lazylists = 'lazylists' in options - del options # no longer needed - # Symbol table: # This is a list of all local and global symbol tables. # The first element (0) is the global scope. Each symbol table is a # dictionary. Element -1 of the dictionary is the parent index. The - # entries are lists of three or four values. The first one is the - # order; the second is the type, the third is the value, and if it's - # a function, the fourth is the parameter list. Functions contain a - # parse tree as their value. - self.symtab = [{-1: None}] - self.scopeindex = 0 + # rest of entries are dictionaries. Each has a 'Kind', which can be + # 'v' for variable, 'f' for function, 'l' for label, 's' for state, + # or 'e' for event. + # Variables have 'Scope', 'Type', and if global, 'Loc'. + # Functions have 'Type', 'Loc', 'ParamTypes' and 'ParamNames'. + # Labels only have 'Scope'. + # States only have 'Loc'. + # Events have 'ParamTypes' and 'ParamNames'. + # Other modules may add information if they need. - self.dictorder = 0 + self.symtab = [{-1: None},] + self.scopeindex = 0 # This is a small hack to prevent circular definitions in globals when # extended expressions are enabled. When false (default), forward @@ -1779,20 +1768,20 @@ class parser(object): self.globals = self.BuildTempGlobalsTable() - # We need a table of undefined jump references anyway, to check later, - # as jumps are local, not global, and allow forward definitions. - self.jump_lookups = [] - # Restart self.pos = 0 self.tok = self.GetToken() + self.tree = [] + # Start the parsing proper self.Parse_script() del self.globals # No longer needed. The data that is not in self.functions is in self.symtab[0]. - del self.jump_lookups # Already used. + + # Insert library functions into symbol table + self.symtab[0].update(self.functions) #while self.tok[0] != 'EOF': # print self.tok @@ -1805,7 +1794,11 @@ class parser(object): # print repr(j[0]) + ':' + repr(j[1]) + ',', # print '}' - return self.symtab + treesymtab = self.tree, self.symtab + del self.tree + del self.symtab + + return treesymtab def parsefile(self, filename, options = set()): """Convenience function to parse a file""" @@ -1874,7 +1867,8 @@ class parser(object): # reference to the implementation; otherwise None. if name in self.functions: warning('Function already defined in bultins.txt, overwriting: ' + name) - self.functions[name] = (typ, tuple(args), getattr(lslfuncs, name, None)) + self.functions[name] = {'Kind':'f', 'Type':typ, 'ParamTypes':args, + 'Loc':getattr(lslfuncs, name, None)} elif match.group(4): # constant name = match.group(5) diff --git a/main.py b/main.py index e994796..c50e962 100644 --- a/main.py +++ b/main.py @@ -64,7 +64,7 @@ means that e.g. a + 3 + 5 is not optimized to a + 8; however a + (3 + 5) is. return 1 optchanges = sys.argv[2].split(',') for chg in optchanges: - if chg[0:1] != '+': + if chg[0:1] not in ('+', '-'): chg = '+' + chg if chg[0] == '-': 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: if fname == '-': script = sys.stdin.read() - p.parse(script, options) + ts = p.parse(script, options) else: - p.parsefile(fname, options) - funcs = p.functions - symtab = p.symtab + ts = p.parsefile(fname, options) except EParse as e: print e.message return 1 del p opt = optimizer() - opt.optimize(symtab, funcs, options) + ts = opt.optimize(ts, options) del opt outs = outscript() - script = outs.output(symtab, options) + script = outs.output(ts, options) del outs - del symtab + del ts sys.stdout.write(script) return 0 diff --git a/testparser.py b/testparser.py index abee123..6dd7d08 100644 --- a/testparser.py +++ b/testparser.py @@ -1,6 +1,6 @@ from lslopt.lslparse import parser,EParseSyntax,EParseUEOF,EParseAlreadyDefined,\ EParseUndefined,EParseTypeMismatch,EParseReturnShouldBeEmpty,EParseReturnIsEmpty,\ - EParseInvalidField,EParseFunctionMismatch,EParseDeclarationScope,EParseUnexpected,\ + EParseInvalidField,EParseFunctionMismatch,EParseDeclarationScope,\ fieldpos from lslopt.lsloutput import outscript from lslopt.lsloptimizer import optimizer @@ -69,8 +69,8 @@ class Test02_Compiler(UnitTestCase): float f; float ff = f; list L = []; - list L2 = [2,3,4,5,6]; - list L3 = [2,3,f,5,6]; + list L2 = [2,3,4,5,-6]; + list L3 = [2,3,f,5,-6.0]; rotation QQ = ; 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; @@ -88,7 +88,7 @@ class Test02_Compiler(UnitTestCase): 1e37;1.1e22;1.; print(V *= 3); fwd("","",""); - L"\n\t\rxxxx"; + L"\n\t\rxxxx";@lbl;jump lbl; {f;} [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(){3%<2,3,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(){key k;k+=k;}''') self.assertRaises(EParseTypeMismatch, self.parser.parse, '''f(){string i;i++;}''') @@ -213,7 +215,7 @@ class Test02_Compiler(UnitTestCase): 'skippreproc'] )) 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(self.outscript.Value2LSL(lslfuncs.Key(u'')), '((key)"")') @@ -235,8 +237,11 @@ class Test03_Optimizer(UnitTestCase): float g = f; string s = "1" "2"; list L = [(key)""]; + list L1 = L; + list L2 = [1,2,3,4,5,6.0]; + list L3 = []; 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()>; float ffff=vvvv.x; vector vvvv2=vvvv; @@ -269,14 +274,14 @@ class Test03_Optimizer(UnitTestCase): ['explicitcast','extendedtypecast','extendedassignment', 'extendedglobalexpr', 'allowmultistrings', 'allowkeyconcat'] ) - self.opt.optimize(p, self.parser.functions) - self.opt.optimize(p, self.parser.functions, ()) + self.opt.optimize(p) + self.opt.optimize(p, ()) print self.outscript.output(p) p = self.parser.parse('''string s = llUnescapeURL("%09");default{timer(){float f=llSqrt(-1);}}''', ['explicitcast','extendedtypecast','extendedassignment', 'extendedglobalexpr', 'allowmultistrings', 'allowkeyconcat'] ) - self.opt.optimize(p, self.parser.functions, ['optimize','foldtabs']) + self.opt.optimize(p, ['optimize','foldtabs']) print self.outscript.output(p) def test_regression(self): p = self.parser.parse(''' @@ -284,7 +289,7 @@ class Test03_Optimizer(UnitTestCase): x() { if (1) { string s = "x"; s = s + (string)a; } } default { timer() { } } ''', ['extendedassignment']) - self.opt.optimize(p, self.parser.functions) + self.opt.optimize(p) self.outscript.output(p) p = self.parser.parse(''' key k = "blah"; @@ -294,10 +299,11 @@ class Test03_Optimizer(UnitTestCase): default{timer(){}} ''', ['extendedassignment']) - self.opt.optimize(p, self.parser.functions) + self.opt.optimize(p) out = self.outscript.output(p) + print out self.assertEqual(out, 'key k = "blah";\nlist L = [k, "xxxx", 1.];\n' - 'float f;\nvector v = ;\ndefault\n{\n timer()\n' + 'float f;\nvector v = <0, 3, 4>;\ndefault\n{\n timer()\n' ' {\n }\n}\n') def tearDown(self):