// Copyright (c) 1996-2002 Brian D. Carlstrom

package bdc.scheme.procedure;

import bdc.scheme.Stack;
import bdc.scheme.exception.CallCCException;
import bdc.scheme.expression.Procedure1;
import bdc.scheme.Scheme;
import bdc.scheme.SchemeException;

/**
    (call-with-current-continuation x)

    CallCC creates an ExitProcedure that marks the point to return to
    and calls its Procedure argument with that ExitProcedure.

    If the ExitProcedure is invoked it packages up its argument and
    itself and throws a CallCCException. CallCC is up on the stack
    waiting for the CallCCException and if found, checks if it is the
    destination, and if so returns the value. Otherwise it is destined
    for someone still up on the stack and it rethrows.
*/
public class CallCC extends Procedure1
{
    public Object apply1 (Stack stack) throws SchemeException
    {
        Object o1 = stack.array[stack.inUse-1];
        ExitProcedure exit = new ExitProcedure();
        stack.addElement(exit);
        try {
            return Scheme.procedure(o1, this).apply1(stack);
        }
        catch (CallCCException cwcc) {
            /* if this is the exit we are looking for return the value */
            if (cwcc.exitProcedure == exit) {
                return cwcc.value;
            }
            /* otherwise rethrow */
            throw cwcc;
        }
        finally {
            /**
                when the ExitProcedure passes this point disable it
            */
            exit.used = true;

            stack.inUse -= 1;
        }
    }
}

/**
    See CallCC above
*/
class ExitProcedure extends Procedure1
{
    boolean used = false;
    public Object apply1 (Stack stack) throws CallCCException
    {
        Object o1 = stack.array[stack.inUse-1];
        if (used) {
            return Scheme.Unspecified;
        }
        used = true;
        throw new CallCCException(o1, this);
    }
}
