/*
 * $Id: eval.c,v 1.7 2005-05-28 13:34:26 bacon Exp $
 */

#include <xp/lsp/lisp.h>
#include <xp/lsp/env.h>
#include <xp/lsp/primitive.h>
#include <xp/bas/assert.h>

static xp_lisp_obj_t* make_func (xp_lisp_t* lsp, xp_lisp_obj_t* cdr, int is_macro);
static xp_lisp_obj_t* eval_cons (xp_lisp_t* lsp, xp_lisp_obj_t* cons);
static xp_lisp_obj_t* apply     (xp_lisp_t* lsp, xp_lisp_obj_t* func, xp_lisp_obj_t* actual);

xp_lisp_obj_t* xp_lisp_eval (xp_lisp_t* lsp, xp_lisp_obj_t* obj)
{
	lsp->error = XP_LISP_ERR_NONE;

	if (XP_LISP_TYPE(obj) == XP_LISP_OBJ_CONS) 
		return eval_cons (lsp, obj);
	else if (XP_LISP_TYPE(obj) == XP_LISP_OBJ_SYMBOL) {
		xp_lisp_assoc_t* assoc; 

		/*
		if (obj == lsp->mem->lambda || obj == lsp->mem->macro) {
			printf ("lambda or macro can't be used as a normal symbol\n");
			lsp->error = XP_LISP_ERR_BAD_SYMBOL;
			return XP_NULL;
		}
		*/

		if ((assoc = xp_lisp_lookup (lsp->mem, obj)) == XP_NULL) {
			if (lsp->opt_undef_symbol) {
				lsp->error = XP_LISP_ERR_UNDEF_SYMBOL;
				return XP_NULL;
			}
			return lsp->mem->nil;
		}

		obj = assoc->value;
	}

	return obj;
}

static xp_lisp_obj_t* make_func (xp_lisp_t* lsp, xp_lisp_obj_t* cdr, int is_macro)
{
	// TODO: lambda expression syntax check.
	xp_lisp_obj_t* func, * formal, * body;

printf ("about to create a function or a macro ....\n");

	if (cdr == lsp->mem->nil) {
		lsp->error = XP_LISP_ERR_TOO_FEW_ARGS;
		return XP_NULL;
	}

	if (XP_LISP_TYPE(cdr) != XP_LISP_OBJ_CONS) {
		lsp->error = XP_LISP_ERR_BAD_ARG;
		return XP_NULL;
	}

	formal = XP_LISP_CAR(cdr);
	body = XP_LISP_CDR(cdr);

	if (body == lsp->mem->nil) {
		lsp->error = XP_LISP_ERR_EMPTY_BODY;
		return XP_NULL;
	}

	func = (is_macro)?
		xp_lisp_make_macro (lsp->mem, formal, body):
		xp_lisp_make_func (lsp->mem, formal, body);
	if (func == XP_NULL) {
		lsp->error = XP_LISP_ERR_MEM;
		return XP_NULL;
	}

	return func;
}

static xp_lisp_obj_t* eval_cons (xp_lisp_t* lsp, xp_lisp_obj_t* cons)
{
	xp_lisp_obj_t* car, * cdr;
   
	xp_assert (XP_LISP_TYPE(cons) == XP_LISP_OBJ_CONS);

	car = XP_LISP_CAR(cons);
	cdr = XP_LISP_CDR(cons);

	if (car == lsp->mem->lambda) {
		return make_func (lsp, cdr, 0);
	}
	else if (car == lsp->mem->macro) {
		return make_func (lsp, cdr, 1);
	}
	else if (XP_LISP_TYPE(car) == XP_LISP_OBJ_SYMBOL) {
		xp_lisp_assoc_t* assoc;

		if ((assoc = xp_lisp_lookup (lsp->mem, car)) != XP_NULL) {
			xp_lisp_obj_t* func = assoc->value;
			if (XP_LISP_TYPE(func) == XP_LISP_OBJ_FUNC ||
			    XP_LISP_TYPE(func) == XP_LISP_OBJ_MACRO) {
				return apply (lsp, func, cdr);
			}
			else if (XP_LISP_TYPE(func) == XP_LISP_OBJ_PRIM) {
				// primitive function
				return XP_LISP_PIMPL(func) (lsp, cdr);
			}
			else {
				printf ("undefined function: ");
				xp_lisp_print (lsp, car);
				printf ("\n");
				lsp->error = XP_LISP_ERR_UNDEF_FUNC;
				return XP_NULL;
			}
		}
		else {
			//TODO: better error handling.
			printf ("undefined function: ");
			xp_lisp_print (lsp, car);
			printf ("\n");
			lsp->error = XP_LISP_ERR_UNDEF_FUNC;
			return XP_NULL;
		}
	}
	else if (XP_LISP_TYPE(car) == XP_LISP_OBJ_FUNC || 
	         XP_LISP_TYPE(car) == XP_LISP_OBJ_MACRO) {
		return apply (lsp, car, cdr);
	}
	else if (XP_LISP_TYPE(car) == XP_LISP_OBJ_CONS) {
		if (XP_LISP_CAR(car) == lsp->mem->lambda) {
			xp_lisp_obj_t* func = make_func (lsp, XP_LISP_CDR(car), 0);
			if (func == XP_NULL) return XP_NULL;
			return apply (lsp, func, cdr);
		}
		else if (XP_LISP_CAR(car) == lsp->mem->macro) {
			xp_lisp_obj_t* func = make_func (lsp, XP_LISP_CDR(car), 1);
			if (func == XP_NULL) return XP_NULL;
			return apply (lsp, func, cdr);
		}
	}

	xp_printf (XP_TEXT("bad function: "));
	xp_lisp_print (lsp, car);
	xp_printf (XP_TEXT("\n"));
	lsp->error = XP_LISP_ERR_BAD_FUNC;
	return XP_NULL;
}

static xp_lisp_obj_t* apply (xp_lisp_t* lsp, xp_lisp_obj_t* func, xp_lisp_obj_t* actual)
{
	xp_lisp_frame_t* frame;
	xp_lisp_obj_t* formal;
	xp_lisp_obj_t* body;
	xp_lisp_obj_t* value;
	xp_lisp_mem_t* mem;

	xp_assert (
		XP_LISP_TYPE(func) == XP_LISP_OBJ_FUNC ||
		XP_LISP_TYPE(func) == XP_LISP_OBJ_MACRO);

	xp_assert (XP_LISP_TYPE(XP_LISP_CDR(func)) == XP_LISP_OBJ_CONS);

	mem = lsp->mem;

	if (XP_LISP_TYPE(func) == XP_LISP_OBJ_MACRO) {
		formal = XP_LISP_MFORMAL (func);
		body   = XP_LISP_MBODY   (func);
	}
	else {
		formal = XP_LISP_FFORMAL (func);
		body   = XP_LISP_FBODY   (func);
	}

	// make a new frame.
	frame = xp_lisp_frame_new ();
	if (frame == XP_NULL) {
		lsp->error = XP_LISP_ERR_MEM;
		return XP_NULL;
	}

	// attach it to the brooding frame list to 
	// make them not to be garbage collected.
	frame->link = mem->brooding_frame;
	mem->brooding_frame = frame;

	// evaluate arguments and push them into the frame.
	while (formal != mem->nil) {
		if (actual == mem->nil) {
			lsp->error = XP_LISP_ERR_TOO_FEW_ARGS;
			mem->brooding_frame = frame->link;
			xp_lisp_frame_free (frame);
			return XP_NULL;
		}

		value = XP_LISP_CAR(actual);
		if (XP_LISP_TYPE(func) != XP_LISP_OBJ_MACRO) {
			// macro doesn't evaluate actual arguments.
			value = xp_lisp_eval (lsp, value);
			if (value == XP_NULL) {
				mem->brooding_frame = frame->link;
				xp_lisp_frame_free (frame);
				return XP_NULL;
			}
		}

		if (xp_lisp_frame_lookup (frame, XP_LISP_CAR(formal)) != XP_NULL) {
			lsp->error = XP_LISP_ERR_DUP_FORMAL;
			mem->brooding_frame = frame->link;
			xp_lisp_frame_free (frame);
			return XP_NULL;
		}
		if (xp_lisp_frame_insert (frame, XP_LISP_CAR(formal), value) == XP_NULL) {
			lsp->error = XP_LISP_ERR_MEM;
			mem->brooding_frame = frame->link;
			xp_lisp_frame_free (frame);
			return XP_NULL;
		}

		actual = XP_LISP_CDR(actual);
		formal = XP_LISP_CDR(formal);
	}

	if (XP_LISP_TYPE(actual) == XP_LISP_OBJ_CONS) {
		lsp->error = XP_LISP_ERR_TOO_MANY_ARGS;
		mem->brooding_frame = frame->link;
		xp_lisp_frame_free (frame);
		return XP_NULL;
	}
	else if (actual != mem->nil) {
		lsp->error = XP_LISP_ERR_BAD_ARG;
		mem->brooding_frame = frame->link;
		xp_lisp_frame_free (frame);
		return XP_NULL;
	}

	// push the frame
	mem->brooding_frame = frame->link;
	frame->link = mem->frame;
	mem->frame = frame;

	// do the evaluation of the body
	value = mem->nil;
	while (body != mem->nil) {
		value = xp_lisp_eval(lsp, XP_LISP_CAR(body));
		if (value == XP_NULL) {
			mem->frame = frame->link;
			xp_lisp_frame_free (frame);
			return XP_NULL;
		}
		body = XP_LISP_CDR(body);
	}

	// pop the frame.
	mem->frame = frame->link;

	// destroy the frame.
	xp_lisp_frame_free (frame);

	//if (XP_LISP_CAR(func) == mem->macro) {
	if (XP_LISP_TYPE(func) == XP_LISP_OBJ_MACRO) {
		value = xp_lisp_eval(lsp, value);
		if (value == XP_NULL) return XP_NULL;
	}

	return value;
}