/* Resp: A programming language
 * Copyright (C) 2008-2009 David Robillard <dave@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 Compile all code (compilation pass 2)
 */

#include "resp.hpp"

using namespace std;

#define COMPILE_LITERAL(CT) \
template<> CVal ALiteral<CT>::compile(CEnv& cenv) throw() { \
	return cenv.engine()->compileLiteral(cenv, this); \
}
COMPILE_LITERAL(int32_t);
COMPILE_LITERAL(float);
COMPILE_LITERAL(bool);

CVal
ASymbol::compile(CEnv& cenv) throw()
{
	if (cenv.vals.topLevel(this) && cenv.type(this)->head()->str() != "Fn")
		return cenv.engine()->getGlobal(cenv, *cenv.vals.ref(this));
	else
		return *cenv.vals.ref(this);
}

CVal
AFn::compile(CEnv& cenv) throw()
{
	const AType* type = cenv.type(this);
	CFunc f = cenv.findImpl(this, type);
	if (!f) {
		f = cenv.engine()->compileFunction(cenv, this, type);
		cenv.vals.def(cenv.penv.sym(name), f);
		cenv.addImpl(this, f);
	}
	return f;
}

CVal
ACall::compile(CEnv& cenv) throw()
{
	CFunc f = (*begin())->compile(cenv);

	if (!f)
		f = cenv.currentFn; // Recursive call (callee defined as a stub)

	vector<CVal> args;
	for (const_iterator e = begin() + 1; e != end(); ++e)
		args.push_back((*e)->compile(cenv));

	return cenv.engine()->compileCall(cenv, f, cenv.type(head()), args);
}

CVal
ADef::compile(CEnv& cenv) throw()
{
	cenv.def(sym(), body(), cenv.type(body()), NULL); // define stub first for recursion
	CVal val = body()->compile(cenv);
	if (cenv.vals.size() == 1 && cenv.type(body())->head()->str() != "Fn") {
		val = cenv.engine()->compileGlobal(
				cenv, cenv.type(body()), sym()->str(), val);
		cenv.lock(this);
	}
	cenv.vals.def(sym(), val);
	return NULL;
}

CVal
AIf::compile(CEnv& cenv) throw()
{
	return cenv.engine()->compileIf(cenv, this);
}

CVal
ACons::compile(CEnv& cenv) throw()
{
	const AType* type = cenv.type(this);
	vector<CVal> fields;
	for (const_iterator i = begin() + 1; i != end(); ++i)
		fields.push_back((*i)->compile(cenv));
	return cenv.engine()->compileTup(cenv, type, fields);
}

CVal
ADot::compile(CEnv& cenv) throw()
{
	const_iterator     i      = begin();
	AST*               tup    = *++i;
	ALiteral<int32_t>* index  = (*++i)->as<ALiteral<int32_t>*>();
	CVal tupVal = tup->compile(cenv);
	return cenv.engine()->compileDot(cenv, tupVal, index->val);
}

CVal
APrimitive::compile(CEnv& cenv) throw()
{
	return cenv.engine()->compilePrimitive(cenv, this);
}