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