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