/* 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 Lexing (build a CST from a string).
 * A CST is a lexeme, or a tuple of CST's.
 */

#include <stdio.h>
#include <stack>
#include "resp.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 ALexeme(loc, t)); t = ""; } }
#define YIELD(s, t) { if (s.empty()) { return new ALexeme(loc, t); } else PUSH(s, t) }
	stack< List<ATuple, AST> > 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': case '\r': case '\f': 
			if (tok != "") YIELD(stk, tok);
			break;
		case '"':
			loc = cur;
			tok.push_back(c); // leading quote
			while ((c = readChar(cur, in)) != '"') {
				if (c == '\\') { // string escape
					switch (c = readChar(cur, in)) {
					case '"':
						tok.push_back('"');
						break;
					case '\\':
						tok.push_back('\\');
						break;
					default:
						cin.putback(c);
						throw Error(cur, string("unknown string escape `\\") + (char)c + "'");
					}
				} else { // any other character
					tok.push_back(c);
				}
			}
			tok.push_back(c); // trailing quote
			YIELD(stk, tok);
			break;
		case '(':
			stk.push(List<ATuple, AST>());
			break;
		case ')':
			switch (stk.size()) {
			case 0:
				cin.putback(c);
				throw Error(cur, "unexpected `)'");
			case 1:
				PUSH(stk, tok);
				return stk.top().head;
			default:
				PUSH(stk, tok);
				List<ATuple, AST> l = stk.top();
				stk.pop();
				stk.top().push_back(l.head);
			}
			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().head;
	default: throw Error(cur, "missing `)'");
	}
	assert(false);
	return new ATuple(cur); // never reached
}