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