ParserContext.java revision 1060:ca67ae7c46cb
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 */
25package jdk.nashorn.internal.parser;
26
27import java.util.Iterator;
28import java.util.NoSuchElementException;
29import 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
39class 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        } else {
145            return getBreakable();
146        }
147    }
148
149    /**
150     * Returns the loop node of the current loop, or null if not inside a loop
151     * @return loop noder
152     */
153    public ParserContextLoopNode getCurrentLoop() {
154        final Iterator<ParserContextLoopNode> iter = new NodeIterator<>(ParserContextLoopNode.class, getCurrentFunction());
155        return iter.hasNext() ? iter.next() : null;
156    }
157
158    private ParserContextLoopNode getContinueTo() {
159        return getCurrentLoop();
160    }
161
162    /**
163     * Find the continue target node corresponding to this label.
164     * @param labelName label name to search for. If null the closest loop node will be returned unconditionally, e.g. a
165     * while loop with no label
166     * @return closest continue target node
167     */
168    public ParserContextLoopNode getContinueTo(final String labelName) {
169        if (labelName != null) {
170            final ParserContextLabelNode foundLabel = findLabel(labelName);
171            if (foundLabel != null) {
172                // iterate to the nearest loop to the foundLabel
173                ParserContextLoopNode loop = null;
174                for (final NodeIterator<ParserContextLoopNode> iter = new NodeIterator<>(ParserContextLoopNode.class, foundLabel); iter.hasNext(); ) {
175                    loop = iter.next();
176                }
177                return loop;
178            }
179            return null;
180        }
181        return getContinueTo();
182    }
183
184    /**
185     * Get the function body of a function node on the stack.
186     * This will trigger an assertion if node isn't present
187     * @param functionNode function node
188     * @return body of function node
189     */
190    public ParserContextBlockNode getFunctionBody(final ParserContextFunctionNode functionNode) {
191        for (int i = sp - 1; i >= 0 ; i--) {
192            if (stack[i] == functionNode) {
193                return (ParserContextBlockNode)stack[i + 1];
194            }
195        }
196        throw new AssertionError(functionNode.getName() + " not on context stack");
197    }
198
199    /**
200     * Check the stack for a given label node by name
201     * @param name name of the label
202     * @return LabelNode if found, null otherwise
203     */
204    public ParserContextLabelNode findLabel(final String name) {
205        for (final Iterator<ParserContextLabelNode> iter = new NodeIterator<>(ParserContextLabelNode.class, getCurrentFunction()); iter.hasNext(); ) {
206            final ParserContextLabelNode next = iter.next();
207            if (next.getLabelName().equals(name)) {
208                return next;
209            }
210        }
211        return null;
212    }
213
214    /**
215     * Prepends a statement to the current node.
216     * @param statement The statement to prepend
217     */
218    public void prependStatementToCurrentNode(final Statement statement) {
219        assert statement != null;
220        stack[sp - 1].prependStatement(statement);
221    }
222
223    /**
224     * Appends a statement to the current Node.
225     * @param statement The statement to append
226     */
227    public void appendStatementToCurrentNode(final Statement statement) {
228        assert statement != null;
229        stack[sp - 1].appendStatement(statement);
230    }
231
232    /**
233     * Returns the innermost function in the context.
234     * @return the innermost function in the context.
235     */
236    public ParserContextFunctionNode getCurrentFunction() {
237        for (int i = sp - 1; i >= 0; i--) {
238            if (stack[i] instanceof ParserContextFunctionNode) {
239                return (ParserContextFunctionNode) stack[i];
240            }
241        }
242        return null;
243    }
244
245    /**
246     * Returns an iterator over all blocks in the context, with the top block (innermost lexical context) first.
247     * @return an iterator over all blocks in the context.
248     */
249    public Iterator<ParserContextBlockNode> getBlocks() {
250        return new NodeIterator<>(ParserContextBlockNode.class);
251    }
252
253    /**
254     * Returns the innermost block in the context.
255     * @return the innermost block in the context.
256     */
257    public ParserContextBlockNode getCurrentBlock() {
258        return getBlocks().next();
259    }
260
261    /**
262     * The last statement added to the context
263     * @return The last statement added to the context
264     */
265    public Statement getLastStatement() {
266        if (sp == 0) {
267            return null;
268        }
269        final ParserContextNode top = stack[sp - 1];
270        final int s = top.getStatements().size();
271        return s == 0 ? null : top.getStatements().get(s - 1);
272    }
273
274    /**
275     * Returns an iterator over all functions in the context, with the top (innermost open) function first.
276     * @return an iterator over all functions in the context.
277     */
278    public Iterator<ParserContextFunctionNode> getFunctions() {
279        return new NodeIterator<>(ParserContextFunctionNode.class);
280    }
281
282    private class NodeIterator <T extends ParserContextNode> implements Iterator<T> {
283        private int index;
284        private T next;
285        private final Class<T> clazz;
286        private ParserContextNode until;
287
288        NodeIterator(final Class<T> clazz) {
289            this(clazz, null);
290        }
291
292        NodeIterator(final Class<T> clazz, final ParserContextNode until) {
293            this.index = sp - 1;
294            this.clazz = clazz;
295            this.until = until;
296            this.next  = findNext();
297        }
298
299        @Override
300        public boolean hasNext() {
301            return next != null;
302        }
303
304        @Override
305        public T next() {
306            if (next == null) {
307                throw new NoSuchElementException();
308            }
309            final T lnext = next;
310            next = findNext();
311            return lnext;
312        }
313
314        @SuppressWarnings("unchecked")
315        private T findNext() {
316            for (int i = index; i >= 0; i--) {
317                final Object node = stack[i];
318                if (node == until) {
319                    return null;
320                }
321                if (clazz.isAssignableFrom(node.getClass())) {
322                    index = i - 1;
323                    return (T)node;
324                }
325            }
326            return null;
327        }
328
329        @Override
330        public void remove() {
331            throw new UnsupportedOperationException();
332        }
333    }
334}
335