/* 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 Lexing (build an unparsed textual AST from a string)
 */

#include <stack>
#include "tuplr.hpp"

using namespace std;

inline int
readChar(Cursor& cur, istream& in)
{
	int ch = in.get();
	switch (ch) {
	case '\n': ++cur.line; cur.col = 0; break;
	default:   ++cur.col;
	}
	return ch;
}

/// Read an expression from @a in
AST*
readExpression(Cursor& cur, istream& in)
{
#define PUSH(s, t)  { if (t != "") { s.top()->push_back(new AString(loc, t)); t = ""; } }
#define YIELD(s, t) { if (s.empty()) { return new AString(loc, t); } else PUSH(s, t) }
	stack<ATuple*> stk;
	string         tok;
	Cursor         loc; // start of tok
	while (int c = readChar(cur, in)) {
		switch (c) {
		case EOF:
			THROW_IF(!stk.empty(), cur, "unexpected end of file")
			return new ATuple(cur);
		case ';':
			while ((c = readChar(cur, in)) != '\n') {}
		case '\n': case ' ': case '\t':
			if (tok != "") YIELD(stk, tok);
			break;
		case '"':
			loc = cur;
			do { tok.push_back(c); } while ((c = readChar(cur, in)) != '"');
			YIELD(stk, tok + '"');
			break;
		case '(':
			stk.push(new ATuple(cur));
			break;
		case ')':
			switch (stk.size()) {
			case 0:
				throw Error(cur, "unexpected `)'");
			case 1:
				PUSH(stk, tok);
				return stk.top();
			default:
				PUSH(stk, tok);
				ATuple* l = stk.top();
				stk.pop();
				stk.top()->push_back(l);
			}
			break;
		case '#':
			if (in.peek() == '|') {
				while (!(readChar(cur, in) == '|' && readChar(cur, in) == '#')) {}
				break;
			}
		default:
			if (tok == "") loc = cur;
			tok += c;
		}
	}
	switch (stk.size()) {
	case 0:  return new AString(loc, tok);
	case 1:  return stk.top();
	default: throw  Error(cur, "missing `)'");
	}
	return new ATuple(cur);
}