package accrue.analysis.visit;

import polyglot.ast.Block;
import polyglot.ast.Node;
import polyglot.ast.NodeFactory;
import polyglot.ast.Try;
import polyglot.frontend.Job;
import polyglot.types.SemanticException;
import polyglot.types.TypeSystem;
import polyglot.util.InternalCompilerError;
import polyglot.visit.ErrorHandlingVisitor;
import polyglot.visit.NodeVisitor;
import accrue.analysis.ext.ExtBlock;
import accrue.analysis.ext.AccrueExt;
import accrue.analysis.ext.AccrueExt_c;

/**
 * This visitor identifies finally blocks, and records for every node
 * how many finally blocks it is lexically nested in. This information is 
 * subsequently used in dataflow analyses, where it is important to handle the
 * "suspended execution contexts" correctly (i.e., when flow enters a finally
 * block it is necessary to maintain a stack of return results based on how
 * flow reached the finally block).  
 * 
 */
public class FindFinallyBlocks extends ErrorHandlingVisitor {

    /**
     * The number of lexically enclosing finally blocks.
     */
    private int finallyDepth;

    public FindFinallyBlocks(Job job, TypeSystem ts, NodeFactory nf) {
        super(job, ts, nf);
    }

    @Override
    protected NodeVisitor enterCall(Node parent, Node n)
            throws SemanticException {
        if (n instanceof Block) {
            ExtBlock eb = (ExtBlock) AccrueExt_c.ext(n);
            eb.isFinallyBlock(false);

            if (parent instanceof Try) {
                Try t = (Try) parent;
                if (n == t.finallyBlock()) {
                    eb.isFinallyBlock(true);

                    // we are entering a finally block, so increment finallyDepth
                    this.finallyDepth++;
                }
            }
        }
        return this;
    }

    @Override
    public Node leaveCall(Node parent, Node old, Node n, NodeVisitor v)
            throws SemanticException {
        n = super.leaveCall(parent, old, n, v);
        if (n instanceof Block) {
            ExtBlock eb = (ExtBlock) AccrueExt_c.ext(n);
            if (eb.isFinallyBlock()) {
                // we are leaving a finally block, so 
                // decrement finallyDepth
                this.finallyDepth--;
                if (this.finallyDepth < 0) {
                    throw new InternalCompilerError("What!?");
                }
            }
        }

        // record the finally depth.
        // Note that finally blocks do not count themselves in the depth
        // (i.e., this code comes after the decrement)
        AccrueExt ext = AccrueExt_c.ext(n);
        ext.setEnclosingFinallyDepth(this.finallyDepth);
        return n;
    }
}
