Lower.java revision 1011:c17045fd979c
1/*
2 * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package jdk.nashorn.internal.codegen;
27
28import static jdk.nashorn.internal.codegen.CompilerConstants.EVAL;
29import static jdk.nashorn.internal.codegen.CompilerConstants.RETURN;
30import static jdk.nashorn.internal.ir.Expression.isAlwaysTrue;
31
32import java.util.ArrayList;
33import java.util.Arrays;
34import java.util.Collections;
35import java.util.List;
36import java.util.ListIterator;
37import jdk.nashorn.internal.ir.BaseNode;
38import jdk.nashorn.internal.ir.BinaryNode;
39import jdk.nashorn.internal.ir.Block;
40import jdk.nashorn.internal.ir.BlockLexicalContext;
41import jdk.nashorn.internal.ir.BlockStatement;
42import jdk.nashorn.internal.ir.BreakNode;
43import jdk.nashorn.internal.ir.CallNode;
44import jdk.nashorn.internal.ir.CaseNode;
45import jdk.nashorn.internal.ir.CatchNode;
46import jdk.nashorn.internal.ir.ContinueNode;
47import jdk.nashorn.internal.ir.EmptyNode;
48import jdk.nashorn.internal.ir.Expression;
49import jdk.nashorn.internal.ir.ExpressionStatement;
50import jdk.nashorn.internal.ir.ForNode;
51import jdk.nashorn.internal.ir.FunctionNode;
52import jdk.nashorn.internal.ir.FunctionNode.CompilationState;
53import jdk.nashorn.internal.ir.IdentNode;
54import jdk.nashorn.internal.ir.IfNode;
55import jdk.nashorn.internal.ir.LabelNode;
56import jdk.nashorn.internal.ir.LexicalContext;
57import jdk.nashorn.internal.ir.LiteralNode;
58import jdk.nashorn.internal.ir.LoopNode;
59import jdk.nashorn.internal.ir.Node;
60import jdk.nashorn.internal.ir.ReturnNode;
61import jdk.nashorn.internal.ir.RuntimeNode;
62import jdk.nashorn.internal.ir.Statement;
63import jdk.nashorn.internal.ir.SwitchNode;
64import jdk.nashorn.internal.ir.Symbol;
65import jdk.nashorn.internal.ir.ThrowNode;
66import jdk.nashorn.internal.ir.TryNode;
67import jdk.nashorn.internal.ir.VarNode;
68import jdk.nashorn.internal.ir.WhileNode;
69import jdk.nashorn.internal.ir.WithNode;
70import jdk.nashorn.internal.ir.visitor.NodeOperatorVisitor;
71import jdk.nashorn.internal.ir.visitor.NodeVisitor;
72import jdk.nashorn.internal.parser.Token;
73import jdk.nashorn.internal.parser.TokenType;
74import jdk.nashorn.internal.runtime.Context;
75import jdk.nashorn.internal.runtime.JSType;
76import jdk.nashorn.internal.runtime.Source;
77import jdk.nashorn.internal.runtime.logging.DebugLogger;
78import jdk.nashorn.internal.runtime.logging.Loggable;
79import jdk.nashorn.internal.runtime.logging.Logger;
80
81/**
82 * Lower to more primitive operations. After lowering, an AST still has no symbols
83 * and types, but several nodes have been turned into more low level constructs
84 * and control flow termination criteria have been computed.
85 *
86 * We do things like code copying/inlining of finallies here, as it is much
87 * harder and context dependent to do any code copying after symbols have been
88 * finalized.
89 */
90@Logger(name="lower")
91final class Lower extends NodeOperatorVisitor<BlockLexicalContext> implements Loggable {
92
93    private final DebugLogger log;
94
95    /**
96     * Constructor.
97     */
98    Lower(final Compiler compiler) {
99        super(new BlockLexicalContext() {
100
101            @Override
102            public List<Statement> popStatements() {
103                final List<Statement> newStatements = new ArrayList<>();
104                boolean terminated = false;
105
106                final List<Statement> statements = super.popStatements();
107                for (final Statement statement : statements) {
108                    if (!terminated) {
109                        newStatements.add(statement);
110                        if (statement.isTerminal() || statement instanceof BreakNode || statement instanceof ContinueNode) { //TODO hasGoto? But some Loops are hasGoto too - why?
111                            terminated = true;
112                        }
113                    } else {
114                        statement.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
115                            @Override
116                            public boolean enterVarNode(final VarNode varNode) {
117                                newStatements.add(varNode.setInit(null));
118                                return false;
119                            }
120                        });
121                    }
122                }
123                return newStatements;
124            }
125
126            @Override
127            protected Block afterSetStatements(final Block block) {
128                final List<Statement> stmts = block.getStatements();
129                for(final ListIterator<Statement> li = stmts.listIterator(stmts.size()); li.hasPrevious();) {
130                    final Statement stmt = li.previous();
131                    // popStatements() guarantees that the only thing after a terminal statement are uninitialized
132                    // VarNodes. We skip past those, and set the terminal state of the block to the value of the
133                    // terminal state of the first statement that is not an uninitialized VarNode.
134                    if(!(stmt instanceof VarNode && ((VarNode)stmt).getInit() == null)) {
135                        return block.setIsTerminal(this, stmt.isTerminal());
136                    }
137                }
138                return block.setIsTerminal(this, false);
139            }
140        });
141
142        this.log       = initLogger(compiler.getContext());
143    }
144
145    @Override
146    public DebugLogger getLogger() {
147        return log;
148    }
149
150    @Override
151    public DebugLogger initLogger(final Context context) {
152        return context.getLogger(this.getClass());
153    }
154
155    @Override
156    public boolean enterBreakNode(final BreakNode breakNode) {
157        addStatement(breakNode);
158        return false;
159    }
160
161    @Override
162    public Node leaveCallNode(final CallNode callNode) {
163        return checkEval(callNode.setFunction(markerFunction(callNode.getFunction())));
164    }
165
166    @Override
167    public Node leaveCatchNode(final CatchNode catchNode) {
168        return addStatement(catchNode);
169    }
170
171    @Override
172    public boolean enterContinueNode(final ContinueNode continueNode) {
173        addStatement(continueNode);
174        return false;
175    }
176
177    @Override
178    public boolean enterEmptyNode(final EmptyNode emptyNode) {
179        return false;
180    }
181
182    @Override
183    public Node leaveExpressionStatement(final ExpressionStatement expressionStatement) {
184        final Expression expr = expressionStatement.getExpression();
185        ExpressionStatement node = expressionStatement;
186
187        final FunctionNode currentFunction = lc.getCurrentFunction();
188
189        if (currentFunction.isProgram()) {
190            if (!isInternalExpression(expr) && !isEvalResultAssignment(expr)) {
191                node = expressionStatement.setExpression(
192                    new BinaryNode(
193                        Token.recast(
194                            expressionStatement.getToken(),
195                            TokenType.ASSIGN),
196                        compilerConstant(RETURN),
197                    expr));
198            }
199        }
200
201        return addStatement(node);
202    }
203
204    @Override
205    public Node leaveBlockStatement(final BlockStatement blockStatement) {
206        return addStatement(blockStatement);
207    }
208
209    @Override
210    public Node leaveForNode(final ForNode forNode) {
211        ForNode newForNode = forNode;
212
213        final Expression test = forNode.getTest();
214        if (!forNode.isForIn() && isAlwaysTrue(test)) {
215            newForNode = forNode.setTest(lc, null);
216        }
217
218        newForNode = checkEscape(newForNode);
219        if(newForNode.isForIn()) {
220            // Wrap it in a block so its internally created iterator is restricted in scope
221            addStatementEnclosedInBlock(newForNode);
222        } else {
223            addStatement(newForNode);
224        }
225        return newForNode;
226    }
227
228    @Override
229    public Node leaveFunctionNode(final FunctionNode functionNode) {
230        log.info("END FunctionNode: ", functionNode.getName());
231        return functionNode.setState(lc, CompilationState.LOWERED);
232    }
233
234    @Override
235    public Node leaveIfNode(final IfNode ifNode) {
236        return addStatement(ifNode);
237    }
238
239    @Override
240    public Node leaveIN(final BinaryNode binaryNode) {
241        return new RuntimeNode(binaryNode);
242    }
243
244    @Override
245    public Node leaveINSTANCEOF(final BinaryNode binaryNode) {
246        return new RuntimeNode(binaryNode);
247    }
248
249    @Override
250    public Node leaveLabelNode(final LabelNode labelNode) {
251        return addStatement(labelNode);
252    }
253
254    @Override
255    public Node leaveReturnNode(final ReturnNode returnNode) {
256        addStatement(returnNode); //ReturnNodes are always terminal, marked as such in constructor
257        return returnNode;
258    }
259
260    @Override
261    public Node leaveCaseNode(final CaseNode caseNode) {
262        // Try to represent the case test as an integer
263        final Node test = caseNode.getTest();
264        if (test instanceof LiteralNode) {
265            final LiteralNode<?> lit = (LiteralNode<?>)test;
266            if (lit.isNumeric() && !(lit.getValue() instanceof Integer)) {
267                if (JSType.isRepresentableAsInt(lit.getNumber())) {
268                    return caseNode.setTest((Expression)LiteralNode.newInstance(lit, lit.getInt32()).accept(this));
269                }
270            }
271        }
272        return caseNode;
273    }
274
275    @Override
276    public Node leaveSwitchNode(final SwitchNode switchNode) {
277        if(!switchNode.isInteger()) {
278            // Wrap it in a block so its internally created tag is restricted in scope
279            addStatementEnclosedInBlock(switchNode);
280        } else {
281            addStatement(switchNode);
282        }
283        return switchNode;
284    }
285
286    @Override
287    public Node leaveThrowNode(final ThrowNode throwNode) {
288        return addStatement(throwNode); //ThrowNodes are always terminal, marked as such in constructor
289    }
290
291    private static Node ensureUniqueNamesIn(final Node node) {
292        return node.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
293            @Override
294            public Node leaveFunctionNode(final FunctionNode functionNode) {
295                final String name = functionNode.getName();
296                return functionNode.setName(lc, lc.getCurrentFunction().uniqueName(name));
297            }
298
299            @Override
300            public Node leaveDefault(final Node labelledNode) {
301                return labelledNode.ensureUniqueLabels(lc);
302            }
303        });
304    }
305
306    private static List<Statement> copyFinally(final Block finallyBody) {
307        final List<Statement> newStatements = new ArrayList<>();
308        for (final Statement statement : finallyBody.getStatements()) {
309            newStatements.add((Statement)ensureUniqueNamesIn(statement));
310            if (statement.hasTerminalFlags()) {
311                return newStatements;
312            }
313        }
314        return newStatements;
315    }
316
317    private Block catchAllBlock(final TryNode tryNode) {
318        final int  lineNumber = tryNode.getLineNumber();
319        final long token      = tryNode.getToken();
320        final int  finish     = tryNode.getFinish();
321
322        final IdentNode exception = new IdentNode(token, finish, lc.getCurrentFunction().uniqueName(CompilerConstants.EXCEPTION_PREFIX.symbolName()));
323
324        final Block catchBody = new Block(token, finish, new ThrowNode(lineNumber, token, finish, new IdentNode(exception), true));
325        assert catchBody.isTerminal(); //ends with throw, so terminal
326
327        final CatchNode catchAllNode  = new CatchNode(lineNumber, token, finish, new IdentNode(exception), null, catchBody, true);
328        final Block     catchAllBlock = new Block(token, finish, catchAllNode);
329
330        //catchallblock -> catchallnode (catchnode) -> exception -> throw
331
332        return (Block)catchAllBlock.accept(this); //not accepted. has to be accepted by lower
333    }
334
335    private IdentNode compilerConstant(final CompilerConstants cc) {
336        final FunctionNode functionNode = lc.getCurrentFunction();
337        return new IdentNode(functionNode.getToken(), functionNode.getFinish(), cc.symbolName());
338    }
339
340    private static boolean isTerminal(final List<Statement> statements) {
341        return !statements.isEmpty() && statements.get(statements.size() - 1).hasTerminalFlags();
342    }
343
344    /**
345     * Splice finally code into all endpoints of a trynode
346     * @param tryNode the try node
347     * @param rethrows list of rethrowing throw nodes from synthetic catch blocks
348     * @param finallyBody the code in the original finally block
349     * @return new try node after splicing finally code (same if nop)
350     */
351    private Node spliceFinally(final TryNode tryNode, final List<ThrowNode> rethrows, final Block finallyBody) {
352        assert tryNode.getFinallyBody() == null;
353
354        final LexicalContext lowerLc = lc;
355
356        final TryNode newTryNode = (TryNode)tryNode.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
357            final List<Node> insideTry = new ArrayList<>();
358
359            @Override
360            public boolean enterDefault(final Node node) {
361                insideTry.add(node);
362                return true;
363            }
364
365            @Override
366            public boolean enterFunctionNode(final FunctionNode functionNode) {
367                // do not enter function nodes - finally code should not be inlined into them
368                return false;
369            }
370
371            @Override
372            public Node leaveThrowNode(final ThrowNode throwNode) {
373                if (rethrows.contains(throwNode)) {
374                    final List<Statement> newStatements = copyFinally(finallyBody);
375                    if (!isTerminal(newStatements)) {
376                        newStatements.add(throwNode);
377                    }
378                    return BlockStatement.createReplacement(throwNode, newStatements);
379                }
380                return throwNode;
381            }
382
383            @Override
384            public Node leaveBreakNode(final BreakNode breakNode) {
385                return copy(breakNode, (Node)Lower.this.lc.getBreakable(breakNode.getLabelName()));
386            }
387
388            @Override
389            public Node leaveContinueNode(final ContinueNode continueNode) {
390                return copy(continueNode, Lower.this.lc.getContinueTo(continueNode.getLabelName()));
391            }
392
393            @Override
394            public Node leaveReturnNode(final ReturnNode returnNode) {
395                final Expression expr  = returnNode.getExpression();
396                final List<Statement> newStatements = new ArrayList<>();
397
398                final Expression resultNode;
399                if (expr != null) {
400                    //we need to evaluate the result of the return in case it is complex while
401                    //still in the try block, store it in a result value and return it afterwards
402                    resultNode = new IdentNode(Lower.this.compilerConstant(RETURN));
403                    newStatements.add(new ExpressionStatement(returnNode.getLineNumber(), returnNode.getToken(), returnNode.getFinish(), new BinaryNode(Token.recast(returnNode.getToken(), TokenType.ASSIGN), resultNode, expr)));
404                    lowerLc.setFlag(lowerLc.getCurrentFunction(), FunctionNode.USES_RETURN_SYMBOL);
405                } else {
406                    resultNode = null;
407                }
408
409                newStatements.addAll(copyFinally(finallyBody));
410                if (!isTerminal(newStatements)) {
411                    newStatements.add(expr == null ? returnNode : returnNode.setExpression(resultNode));
412                }
413
414                return BlockStatement.createReplacement(returnNode, lc.getCurrentBlock().getFinish(), newStatements);
415            }
416
417            private Node copy(final Statement endpoint, final Node targetNode) {
418                if (!insideTry.contains(targetNode)) {
419                    final List<Statement> newStatements = copyFinally(finallyBody);
420                    if (!isTerminal(newStatements)) {
421                        newStatements.add(endpoint);
422                    }
423                    return BlockStatement.createReplacement(endpoint, tryNode.getFinish(), newStatements);
424                }
425                return endpoint;
426            }
427        });
428
429        addStatement(newTryNode);
430        for (final Node statement : finallyBody.getStatements()) {
431            addStatement((Statement)statement);
432        }
433
434        return newTryNode;
435    }
436
437    @Override
438    public Node leaveTryNode(final TryNode tryNode) {
439        final Block finallyBody = tryNode.getFinallyBody();
440
441        if (finallyBody == null) {
442            return addStatement(ensureUnconditionalCatch(tryNode));
443        }
444
445        /*
446         * create a new trynode
447         *    if we have catches:
448         *
449         *    try            try
450         *       x              try
451         *    catch               x
452         *       y              catch
453         *    finally z           y
454         *                   catchall
455         *                        rethrow
456         *
457         *   otheriwse
458         *
459         *   try              try
460         *      x               x
461         *   finally          catchall
462         *      y               rethrow
463         *
464         *
465         *   now splice in finally code wherever needed
466         *
467         */
468        TryNode newTryNode;
469
470        final Block catchAll = catchAllBlock(tryNode);
471
472        final List<ThrowNode> rethrows = new ArrayList<>();
473        catchAll.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
474            @Override
475            public boolean enterThrowNode(final ThrowNode throwNode) {
476                rethrows.add(throwNode);
477                return true;
478            }
479        });
480        assert rethrows.size() == 1;
481
482        if (tryNode.getCatchBlocks().isEmpty()) {
483            newTryNode = tryNode.setFinallyBody(null);
484        } else {
485            final Block outerBody = new Block(tryNode.getToken(), tryNode.getFinish(), ensureUnconditionalCatch(tryNode.setFinallyBody(null)));
486            newTryNode = tryNode.setBody(outerBody).setCatchBlocks(null);
487        }
488
489        newTryNode = newTryNode.setCatchBlocks(Arrays.asList(catchAll)).setFinallyBody(null);
490
491        /*
492         * Now that the transform is done, we have to go into the try and splice
493         * the finally block in front of any statement that is outside the try
494         */
495        return spliceFinally(newTryNode, rethrows, finallyBody);
496    }
497
498    private TryNode ensureUnconditionalCatch(final TryNode tryNode) {
499        final List<CatchNode> catches = tryNode.getCatches();
500        if(catches == null || catches.isEmpty() || catches.get(catches.size() - 1).getExceptionCondition() == null) {
501            return tryNode;
502        }
503        // If the last catch block is conditional, add an unconditional rethrow block
504        final List<Block> newCatchBlocks = new ArrayList<>(tryNode.getCatchBlocks());
505
506        newCatchBlocks.add(catchAllBlock(tryNode));
507        return tryNode.setCatchBlocks(newCatchBlocks);
508    }
509
510    @Override
511    public Node leaveVarNode(final VarNode varNode) {
512        addStatement(varNode);
513        if (varNode.getFlag(VarNode.IS_LAST_FUNCTION_DECLARATION) && lc.getCurrentFunction().isProgram()) {
514            new ExpressionStatement(varNode.getLineNumber(), varNode.getToken(), varNode.getFinish(), new IdentNode(varNode.getName())).accept(this);
515        }
516        return varNode;
517    }
518
519    @Override
520    public Node leaveWhileNode(final WhileNode whileNode) {
521        final Expression test = whileNode.getTest();
522        final Block body = whileNode.getBody();
523
524        if (isAlwaysTrue(test)) {
525            //turn it into a for node without a test.
526            final ForNode forNode = (ForNode)new ForNode(whileNode.getLineNumber(), whileNode.getToken(), whileNode.getFinish(), body, ForNode.IS_FOR).accept(this);
527            lc.replace(whileNode, forNode);
528            return forNode;
529        }
530
531         return addStatement(checkEscape(whileNode));
532    }
533
534    @Override
535    public Node leaveWithNode(final WithNode withNode) {
536        return addStatement(withNode);
537    }
538
539    /**
540     * Given a function node that is a callee in a CallNode, replace it with
541     * the appropriate marker function. This is used by {@link CodeGenerator}
542     * for fast scope calls
543     *
544     * @param function function called by a CallNode
545     * @return transformed node to marker function or identity if not ident/access/indexnode
546     */
547    private static Expression markerFunction(final Expression function) {
548        if (function instanceof IdentNode) {
549            return ((IdentNode)function).setIsFunction();
550        } else if (function instanceof BaseNode) {
551            return ((BaseNode)function).setIsFunction();
552        }
553        return function;
554    }
555
556    /**
557     * Calculate a synthetic eval location for a node for the stacktrace, for example src#17<eval>
558     * @param node a node
559     * @return eval location
560     */
561    private String evalLocation(final IdentNode node) {
562        final Source source = lc.getCurrentFunction().getSource();
563        final int pos = node.position();
564        return new StringBuilder().
565            append(source.getName()).
566            append('#').
567            append(source.getLine(pos)).
568            append(':').
569            append(source.getColumn(pos)).
570            append("<eval>").
571            toString();
572    }
573
574    /**
575     * Check whether a call node may be a call to eval. In that case we
576     * clone the args in order to create the following construct in
577     * {@link CodeGenerator}
578     *
579     * <pre>
580     * if (calledFuntion == buildInEval) {
581     *    eval(cloned arg);
582     * } else {
583     *    cloned arg;
584     * }
585     * </pre>
586     *
587     * @param callNode call node to check if it's an eval
588     */
589    private CallNode checkEval(final CallNode callNode) {
590        if (callNode.getFunction() instanceof IdentNode) {
591
592            final List<Expression> args = callNode.getArgs();
593            final IdentNode callee = (IdentNode)callNode.getFunction();
594
595            // 'eval' call with at least one argument
596            if (args.size() >= 1 && EVAL.symbolName().equals(callee.getName())) {
597                final List<Expression> evalArgs = new ArrayList<>(args.size());
598                for(final Expression arg: args) {
599                    evalArgs.add((Expression)ensureUniqueNamesIn(arg).accept(this));
600                }
601                return callNode.setEvalArgs(new CallNode.EvalArgs(evalArgs, evalLocation(callee)));
602            }
603        }
604
605        return callNode;
606    }
607
608    /**
609     * Helper that given a loop body makes sure that it is not terminal if it
610     * has a continue that leads to the loop header or to outer loops' loop
611     * headers. This means that, even if the body ends with a terminal
612     * statement, we cannot tag it as terminal
613     *
614     * @param loopBody the loop body to check
615     * @return true if control flow may escape the loop
616     */
617    private static boolean controlFlowEscapes(final LexicalContext lex, final Block loopBody) {
618        final List<Node> escapes = new ArrayList<>();
619
620        loopBody.accept(new NodeVisitor<LexicalContext>(new LexicalContext()) {
621            @Override
622            public Node leaveBreakNode(final BreakNode node) {
623                escapes.add(node);
624                return node;
625            }
626
627            @Override
628            public Node leaveContinueNode(final ContinueNode node) {
629                // all inner loops have been popped.
630                if (lex.contains(lex.getContinueTo(node.getLabelName()))) {
631                    escapes.add(node);
632                }
633                return node;
634            }
635        });
636
637        return !escapes.isEmpty();
638    }
639
640    @SuppressWarnings("unchecked")
641    private <T extends LoopNode> T checkEscape(final T loopNode) {
642        final boolean escapes = controlFlowEscapes(lc, loopNode.getBody());
643        if (escapes) {
644            return (T)loopNode.
645                setBody(lc, loopNode.getBody().setIsTerminal(lc, false)).
646                setControlFlowEscapes(lc, escapes);
647        }
648        return loopNode;
649    }
650
651
652    private Node addStatement(final Statement statement) {
653        lc.appendStatement(statement);
654        return statement;
655    }
656
657    private void addStatementEnclosedInBlock(final Statement stmt) {
658        BlockStatement b = BlockStatement.createReplacement(stmt, Collections.<Statement>singletonList(stmt));
659        if(stmt.isTerminal()) {
660            b = b.setBlock(b.getBlock().setIsTerminal(null, true));
661        }
662        addStatement(b);
663    }
664
665    /**
666     * An internal expression has a symbol that is tagged internal. Check if
667     * this is such a node
668     *
669     * @param expression expression to check for internal symbol
670     * @return true if internal, false otherwise
671     */
672    private static boolean isInternalExpression(final Expression expression) {
673        if (!(expression instanceof IdentNode)) {
674            return false;
675        }
676        final Symbol symbol = ((IdentNode)expression).getSymbol();
677        return symbol != null && symbol.isInternal();
678    }
679
680    /**
681     * Is this an assignment to the special variable that hosts scripting eval
682     * results, i.e. __return__?
683     *
684     * @param expression expression to check whether it is $evalresult = X
685     * @return true if an assignment to eval result, false otherwise
686     */
687    private static boolean isEvalResultAssignment(final Node expression) {
688        final Node e = expression;
689        if (e instanceof BinaryNode) {
690            final Node lhs = ((BinaryNode)e).lhs();
691            if (lhs instanceof IdentNode) {
692                return ((IdentNode)lhs).getName().equals(RETURN.symbolName());
693            }
694        }
695        return false;
696    }
697}
698