/* Resp: A programming language
 * Copyright (C) 2008-2009 David Robillard <http://drobilla.net>
 *
 * Resp is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * Resp is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General
 * Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Resp.  If not, see <http://www.gnu.org/licenses/>.
 */

/** @file
 * @brief Expand built-in macros (i.e. def)
 */

#include "resp.hpp"

using namespace std;

static inline const ATuple*
expand_list(PEnv& penv, const ATuple* e)
{
	List ret;
	FOREACHP(ATuple::const_iterator, i, e)
		ret.push_back(penv.expand(*i));
	ret.head->loc = e->loc;
	return ret.head;
}

static inline const AST*
expand_fn(PEnv& penv, const AST* exp, void* arg)
{
	const ATuple* tup = exp->to_tuple();
	ATuple::const_iterator a = tup->begin();
	THROW_IF(++a == tup->end(), exp->loc, "Unexpected end of `fn' form");
	THROW_IF(!(*a)->to_tuple(), (*a)->loc, "First argument of `fn' is not a list");
	const ATuple* prot = (*a++)->to_tuple();
	List ret(new ATuple(penv.sym("fn"), NULL, exp->loc));
	ret.push_back(prot);
	while (a != tup->end())
		ret.push_back(penv.expand(*a++));
	return ret.head;
}

static inline const AST*
expand_def(PEnv& penv, const AST* exp, void* arg)
{
	const ATuple* tup = exp->as_tuple();
	THROW_IF(tup->list_len() < 3, tup->loc, "`def' requires at least 2 arguments");

	ATuple::const_iterator i    = tup->begin();
	const AST*             arg1 = *(++i);
	if (arg1->to_tuple()) {
		// (def (f x) y) => (def f (fn (x) y))
		const ATuple* pat  = arg1->to_tuple();

		List argsExp;
		ATuple::const_iterator j = pat->begin();
		for (++j; j != pat->end(); ++j)
			argsExp.push_back(*j);
		argsExp.head->loc = exp->loc;

		List fnExp;
		fnExp.push_back(penv.sym("fn"));
		fnExp.push_back(argsExp.head);
		for (++i; i != tup->end(); ++i)
			fnExp.push_back(*i);
		fnExp.head->loc = exp->loc;
		
		List ret;
		ret.push_back(tup->fst());
		ret.push_back(pat->fst());
		ret.push_back(fnExp.head);
		ret.head->loc = exp->loc;

		return expand_list(penv, ret.head);
	} else {
		return expand_list(penv, tup);
	}
}

const AST*
PEnv::expand(const AST* exp)
{
	const ATuple* tup = exp->to_tuple();
	if (!tup)
		return exp;
	
	THROW_IF(tup->empty(), exp->loc, "Call to empty list");

	if (is_form(tup, "def"))
		return expand_def(*this, exp, NULL);
	else if (is_form(tup, "fn"))
		return expand_fn(*this, exp, NULL);
	else
		return expand_list(*this, tup);
}


/***************************************************************************
 * Language Definition                                                     *
 ***************************************************************************/

void
initLang(PEnv& penv, TEnv& tenv)
{
	// Types
	const char* types[] = {
		"Bool", "Float", "Int",  "Nothing", "String", "Symbol", "List", "Expr", 0 };
	for (const char** t = types; *t; ++t) {
		const ASymbol* sym = penv.sym(*t);
		tenv.def(sym, sym); // FIXME: define to NULL?
	}

	const char* primitives[] = {
		"+", "-", "*", "/", "%", "and", "or", "xor", "=", "!=", ">", ">=", "<", "<=", 0 };
	for (const char** p = primitives; *p; ++p)
		penv.primitives.insert(*p);
}