// Copyright (c) 1996-2002 Brian D. Carlstrom

package bdc.scheme.expression;

import bdc.scheme.Environment;
import bdc.scheme.Scheme;
import bdc.scheme.SchemeException;
import bdc.scheme.Stack;
import bdc.scheme.Variable;
import bdc.scheme.exception.ArgumentCountException;

public class Compound extends ProcedureN
{
    Lambda      lambda;
    Environment environment;

    public Compound (Lambda      lambda,
                     Environment environment)
    {
        this.lambda      = lambda;
        this.environment = environment;
    }

    public Object applyN (int n, Stack stack) throws SchemeException
    {
        if (n != lambda.variables.arguments) {
            throw new ArgumentCountException(this,
                                             lambda.variables.arguments,
                                             n);
        }

        /*
            Extend the environment

            if we don't want to add a frame, just use the environment
            we were defined in.

            if we want to add a frame, make one and copy the necessary
            values into it
        */
        Environment environment;
        if (lambda.variables.heap == 0) {
            environment = this.environment;
        }
        else {
            /*
                Allocate the new environment frame
            */
            Object[] frame = new Object[lambda.variables.heap];

            /*
                Copy the heap referenced arguments to the frame
            */
            Variable[] variables = lambda.variables.array();
            int h = 0;
            for (int v = 1; v <= lambda.variables.arguments; v++) {
                if (variables[v-1].heap) {
                    frame[h] = stack.array[stack.inUse-v];
                    h++;
                }
            }
            environment = Environment.extend(this.environment,
                                             lambda.variables,
                                             frame);

        }

        int saved = stack.frame;
        try {
            stack.frame = stack.inUse;

            /*
                Push the space for the true locals on the stack
            */
            for (int l = 0; l < lambda.variables.local; l++) {
                stack.addElement(null);
            }

            Expression[] expressions = lambda.body;
            int i = 0;
            try {
                Object result = Scheme.Unspecified;
                for (; i < expressions.length; i++)
                    result = expressions[i].eval(environment, stack);
                return result;
            }
            catch (SchemeException se) {
                Expression.backTrace(expressions[i], se);
                return Scheme.NotReached;
            }
        }
        finally {
            stack.inUse -= lambda.variables.local;
            stack.frame = saved;
        }
    }
}
