package org.yinwang.pysonar.ast;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.yinwang.pysonar.Indexer;
import org.yinwang.pysonar.Scope;
import org.yinwang.pysonar.types.Type;
import org.yinwang.pysonar.types.UnionType;
import java.util.ArrayList;
import java.util.List;
public abstract class Node implements java.io.Serializable {
public int start = -1;
public int end = -1;
@Nullable
protected Node parent = null;
public Node() {
}
public Node(int start, int end) {
this.start = start;
this.end = end;
}
public void setParent(Node parent) {
this.parent = parent;
}
@Nullable
public Node getParent() {
return parent;
}
@NotNull
public Node getAstRoot() {
if (parent == null) {
return this;
}
return parent.getAstRoot();
}
public int length() {
return end - start;
}
/**
* Returns {@code true} if this is a name-binding node.
* Includes functions/lambdas, function/lambda params, classes,
* assignments, imports, and implicit assignment via for statements
* and except clauses.
* @see "http://www.python.org/dev/peps/pep-0227"
*/
public boolean bindsName() {
return false;
}
/**
* @return the path to the code that generated this AST
*/
@Nullable
public String getFile() {
return parent != null ? parent.getFile() : null;
}
public void addChildren(@Nullable Node... nodes) {
if (nodes != null) {
for (Node n : nodes) {
if (n != null) {
n.setParent(this);
}
}
}
}
public void addChildren(@Nullable List<? extends Node> nodes) {
if (nodes != null) {
for (Node n : nodes) {
if (n != null) {
n.setParent(this);
}
}
}
}
@Nullable
public Str docstring() {
Node body = null;
if (this instanceof FunctionDef) {
body = ((FunctionDef)this).body;
} else if (this instanceof ClassDef) {
body = ((ClassDef)this).body;
} else if (this instanceof Module) {
body = ((Module)this).body;
}
if (body instanceof Block && ((Block)body).seq.size() >= 1) {
Node firstExpr = ((Block)body).seq.get(0);
if (firstExpr instanceof Expr) {
Node docstrNode = ((Expr)firstExpr).value;
if (docstrNode != null && docstrNode instanceof Str) {
return (Str)docstrNode;
}
}
}
return null;
}
@NotNull
public static Type resolveExpr(@NotNull Node n, Scope s, int tag) {
return n.resolve(s, tag);
}
/**
* @param s the symbol table
* @param tag thread tag of the execution path
*/
@NotNull
abstract public Type resolve(Scope s, int tag);
public boolean isCall() {
return this instanceof Call;
}
public boolean isModule() {
return this instanceof Module;
}
public boolean isClassDef() {
return false;
}
public boolean isFunctionDef() {
return false;
}
public boolean isLambda() {
return false;
}
public boolean isName() {
return this instanceof Name;
}
public boolean isGlobal() {
return this instanceof Global;
}
@NotNull
public Call asCall() {
return (Call)this;
}
@NotNull
public Module asModule() {
return (Module)this;
}
@NotNull
public ClassDef asClassDef() {
return (ClassDef)this;
}
@NotNull
public FunctionDef asFunctionDef() {
return (FunctionDef)this;
}
@NotNull
public Lambda asLambda() {
return (Lambda)this;
}
@NotNull
public Name asName() {
return (Name)this;
}
@NotNull
public Global asGlobal() {
return (Global)this;
}
protected void visitNode(@Nullable Node n, NodeVisitor v) {
if (n != null) {
n.visit(v);
}
}
protected void visitNodeList(@Nullable List<? extends Node> nodes, NodeVisitor v) {
if (nodes != null) {
for (Node n : nodes) {
if (n != null) {
n.visit(v);
}
}
}
}
/**
* Visits this node and optionally its children. <p>
*
* @param visitor the object to call with this node.
* If the visitor returns {@code true}, the node also
* passes its children to the visitor.
*/
public abstract void visit(NodeVisitor visitor);
protected void addWarning(String msg) {
Indexer.idx.putProblem(this, msg);
}
protected void addError(String msg) {
Indexer.idx.putProblem(this, msg);
}
/**
* Utility method to resolve every node in {@code nodes} and
* return the union of their types. If {@code nodes} is empty or
* {@code null}, returns a new {@link org.yinwang.pysonar.types.UnknownType}.
*/
@Nullable
protected Type resolveListAsUnion(@Nullable List<? extends Node> nodes, Scope s, int tag) {
if (nodes == null || nodes.isEmpty()) {
return Indexer.idx.builtins.unknown;
}
Type result = null;
for (Node node : nodes) {
Type nodeType = resolveExpr(node, s, tag);
if (result == null) {
result = nodeType;
} else {
result = UnionType.union(result, nodeType);
}
}
return result;
}
/**
* Resolves each element of a node list in the passed scope.
* Node list may be empty or {@code null}.
*/
static protected void resolveList(@Nullable List<? extends Node> nodes, Scope s, int tag) {
if (nodes != null) {
for (Node n : nodes) {
resolveExpr(n, s, tag);
}
}
}
@Nullable
static protected List<Type> resolveAndConstructList(@Nullable List<? extends Node> nodes, Scope s, int tag) {
if (nodes == null) {
return null;
} else {
List<Type> typeList = new ArrayList<>();
for (Node n : nodes) {
typeList.add(resolveExpr(n, s, tag));
}
return typeList;
}
}
/**
* Assumes nodes are always traversed in increasing order of their start
* positions.
*/
static class DeepestOverlappingNodeFinder extends GenericNodeVisitor {
private int offset;
private Node deepest;
public DeepestOverlappingNodeFinder(int offset) {
this.offset = offset;
}
/**
* Returns the deepest node overlapping the desired source offset.
* @return the node, or {@code null} if no node overlaps the offset
*/
public Node getNode() {
return deepest;
}
@Override
public boolean dispatch(@NotNull Node node) {
// This node ends before the offset, so don't look inside it.
if (offset > node.end) {
return false; // don't traverse children, but do keep going
}
if (offset >= node.start && offset <= node.end) {
deepest = node;
return true; // visit kids
}
// this node starts after the offset, so we're done
throw new NodeVisitor.StopIterationException();
}
}
/**
* Searches the AST for the deepest node that overlaps the specified source
* offset. Can be called from any node in the AST, as it traverses to the
* parent before beginning the search.
* @param sourceOffset the spot at which to look for a node
* @return the deepest AST node whose start is greater than or equal to the offset,
* and whose end is less than or equal to the offset. Returns {@code null}
* if no node overlaps {@code sourceOffset}.
*/
public Node getDeepestNodeAtOffset(int sourceOffset) {
Node ast = getAstRoot();
DeepestOverlappingNodeFinder finder = new DeepestOverlappingNodeFinder(sourceOffset);
try {
ast.visit(finder);
} catch (NodeVisitor.StopIterationException six) {
// expected
}
return finder.getNode();
}
}