/* * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package jdk.nashorn.internal.parser; import java.util.Iterator; import java.util.NoSuchElementException; import jdk.nashorn.internal.ir.Statement; /** * A class that tracks the current lexical context of node visitation as a stack of {@code ParserContextNode} nodes. Has special * methods to retrieve useful subsets of the context. * * This is implemented with a primitive array and a stack pointer, because it really makes a difference * performance wise. None of the collection classes were optimal */ class ParserContext { private ParserContextNode[] stack; private int sp; private static final int INITIAL_DEPTH = 16; /** * Constructs a ParserContext, * initializes the stack */ public ParserContext(){ this.sp = 0; this.stack = new ParserContextNode[INITIAL_DEPTH]; } /** * Pushes a new block on top of the context, making it the innermost open block. * @param node the new node * @return The node that was pushed */ public T push(final T node) { assert !contains(node); if (sp == stack.length) { final ParserContextNode[] newStack = new ParserContextNode[sp * 2]; System.arraycopy(stack, 0, newStack, 0, sp); stack = newStack; } stack[sp] = node; sp++; return node; } /** * The topmost node on the stack * @return The topmost node on the stack */ public ParserContextNode peek() { return stack[sp - 1]; } /** * Removes and returns the topmost Node from the stack. * @param node The node expected to be popped, used for sanity check * @return The removed node */ public T pop(final T node) { --sp; @SuppressWarnings("unchecked") final T popped = (T)stack[sp]; stack[sp] = null; assert node == popped; return popped; } /** * Tests if a node is on the stack. * @param node The node to test * @return true if stack contains node, false otherwise */ public boolean contains(final ParserContextNode node) { for (int i = 0; i < sp; i++) { if (stack[i] == node) { return true; } } return false; } /** * Returns the topmost {@link ParserContextBreakableNode} on the stack, null if none on stack * @return Returns the topmost {@link ParserContextBreakableNode} on the stack, null if none on stack */ private ParserContextBreakableNode getBreakable() { for (final NodeIterator iter = new NodeIterator<>(ParserContextBreakableNode.class, getCurrentFunction()); iter.hasNext(); ) { final ParserContextBreakableNode next = iter.next(); if (next.isBreakableWithoutLabel()) { return next; } } return null; } /** * Find the breakable node corresponding to this label. * @param labelName name of the label to search for. If null, the closest breakable node will be returned * unconditionally, e.g. a while loop with no label * @return closest breakable node */ public ParserContextBreakableNode getBreakable(final String labelName) { if (labelName != null) { final ParserContextLabelNode foundLabel = findLabel(labelName); if (foundLabel != null) { // iterate to the nearest breakable to the foundLabel ParserContextBreakableNode breakable = null; for (final NodeIterator iter = new NodeIterator<>(ParserContextBreakableNode.class, foundLabel); iter.hasNext(); ) { breakable = iter.next(); } return breakable; } return null; } return getBreakable(); } /** * Returns the loop node of the current loop, or null if not inside a loop * @return loop noder */ public ParserContextLoopNode getCurrentLoop() { final Iterator iter = new NodeIterator<>(ParserContextLoopNode.class, getCurrentFunction()); return iter.hasNext() ? iter.next() : null; } private ParserContextLoopNode getContinueTo() { return getCurrentLoop(); } /** * Find the continue target node corresponding to this label. * @param labelName label name to search for. If null the closest loop node will be returned unconditionally, e.g. a * while loop with no label * @return closest continue target node */ public ParserContextLoopNode getContinueTo(final String labelName) { if (labelName != null) { final ParserContextLabelNode foundLabel = findLabel(labelName); if (foundLabel != null) { // iterate to the nearest loop to the foundLabel ParserContextLoopNode loop = null; for (final NodeIterator iter = new NodeIterator<>(ParserContextLoopNode.class, foundLabel); iter.hasNext(); ) { loop = iter.next(); } return loop; } return null; } return getContinueTo(); } /** * Get the function body of a function node on the stack. * This will trigger an assertion if node isn't present * @param functionNode function node * @return body of function node */ public ParserContextBlockNode getFunctionBody(final ParserContextFunctionNode functionNode) { for (int i = sp - 1; i >= 0 ; i--) { if (stack[i] == functionNode) { return (ParserContextBlockNode)stack[i + 1]; } } throw new AssertionError(functionNode.getName() + " not on context stack"); } /** * Check the stack for a given label node by name * @param name name of the label * @return LabelNode if found, null otherwise */ public ParserContextLabelNode findLabel(final String name) { for (final Iterator iter = new NodeIterator<>(ParserContextLabelNode.class, getCurrentFunction()); iter.hasNext(); ) { final ParserContextLabelNode next = iter.next(); if (next.getLabelName().equals(name)) { return next; } } return null; } /** * Prepends a statement to the current node. * @param statement The statement to prepend */ public void prependStatementToCurrentNode(final Statement statement) { assert statement != null; stack[sp - 1].prependStatement(statement); } /** * Appends a statement to the current Node. * @param statement The statement to append */ public void appendStatementToCurrentNode(final Statement statement) { assert statement != null; stack[sp - 1].appendStatement(statement); } /** * Returns the innermost function in the context. * @return the innermost function in the context. */ public ParserContextFunctionNode getCurrentFunction() { for (int i = sp - 1; i >= 0; i--) { if (stack[i] instanceof ParserContextFunctionNode) { return (ParserContextFunctionNode) stack[i]; } } return null; } /** * Returns an iterator over all blocks in the context, with the top block (innermost lexical context) first. * @return an iterator over all blocks in the context. */ public Iterator getBlocks() { return new NodeIterator<>(ParserContextBlockNode.class); } /** * Returns the innermost block in the context. * @return the innermost block in the context. */ public ParserContextBlockNode getCurrentBlock() { return getBlocks().next(); } /** * The last statement added to the context * @return The last statement added to the context */ public Statement getLastStatement() { if (sp == 0) { return null; } final ParserContextNode top = stack[sp - 1]; final int s = top.getStatements().size(); return s == 0 ? null : top.getStatements().get(s - 1); } /** * Returns an iterator over all functions in the context, with the top (innermost open) function first. * @return an iterator over all functions in the context. */ public Iterator getFunctions() { return new NodeIterator<>(ParserContextFunctionNode.class); } public ParserContextModuleNode getCurrentModule() { final Iterator iter = new NodeIterator<>(ParserContextModuleNode.class, getCurrentFunction()); return iter.hasNext() ? iter.next() : null; } private class NodeIterator implements Iterator { private int index; private T next; private final Class clazz; private ParserContextNode until; NodeIterator(final Class clazz) { this(clazz, null); } NodeIterator(final Class clazz, final ParserContextNode until) { this.index = sp - 1; this.clazz = clazz; this.until = until; this.next = findNext(); } @Override public boolean hasNext() { return next != null; } @Override public T next() { if (next == null) { throw new NoSuchElementException(); } final T lnext = next; next = findNext(); return lnext; } @SuppressWarnings("unchecked") private T findNext() { for (int i = index; i >= 0; i--) { final Object node = stack[i]; if (node == until) { return null; } if (clazz.isAssignableFrom(node.getClass())) { index = i - 1; return (T)node; } } return null; } @Override public void remove() { throw new UnsupportedOperationException(); } } }