/*
* Copyright (c) 2010, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
*
* Subject to the condition set forth below, permission is hereby granted to any
* person obtaining a copy of this software, associated documentation and/or
* data (collectively the "Software"), free of charge and under any and all
* copyright rights in the Software, and any and all patent rights owned or
* freely licensable by each licensor hereunder covering either (i) the
* unmodified Software as contributed to or provided by such licensor, or (ii)
* the Larger Works (as defined below), to deal in both
*
* (a) the Software, and
*
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
* one is included with the Software each a "Larger Work" to which the Software
* is contributed by such licensors),
*
* without restriction, including without limitation the rights to copy, create
* derivative works of, display, perform, and distribute the Software and make,
* use, sell, offer for sale, import, export, have made, and have sold the
* Software and the Larger Work(s), and to sublicense the foregoing rights on
* either these or other terms.
*
* This license is subject to the following condition:
*
* The above copyright notice and either this complete permission notice or at a
* minimum a reference to the UPL must be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.oracle.js.parser.ir;
import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;
import com.oracle.js.parser.Source;
A class that tracks the current lexical context of node visitation as a stack of Block
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. /**
* A class that tracks the current lexical context of node visitation as a stack of {@link Block}
* 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.
*/
public class LexicalContext {
private LexicalContextNode[] stack;
private int sp;
Creates a new empty lexical context.
/**
* Creates a new empty lexical context.
*/
public LexicalContext() {
this.stack = new LexicalContextNode[16];
}
Creates a copy of a lexical context.
/**
* Creates a copy of a lexical context.
*/
private LexicalContext(LexicalContext from) {
this.stack = Arrays.copyOf(from.stack, from.stack.length);
this.sp = from.sp;
}
Returns: all nodes in the LexicalContext.
/**
* @return all nodes in the LexicalContext.
*/
public Iterator<LexicalContextNode> getAllNodes() {
return new NodeIterator<>(LexicalContextNode.class);
}
Pushes a new block on top of the context, making it the innermost open block.
Params: - node – the new node
Type parameters: - <T> – the type of the new node
Returns: the node that was pushed
/**
* Pushes a new block on top of the context, making it the innermost open block.
*
* @param <T> the type of the new node
* @param node the new node
*
* @return the node that was pushed
*/
public <T extends LexicalContextNode> T push(final T node) {
assert !contains(node);
if (sp == stack.length) {
final LexicalContextNode[] newStack = new LexicalContextNode[sp * 2];
System.arraycopy(stack, 0, newStack, 0, sp);
stack = newStack;
}
stack[sp] = node;
sp++;
return node;
}
Is the context empty?
Returns: true
if empty
/**
* Is the context empty?
*
* @return {@code true} if empty
*/
public boolean isEmpty() {
return sp == 0;
}
Pops the innermost block off the context and all nodes that has been contributed since it was
put there.
Params: - node – the node expected to be popped, used to detect unbalanced pushes/pops
Type parameters: - <T> – the type of the node to be popped
Returns: the node that was popped
/**
* Pops the innermost block off the context and all nodes that has been contributed since it was
* put there.
*
* @param <T> the type of the node to be popped
* @param node the node expected to be popped, used to detect unbalanced pushes/pops
*
* @return the node that was popped
*/
@SuppressWarnings("unchecked")
public <T extends LexicalContextNode> T pop(final T node) {
--sp;
final LexicalContextNode popped = stack[sp];
stack[sp] = null;
return (T) popped;
}
Check if a node is in the lexical context.
Params: - node – node to check for
Returns: true
if in the context
/**
* Check if a node is in the lexical context.
*
* @param node node to check for
*
* @return {@code true} if in the context
*/
public boolean contains(final LexicalContextNode node) {
for (int i = 0; i < sp; i++) {
if (stack[i] == node) {
return true;
}
}
return false;
}
Replace a node on the lexical context with a new one. Normally you should try to engineer IR
traversals so this isn't needed
Params: - oldNode – old node
- newNode – new node
Returns: the new node
/**
* Replace a node on the lexical context with a new one. Normally you should try to engineer IR
* traversals so this isn't needed
*
* @param oldNode old node
* @param newNode new node
*
* @return the new node
*/
public LexicalContextNode replace(final LexicalContextNode oldNode, final LexicalContextNode newNode) {
for (int i = sp - 1; i >= 0; i--) {
if (stack[i] == oldNode) {
assert i == sp - 1 : "violation of contract - we always expect to find the replacement node on top of the lexical context stack: " + newNode + " has " + stack[i + 1].getClass() +
" above it";
stack[i] = newNode;
break;
}
}
return newNode;
}
Returns an iterator over all blocks in the context, with the top block (innermost lexical
context) first.
Returns: an iterator over all blocks in the context.
/**
* 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<Block> getBlocks() {
return new NodeIterator<>(Block.class);
}
Returns an iterator over all functions in the context, with the top (innermost open) function
first.
Returns: an iterator over all functions in the context.
/**
* 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<FunctionNode> getFunctions() {
return new NodeIterator<>(FunctionNode.class);
}
Returns: the innermost block in the context.
/**
* @return the innermost block in the context.
*/
public Block getCurrentBlock() {
return getBlocks().next();
}
Returns: the innermost function in the context.
/**
* @return the innermost function in the context.
*/
public FunctionNode getCurrentFunction() {
for (int i = sp - 1; i >= 0; i--) {
if (stack[i] instanceof FunctionNode) {
return (FunctionNode) stack[i];
}
}
return null;
}
public FunctionNode getCurrentNonArrowFunction() {
final Iterator<FunctionNode> iter = getFunctions();
while (iter.hasNext()) {
final FunctionNode fn = iter.next();
if (!fn.isArrow()) {
return fn;
}
}
return null;
}
Returns the innermost scope in the context.
/**
* Returns the innermost scope in the context.
*/
public Scope getCurrentScope() {
NodeIterator<LexicalContextScope> iterator = new NodeIterator<>(LexicalContextScope.class);
return iterator.hasNext() ? iterator.next().getScope() : null;
}
Returns: the innermost class in the context.
/**
* @return the innermost class in the context.
*/
public ClassNode getCurrentClass() {
NodeIterator<ClassNode> iterator = new NodeIterator<>(ClassNode.class);
return iterator.hasNext() ? iterator.next() : null;
}
public boolean inModule() {
for (Iterator<FunctionNode> functions = getFunctions(); functions.hasNext();) {
FunctionNode function = functions.next();
if (function.isModule()) {
return true;
}
}
return false;
}
public LexicalContext copy() {
return new LexicalContext(this);
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
sb.append("[ ");
for (int i = 0; i < sp; i++) {
final Object node = stack[i];
sb.append(node.getClass().getSimpleName());
sb.append('@');
sb.append(id(node));
sb.append(':');
if (node instanceof FunctionNode) {
final FunctionNode fn = (FunctionNode) node;
final Source source = fn.getSource();
String src = source.toString();
int indexSep = src.lastIndexOf(':');
if (indexSep >= 0) {
src = src.substring(indexSep);
}
sb.append(src);
sb.append(' ');
sb.append(fn.getLineNumber());
}
sb.append(' ');
}
sb.append(" ==> ]");
return sb.toString();
}
private static String id(Object x) {
return String.format("0x%08x", System.identityHashCode(x));
}
private class NodeIterator<T extends LexicalContextNode> implements Iterator<T> {
private int index;
private T next;
private final Class<T> clazz;
private LexicalContextNode until;
NodeIterator(final Class<T> clazz) {
this(clazz, null);
}
NodeIterator(final Class<T> clazz, final LexicalContextNode 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;
}
}
}