/* $Id$ 
 *
 * ParserDriver: glue class to driver between scanner and parser.
 *
 * Copyright (C) 2007-2009 FAUmachine Team <info@faumachine.org>.
 * This program is free software. You can redistribute it and/or modify it
 * under the terms of the GNU General Public License, either version 2 of
 * the License, or (at your option) any later version. See COPYING.
 */


#include "frontend/newparser/ParserDriver.hpp"
#include "frontend/newparser/FAUhdlScanner.hpp"
#include <iostream>
#include <fstream>
#include <cctype>
#include <algorithm>
#include <stdexcept>
#include <cassert>
#include <sstream>
#include "frontend/reporting/SyntaxError.hpp"
#include "frontend/ast/NodeFactory.hpp"
#include "util/MiscUtil.hpp"
#include "frontend/ast/Callable.hpp"
#include "frontend/misc/NameLookup.hpp"
#include "frontend/reporting/UndefinedSymbol.hpp"
#include "frontend/reporting/ErrorRegistry.hpp"

namespace yy {

ParserDriver::ParserDriver(
	ast::SymbolTable& symTab
	) : 	scanner(NULL),
		topNode(new ast::LibraryList()), 
		currentLibrary(NULL),
		symbolTable(symTab),
		nameLookup(*new ast::NameLookup(symTab)),
		lookupIdentifiers(true),
		label(NULL),
		currentFile(NULL), 
		currentLoc(NULL)
{
}

ParserDriver::~ParserDriver() 
{
	delete this->scanner;

	if (this->topNode) {
		util::MiscUtil::terminate(this->topNode);
	}

	delete &this->nameLookup;
}

int 
ParserDriver::yylex(FAUhdlParser::semantic_type* yylval,
	  	    FAUhdlParser::location_type* yylloc)
{
	this->currentLoc = yylloc;

	FAUhdlParser::token_type token =
		this->scanner->yylex(yylval, yylloc, *this);

	/* forward to lexer */
	return token;
}

void 
ParserDriver::error(const FAUhdlParser::location_type& l,
		    const std::string& msg) /* throw(SyntaxError) */
{

	std::stringstream stream;

	stream << "ERROR> ";
	if (this->currentFile) {
		stream << *this->currentFile << ":";
	}

	stream << l.begin.line << ": Parse error: "
	       << msg << std::endl;

	std::string *emsg = new std::string();
	*emsg = stream.str();

	SyntaxError err = SyntaxError(*emsg);
	throw err;
}


void
ParserDriver::parse(const std::string& filename, const char* libName) 
	/* throw(std::runtime_error, SyntaxError, ast::CompileError) */
{
	std::ifstream inStream;
	// open file
	inStream.open(filename.c_str(), std::ifstream::in);

	if (! inStream.good()) {
		inStream.close();
		throw std::runtime_error("Failed to open file <" 
					 + filename + ">");
	}

	// set up scanner 
	this->scanner = new FAUhdlScanner(&inStream, &std::cout);
	FAUhdlParser *parser = new FAUhdlParser(*this);

	this->currentFile = new std::string(filename);
	this->currentLibrary = this->topNode->enterLibrary(libName);

	// enter library region
	this->symbolTable.pushLibrary(*(this->currentLibrary));

	// push new region that will be the region of the first
	// design unit (and will get pop'd from within the parser)
	this->symbolTable.pushNewRegion();

	// parse stuff
	parser->parse();
	// close stream again
	inStream.close();

	this->currentFile = NULL;

	// leave anonymous region that would contain the next design_unit
	// again
	this->symbolTable.popRegion();
	// leave library region again
	this->symbolTable.popRegion();
	delete parser;
}

void
ParserDriver::toLower(std::string& mixed)
{
	std::transform(mixed.begin(), mixed.end(), mixed.begin(),
			static_cast<int(*)(int)>(std::tolower));
}

long
ParserDriver::makeLong(const std::string& intval, unsigned char base)
	/* throw(std::invalid_argument, std::out_of_range) */
{
	long result = 0;
	unsigned char basemul = 1;
	unsigned int i=0;
	char c;
	int digitValue;
	bool negative = false;

	if (intval[i] == '-' || intval[i] == '+') {
		if (intval[i] == '-') {
			negative = true;
		}
		i++;
	}

	
	for (; i < intval.length(); i++) {
		c = intval[i];
		digitValue = ParserDriver::getDigitValue(c);
		if (digitValue == -1) {
			throw std::invalid_argument(
				"not a correct parameter <intval>"
			        " number: " + intval);
		}

		if (base < digitValue) {
			throw std::out_of_range(
				"digit in <intval> greater than base " 
				" number: " + intval);
		}

		result *= basemul;
		result += digitValue;
		basemul = base;
	}
	
	if (negative) {
		return -result;
	}

	return result;
}

std::string*
ParserDriver::removeQuotes(std::string s)
{
	size_t i;
	/* surrounding quotes */
	s.erase(0, 1);
	s.erase(s.length() - 1, 1);
	
	/* replace all "" with " */
	i = s.find("\"\"", 0);
	while (i != std::string::npos) {
		s.erase(i, 1);
		i = s.find("\"\"", i + 1);
	}

	return new std::string(s);
}

std::string*
ParserDriver::makeBitString(std::string s) /* throw(std::out_of_range) */
{
	char spec = s[0];

	if (spec == 'X' || spec == 'x') {
		return ParserDriver::makeHexBitString(s);
	} else if (spec == 'o' || spec == 'O') {
		return ParserDriver::makeOctBitString(s);
	}

	assert(spec == 'b' || spec == 'B');

	unsigned long i;
	i = s.find('_');
	while (i != std::string::npos) {
		s.erase(i, 1);
		i = s.find('_');
	}

	// spec && first "
	s.erase(0, 2);
	// last "
	s.erase(s.length() - 1, 1);
	
	return new std::string(s);
}

void
ParserDriver::registerLibUnit(ast::LibUnit *unit)
{
	assert(this->currentLibrary);
	this->currentLibrary->units.push_back(unit);
}

FAUhdlParser::token_type
ParserDriver::getTokenForId(
	const char *id,
	ast::NodeFactory::Identifier *&semanticValue,
	const FAUhdlParser::location_type &loc
) const
{
	std::string* s = new std::string(id); 
	ParserDriver::toLower(*s);

	std::list<ast::Symbol*> syms = std::list<ast::Symbol*>(); 

	if (this->lookupIdentifiers) {
		syms = this->nameLookup.lookup(*s);

		// eventually register an error
		if (syms.empty()) {
			this->reportNameError(id, loc);
		}
	}

	semanticValue = new ast::NodeFactory::Identifier(s, syms);

	return ParserDriver::reduceSymbolToToken(semanticValue->candidates);

}

ast::SimpleName*
ParserDriver::buildOperatorName(
	const char *op,
	const FAUhdlParser::location_type &loc
) const
{
	ast::SimpleName* s = new ast::SimpleName(op, this->bl(loc));

	// FIXME currently, this bypasses any selected scope by a 
	// direct lookup in the SymbolTable. The current grammar 
	// doesn't support expanded name as an operator, so it 
	// doesn't matter. If the grammar is fixed, this should use
	// NameLookup instead.
	s->candidates = this->symbolTable.lookup(std::string(op));
	if (s->candidates.empty()) {
		this->reportNameError(op, loc);
	}

	return s;
}

std::string*
ParserDriver::makeHexBitString(std::string& s)
{
	const char lookupHex[16][5] = {
		"0000",
		"0001",
		"0010",
		"0011",
		"0100",
		"0101",
		"0110",
		"0111",
		"1000",
		"1001",
		"1010",
		"1011",
		"1100",
		"1101",
		"1110",
		"1111"
	};

	std::string *result = new std::string();

	for (unsigned int i=2; i < s.length() - 1; i++) {
		int v = ParserDriver::getDigitValue(s[i]);
		if (v < 0) {
			continue;
		}
		/* should not happen. scanner's fault otherwise */
		assert(v <= 0xF);
		result->append(lookupHex[static_cast<short>(v)]);
	}

	return result;
}

std::string*
ParserDriver::makeOctBitString(std::string& s) /* throw(std::out_of_range) */
{
	const char lookupOctal[8][4] = {
		"000",
		"001",
		"010",
		"011",
		"100",
		"101",
		"110",
		"111"
	};


	std::string *result = new std::string();

	/* x"3314_F_124_4" */
	for (unsigned int i=2; i < s.length() - 1; i++) {
		int v = ParserDriver::getDigitValue(s[i]);
		if (v < 0) {
			continue;
		}
		if (v > 7) {
			delete result;
			throw std::out_of_range("digit out of range: "
					+ s);
		}
		result->append(lookupOctal[static_cast<short>(v)]);
	}
	
	return result;
}

int
ParserDriver::getDigitValue(char c)
{
	if (('0' <= c) && (c <= '9')) {
		return static_cast<int>(c - '0');
	} else if (('A' <= c) && (c <= 'Z')) {
		return static_cast<int>(c - 'A' + 10);
	} else if (('a' <= c) && (c <= 'z')) {
		return static_cast<int>(c - 'a' + 10);
	}

	return -1;
}

FAUhdlParser::token_type
ParserDriver::reduceSymbolToToken(std::list<ast::Symbol*> candidates)
{
	bool overloadPossible = false;

	// no candidates -> symbol was not found. unclassified identifier.
	if (candidates.empty()) {
		return FAUhdlParser::token::t_Identifier;
	}

	FAUhdlParser::token_type ret = FAUhdlParser::token::t_Identifier;

	switch(candidates.front()->type) {
	case ast::SYMBOL_PROCEDURE:
		ret = FAUhdlParser::token::ID_procedure;
		overloadPossible = true;
		break;

	case ast::SYMBOL_TYPE:
		ret = FAUhdlParser::token::ID_type;
		overloadPossible = true;
		break;

	case ast::SYMBOL_FUNCTION:
		ret = FAUhdlParser::token::ID_function;
		overloadPossible = true;
		break;

	case ast::SYMBOL_ALL:
		assert(false);

	default:
		ret = FAUhdlParser::token::t_Identifier;
		break;
	}

	if (candidates.size() == 1) {
		return ret;
	}

	/* more than one candidate: all should be of same type, otherwise
	 * s.th. is wrong, at least if not of an overloadable type.
	 * (FIXME: enum literal == function,
	 *         procedure vs. function (necessary?)
	 */
	for (std::list<ast::Symbol*>::const_iterator i = candidates.begin();
		i != candidates.end(); i++) {

		switch((*i)->type) {
		case ast::SYMBOL_PROCEDURE:
			assert(overloadPossible);
			break;

		case ast::SYMBOL_FUNCTION:
			assert(ret == FAUhdlParser::token::ID_function);
			break;

		default:
			assert(ret == FAUhdlParser::token::t_Identifier);
			break;
		}
	}

	return ret;

}

void
ParserDriver::reportNameError(
	const char *id,
	const FAUhdlParser::location_type &loc
) const
{
	assert(this->lookupIdentifiers);
	ast::UndefinedSymbol *e = new ast::UndefinedSymbol(
					std::string(id),
					this->bl(loc));

	ast::ErrorRegistry::addError(e);
}

}; /* namespace yy */

