/*-*-c++-*-
 * $Id: parser.cpp,v 1.2 2002/03/05 14:47:14 holzheu Exp $
 *
 * This file is part of qlcrash, a GUI for the dump-analysis tool lcrash.
 *
 * Copyright (C) 2001 IBM Deutschland Entwicklung GmbH, IBM Corporation
 *
 * Authors:
 * Michael Geselbracht (let@users.sourceforge.net)
 * Fritz Elfert (elfert@de.ibm.com)
 * Michael Holzheu (holzheu@de.ibm.com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version. See the file COPYING for more
 * information.
 */
#include "osdep.h"
#include "parser.h"
#include "gram.h"
#include "whatis.h"
#include "lemon.h"
#include "crashtypes.h"

#include <assert.h>
#include <ctype.h>
#include <stdlib.h>

#include <iostream.h>

#define TOKTL_NEWLINE	0
#define TOKTL_STRING	1
#define TOKTL_SEPARATOR	2

// first column in task list
#define CMD_TASK_ADDR "ADDR"

bool my_isalnum(int);

bool my_isalnum(int c)
{
	if (isalnum(c) || c == '_' || c == '*' || c == '.') {
		return true;
	}
	
	return false;
}

CParser::CParser(QObject* parent, const char* name) : QObject(parent, name)
{
	oIndex = 0;
	oParser = ParseAlloc((void*(*)(int)) malloc);
	oParserWhatIs = ParseWhatIsAlloc((void*(*)(int)) malloc);
	oRoot = 0;
	oTypeMap = 0;
	oTaskList.setAutoDelete(true);
}

CParser::~CParser()
{
	ParseFree(oParser, free);
	ParseWhatIsFree(oParserWhatIs, free);
}

void
CParser::reset()
{
	ParseFree(oParser, free);
	ParseWhatIsFree(oParserWhatIs, free);
	oParser = ParseAlloc((void*(*)(int)) malloc);
	oParserWhatIs = ParseWhatIsAlloc((void*(*)(int)) malloc);
}

bool
CParser::getTokenTask(int& id, QString& value)
{
	int len = oLexString.length();
	value = "";
	
	// ignore whitespace
	for (;;) {
		if (oIndex >= len) {
			return false;
		}
		char ch = oLexString[oIndex].latin1();
		if (!isblank(ch))
			break;
		oIndex++;
	}
	if (oIndex >= len) {
		return false;
	}
	
	switch (oLexString[oIndex].latin1()) {
		case '\n':
			id = TOKTL_NEWLINE;
			oIndex++;
			break;
		case '=':
			while (++oIndex < len && oLexString[oIndex].latin1() != '\n') { /* empty */ }
			id = TOKTL_SEPARATOR;
			oIndex++;
			break;
		default:
			id = TOKTL_STRING;
			for (;;) {
				value += oLexString[oIndex++];
				char ch = oLexString[oIndex].latin1();
				if (oIndex >= len || ch == '\n' || isblank(ch))
					break;
			}
	}
		
	return true;
}

void
CParser::parseTaskList()
{
	int id;
	QString value;
	oIndex = 0;
	
	setError(false);
	setFinished(false);
	
	// ignore everything up to the column header
	while (getTokenTask(id, value) != false) {
		if (value == CMD_TASK_ADDR) {
			oIndex -= 4;
			break;
		}
	}
	
	//// process column header
	
	// count number of columns
	int pos = oIndex;
	int col = 0;
	do {
		if (getTokenTask(id, value) == false) {
			setError(true);
			return;
		}
		
		if (id == TOKTL_STRING) {
			col++;
		}
	} while (id == TOKTL_STRING);
	assert(col > 0);
	assert(id == TOKTL_NEWLINE);
	oIndex = pos;
	
	// add column names to first list entry
	QVector<QString>* vec = new QVector<QString>(col);
	vec->setAutoDelete(true);
	oTaskList.clear();
	oTaskList.append(vec);
	int i = 0;
	do {
		bool ret = getTokenTask(id, value);
		assert(ret); // no error expected this time
		
		if (id == TOKTL_STRING) {
			vec->insert(i++, new QString(value));
		}
	} while (id == TOKTL_STRING);
	assert(id == TOKTL_NEWLINE);
	
	// ignore next line
	do {
		if (getTokenTask(id, value) == false) {
			setError(true);
			return;
		}
	} while (id != TOKTL_SEPARATOR);
	
	// process list entries
	do {
		i = 0;
		
		do {
			if (getTokenTask(id, value) == false) {
				setError(true);
				return;
			}
			
			if (id == TOKTL_STRING) {
				if (i == 0) {
					vec = new QVector<QString>(col);
					vec->setAutoDelete(true);
					oTaskList.append(vec);
				}
				vec->insert(i++, new QString(value));
			}
		} while (id == TOKTL_STRING);
	} while (id != TOKTL_SEPARATOR);
	
	setFinished(true);
	
	PRINT(cerr << "Done parsing." << endl << endl);
}

bool
CParser::getTokenStruct(int& id, TokenStruct& value)
{
	char ch = ' ';
	do {
		ch = oLexString[oIndex].latin1();
		switch (ch) {
			case '\0':
				id = 0;
				return false;
			case '=':
				id = TOK_EQUAL;
				break;
			case '{':
				id = TOK_BRACE_OPEN;
				break;
			case '}':
				id = TOK_BRACE_CLOSE;
				break;
			case '(':
				id = TOK_PAREN_OPEN;
				break;
			case ')':
				id = TOK_PAREN_CLOSE;
				break;
			case '[':
				id = TOK_BRACKET_OPEN;
				break;
			case ']':
				id = TOK_BRACKET_CLOSE;
				break;
			case ';':
				id = TOK_SEP;
				break;
			case ',':
				id = TOK_SEP;
				break;
			case '\n':
				id = TOK_SEP;
				break;
			case '\'':
				id = TOK_STRING;
				value.string = new QString("'");
				while (oLexString[++oIndex].latin1() != '\'') { // may be a quoted octal char
					assert(oIndex < (int) oLexString.length());
					value.string->append(oLexString[oIndex]);
				}
				value.string->append("'");
				break;
			case '"':
				id = TOK_STRING;
				value.string = new QString;
				while (ch != '\n' && oIndex < (int) oLexString.length()) {
					value.string->append(ch);
					ch = oLexString[++oIndex].latin1();
				}
				oIndex--;
				break;
			default:
				/* Itīs not worth it to create a hash table for a few keywords...
				*/
				if (!strncmp("struct", ((const char*)oLexString) + oIndex, 6)) {
					id = TOK_STRUCT;
					oIndex += 6 - 1;
				}
				else if (!strncmp("union", ((const char*)oLexString) + oIndex, 5)) {
					id = TOK_STRUCT;
					oIndex += 5 - 1;
				}
				else if (my_isalnum(ch)) {
					bool inQuote = false;
					value.string = new QString;
					value.string->insert(0, ch);
					ch = oLexString[++oIndex].latin1();
					while (my_isalnum(ch) || inQuote) {
						value.string->append(ch);
						ch = oLexString[++oIndex].latin1();
					}
					oIndex--;
					id = TOK_STRING;
				}
				else if (oLexString.mid(oIndex, CMD_PROMPT_LEN) == CMD_PROMPT) {
					setFinished(true);
				}
				else {
					oIndex++;
					continue;
				}
		}
		
		break;
	} while (true);
	
	PRINT(cout << id << "   ");
	PRINT(
		if (id == TOK_STRING) {
			cout << value.string->latin1() << endl;
		}
		else {
			cout << ch << endl;
		}
	);
	
	oIndex++;
	
	return true;
}

void
CParser::setString(QString str)
{
	oLexString = str;
	oIndex = 0;
}

void
CParser::parseStruct()
{
	int id;
	TokenStruct value;
	
	value.string = 0;
	oIndex = 0;
	oError = false;
	oFinished = false;
	delete oRoot;
	oRoot = 0;
	
	PRINT(cerr << "Parse <begin>" << endl << oLexString << "<end>" << endl);
	
	while (getTokenStruct(id, value) && !error() && !finished()) {
		Parse(oParser, id, value, this);
		
		value.string = 0;
	}
	Parse(oParser, 0, value, this);
	
	PRINT(printNodes(oRoot));
}

void
CParser::printNodes(TreeNode* root, int level)
{
	if (root == 0)
		return;
	
	for (int i = 0; i < level; i++) {
		cout << "    ";
	}
	
	// ostream of gcc 3.0 does not support the printing of null strings
	if (root->name == QString::null) {
		root->name = "";
	}
	if (root->value == QString::null) {
		root->value = "";
	}
	
	cout << "Type: " << root->type << " Name: " << root->name << " Value: " << root->value << endl;
		
	TreeNode* walker = root->childList.first();
	while (walker != 0) {
		printNodes(walker, level + 1);
		walker = root->childList.next();
	}
}

bool
CParser::getTokenWhatIs(int& id, TokenWhatIs& value)
{
	while (true) {
		char ch = oLexString[oIndex].latin1();
		switch (ch) {
			case '\0':
				id = 0;
				return false;
			case '\n':
				oIndex++;
				continue;
			case ',':
				id = TOKWH_COMMA;
				break;
			case ';':
				id = TOKWH_SEP;
				break;
			case ':':
				id = TOKWH_COLON;
				break;
			case '{':
				id = TOKWH_BRACE_OPEN;
				break;
			case '}':
				id = TOKWH_BRACE_CLOSE;
				break;
			case '[':
				id = TOKWH_BRACKET_OPEN;
				break;
			case ']':
				id = TOKWH_BRACKET_CLOSE;
				break;
			case '(':
				id = TOKWH_PAREN_OPEN;
				break;
			case ')':
				id = TOKWH_PAREN_CLOSE;
				break;
			case '=':
				id = TOKWH_EQUAL;
				break;
			default:
				if (my_isalnum(ch)) {
					value.string = new QString;
					
					if (oLexString.mid(oIndex, 6) == "struct") {
						id = TOKWH_STRUCT;
						oIndex += 6 - 1;
					}
					else if (oLexString.mid(oIndex, 5) == "union") {
						id = TOKWH_STRUCT;
						oIndex += 5 - 1;
					}
					else if (oLexString.mid(oIndex, 4) == "enum") {
						id = TOKWH_ENUM;
						oIndex += 4 - 1;
					}
					else {
						do {
							value.string->append(ch);
							ch = oLexString[++oIndex].latin1();
						} while (my_isalnum(ch));
						
						oIndex--;
						id = TOKWH_WORD;
					}
				}
				else if (oLexString.mid(oIndex, CMD_PROMPT_LEN) == CMD_PROMPT) {
					setFinished(true);
				}
				else {
					oIndex++;
					continue;
				}
		}
		
		break;
	}
	
	PRINT(cout << "id: " << id << " Value: " << (value.string ? value.string->latin1() : "(null)") << endl);
	
	oIndex++;
	return true;
}

void
CParser::parseWhatIs()
{
	int id;
	TokenWhatIs value;
	
	value.string = 0;
	oIndex = 0;
	oFinished = false;
	oError = false;
	
	// the last type map has been added to the document
	oTypeMap = 0;
	PRINT(cout << "Parse 'whatis' <begin>" << oLexString << "<end>" << endl);
	
	while (getTokenWhatIs(id, value) && !error() && !finished()) {
		PRINT(cerr << id << " : " << (value.string ? *value.string : QString("")) << endl);
		ParseWhatIs(oParserWhatIs, id, value, this);
		value.string = 0;
	}
	ParseWhatIs(oParserWhatIs, 0, value, this);
	PRINT(cout << "Done parsing (whatis)." << endl << endl);
	
//	PRINT(printMap());
}

void
CParser::printMap()
{
	if (oTypeMap != 0) {
		oTypeMap->statistics();
	}
	else {
		cerr << "(null)" << endl;
	}
}

void
CParser::setError(bool w)
{
	oError = w;
}

void
CParser::splitType(QList<QString>* list, QString& name, QString& type)
{
	assert(list != 0);
	QString* walker = list->first();
	
	while (walker != 0 && walker != list->getLast()) {
		type.append(*walker);
		if (type.length() > 0) {
			type.append(" ");
		}
		walker = list->next();
	}
	
	name = *list->last();
	name = name.stripWhiteSpace();
	type = type.stripWhiteSpace();
	
	// asterics are belonging to the type
	while (*((const char*)name) == '*') {
		type.append("*");
		// remove the first character
		name.remove(0, 1);
	}
	// indicies are belonging to the type
	if (name.right(1) == "]") {
		int pos = name.findRev("[");
		QString tmp = name.mid(pos);
		name.truncate(pos);
		type.append(tmp);
	}

}

/*
int main()
{
	CParser p;
	p.setString(
		"struct task_struct {\n"
			"item = 4\n"
			"item = (null)\n"
			"item = {\n"
				"[0] 4\n"
			"}\n"
			"item = {\n"
				"[3] struct name {\n"
					"item = 3\n"
				"}\n"
			"}\n"
		"}"
	);	
	p.parse();
	
	p.setString(
		"struct task_struct {\n"
			"int a;\n"
			"unsigned int b;\n"
		"};\n"
	);
	p.parseWhatIs();
}
*/
