1 /*
   2  * Copyright (c) 2014, 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 package jdk.nashorn.internal.parser;
  26 
  27 import java.util.Iterator;
  28 import java.util.NoSuchElementException;
  29 import jdk.nashorn.internal.ir.Statement;
  30 
  31 /**
  32  * A class that tracks the current lexical context of node visitation as a stack of {@code ParserContextNode} nodes. Has special
  33  * methods to retrieve useful subsets of the context.
  34  *
  35  * This is implemented with a primitive array and a stack pointer, because it really makes a difference
  36  * performance wise. None of the collection classes were optimal
  37  */
  38 
  39 class ParserContext {
  40 
  41     private ParserContextNode[] stack;
  42     private int sp;
  43 
  44     private static final int INITIAL_DEPTH = 16;
  45 
  46     /**
  47      * Constructs a ParserContext,
  48      * initializes the stack
  49      */
  50     public ParserContext(){
  51         this.sp    = 0;
  52         this.stack = new ParserContextNode[INITIAL_DEPTH];
  53     }
  54 
  55     /**
  56      * Pushes a new block on top of the context, making it the innermost open block.
  57      * @param node the new node
  58      * @return The node that was pushed
  59      */
  60     public <T extends ParserContextNode> T push(final T node) {
  61         assert !contains(node);
  62         if (sp == stack.length) {
  63             final ParserContextNode[] newStack = new ParserContextNode[sp * 2];
  64             System.arraycopy(stack, 0, newStack, 0, sp);
  65             stack = newStack;
  66         }
  67         stack[sp] = node;
  68         sp++;
  69 
  70         return node;
  71     }
  72 
  73     /**
  74      * The topmost node on the stack
  75      * @return The topmost node on the stack
  76      */
  77     public ParserContextNode peek() {
  78         return stack[sp - 1];
  79     }
  80 
  81     /**
  82      * Removes and returns the topmost Node from the stack.
  83      * @param node The node expected to be popped, used for sanity check
  84      * @return The removed node
  85      */
  86     public <T extends ParserContextNode> T pop(final T node) {
  87         --sp;
  88         @SuppressWarnings("unchecked")
  89         final T popped = (T)stack[sp];
  90         stack[sp] = null;
  91         assert node == popped;
  92 
  93         return popped;
  94     }
  95 
  96     /**
  97      * Tests if a node is on the stack.
  98      * @param node  The node to test
  99      * @return true if stack contains node, false otherwise
 100      */
 101     public boolean contains(final ParserContextNode node) {
 102         for (int i = 0; i < sp; i++) {
 103             if (stack[i] == node) {
 104                 return true;
 105             }
 106         }
 107         return false;
 108     }
 109 
 110     /**
 111      * Returns the topmost {@link ParserContextBreakableNode} on the stack, null if none on stack
 112      * @return Returns the topmost {@link ParserContextBreakableNode} on the stack, null if none on stack
 113      */
 114     private ParserContextBreakableNode getBreakable() {
 115         for (final NodeIterator<ParserContextBreakableNode> iter = new NodeIterator<>(ParserContextBreakableNode.class, getCurrentFunction()); iter.hasNext(); ) {
 116             final ParserContextBreakableNode next = iter.next();
 117             if (next.isBreakableWithoutLabel()) {
 118                 return next;
 119             }
 120         }
 121         return null;
 122     }
 123 
 124 
 125 
 126     /**
 127      * Find the breakable node corresponding to this label.
 128      * @param labelName name of the label to search for. If null, the closest breakable node will be returned
 129      * unconditionally, e.g. a while loop with no label
 130      * @return closest breakable node
 131      */
 132     public ParserContextBreakableNode getBreakable(final String labelName) {
 133         if (labelName != null) {
 134             final ParserContextLabelNode foundLabel = findLabel(labelName);
 135             if (foundLabel != null) {
 136                 // iterate to the nearest breakable to the foundLabel
 137                 ParserContextBreakableNode breakable = null;
 138                 for (final NodeIterator<ParserContextBreakableNode> iter = new NodeIterator<>(ParserContextBreakableNode.class, foundLabel); iter.hasNext(); ) {
 139                     breakable = iter.next();
 140                 }
 141                 return breakable;
 142             }
 143             return null;
 144         }
 145         return getBreakable();
 146     }
 147 
 148     /**
 149      * Returns the loop node of the current loop, or null if not inside a loop
 150      * @return loop noder
 151      */
 152     public ParserContextLoopNode getCurrentLoop() {
 153         final Iterator<ParserContextLoopNode> iter = new NodeIterator<>(ParserContextLoopNode.class, getCurrentFunction());
 154         return iter.hasNext() ? iter.next() : null;
 155     }
 156 
 157     private ParserContextLoopNode getContinueTo() {
 158         return getCurrentLoop();
 159     }
 160 
 161     /**
 162      * Find the continue target node corresponding to this label.
 163      * @param labelName label name to search for. If null the closest loop node will be returned unconditionally, e.g. a
 164      * while loop with no label
 165      * @return closest continue target node
 166      */
 167     public ParserContextLoopNode getContinueTo(final String labelName) {
 168         if (labelName != null) {
 169             final ParserContextLabelNode foundLabel = findLabel(labelName);
 170             if (foundLabel != null) {
 171                 // iterate to the nearest loop to the foundLabel
 172                 ParserContextLoopNode loop = null;
 173                 for (final NodeIterator<ParserContextLoopNode> iter = new NodeIterator<>(ParserContextLoopNode.class, foundLabel); iter.hasNext(); ) {
 174                     loop = iter.next();
 175                 }
 176                 return loop;
 177             }
 178             return null;
 179         }
 180         return getContinueTo();
 181     }
 182 
 183     /**
 184      * Get the function body of a function node on the stack.
 185      * This will trigger an assertion if node isn't present
 186      * @param functionNode function node
 187      * @return body of function node
 188      */
 189     public ParserContextBlockNode getFunctionBody(final ParserContextFunctionNode functionNode) {
 190         for (int i = sp - 1; i >= 0 ; i--) {
 191             if (stack[i] == functionNode) {
 192                 return (ParserContextBlockNode)stack[i + 1];
 193             }
 194         }
 195         throw new AssertionError(functionNode.getName() + " not on context stack");
 196     }
 197 
 198     /**
 199      * Check the stack for a given label node by name
 200      * @param name name of the label
 201      * @return LabelNode if found, null otherwise
 202      */
 203     public ParserContextLabelNode findLabel(final String name) {
 204         for (final Iterator<ParserContextLabelNode> iter = new NodeIterator<>(ParserContextLabelNode.class, getCurrentFunction()); iter.hasNext(); ) {
 205             final ParserContextLabelNode next = iter.next();
 206             if (next.getLabelName().equals(name)) {
 207                 return next;
 208             }
 209         }
 210         return null;
 211     }
 212 
 213     /**
 214      * Prepends a statement to the current node.
 215      * @param statement The statement to prepend
 216      */
 217     public void prependStatementToCurrentNode(final Statement statement) {
 218         assert statement != null;
 219         stack[sp - 1].prependStatement(statement);
 220     }
 221 
 222     /**
 223      * Appends a statement to the current Node.
 224      * @param statement The statement to append
 225      */
 226     public void appendStatementToCurrentNode(final Statement statement) {
 227         assert statement != null;
 228         stack[sp - 1].appendStatement(statement);
 229     }
 230 
 231     /**
 232      * Returns the innermost function in the context.
 233      * @return the innermost function in the context.
 234      */
 235     public ParserContextFunctionNode getCurrentFunction() {
 236         for (int i = sp - 1; i >= 0; i--) {
 237             if (stack[i] instanceof ParserContextFunctionNode) {
 238                 return (ParserContextFunctionNode) stack[i];
 239             }
 240         }
 241         return null;
 242     }
 243 
 244     /**
 245      * Returns an iterator over all blocks in the context, with the top block (innermost lexical context) first.
 246      * @return an iterator over all blocks in the context.
 247      */
 248     public Iterator<ParserContextBlockNode> getBlocks() {
 249         return new NodeIterator<>(ParserContextBlockNode.class);
 250     }
 251 
 252     /**
 253      * Returns the innermost block in the context.
 254      * @return the innermost block in the context.
 255      */
 256     public ParserContextBlockNode getCurrentBlock() {
 257         return getBlocks().next();
 258     }
 259 
 260     /**
 261      * The last statement added to the context
 262      * @return The last statement added to the context
 263      */
 264     public Statement getLastStatement() {
 265         if (sp == 0) {
 266             return null;
 267         }
 268         final ParserContextNode top = stack[sp - 1];
 269         final int s = top.getStatements().size();
 270         return s == 0 ? null : top.getStatements().get(s - 1);
 271     }
 272 
 273     /**
 274      * Returns an iterator over all functions in the context, with the top (innermost open) function first.
 275      * @return an iterator over all functions in the context.
 276      */
 277     public Iterator<ParserContextFunctionNode> getFunctions() {
 278         return new NodeIterator<>(ParserContextFunctionNode.class);
 279     }
 280 
 281     public ParserContextModuleNode getCurrentModule() {
 282         final Iterator<ParserContextModuleNode> iter = new NodeIterator<>(ParserContextModuleNode.class, getCurrentFunction());
 283         return iter.hasNext() ? iter.next() : null;
 284     }
 285 
 286     private class NodeIterator<T extends ParserContextNode> implements Iterator<T> {
 287         private int index;
 288         private T next;
 289         private final Class<T> clazz;
 290         private ParserContextNode until;
 291 
 292         NodeIterator(final Class<T> clazz) {
 293             this(clazz, null);
 294         }
 295 
 296         NodeIterator(final Class<T> clazz, final ParserContextNode until) {
 297             this.index = sp - 1;
 298             this.clazz = clazz;
 299             this.until = until;
 300             this.next  = findNext();
 301         }
 302 
 303         @Override
 304         public boolean hasNext() {
 305             return next != null;
 306         }
 307 
 308         @Override
 309         public T next() {
 310             if (next == null) {
 311                 throw new NoSuchElementException();
 312             }
 313             final T lnext = next;
 314             next = findNext();
 315             return lnext;
 316         }
 317 
 318         @SuppressWarnings("unchecked")
 319         private T findNext() {
 320             for (int i = index; i >= 0; i--) {
 321                 final Object node = stack[i];
 322                 if (node == until) {
 323                     return null;
 324                 }
 325                 if (clazz.isAssignableFrom(node.getClass())) {
 326                     index = i - 1;
 327                     return (T)node;
 328                 }
 329             }
 330             return null;
 331         }
 332 
 333         @Override
 334         public void remove() {
 335             throw new UnsupportedOperationException();
 336         }
 337     }
 338 }