All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.shapesecurity.shift.scope.ScopeLookup Maven / Gradle / Ivy

The newest version!
package com.shapesecurity.shift.scope;

import com.shapesecurity.functional.Pair;
import com.shapesecurity.functional.Tuple3;
import com.shapesecurity.functional.Tuple4;
import com.shapesecurity.functional.data.HashTable;
import com.shapesecurity.functional.data.Maybe;
import com.shapesecurity.shift.ast.*;

import org.jetbrains.annotations.NotNull;

import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.NoSuchElementException;


public class ScopeLookup {
    @NotNull
    private final Map bindingIdentifierDeclarationCache = new IdentityHashMap<>();
    @NotNull
    private final Map bindingIdentifierReferenceCache = new IdentityHashMap<>(); // bi to referenced variable
    @NotNull
    private final Map identifierExpressionReferenceCache = new IdentityHashMap<>(); // ie to referenced variable
    @NotNull
    private final Map>> functionDeclarationCache = new IdentityHashMap<>();
    @NotNull
    private final Map isGlobalCache = new IdentityHashMap<>();
    @NotNull
    private final Map nodeScopeCache = new IdentityHashMap<>(); // node to the *outermost* scope associated with it. depends on the assumption that there is a unique such.

    public ScopeLookup(@NotNull GlobalScope scope) {
        scopeHelper(scope);
    }

    private void scopeHelper(@NotNull Scope scope) {
        scope.children.foreach(this::scopeHelper); // logic depends on this occurring first.
        for (Variable v : scope.variables()) { // TODO make this functional
            variableHelper(v);
            isGlobalCache.put(v, scope instanceof GlobalScope);
        }
        nodeScopeCache.put(scope.astNode, scope);
    }

    private void variableHelper(@NotNull Variable variable) {
        variable.declarations.foreach(decl -> {
            if (bindingIdentifierDeclarationCache.containsKey(decl.node)) {
                functionDeclarationCache.put(decl.node, new Pair<>(bindingIdentifierDeclarationCache.get(decl.node), Maybe.of(variable)));
            } else {
                bindingIdentifierDeclarationCache.put(decl.node, variable);
            }
        });
        variable.references.foreach(ref -> {
            ref.node.foreach(
                    bi -> bindingIdentifierReferenceCache.put(bi, variable),
                    ie -> identifierExpressionReferenceCache.put(ie, variable)
            );
        });
    }

    @NotNull
    public Maybe findVariableDeclaredBy(@NotNull BindingIdentifier bindingIdentifier) { // NB: When used with function declarations, which can declare multiple variables under B.3.3, returns the lexical binding. When used with class declarations, returns the class-local binding.
        return Maybe.fromNullable(bindingIdentifierDeclarationCache.get(bindingIdentifier));
    }

    @NotNull
    public Maybe findVariableReferencedBy(@NotNull BindingIdentifier bindingIdentifier) {
        return Maybe.fromNullable(bindingIdentifierReferenceCache.get(bindingIdentifier));
    }

    @NotNull
    public Variable findVariableReferencedBy(@NotNull IdentifierExpression identifierExpression) {
        Variable v = identifierExpressionReferenceCache.get(identifierExpression);
        if (v == null) {
            throw new NoSuchElementException("Identifier expression not present in AST");
        }
        return v;
    }

    // Because of annex B.3.3, in addition to a lexical binding (outside of scripts, which ???), functions may create a variable
    // binding for themselves. This helper gets both the (necessarily created) lexical binding and the (possible) variable binding.
    // Takes a FunctionDeclaration to ensure it is not misused, but only actually needs its binding identifier.
    // Assuming the function declaration occurs somewhere in the AST corresponding to this global scope,
    // there always will be at least one variable declared by the given function.
    // Returns (lexical, variable)
    @NotNull
    public Pair> findVariablesForFuncDecl(@NotNull final FunctionDeclaration func) {
        if (func.name.name.equals("*default*")) {
            throw new IllegalArgumentException("Can't lookup default exports");
        }
        if (functionDeclarationCache.containsKey(func.name)) {
            return functionDeclarationCache.get(func.name);
        } else {
            Variable v = bindingIdentifierDeclarationCache.get(func.name);
            if (v == null) {
                throw new NoSuchElementException("Function declaration present in AST");
            }
            return new Pair<>(v, Maybe.empty());
        }
    }

    // Class declarations always create two variables. This function returns both: (class-local, outer).
    @NotNull
    public Pair findVariablesForClassDecl(@NotNull final ClassDeclaration cl) {
        if (cl.name.name.equals("*default*")) {
            throw new IllegalArgumentException("Can't lookup default exports");
        }
        Pair> vs = functionDeclarationCache.get(cl.name);
        if (vs == null) {
            throw new NoSuchElementException("Class declaration not present in AST");
        }
        return new Pair<>(vs.left, vs.right.fromJust());
    }

    public boolean isGlobal(@NotNull Variable variable) {
        return isGlobalCache.get(variable);
    }

    @NotNull
    public Maybe findScopeFor(@NotNull Node node) {
        return Maybe.fromNullable(nodeScopeCache.get(node));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy