// Copyright (c) 1996-2002 Brian D. Carlstrom

package bdc.scheme.compiler;

import bdc.scheme.Pair;
import bdc.scheme.Scheme;
import bdc.scheme.SchemeException;
import bdc.scheme.Stack;
import bdc.scheme.exception.SyntaxException;
import bdc.scheme.expression.Procedure1;

/**
    Let2Application converts reduces derived Let syntax into another form.
*/
public class Let2Application extends Procedure1
{
    public Object apply1 (Stack stack) throws SchemeException
    {
        Object o1 = stack.array[stack.inUse-1];

        Pair source = Scheme.pair(o1, this);
        Object expression = source.cdr;

        if (expression == Scheme.Null) {
            throw new SyntaxException("Unexpected end of let", source);
        }
        Pair pair = Scheme.pair(expression, this);

        Object variables = Scheme.Null;
        Object inits     = Scheme.Null;

        Object clauses = pair.car;
        Object body    = pair.cdr;
        if ((!(clauses instanceof Pair)) &&
            (clauses != Scheme.Null))
            throw new SyntaxException("Bindings missing from let", source);

        while (clauses != Scheme.Null) {
            pair = Scheme.pair(clauses, this);

            if (pair.car == Scheme.Null) {
                throw new SyntaxException("Let binding missing variable",
                                          source);
            }
            if (!(pair.car instanceof Pair)) {
                throw new SyntaxException("Let binding invalid", source);
            }
            Pair clause = Scheme.pair(pair.car, this);
            Object variable = clause.car;

            if (!(clause.cdr instanceof Pair)) {
                throw new SyntaxException("Let binding missing initializer",
                                          source);
            }
            if (Scheme.pair(clause.cdr, this).cdr != Scheme.Null) {
                throw
                    new SyntaxException(
                        "Let binding has garbage after initializer",
                        source);
            }
            Object init = Scheme.pair(clause.cdr, this).car;

            variables = new Pair(variable, variables);
            inits     = new Pair(init, inits);
            clauses   = Scheme.pair(clauses, this).cdr;
        }

        return new Pair(new Pair(Compiler.Lambda,
                                 new Pair(variables,
                                          body)),
                         inits);
    }
}

