示例#1
0
    def LastPassPostOrder(self, parent, index):
        node = parent[index]
        nt = node.nt
        child = node.ch

        if nt == 'FNDEF':
            if hasattr(node, 'scope') and self.BadStCh:
                # There is at least one bad state change statement in the
                # function (must be the result of optimization).
                # Insert dummy IF(1){...} statement covering the whole function
                # (if it returs a value, insert a return statement too).
                child[0] = nr(nt='{}',
                              t=None,
                              scope=len(self.symtab),
                              ch=[
                                  nr(nt='IF',
                                     t=None,
                                     ch=[
                                         nr(nt='CONST',
                                            t='integer',
                                            value=1,
                                            SEF=True), child[0]
                                     ])
                              ])
                self.symtab.append({})
                child = node.ch
                if node.t is not None:
                    # Inserting a state switch in a function that returns a
                    # value must count as one of the dumbest things to do.
                    # We do as best as we can: add a return statement with the
                    # default value for the type.
                    child[0].ch.append(
                        nr(nt='RETURN',
                           t=None,
                           ch=[
                               nr(nt='CONST',
                                  t=node.t,
                                  SEF=True,
                                  value=lslcommon.LSLTypeDefaults[node.t])
                           ]))
            del self.BadStCh
            return

        if nt == 'EXPR':
            self.inExpr = False

        if nt == '{}':
            self.scopeStack.pop()

        if nt == 'WHILE' or nt == 'DO' or nt == 'FOR':
            self.prevStmt = self.lastStmt
            self.lastStmt = rec(node=node, parent=parent, index=index)
 def Cast(self, value, newtype):
     """Return a CAST node if the types are not equal, otherwise the
     value unchanged.
     """
     if value.t == newtype:
         return value
     ret = nr(nt='CAST', t=newtype, ch=[value], SEF=value.SEF)
     if value.SEF:
         ret.SEF = True
     if hasattr(value, 'X'):
         ret.X = value.X
     return ret
示例#3
0
def CastDL2S(self, node, index):
    """Cast a list element to string, wrapping it in a list if it's a vector or
    rotation.
    """
    elem = self.GetListNodeElement(node, index)
    assert elem is not False
    if type(elem) != nr:
        elem = nr(nt='CONST',
                  t=lslcommon.PythonType2LSL[type(elem)],
                  SEF=True,
                  value=elem)
    if elem.t in ('vector', 'rotation'):
        return self.Cast(self.Cast(elem, 'list'), 'string')
    return self.Cast(elem, 'string')
    def RecurseSingleStatement(self, parent, index, scope):
        # Synthesize a block node whose child is the statement.
        newscope = self.newSymtab()
        node = nr(nt='{}',
                  t=None,
                  scope=newscope,
                  ch=[parent[index]],
                  SEF=parent[index].SEF)

        # Recurse into that node, so that any additions are made right there.
        self.RecurseStatement(node.ch, 0, newscope)

        # If it's no longer a single statement, promote it to a block.
        if len(node.ch) != 1:
            parent[index] = node
        else:
            # The new level won't be necessary. We can't delete the symbol
            # table, though, because that shifts any new symbol tables.
            assert not self.symtab[newscope]
            parent[index] = node.ch[0]
    def inline(self, tree, symtab):
        self.tree = tree
        self.symtab = symtab
        self.symCtr = {}
        self.expanding = []
        for i in range(len(tree)):
            if tree[i].nt == 'STDEF':
                for j in range(len(tree[i].ch)):  # for each event in the state
                    self.RecurseStatement(tree[i].ch[j].ch, 0,
                                          tree[i].ch[j].ch[0].scope)
            elif (tree[i].nt == 'FNDEF'
                  and not symtab[tree[i].scope][tree[i].name].get(
                      'Inline', False)):
                # Must be an UDF
                self.RecurseStatement(tree[i].ch, 0, tree[i].ch[0].scope)

        # Remove all inline function definitions
        for i in range(len(tree)):
            if (tree[i].nt == 'FNDEF'
                    and symtab[tree[i].scope][tree[i].name].get(
                        'Inline', False)):
                tree[i] = nr(nt='LAMBDA', t=None)
示例#6
0
    def LastPassPreOrder(self, parent, index):
        node = parent[index]
        nt = node.nt
        child = node.ch

        if (nt != '{}' and nt != ';' and nt != 'LAMBDA' and nt != '@'
                and not self.inExpr):
            # Node that generates code.
            # EXPR counts as a single statement for JUMP purposes.
            self.prevStmt = self.lastStmt
            self.lastStmt = rec(node=node, parent=parent, index=index)
            # These are all the labels that label the current statement
            self.lastLabels = self.curLabels
            self.curLabels = set()

        if nt == 'EXPR':
            self.inExpr = True
            return

        if (self.optlistadd and not self.globalmode
                and (nt == 'CONST' and node.t == 'list' or nt == 'LIST'
                     or nt == '+' and child[0].t == 'list' and
                     (child[1].nt == 'CONST' and child[1].t == 'list'
                      or child[1].nt == 'LIST'))):
            # Perform these transformations if the list is SEF:
            # [a, b, ...] -> (list)a + b...
            # ListExpr + [a, b, ..]  ->  ListExpr + a + b...
            # (ListExpr doesn't need to be SEF for this to work)

            # This transformation makes it difficult to handle lists during
            # optimization, that's why it's done in a separate pass.

            # Make listnode be the LIST or CONST element.
            listnode = child[1] if nt == '+' else node

            # left is the leftmost element in the addition.
            # left = None means the first element of the list must be
            # turned into a cast within the loop.
            left = child[0] if nt == '+' else None
            if listnode.SEF:
                for elem in (listnode.value
                             if listnode.nt == 'CONST' else listnode.ch):
                    elemnode = nr(nt='CONST',
                                  t=lslcommon.PythonType2LSL[type(elem)],
                                  value=elem,
                                  SEF=True) if listnode.nt == 'CONST' else elem
                    left = (self.Cast(elemnode, 'list') if left is None else
                            nr(nt='+', t='list', SEF=True, ch=[left, elemnode
                                                               ]))
                    del elemnode
                if left is not None:  # it's none for empty lists
                    parent[index] = left
                    # Do another pass on the result
                    self.RecursiveLastPass(parent, index)
                return

            del listnode, left

        if nt == 'FNDEF':
            # StChAreBad will be True if this is a user-defined function,
            # where state changes are considered bad.
            # BadStCh will be True if at least one state change statement
            # is found while monitoring state changes.
            self.subinfo['StChAreBad'] = hasattr(node, 'scope')
            self.BadStCh = False
            return

        if nt == 'IF':
            if len(child) == 2:
                # Don't monitor the children.
                self.subinfo['StChAreBad'] = False
            return

        if nt == 'DO':
            self.subinfo['StChAreBad'] = False
            return

        if nt == 'FOR':
            self.subinfo['StChAreBad'] = False
            return

        if nt == 'WHILE':
            self.subinfo['StChAreBad'] = False
            return

        if nt == 'STSW':
            if self.subinfo['StChAreBad']:
                # Found one.
                self.BadStCh = True
            return

        if nt == 'FNCALL':
            # lslrenamer can benefit from a list of used library functions,
            # so provide it.
            if 'Loc' not in self.symtab[0][node.name]:
                # system library function
                self.usedlibfuncs.add(node.name)

        if nt == 'JUMP':
            if self.lastLabels:
                # This jump is labeled. The jumps that go to any of its labels
                # might be changeable to the destination of this jump, if the
                # scope of the destination is visible from those jumps, and the
                # jump eliminated.
                # TODO: We need the positions of these jumps for this to work.
                # We could perhaps change 'ref' in the symbol to a list instead
                # of a counter, pointing to the jumps.
                pass

        if nt == '@':
            self.curLabels.add(node)
            if self.lastStmt.node.nt == 'JUMP':
                jump = self.lastStmt.node
                if jump.scope == node.scope and jump.name == node.name:
                    # Remove the jump
                    self.lastStmt.parent[self.lastStmt.index] = nr(nt=';')
                    assert self.symtab[node.scope][node.name]['ref'] > 0
                    self.symtab[node.scope][node.name]['ref'] -= 1

        if nt == '{}':
            self.scopeStack.append(node.scope)
示例#7
0
def OptimizeFunc(self, parent, index):
    """Look for possible optimizations taking advantage of the specific LSL
    library function semantics.
    """
    node = parent[index]
    assert node.nt == 'FNCALL'
    name = node.name
    child = node.ch
    if self.optlistlength and name == 'llGetListLength':
        # Convert llGetListLength(expr) to (expr != [])
        node = nr(nt='CONST', t='list', value=[], SEF=True)
        parent[index] = node = nr(nt='!=',
                                  t='integer',
                                  ch=[child[0], node],
                                  SEF=child[0].SEF)

    if name == 'llDumpList2String':
        assert child[0].t == 'list'
        if (child[1].nt == 'CONST' and child[1].t in ('string', 'key')
                and child[1].value == u""):
            # Convert llDumpList2String(expr, "") to (string)(expr)
            node.nt = 'CAST'
            del child[1]
            del node.name
            return

        list_len = self.GetListNodeLength(child[0])
        if list_len is not False and list_len == 1 and child[1].SEF:
            # A single-element list can always be transformed regardless of
            # the presence of function calls with side effects
            parent[index] = CastDL2S(self, child[0], 0)
            return

        if node.SEF:
            # Attempt to convert the function call into a sum of strings when
            # possible and productive.

            if list_len is False:
                # Can't identify the length, which means we can't optimize.
                return

            if list_len == 0:
                # Empty list -> empty string, no matter the separator
                # (remember we're SEF).
                parent[index] = nr(nt='CONST', t='string', value=u'', SEF=True)
                return

            # Only optimize if the second param is a very simple expression,
            # otherwise the sums can get large.
            if child[1].nt in ('CONST', 'IDENT'):
                threshold = 10
            else:
                return

            # Apply a threshold for optimizing as a sum.
            if list_len > threshold:
                return

            elems = [
                self.GetListNodeElement(child[0], i) for i in range(list_len)
            ]

            # Don't optimize if an element can't be extracted or is a list
            if any(i is False or type(i) == nr and i.t == 'list'
                   for i in elems):
                return

            # We reorder list constructors as right-to-left sums. When an
            # element contains function calls, it will generate a savepoint,
            # but with that strategy, the maximum extra stack size at the time
            # of each savepoint is 1.
            # If the first element has a function call, we may end up causing
            # more memory usage, because in a list constructor, the first
            # element has no stack to save; however, if any elements past the
            # third have function calls at the same time, the memory we add
            # will be compensated by the memory we save, because the 3rd
            # element has 2 elements in the stack, therefore reducing it to 1
            # is a save; similarly, any elements past the 3rd containing
            # function calls cause bigger and bigger saves.

            # Since we're also eliminating the llDumpList2String function call,
            # that may count for the extra stack element added. Therefore, we
            # disable this condition and optimize unconditionally.

            #if (child[0].nt in ('LIST', 'CONST') and list_len >= 3
            #    and type(elems[0]) == nr and not FnFree(self, elems[0])
            #    and all(type(i) != nr or FnFree(self, i) for i in elems[2:])
            #   ):
            #    return

            # Optimize to a sum of strings, right-to-left to save stack.
            i = list_len - 1
            newnode = CastDL2S(self, child[0], i)
            while i > 0:
                i -= 1
                newnode = nr(
                    nt='+',
                    t='string',
                    SEF=True,
                    ch=[
                        CastDL2S(self, child[0], i),
                        nr(nt='+',
                           t='string',
                           SEF=True,
                           ch=[self.Cast(child[1], 'string'), newnode])
                    ])
            parent[index] = newnode
            # Re-fold
            self.FoldTree(parent, index)
            return

    if (name in ('llList2String', 'llList2Key', 'llList2Integer',
                 'llList2Float', 'llList2Vector', 'llList2Rot')
            and child[1].nt == 'CONST'):
        # 2nd arg to llList2XXXX must be integer
        assert child[1].t == 'integer'

        listarg = child[0]
        idx = child[1].value
        value = self.GetListNodeElement(listarg, idx)
        tvalue = self.TypeFromNodeOrConst(value)
        const = self.ConstFromNodeOrConst(value)
        if const is not False and node.SEF:
            # Managed to get a constant from a list, even if the
            # list wasn't constant. Handle the type conversion.
            if (node.t[0] + tvalue[0]) in listCompat:
                const = lslfuncs.InternalTypecast(
                    const,
                    lslcommon.LSLType2Python[node.t],
                    InList=True,
                    f32=True)
            else:
                const = defaultListVals[name]

            parent[index] = nr(nt='CONST', t=node.t, value=const, SEF=True)
            return

        if listarg.nt == 'FNCALL' \
           and listarg.name == 'llGetObjectDetails':

            # make it the list argument of llGetObjectDetails
            listarg = listarg.ch[1]
            value = self.GetListNodeElement(listarg, idx)
            tvalue = self.TypeFromNodeOrConst(value)
            const = self.ConstFromNodeOrConst(value)
            if type(const) == int and self.GetListNodeLength(listarg) == 1:
                # Some of these can be handled with a typecast to string.
                if name == 'llList2String':
                    # turn the node into a cast of arg 0 to string
                    node.nt = 'CAST'
                    del child[1]
                    del node.name
                    return
                # The other ones that support cast to string then to
                # the final type in some cases (depending on the
                # list type, which we know) are key/int/float.
                finaltype = objDetailsTypes[const:const + 1]
                if (name == 'llList2Key'  # checked via listCompat
                        or
                    (name == 'llList2Integer' and finaltype in ('s', 'i')
                     )  # won't work for floats
                        or (name == 'llList2Float' and finaltype in ('s', 'i')
                            )  # won't work for floats
                    ) and (node.t[0] + finaltype) in listCompat:
                    # ->  (key)((string)llGetObjectDetails...)
                    # or (integer)((string)llGetObjectDetails...)
                    node.nt = 'CAST'
                    del child[1]
                    del node.name
                    child[0] = self.Cast(child[0], 'string')
                    return

            # Check for type incompatibility or index out of range
            # and replace node with a constant if that's the case
            if (value is False or type(const) == int and
                (node.t[0] + objDetailsTypes[const])
                    not in listCompat) and node.SEF:
                parent[index] = nr(nt='CONST',
                                   t=node.t,
                                   value=defaultListVals[name],
                                   SEF=True)

        elif listarg.nt == 'FNCALL' and listarg.name in (
                'llGetPrimitiveParams', 'llGetLinkPrimitiveParams'):
            # We're going to work with the primitive params list.
            listarg = listarg.ch[0 if listarg.name ==
                                 'llGetPrimitiveParams' else 1]
            length = self.GetListNodeLength(listarg)
            if length is not False:
                # Construct a list (string) of return types.
                # A '*' in the list means the type can't be
                # determined past this point (used with PRIM_TYPE).
                i = 0
                returntypes = ''
                while i < length:
                    param = self.GetListNodeElement(listarg, i)
                    param = self.ConstFromNodeOrConst(param)
                    if (param is False or type(param) != int
                            # Parameters with arguments have
                            # side effects (errors).
                            # We could check whether there's a face
                            # argument and the face is 0, which is
                            # guaranteed to exist, but it's not worth
                            # the effort.
                            or param in primParamsArgs or param < 0 or
                            param >= len(primParamsTypes) or
                            primParamsTypes[param] is False):
                        # Can't process this list.
                        returntypes = '!'
                        break
                    returntypes += primParamsTypes[param]
                    i += 1
                if returntypes != '!':
                    if (len(returntypes) == 1 and returntypes != '*'
                            and idx in (0, -1)):
                        if name == 'llList2String':
                            node.nt = 'CAST'
                            del child[1]
                            del node.name
                            return
                        if ((name == 'llList2Key' or name == 'llList2Integer'
                             and returntypes in ('s', 'i') or name
                             == 'llList2Float' and returntypes in ('s', 'i'))
                                and (node.t[0] + returntypes) in listCompat):
                            node.nt = 'CAST'
                            del child[1]
                            del node.name
                            child[0] = nr(nt='CAST',
                                          t='string',
                                          ch=[child[0]],
                                          SEF=child[0].SEF)
                            return

                    if (returntypes.find('*') == -1
                            or idx >= 0 and idx < returntypes.find('*')
                            or idx < 0 and
                            idx > returntypes.rfind('*') - len(returntypes)):
                        # Check for type incompatibility or index
                        # out of range.
                        if idx < 0:
                            # s[-1:0] doesn't return the last char
                            # so we have to compensate
                            idx += len(returntypes)
                        if ((node.t[0] + returntypes[idx:idx + 1])
                                not in listCompat and node.SEF):
                            parent[index] = nr(nt='CONST',
                                               t=node.t,
                                               value=defaultListVals[name],
                                               SEF=True)
                            return

                del returntypes

        del listarg, idx, value, tvalue, const
        return

    if name == 'llDialog':
        if self.GetListNodeLength(child[2]) == 1:
            button = self.ConstFromNodeOrConst(
                self.GetListNodeElement(child[2], 0))
            if type(button) == unicode and button == u'OK':
                # remove the element, as 'OK' is the default button in SL
                child[2] = nr(nt='CONST', t='list', value=[], SEF=True)
        return

    if (name == 'llDeleteSubList' or name == 'llListReplaceList'
            and child[1].nt == 'CONST' and not child[1].value):
        # llDeleteSubList(x, 0, -1)  ->  [] if x is SEF
        # llListReplaceList(x, [], 0, -1)  ->  [] if x is SEF
        if (child[0].SEF and child[-2].nt == 'CONST'
                and child[-1].nt == 'CONST' and child[-2].value == 0
                and child[-1].value == -1):
            parent[index] = nr(nt='CONST', t='list', value=[], SEF=True)
            return
    def GetFuncCopy(self, node, scope=0):
        """Get a copy of the function's body

        Replaces 'return expr' with assignment+jump, 'return' with jump, and
        existing labels with fresh labels. Also creates new symtabs for locals
        and adjusts scopes of symbols.
        """
        nt = node.nt
        if nt == 'FNDEF':
            # We're at the top level. Check return type and create a label,
            # then recurse into the block.
            assert node.ch[0].nt == '{}'
            copy = self.GetFuncCopy(node.ch[0], node.ch[0].scope)
            assert copy.nt == '{}'
            self.FixJumps(copy)
            return copy

        if nt == '{}':
            copy = node.copy()
            copy.scope = self.newSymtab()
            copy.ch = []
            for i in node.ch:
                copy.ch.append(self.GetFuncCopy(i, node.scope))
                if i.nt == 'DECL':
                    self.symtab[copy.scope][i.name] = \
                        self.symtab[i.scope][i.name].copy()
                    self.symtab[node.scope][i.name]['NewSymbolScope'] = \
                        copy.scope
                    copy.ch[-1].scope = copy.scope
            return copy

        if nt == '@':
            copy = node.copy()
            oldscope = node.scope
            oldname = node.name
            copy.name = self.newId('lbl', scope, {
                'Kind': 'l',
                'Scope': scope,
                'ref': 0
            })
            copy.scope = scope
            self.symtab[oldscope][oldname]['NewSymbolName'] = copy.name
            self.symtab[oldscope][oldname]['NewSymbolScope'] = scope
            return copy

        if nt == 'RETURN':
            newnode = nr(nt='JUMP',
                         t=None,
                         name=self.retlabel,
                         scope=self.retlscope)
            self.symtab[self.retlscope][self.retlabel]['ref'] += 1
            if node.ch:
                # Returns a value. Wrap in {} and add an assignment.
                # BUG: We don't honour ExplicitCast here.
                newnode = nr(nt='{}',
                             t=None,
                             scope=self.newSymtab(),
                             ch=[
                                 nr(nt='EXPR',
                                    t=self.rettype,
                                    ch=[
                                        nr(nt='=',
                                           t=self.rettype,
                                           ch=[
                                               nr(nt='IDENT',
                                                  t=node.ch[0].t,
                                                  name=self.retvar,
                                                  scope=self.retscope),
                                               self.GetFuncCopy(node.ch[0])
                                           ])
                                    ]), newnode
                             ])
            return newnode

        if nt == 'IDENT':
            copy = node.copy()
            if 'NewSymbolScope' in self.symtab[node.scope][node.name]:
                copy.scope = \
                    self.symtab[node.scope][node.name]['NewSymbolScope']
            return copy

        if not node.ch:
            return node.copy()

        copy = node.copy()
        copy.ch = []
        for i in node.ch:
            copy.ch.append(self.GetFuncCopy(i, scope))
        return copy
    def RecurseStatement(self, parent, index, scope):
        node = parent[index]
        nt = node.nt
        child = node.ch
        fns = None

        if nt in SINGLE_OPT_EXPR_CHILD_NODES:
            if child:
                fns = self.RecurseExpression(child, 0, scope)
                if nt == 'EXPR' and not node.ch:
                    del parent[index]
            else:
                return

        elif nt == '{}':
            i = -len(child)
            while i:
                self.RecurseStatement(child, len(child) + i, node.scope)
                i += 1

        elif nt == 'IF':
            fns = self.RecurseExpression(child, 0, scope)
            self.RecurseSingleStatement(child, 1, scope)
            if len(child) > 2:
                self.RecurseSingleStatement(child, 2, scope)

        # Loop handling is tricky.
        #
        # Consider this:
        #
        #   integer f()
        #   {
        #       llOwnerSay("body of f");
        #       return 1;
        #   }
        #
        #   while (f())
        #       llOwnerSay("doing stuff");
        #
        # In order to execute it every loop iteration, the while() loop must be
        # converted to an if+jump:
        #
        #   integer ___ret__00001;
        #   @___lbl__00001;
        #   {
        #       llOwnerSay("body_of_f");
        #       __ret__00001 = 1;
        #   }
        #   if (___ret__00001)
        #   {
        #       llOwnerSay("doing stuff");
        #       jump ___lbl__00001;
        #   }
        #
        # The for loop is similar, but the initializer and iterator must be
        # expanded as well, to convert it to a while loop. When expanding the
        # iterator, care must be taken to avoid name clashes. For example:
        #
        #   for (i = 0; f(i); i++)
        #   {
        #       integer i;
        #   }
        #
        # should be expanded to:
        #
        #   i = 0;
        #   @loop_label;
        #   integer ___ret__00001;
        #   {
        #       llOwnerSay("body of f");
        #       ___ret__00001 = 1,
        #   }
        #   if (___ret__00001)
        #   {
        #       {
        #           integer i;
        #       }
        #       i++;
        #   }
        #
        # The extra {} inside the 'if' are needed to protect the iterator from
        # being affected by the variables defined in the inner block.
        #
        # Do loops are different:
        #
        #   do
        #       llOwnerSay("doing stuff");
        #   while (f());
        #
        # is converted to:
        #
        #   integer ___ret__00001;
        #   do
        #   {
        #       llOwnerSay("doing stuff");
        #       {
        #           llOwnerSay("body_of_f");
        #           ___ret__00001 = 1;
        #       }
        #   }
        #   while (___ret__00001);
        #

        elif nt == 'DO':
            self.RecurseSingleStatement(child, 0, scope)
            fns = self.RecurseExpression(child, 1, scope)
            if fns:
                # Need to do some plumbing to move the bodies into the loop
                i = 0
                while i < len(fns):
                    if fns[i].nt != 'DECL':
                        assert fns[i].nt in ('{}', '@')
                        # All bodies must be moved inside the loop
                        if child[0].nt != '{}':
                            # Needs wrapping now
                            child[0] = nr(nt='{}',
                                          t=None,
                                          ch=[child[0]],
                                          scope=self.newSymtab())
                        child[0].ch.append(fns.pop(i))
                    else:
                        i += 1

        elif nt == 'WHILE':
            # Convert to if()
            lbl = self.newId('whl', scope, {
                'Kind': 'l',
                'Scope': scope,
                'ref': 1
            })
            parent.insert(index, nr(nt='@', t=None, name=lbl, scope=scope))
            index += 1
            node.nt = 'IF'
            if child[1].nt != '{}':
                # Needs wrapping now
                child[1] = nr(nt='{}',
                              t=None,
                              ch=[child[1]],
                              scope=self.newSymtab())
            child[1].ch.append(nr(nt='JUMP', t=None, name=lbl, scope=scope))
            fns = self.RecurseExpression(child, 0, scope)
            self.RecurseSingleStatement(child, 1, scope)

        elif nt == 'FOR':
            assert child[0].nt == 'EXPRLIST'
            assert child[2].nt == 'EXPRLIST'
            for i in child[0].ch:
                parent.insert(index, nr(nt='EXPR', t=i.t, ch=[i]))
                fns = self.RecurseExpression(parent, index, scope)
                parent[index:index] = fns
                index += 1 + len(fns)
            lbl = self.newId('for', scope, {
                'Kind': 'l',
                'Scope': scope,
                'ref': 1
            })
            parent.insert(index, nr(nt='@', t=None, name=lbl, scope=scope))
            index += 1
            node.nt = 'IF'
            if child[3].nt != '{}':
                # Needs wrapping now
                child[3] = nr(nt='{}',
                              t=None,
                              ch=[child[3]],
                              scope=self.newSymtab())
            # Needs another wrapping if iterator is not empty
            if child[2].ch:
                child[3] = nr(nt='{}',
                              t=None,
                              ch=[child[3]],
                              scope=self.newSymtab())
            for i in child[2].ch:
                child[3].ch.append(nr(nt='EXPR', t=i.t, ch=[i]))
            del child[2]
            del child[0]
            child[1].ch.append(nr(nt='JUMP', t=None, name=lbl, scope=scope))
            fns = self.RecurseExpression(child, 0, scope)
            self.RecurseSingleStatement(child, 1, scope)

        else:
            assert False, u"Unexpected node type: %s" % nt.decode('utf8')

        if fns:
            parent[index:index] = fns
    def ConvertFunction(self, parent, index, scope):
        node = parent[index]
        fns = []
        for i in range(len(node.ch)):
            fns.extend(self.RecurseExpression(node.ch, i, scope))
        fnsym = self.symtab[0][node.name]
        rettype = fnsym['Type']
        self.rettype = rettype
        retvar = None
        if rettype is not None:
            # Returns a value. Create a local variable at the starting level.
            retvar = self.newId('ret', scope, {
                'Kind': 'v',
                'Scope': scope,
                'Type': rettype
            })
            # Add the declaration to the list of statements
            fns.append(nr(nt='DECL', t=rettype, name=retvar, scope=scope))

        # Begin expansion
        if node.name in self.expanding:
            raise EExpansionLoop()

        outer = None
        if fnsym['ParamNames']:
            # Add a new block + symbols + assignments for parameter values
            pscope = self.newSymtab()
            outer = nr(nt='{}', t=None, scope=pscope, ch=[])
            origpscope = self.tree[fnsym['Loc']].pscope
            for i in range(len(fnsym['ParamNames'])):
                # Add parameter assignments and symbol table entries
                pname = fnsym['ParamNames'][i]
                ptype = fnsym['ParamTypes'][i]
                value = node.ch[i]
                self.symtab[pscope][pname] = {
                    'Kind': 'v',
                    'Type': ptype,
                    'Scope': pscope
                }
                self.symtab[origpscope][pname]['NewSymbolScope'] = pscope
                # BUG: We don't honour ExplicitCast here.
                outer.ch.append(
                    nr(nt='DECL',
                       t=ptype,
                       name=pname,
                       scope=pscope,
                       ch=[value]))

        self.expanding.append(node.name)
        self.retvar = retvar
        self.retscope = scope
        self.retlscope = scope
        retlabel = self.newId('rtl', scope, {
            'Kind': 'l',
            'Scope': scope,
            'ref': 0
        })
        self.retlabel = retlabel

        # Get a copy of the function
        blk = [self.GetFuncCopy(self.tree[fnsym['Loc']])]
        if outer:
            outer.ch.extend(blk)
            blk = [outer]
        retused = self.symtab[scope][retlabel]['ref'] != 0

        self.RecurseStatement(blk, 0, scope)  # recursively expand functions

        # Add return label if used, otherwise remove it from the symbol table
        if retused:
            blk.append(nr(nt='@', name=retlabel, scope=scope))
        else:
            del self.symtab[scope][retlabel]
        self.expanding.pop()
        # End expansion

        fns.extend(blk)

        if rettype is None:
            del parent[index]
        else:
            parent[index] = nr(nt='IDENT', t=rettype, name=retvar, scope=scope)

        return fns
    def CleanNode(self, curnode):
        """Recursively checks if the children are used, deleting those that are
        not.
        """
        if curnode.ch is None or (curnode.nt == 'DECL' and curnode.scope == 0):
            return
        # NOTE: Should not depend on 'Loc', since the nodes that are the
        # destination of 'Loc' are renumbered as we delete stuff from globals.

        index = int(curnode.nt
                    in self.assign_ops)  # don't recurse into lvalues

        while index < len(curnode.ch):
            node = curnode.ch[index]

            if not hasattr(node, 'X'):
                if curnode.ch[index].nt == 'JUMP':
                    # Decrease label reference count
                    scope = curnode.ch[index].scope
                    name = curnode.ch[index].name
                    assert self.symtab[scope][name]['ref'] > 0
                    self.symtab[scope][name]['ref'] -= 1
                del curnode.ch[index]
                continue

            nt = node.nt

            if nt == 'DECL':
                if self.OKtoRemoveSymbol(node):
                    if not node.ch or node.ch[0].SEF:
                        del curnode.ch[index]
                        continue
                    node = curnode.ch[index] = nr(
                        nt='EXPR',
                        t=node.t,
                        ch=[self.Cast(node.ch[0], node.t)])

            elif nt == 'FLD':
                sym = self.OKtoRemoveSymbol(node.ch[0])
                if sym:
                    value = sym['W']
                    # Mark as executed, so it isn't optimized out.
                    value.X = True
                    fieldidx = 'xyzs'.index(node.fld)
                    if value.nt == 'CONST':
                        value = value.value[fieldidx]
                        value = nr(nt='CONST',
                                   X=True,
                                   SEF=True,
                                   t=self.PythonType2LSL[type(value)],
                                   value=value)
                        value = self.Cast(value, 'float')
                        SEF = True
                    else:  # assumed VECTOR or ROTATION per OKtoRemoveSymbol
                        SEF = value.SEF
                        value = self.Cast(value.ch[fieldidx], 'float')
                    # Replace it
                    node = curnode.ch[index] = value
                    node.SEF = SEF

            elif nt == 'IDENT':
                sym = self.OKtoRemoveSymbol(node)
                if sym:
                    # Mark as executed, so it isn't optimized out.
                    # Make shallow copy.
                    # TODO: Should the copy be a deep copy?
                    assert sym.get('W', False) is not False
                    new = sym['W'].copy()
                    if hasattr(new, 'orig'):
                        del new.orig

                    new.X = True

                    # this part makes no sense?
                    #new.SEF = sym['W'].SEF

                    if new.t != node.t:
                        new = self.Cast(new, node.t)
                    curnode.ch[index] = node = new
                    # Delete orig if present, as we've eliminated the original
                    #if hasattr(sym['W'], 'orig'):
                    #    del sym['W'].orig

            elif nt in self.assign_ops:
                ident = node.ch[0]
                if ident.nt == 'FLD':
                    ident = ident.ch[0]
                sym = self.OKtoRemoveSymbol(ident)
                if sym:
                    node = curnode.ch[index] = self.Cast(node.ch[1], node.t)

            elif nt in ('IF', 'WHILE', 'DO', 'FOR'):
                # If the mandatory statement is to be removed, replace it
                # with a ; to prevent leaving the statement empty.
                child = node.ch
                idx = 3 if nt == 'FOR' else 0 if nt == 'DO' else 1
                if not hasattr(child[idx], 'X'):
                    child[idx] = nr(nt=';', t=None, X=True, SEF=True)
                if nt == 'DO' and not hasattr(child[1], 'X'):
                    # Mandatory condition but not executed - replace
                    child[1] = nr(nt='CONST',
                                  X=True,
                                  SEF=True,
                                  t='integer',
                                  value=0)

            self.CleanNode(node)
            index += 1
    def MarkReferences(self, node):
        """Marks each node it passes through as executed (X), and each variable
        as read (R) (with count) and/or written (W) (with node where it is, or
        False if written more than once) as appropriate. Traces execution to
        determine if any part of the code is never executed.
        """
        # The 'X' key, when present, indicates whether a node is executed.
        # Its value means whether this instruction will proceed to the next
        # (True: it will; False: it won't).
        if hasattr(node, 'X'):
            return node.X  # branch already analyzed

        nt = node.nt
        child = node.ch

        # Control flow statements

        if nt == 'STSW':
            node.X = False  # Executed but path-breaking.
            sym = self.symtab[0][node.name]
            if not hasattr(self.tree[sym['Loc']], 'X'):
                self.MarkReferences(self.tree[sym['Loc']])

            return False

        if nt == 'JUMP':
            node.X = False  # Executed but path-breaking.
            sym = self.symtab[node.scope][node.name]
            if 'R' in sym:
                sym['R'] += 1
            else:
                sym['R'] = 1
            return False

        if nt == 'RETURN':
            node.X = False  # Executed but path-breaking.
            if child:
                self.MarkReferences(child[0])
            return False

        if nt == 'IF':
            # "When you get to a fork in the road, take it."
            node.X = None  # provisional value, refined later
            self.MarkReferences(child[0])
            condnode = child[0]
            if condnode.nt == 'CONST':
                if lslfuncs.cond(condnode.value):
                    # TRUE - 'then' branch always executed.
                    node.X = self.MarkReferences(child[1])
                    return node.X
                elif len(child) == 3:
                    # FALSE - 'else' branch always executed.
                    node.X = self.MarkReferences(child[2])
                    return node.X
                # else fall through
            else:
                cont = self.MarkReferences(child[1])
                if len(child) == 3:
                    if not cont:
                        cont = self.MarkReferences(child[2])
                        node.X = cont
                        return cont
                    self.MarkReferences(child[2])
            node.X = True
            return True

        if nt == 'WHILE':
            node.X = None  # provisional value, refined later
            self.MarkReferences(child[0])

            if child[0].nt == 'CONST':
                if lslfuncs.cond(child[0].value):
                    # Infinite loop - unless it returns, it stops
                    # execution. But it is executed itself.
                    self.MarkReferences(child[1])
                    node.X = False
                    return node.X
                # else the inside isn't executed at all, so don't mark it
            else:
                self.MarkReferences(child[1])
            node.X = True
            return True

        if nt == 'DO':
            node.X = None  # provisional value, refined later
            if not self.MarkReferences(child[0]):
                node.X = False
                return False
            self.MarkReferences(child[1])
            # It proceeds to the next statement unless it's an infinite loop
            node.X = not (child[1].nt == 'CONST'
                          and lslfuncs.cond(child[1].value))
            return node.X

        if nt == 'FOR':
            node.X = None  # provisional value, refined later
            self.MarkReferences(child[0])
            self.MarkReferences(child[1])
            if child[1].nt == 'CONST':
                if lslfuncs.cond(child[1].value):
                    # Infinite loop - unless it returns, it stops
                    # execution. But it is executed itself.
                    node.X = False
                    self.MarkReferences(child[3])
                    self.MarkReferences(child[2])  # this can't stop execution
                    return node.X
                # else the body and the iterator aren't executed at all, so
                # don't mark them
                node.X = True
            else:
                node.X = True
                self.MarkReferences(child[3])
                self.MarkReferences(child[2])
            # Mark the EXPRLIST as always executed, but not the subexpressions.
            # That forces the EXPRLIST (which is a syntactic requirement) to be
            # kept, while still simplifying the contents properly.
            child[2].X = True
            return True

        if nt == '{}':
            # Go through each statement in turn. If one stops execution,
            # continue reading until either we find a used label (and resume
            # execution) or reach the end (and return False). Otherwise return
            # True.

            continues = True
            node.X = None  # provisional
            for stmt in child:
                if continues or stmt.nt == '@':
                    continues = self.MarkReferences(stmt)
            node.X = continues
            return continues

        if nt == 'FNCALL':
            node.X = None  # provisional
            sym = self.symtab[0][node.name]

            fdef = self.tree[sym['Loc']] if 'Loc' in sym else None
            for idx in xrange(len(child) - 1, -1, -1):
                # Each element is a "write" on the callee's parameter.
                # E.g. f(integer a, integer b) { f(2,3); } means 2, 3 are
                # writes to a and b.
                # This has been eliminated, as it causes more trouble than
                # it fixes.
                self.MarkReferences(child[idx])
                if fdef is not None:
                    psym = self.symtab[fdef.pscope][fdef.pnames[idx]]
                    #if 'W' in psym:
                    #    psym['W'] = False
                    #else:
                    #    psym['W'] = child[idx]
                    psym['W'] = False

            if 'Loc' in sym:
                if not hasattr(self.tree[sym['Loc']], 'X'):
                    self.MarkReferences(self.tree[sym['Loc']])
                node.X = self.tree[sym['Loc']].X
            else:
                node.X = 'stop' not in sym
            # Note that JUMP analysis is incomplete. To do it correctly, we
            # should follow the jump right to its destination, in order to know
            # if that branch leads to a RETURN or completely stops the event.
            # With our code structure, following the JUMP is unfeasible.
            # For that reason, we can't track whether a branch ends in RETURN
            # or in something more powerful like a script reset, in order to
            # propagate it through the function definition to the caller.
            #
            # In practice, this means that the caller can't distinguish this:
            #     fn() { return; }
            # from this:
            #     fn() { llResetScript(); }
            # and therefore, invocations of the function that are followed by
            # code can't know whether that code is dead or not.
            #
            # What does that have to do with jumps? Well, imagine this:
            #     fn(integer x) { if (x) jump x1; else jump x2;
            #                     @x1; return; @x2; llResetScript(); }
            # What of the branches of if() is taken, depends on where the jumps
            # lead to. Assuming the last one always is wrong, because it would
            # mark in the caller code that may execute, as dead, e.g. here:
            #     fn2() { fn(); x = 1; }

            return node.X

        if nt == 'DECL':
            sym = self.symtab[node.scope][node.name]
            if child is not None:
                sym['W'] = child[0]
            else:
                sym['W'] = nr(nt='CONST',
                              t=node.t,
                              value=self.DefaultValues[node.t])

            node.X = True
            if child is not None:
                if hasattr(child[0], 'orig'):
                    orig = child[0].orig
                    self.MarkReferences(orig)
                    child[0].X = orig.X
                    if orig.nt == 'LIST':
                        # Add fake writes to variables used in list elements in
                        # 'orig', so they don't get deleted (Issue #3)
                        for subnode in orig.ch:
                            if subnode.nt == 'IDENT':
                                # can only happen in globals
                                assert subnode.scope == 0
                                sym = self.symtab[0][subnode.name]
                                sym['W'] = False
                                self.tree[sym['Loc']].X = True
                            elif subnode.nt in ('VECTOR', 'ROTATION'):
                                for sub2node in subnode.ch:
                                    if sub2node.nt == 'IDENT':
                                        # can only happen in globals
                                        assert sub2node.scope == 0
                                        sym = self.symtab[0][sub2node.name]
                                        sym['W'] = False
                                        self.tree[sym['Loc']].X = True
                else:
                    self.MarkReferences(child[0])
            return True

        # ---- Starting here, all node types return through the bottom
        #      (except '=').

        node.X = None  # provisional
        if nt in self.assign_ops or nt in ('--V', '++V', 'V++', 'V--'):
            ident = node.ch[0]
            if ident.nt == 'FLD':
                ident = ident.ch[0]
            assert ident.nt == 'IDENT'
            sym = self.symtab[ident.scope][ident.name]
            if ident.scope == 0:
                # Mark the global first.
                self.MarkReferences(self.tree[sym['Loc']])
            # In any case, this is at least the second write, so mark it as such
            # (SSA would be a plus for this to be optimal)
            sym['W'] = False

            if nt == '=':
                # Prevent the first node from being mistaken as a read, by
                # recursing only on the RHS node.
                self.MarkReferences(child[1])
                node.X = True
                return True

        elif nt == 'FLD':
            # Mark this variable as referenced by a Field (recursing will mark
            # the ident as read later)
            self.symtab[child[0].scope][child[0].name]['Fld'] = True

        elif nt == 'IDENT':
            sym = self.symtab[node.scope][node.name]
            # Mark global if it's one.
            if 'W' not in sym and node.scope == 0:
                self.MarkReferences(self.tree[sym['Loc']])
            # Increase read counter
            if 'R' in sym:
                sym['R'] += 1
            else:
                sym['R'] = 1

        node.X = True
        if child is not None:
            for subnode in child:
                self.MarkReferences(subnode)

        return True
    def test_coverage_misc(self):
        """Miscellaneous tests that can't be computed or are too difficult
        to compute with scripts
        """
        sys.stderr.write('\nRunning misc coverage tests: ')
        # Doesn't accept bytes
        self.assertRaises(lslfuncs.ELSLInvalidType, lslfuncs.zstr, b"blah")
        # Can't typecast float to vector
        self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.typecast,
                          lslfuncs.F32(1.2), lslcommon.Vector)
        # Can't typecast integer to vector
        self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.typecast, 1,
                          lslcommon.Vector)
        # Can't typecast vector to key
        self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.typecast,
                          lslcommon.Vector((1., 2., 3.)), lslcommon.Key)
        # Can't typecast quaternion to key
        self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.typecast,
                          lslcommon.Quaternion((1., 2., 3., 4.)),
                          lslcommon.Key)
        # Can't typecast list to vector
        self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.typecast, [
            1, 1.,
            lslcommon.Key(u'blah'),
            lslcommon.Quaternion((1., 0., 0., 0.))
        ], lslcommon.Vector)
        # Can't typecast key to integer
        self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.typecast,
                          lslcommon.Key(u"1"), int)
        # Can't negate string
        self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.neg, u"3")
        # Can't add two keys
        self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.add,
                          lslcommon.Key(u"1"), lslcommon.Key(u"2"))
        # Can't subtract two strings
        self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.sub, u"1", u"2")
        # Can't multiply two strings
        self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.mul, u"1", u"2")
        # Can't multiply quaternion and float in any order
        self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.mul,
                          lslcommon.Quaternion((1., 2., 3., 4.)), 1.)
        self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.mul, 1.,
                          lslcommon.Quaternion((1., 2., 3., 4.)))
        # Can't multiply quaternion by vector (but the opposite order is OK)
        self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.mul,
                          lslcommon.Quaternion((1., 2., 3., 4.)),
                          lslcommon.Vector((1., 2., 3.)))
        # Can't divide quaternion by vector either
        self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.div,
                          lslcommon.Quaternion((1., 2., 3., 4.)),
                          lslcommon.Vector((1., 2., 3.)))
        # Can't mod floats
        self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.mod, 3., 3)
        # Can't compare string and integer
        self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.compare, u'3', 4)
        self.assertRaises(lslfuncs.ELSLTypeMismatch, lslfuncs.less, u'3', 4)

        # Bytes is not a valid type to multiply by (in any order)
        self.assertRaises(lslfuncs.ELSLInvalidType, lslfuncs.mul, b"a", 3)
        self.assertRaises(lslfuncs.ELSLInvalidType, lslfuncs.mul,
                          lslcommon.Vector((3., 4., 5.)), b"a")
        self.assertRaises(lslfuncs.ELSLInvalidType, lslfuncs.typecast, b"",
                          unicode)

        # v2f/q2f coverage (force conversion from ints to floats)
        self.assertEqual(repr(lslfuncs.v2f(lslcommon.Vector((1, 0, 0)))),
                         'Vector((1.0, 0.0, 0.0))')
        self.assertEqual(
            repr(lslfuncs.q2f(lslcommon.Quaternion((1, 0, 0, 0)))),
            'Quaternion((1.0, 0.0, 0.0, 0.0))')
        # Key repr coverage
        self.assertEqual(repr(lslcommon.Key(u'')),
                         "Key(u'')" if str != unicode else "Key('')")

        # string + key coverage
        self.assertEqual(lslfuncs.add(u'a', lslcommon.Key(u'b')), u'ab')
        self.assertEqual(type(lslfuncs.add(u'a', lslcommon.Key(u'b'))),
                         unicode)

        # The SEF table prevents this assertion from being reachable via script.
        self.assertRaises(lslfuncs.ELSLCantCompute,
                          lslfuncs.llXorBase64Strings, u"AABA", u"AABA")
        self.assertRaises(lslfuncs.ELSLCantCompute, lslfuncs.llModPow, 3, 5, 7)
        # Check invalid type in llGetListEntryType
        self.assertRaises(lslfuncs.ELSLInvalidType,
                          lslfuncs.llGetListEntryType, [b'a'], 0)

        # Check that Value2LSL raises an exception if the type is unknown.
        outmod = lsloutput.outscript()
        # Script with a single node of type Expression, containing a constant
        # of type Bytes. That's rejected by the output module.
        msg = None
        script = [
            nr(nt='EXPR',
               t='string',
               ch=[nr(nt='CONST', t='string', value=b'ab')])
        ]
        save_IsCalc = lslcommon.IsCalc
        lslcommon.IsCalc = True
        try:
            try:
                outmod.output((script, ()))
            except AssertionError as e:
                msg = str(e)
        finally:
            lslcommon.IsCalc = save_IsCalc
        self.assertEqual(
            msg, u"Value of unknown type in Value2LSL: 'ab'"
            if python2 else u"Value of unknown type in Value2LSL: b'ab'")
        del msg
        # Extended assignment in output
        script = [
            nr(nt='EXPR',
               t='integer',
               ch=[
                   nr(nt='^=',
                      t='integer',
                      ch=[
                          nr(nt='IDENT', t='integer', name='a', scope=0),
                          nr(nt='CONST', t='integer', value=3)
                      ])
               ])
        ]
        save_IsCalc = lslcommon.IsCalc
        lslcommon.IsCalc = True
        try:
            out = outmod.output((script, [{
                'a': {
                    'Kind': 'v',
                    'Loc': 1,
                    'Scope': 0,
                    'Type': 'integer'
                }
            }]))
        finally:
            lslcommon.IsCalc = save_IsCalc

        self.assertEqual(out, 'a = a ^ (3)')
        del out, script, outmod, save_IsCalc