mirror of
https://github.com/Sei-Lisa/LSL-PyOptimizer
synced 2025-07-01 23:58:20 +00:00
The algorithm for adding parentheses around unary operators was not working properly. It converted a * (-b) * c into a * -b * c, which LSL handles as a * -(b * c). Fix and add test cases for that. One of the test cases shows an example where the difference matters: 0 * (-1e20) * 1e20 should result in 0.0, but if wrongly parenthesized, it gives NaN, because 1e20*1e20 gives infinity due to float overflow, and minus infinity times 0 is indeterminate. The addition of parentheses has been improved, but it still does not eliminate every redundant parenthesis. Also fix the horrendous typo of using "operands" where it should be "operators".
559 lines
23 KiB
Python
559 lines
23 KiB
Python
# (C) Copyright 2015-2019 Sei Lisa. All rights reserved.
|
|
#
|
|
# This file is part of LSL PyOptimizer.
|
|
#
|
|
# LSL PyOptimizer is free software: you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License as
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
# License, or (at your option) any later version.
|
|
#
|
|
# LSL PyOptimizer is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with LSL PyOptimizer. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
# Convert an abstract syntax tree + symbol table back to a script as text.
|
|
|
|
from lslopt import lslfuncs
|
|
from lslopt import lslcommon
|
|
from lslopt.lslcommon import Key, Vector, Quaternion, warning
|
|
from math import copysign
|
|
|
|
debugScopes = False
|
|
|
|
class outscript(object):
|
|
|
|
binary_operators = frozenset(('||','&&','^','|','&','==','!=','<','<=','>',
|
|
'>=','<<','>>','+','-','*','/','%', '=', '+=', '-=', '*=', '/=','%=',
|
|
))
|
|
extended_assignments = frozenset(('&=', '|=', '^=', '<<=', '>>='))
|
|
unary_operators = 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, }
|
|
assignment_ops = ('=', '+=', '-=', '*=', '/=','%=')
|
|
|
|
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(u"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 u'\t' in value and self.warntabs:
|
|
warning(u"A string contains a tab. Tabs are expanded to four"
|
|
" spaces by the viewer when copy-pasting the code"
|
|
" (disable this warning by disabling the 'warntabs'"
|
|
" option).")
|
|
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.optfloats and value.is_integer() and -2147483648.0 <= value < 2147483648.0:
|
|
if self.globalmode and not self.listmode:
|
|
if value == 0 and copysign(1, value) == -1:
|
|
return '-0.'
|
|
return str(int(value))
|
|
elif not self.globalmode:
|
|
# Important inside lists!!
|
|
if value == 0 and copysign(1, value) == -1:
|
|
return '(-(float)0)'
|
|
return '((float)' + str(int(value)) + ')'
|
|
s = repr(value)
|
|
if s == 'nan':
|
|
return '(1e40*0)' if copysign(1, value) < 0 else '(-1e40*0)'
|
|
if s == 'inf':
|
|
return '1e40'
|
|
if s == '-inf':
|
|
return '-1e40' if self.globalmode else '((float)-1e40)'
|
|
# 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[:-1]
|
|
neg = ''
|
|
if s[0] == '-':
|
|
news = news[1:]
|
|
neg = '-'
|
|
# Try harder
|
|
point = news.index('.') + 1 - len(news)
|
|
if point:
|
|
news = str(int(news[:point-1] + news[point:]) + 1).zfill(len(news)-1) # Increment
|
|
else:
|
|
news = str(int(news[:-1])+1).zfill(len(news)-1)
|
|
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+exp)) == value:
|
|
# Success! But we try even harder. We may have converted
|
|
# 9.9999e3 into 10.e3; that needs to be turned into 1.e4.
|
|
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+newexp)) == value:
|
|
news = news2
|
|
exp = newexp
|
|
s = neg+news
|
|
if exp and s[-1] == '.':
|
|
s = s[:-1] # transfrom e.g. 1.e-30 into 1e-30
|
|
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:
|
|
save_listmode = self.listmode
|
|
self.listmode = True
|
|
ret = '[' + self.Value2LSL(value[0])
|
|
for elem in value[1:]:
|
|
ret += ', ' + self.Value2LSL(elem)
|
|
ret += ']'
|
|
self.listmode = save_listmode
|
|
return ret
|
|
ret = '' if lslcommon.IsCalc else '\n'
|
|
first = True
|
|
self.indentlevel += 0 if lslcommon.IsCalc else 1
|
|
for entry in value:
|
|
ret += self.dent() + ('[ ' if first else ', ')
|
|
save_listmode = self.listmode
|
|
self.listmode = True
|
|
ret += self.Value2LSL(entry) + '\n'
|
|
self.listmode = save_listmode
|
|
first = False
|
|
ret += self.dent()
|
|
self.indentlevel -= 0 if lslcommon.IsCalc else 1
|
|
return ret + ']'
|
|
|
|
assert False, u'Value of unknown type in Value2LSL: ' + repr(value)
|
|
|
|
def dent(self):
|
|
return self.indent * self.indentlevel
|
|
|
|
def FindName(self, node, scope = None):
|
|
if scope is None:
|
|
# node is a node
|
|
if (hasattr(node, 'scope')
|
|
and 'NewName' in self.symtab[node.scope][node.name]):
|
|
return self.symtab[node.scope][node.name]['NewName']
|
|
if node.nt == 'FNCALL' and 'NewName' in self.symtab[0][node.name]:
|
|
return self.symtab[0][node.name]['NewName']
|
|
|
|
return node.name
|
|
# node is a name
|
|
if 'NewName' in self.symtab[scope][node]:
|
|
return self.symtab[scope][node]['NewName']
|
|
return node
|
|
|
|
def OutIndented(self, node):
|
|
if node.nt != '{}':
|
|
self.indentlevel += 1
|
|
ret = self.OutCode(node)
|
|
if node.nt != '{}':
|
|
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)
|
|
nt = expr.nt
|
|
child = expr.ch
|
|
|
|
if nt in self.binary_operators:
|
|
lnt = child[0].nt
|
|
lparen = False
|
|
rnt = child[1].nt
|
|
rparen = False
|
|
if nt in self.assignment_ops and nt in self.op_priority:
|
|
# Assignment is right-associative, so it needs to be dealt with
|
|
# separately.
|
|
base_pri = self.op_priority[nt]
|
|
if rnt in self.op_priority:
|
|
if self.op_priority[rnt] < base_pri: # should never happen
|
|
rparen = True
|
|
elif 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
|
|
|
|
# This situation has ugly cases due to the strange precedence
|
|
# of unary minus. Consider the following two statements, paying
|
|
# attention to the binding power of the binary not ~ operator:
|
|
# (~-a) * a
|
|
# a * (~-a) * a
|
|
# In one case, the (~-a) is a left child; in the other, it's
|
|
# part of a right child. In both, cases, the parentheses are
|
|
# mandatory, or they would be interpreted respectively as:
|
|
# ~-(a * a)
|
|
# a * ~-(a * a)
|
|
# Yet the tree structure makes it quite hard to detect these.
|
|
# We have to descend down the left nodes as we keep finding
|
|
# binary operators or unary operators, to find whether the
|
|
# symbol is followed by unary minus. If that's the case, we
|
|
# need to act as if there was a negation right here.
|
|
elif lnt in ('~', '!'):
|
|
lnode = child[0]
|
|
while True:
|
|
lnode = lnode.ch[0]
|
|
if (lnode.nt not in self.op_priority
|
|
and lnode.nt not in ('~', '!')
|
|
):
|
|
break
|
|
if lnode.nt == 'NEG' and base_pri > self.op_priority['-']:
|
|
lparen = True
|
|
|
|
if rnt in self.op_priority:
|
|
if self.op_priority[rnt] <= base_pri:
|
|
rparen = True
|
|
elif rnt == 'NEG' and self.op_priority['-'] < base_pri:
|
|
rparen = True
|
|
# see above
|
|
elif rnt in ('~', '!'):
|
|
lnode = child[1]
|
|
while True:
|
|
# Descend down the left nodes of the right node, to
|
|
# find whether it's immediately followed by -
|
|
lnode = lnode.ch[0]
|
|
if lnode.nt not in self.op_priority and lnode.nt not in ('~', '!'):
|
|
break
|
|
if lnode.nt == 'NEG' and base_pri > self.op_priority['-']:
|
|
# TODO: Improve right operand parenthesis removal
|
|
# for minus signs.
|
|
# Shouldn't this look into the RHS node?
|
|
# a * -b * c
|
|
# is currently translated to
|
|
# a * (-b * c)
|
|
# which is correct but not optimal.
|
|
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':
|
|
if debugScopes:
|
|
return self.FindName(expr) + '\\' + str(expr.scope) + '/'
|
|
return self.FindName(expr)
|
|
|
|
if nt == 'CONST':
|
|
if (self.foldconst and expr.t == 'list' and len(expr.value) == 1
|
|
and not self.globalmode):
|
|
return '(list)' + self.Value2LSL(expr.value[0])
|
|
return self.Value2LSL(expr.value)
|
|
|
|
if nt == 'CAST' or self.foldconst and nt in ('LIST', 'CONST') and len(child)==1 and not self.globalmode:
|
|
ret = '(' + expr.t + ')'
|
|
expr = child[0]
|
|
if expr.nt in ('CONST', 'IDENT', 'V++', 'V--', 'VECTOR',
|
|
'ROTATION', 'LIST', 'FIELD', 'PRINT', 'FNCALL'):
|
|
if expr.nt != 'LIST' or len(expr.ch) != 1:
|
|
return ret + self.OutExpr(expr)
|
|
return ret + '(' + self.OutExpr(expr) + ')'
|
|
|
|
if nt == 'LIST':
|
|
self.listmode = True
|
|
if len(child) < 5:
|
|
ret = '[' + self.OutExprList(child) + ']'
|
|
else:
|
|
self.indentlevel += 0 if lslcommon.IsCalc else 1
|
|
ret = '' if lslcommon.IsCalc else '\n'
|
|
first = True
|
|
for elem in child:
|
|
ret += self.dent() + ('[ ' if first else ', ')
|
|
ret += self.OutExpr(elem) + '\n'
|
|
first = False
|
|
ret += self.dent() + ']'
|
|
self.indentlevel -= 0 if lslcommon.IsCalc else 1
|
|
self.listmode = False
|
|
return ret
|
|
|
|
if nt in ('VECTOR', 'ROTATION'):
|
|
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) + ')'
|
|
|
|
if nt == 'PRINT':
|
|
return 'print(' + self.OutExpr(child[0]) + ')'
|
|
|
|
if nt in self.unary_operators:
|
|
ret = nt
|
|
lnt = child[0].nt
|
|
paren = False
|
|
if nt == 'NEG':
|
|
ret = '-'
|
|
if (lnt == 'CONST' and child[0].t == 'integer'
|
|
and child[0].value < 0
|
|
):
|
|
# shortcut
|
|
ret += str(child[0].value + 4294967296)
|
|
return ret
|
|
if lnt in self.op_priority:
|
|
paren = self.op_priority[lnt] <= self.op_priority['-']
|
|
elif (lnt == 'NEG' or lnt == '--V'
|
|
or lnt == 'CONST'
|
|
and child[0].t == 'float'
|
|
and child[0].value < 0
|
|
):
|
|
ret += ' ' # don't output "--" as that's a different token
|
|
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
|
|
|
|
if nt in ('V--', 'V++'):
|
|
return self.OutExpr(child[0]) + ('++' if nt == 'V++' else '--')
|
|
|
|
if nt in ('--V', '++V'):
|
|
return ('++' if nt == '++V' else '--') + self.OutExpr(child[0])
|
|
|
|
if nt in self.extended_assignments:
|
|
lvalue = self.OutExpr(child[0])
|
|
return lvalue + ' = ' + lvalue + ' ' + nt[:-1] + ' (' + self.OutExpr(child[1]) + ')'
|
|
|
|
if nt == 'EXPRLIST':
|
|
return self.OutExprList(child)
|
|
|
|
if nt == 'SUBIDX':
|
|
return '(MISSING TYPE)' + self.OutExpr(child[0]) + '[' + self.OutExprList(child[1:]) + ']'
|
|
|
|
assert False, 'Internal error: expression type "' + nt + '" not handled' # pragma: no cover
|
|
|
|
def OutCode(self, node):
|
|
nt = node.nt
|
|
child = node.ch
|
|
|
|
if nt == 'IF':
|
|
ret = self.dent()
|
|
while True:
|
|
ret += 'if (' + self.OutExpr(child[0]) + ')\n'
|
|
# Do we need to add braces around the THEN side?
|
|
needs_braces = False
|
|
if len(child) == 3:
|
|
testnode = child[1]
|
|
# Find last IF in an ELSE IF chain
|
|
while testnode.nt == 'IF' and len(testnode.ch) == 3:
|
|
testnode = testnode.ch[2]
|
|
if testnode.nt == 'IF':
|
|
# hit an IF without ELSE at the end of the chain
|
|
needs_braces = True
|
|
if needs_braces:
|
|
ret += self.dent() + '{\n'
|
|
ret += self.OutIndented(child[1])
|
|
ret += self.dent() + '}\n'
|
|
else:
|
|
ret += self.OutIndented(child[1])
|
|
if len(child) < 3:
|
|
return ret
|
|
if child[2].nt != 'IF':
|
|
ret += self.dent() + 'else\n' + self.OutIndented(child[2])
|
|
return ret
|
|
ret += self.dent() + 'else '
|
|
node = child[2]
|
|
child = node.ch
|
|
if nt == 'WHILE':
|
|
ret = self.dent() + 'while (' + self.OutExpr(child[0]) + ')\n'
|
|
ret += self.OutIndented(child[1])
|
|
return ret
|
|
if nt == 'DO':
|
|
ret = self.dent() + 'do\n'
|
|
ret += self.OutIndented(child[0])
|
|
return ret + self.dent() + 'while (' + self.OutExpr(child[1]) + ');\n'
|
|
if nt == '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 nt == '@':
|
|
if debugScopes:
|
|
return self.dent() + '@' + self.FindName(node) \
|
|
+ '\\' + str(node.scope) + '/;\n'
|
|
return self.dent() + '@' + self.FindName(node) + ';\n'
|
|
if nt == 'JUMP':
|
|
if debugScopes:
|
|
return self.dent() + 'jump ' + self.FindName(node) \
|
|
+ '\\' + str(node.scope) + '/;\n'
|
|
return self.dent() + 'jump ' + self.FindName(node) + ';\n'
|
|
if nt == 'STSW':
|
|
return self.dent() + 'state ' + self.FindName(node) + ';\n'
|
|
if nt == 'RETURN':
|
|
if child:
|
|
return self.dent() + 'return ' + self.OutExpr(child[0]) + ';\n'
|
|
return self.dent() + 'return;\n'
|
|
if nt == 'DECL':
|
|
ret = self.dent() + node.t + ' ' + self.FindName(node)
|
|
if debugScopes:
|
|
ret += '\\' + str(node.scope) + '/'
|
|
if child:
|
|
if hasattr(child[0], 'orig') and (child[0].orig.nt != 'IDENT'
|
|
or child[0].orig.name
|
|
in self.symtab[child[0].orig.scope]):
|
|
ret += ' = ' + self.OutExpr(child[0].orig)
|
|
else:
|
|
ret += ' = ' + self.OutExpr(child[0])
|
|
return ret + ';\n'
|
|
if nt == ';':
|
|
return self.dent() + ';\n'
|
|
|
|
if nt in ('STDEF', '{}'):
|
|
ret = ''
|
|
if nt == 'STDEF':
|
|
if node.name == 'default':
|
|
ret = self.dent() + 'default\n'
|
|
else:
|
|
ret = self.dent() + 'state ' + self.FindName(node) + '\n'
|
|
|
|
ret += self.dent() + '{\n'
|
|
self.indentlevel += 1
|
|
firstnode = True
|
|
for stmt in node.ch:
|
|
if stmt.nt == 'LAMBDA':
|
|
continue
|
|
if nt == 'STDEF' and not firstnode:
|
|
ret += '\n'
|
|
ret += self.OutCode(stmt)
|
|
firstnode = False
|
|
self.indentlevel -= 1
|
|
return ret + self.dent() + '}\n'
|
|
|
|
if nt == 'FNDEF':
|
|
ret = self.dent()
|
|
if node.t is not None:
|
|
ret += node.t + ' '
|
|
ret += self.FindName(node) + '('
|
|
scope = node.pscope
|
|
ret += ', '.join(typ + ' ' + self.FindName(name, scope)
|
|
for typ, name in zip(node.ptypes, node.pnames))
|
|
return ret + ')\n' + self.OutCode(child[0])
|
|
|
|
if nt == 'EXPR':
|
|
return self.dent() + self.OutExpr(child[0]) + (
|
|
';\n' if not lslcommon.IsCalc else '')
|
|
|
|
if nt == 'LAMBDA':
|
|
return ''
|
|
|
|
assert False, "Internal error: node type not handled: " + nt # pragma: no cover
|
|
|
|
def output(self, treesymtab, options = ('optimize',
|
|
'optsigns','optfloats','warntabs')):
|
|
# Build a sorted list of dict entries
|
|
self.tree, self.symtab = treesymtab
|
|
|
|
# Grab options
|
|
self.optimize = 'optimize' in options
|
|
# These are optimization options that depend on the above:
|
|
self.optsigns = self.optimize and 'optsigns' in options
|
|
self.optfloats = self.optimize and 'optfloats' in options
|
|
self.foldconst = self.optimize and 'constfold' in options
|
|
|
|
self.warntabs = 'warntabs' in options
|
|
|
|
ret = ''
|
|
self.indent = ' '
|
|
self.indentlevel = 0
|
|
self.globalmode = False
|
|
self.listmode = False
|
|
firstnode = True
|
|
prevnt = None
|
|
for node in self.tree:
|
|
if node.nt == 'LAMBDA':
|
|
# these don't produce output, skip
|
|
continue
|
|
if not firstnode and (node.nt != 'DECL' or prevnt != 'DECL'):
|
|
ret += '\n'
|
|
self.globalmode = node.nt == 'DECL'
|
|
ret += self.OutCode(node)
|
|
self.globalmode = False
|
|
firstnode = False
|
|
prevnt = node.nt
|
|
|
|
return ret
|