/* Tuplr: A programming language
 * Copyright (C) 2008-2009 David Robillard <dave@drobilla.net>
 *
 * Tuplr 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.
 *
 * Tuplr 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 Tuplr.  If not, see <http://www.gnu.org/licenses/>.
 */

/** @file
 * @brief REPL and eval
 */

#include <cerrno>
#include <cstring>
#include <fstream>
#include "tuplr.hpp"

using namespace std;

/// Compile and evaluate code from @a is
int
eval(CEnv& cenv, const string& name, istream& is)
{
	AST*   result     = NULL;
	AType* resultType = NULL;
	list< pair<SExp, AST*> > exprs;
	Cursor cursor(name);
	try {
		while (true) {
			SExp exp = readExpression(cursor, is);
			if (exp.type == SExp::LIST && exp.empty())
				break;

			result = cenv.penv.parse(exp); // Parse input
			Constraints c;
			result->constrain(cenv.tenv, c); // Constrain types
			cenv.tsubst = Subst::compose(cenv.tsubst, unify(c)); // Solve type constraints
			resultType = cenv.type(result);
			result->lift(cenv); // Lift functions
			exprs.push_back(make_pair(exp, result));

			// Add definitions as GC roots
			if (result->to<ADef*>())
				cenv.lock(result);

			// Add types in type substition as GC roots
			for (Subst::iterator i = cenv.tsubst.begin(); i != cenv.tsubst.end(); ++i) {
				Object::pool.addRoot(i->first);
				Object::pool.addRoot(i->second);
			}
		}

		// Print CPS form
		CValue val = NULL;
		/*for (list< pair<SExp, AST*> >::const_iterator i = exprs.begin(); i != exprs.end(); ++i) {
			cout << "; CPS" << endl;
			pprint(cout, i->second->cps(cenv.tenv, cenv.penv.sym("cont")));
		}*/

		if (resultType->concrete()) {
			// Create function for top-level of program
			CFunction f = cenv.engine()->startFunction(cenv, "main", resultType, ATuple(cursor));

			// Compile all expressions into it
			for (list< pair<SExp, AST*> >::const_iterator i = exprs.begin(); i != exprs.end(); ++i)
				val = cenv.compile(i->second);

			// Finish and call it
			cenv.engine()->finishFunction(cenv, f, resultType, val);
			cenv.out << cenv.engine()->call(cenv, f, resultType);
		}
		cenv.out << " : " << resultType << endl;

		Object::pool.collect(Object::pool.roots());

		if (cenv.args.find("-d") != cenv.args.end())
			cenv.engine()->writeModule(cenv, cenv.out);

	} catch (Error& e) {
		cenv.err << e.what() << endl;
		return 1;
	}
	return 0;
}

/// Read Eval Print Loop
int
repl(CEnv& cenv)
{
	while (1) {
		cenv.out << "() ";
		cenv.out.flush();
		Cursor cursor("(stdin)");

		try {
			SExp exp = readExpression(cursor, std::cin);
			if (exp.type == SExp::LIST && exp.empty())
				break;

			AST* body = cenv.penv.parse(exp); // Parse input
			Constraints c;
			body->constrain(cenv.tenv, c); // Constrain types

			Subst oldSubst = cenv.tsubst;
			cenv.tsubst = Subst::compose(cenv.tsubst, unify(c)); // Solve type constraints

			AType* bodyT = cenv.type(body);
			THROW_IF(!bodyT, cursor, "call to untyped body")

			body->lift(cenv);

			CFunction f = NULL;
			try {
				// Create anonymous function to insert code into
				f = cenv.engine()->startFunction(cenv, cenv.penv.gensymstr("_repl"), bodyT, ATuple(cursor));
				CValue retVal = cenv.compile(body);
				cenv.engine()->finishFunction(cenv, f, bodyT, retVal);
				cenv.out << cenv.engine()->call(cenv, f, bodyT);
			} catch (Error& e) {
				ADef* def = body->to<ADef*>();
				if (def)
					cenv.out << def->sym();
				else
					cenv.out << "?";
				cenv.engine()->eraseFunction(cenv, f);
			}
			cenv.out << " : " << cenv.type(body) << endl;

			// Add definitions as GC roots
			if (body->to<ADef*>())
				cenv.lock(body);

			Object::pool.collect(Object::pool.roots());

			cenv.tsubst = oldSubst;
			if (cenv.args.find("-d") != cenv.args.end())
				cenv.engine()->writeModule(cenv, cenv.out);

		} catch (Error& e) {
			cenv.err << e.what() << endl;
		}
	}
	return 0;
}