/* vim: ts=4 sw=4 sts=4 ff=unix fenc=utf-8 :
 * =====================================================================
 *  kscript_token.c
 *  Copyright (c)  2003 - 2011  sys0tem
 *  LICENSE :
 *	LGPL (GNU Lesser General Public License - Version 3,29 June 2007)
 *	http://www.gnu.org/copyleft/lesser.html
 *	or
 *	EPL (Eclipse Public License - v1.0)
 *	http://www.eclipse.org/legal/epl-v10.html
 * =====================================================================
 */
#include <stdio.h>
#include <sc_number.h>

#include "kscript_token.h"


/* シンボルの最大長	*/
#define KSCRIPT_SYMBOL_LENGTH_MAX (8)


/**
 * TokenTypeと対象タイプのシンボル文字列のペア定義用構造体。
 */
typedef struct {
	enum TokenType type;
	char           str[KSCRIPT_SYMBOL_LENGTH_MAX];
} KScriptTokenTypePair;


/**
 * 同じシンボル文字で始まるシンボルをまとめた構造体
 */
typedef struct {
	int                  count;
	KScriptTokenTypePair typePair[8];
} KScriptTokenTypeTableEntry;


/**
 * トークンタイプマップ。
 *
 * 最長一致で検索するために、文字列の短い順に定義すること。
 */
static
KScriptTokenTypeTableEntry KScript_tokenTypeTable[] =
{
	{ 8,{
		{ T_NOT			, "!"		},
		{ T_NOTE		, "!="		},
		{ T_NOTLT		, "!<"		},
		{ T_NOTGT		, "!>"		},
		{ T_NOTLTE		, "!<="		},
		{ T_NOTGTE		, "!>="		},
		{ T_NOTLTGT		, "!<>"		},
		{ T_NOTLTGTE	, "!<>="	}
		}
	},

	{ 8,{
		{ T_LT			, "<"		},
		{ T_ARROWL		, "<-"		},
		{ T_LTE			, "<="		},
		{ T_LTD			, "<<"		},
		{ T_LTGT		, "<>"		},
		{ T_LTDE		, "<<="		},
		{ T_LTGTE		, "<>="		},
		{ T_HTMLCOMMENTS, "<!--"	}
		}
	},

	{ 6,{
		{ T_GT			, ">"		},
		{ T_GTE			, ">="		},
		{ T_GTD			, ">>"		},
		{ T_GTDE		, ">>="		},
		{ T_GTT			, ">>>"		},
		{ T_GTTE		, ">>>="	},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		}
		}
	},

	{ 3,{
		{ T_DOT			, "."		},
		{ T_DOTD		, ".."		},
		{ T_DOTT		, "..."		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		}
		}
	},

	{ 3,{
		{ T_AMP			, "&"		},
		{ T_AMPE		, "&="		},
		{ T_AMPD		, "&&"		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		}
		}
	},

	{ 3,{
		{ T_PIPE		, "|"		},
		{ T_PIPEE		, "|="		},
		{ T_PIPED		, "||"		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		}
		}
	},

	{ 5,{
		{ T_MINUS		, "-"		},
		{ T_MINUSE		, "-="		},
		{ T_MINUSD		, "--"		},
		{ T_ARROWR		, "->"		},
		{ T_HTMLCOMMENTE, "-->"		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		}
		}
	},

	{ 3,{
		{ T_PLUS		, "+"		},
		{ T_PLUSE		, "+="		},
		{ T_PLUSD		, "++"		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		}
		}
	},

	{ 2,{
		{ T_SLASH		, "/"		},
		{ T_SLASHE		, "/="		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		}
		}
	},

	{ 2,{
		{ T_STAR		, "*"		},
		{ T_STARE		, "*="		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		}
		}
	},

	{ 2,{
		{ T_EQUAL		, "="		},
		{ T_EQUALE		, "=="		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		}
		}
	},

	{ 2,{
		{ T_PARCENT		, "%"		},
		{ T_PARCENTE	, "%="		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		}
		}
	},

	{ 2,{
		{ T_HAT			, "^"		},
		{ T_HATE		, "^="		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		}
		}
	},

	{ 2,{
		{ T_TILD		, "~"		},
		{ T_TILD		, "~="		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		}
		}
	},

	{ 2,{
		{ T_HASH		, "#"		},
		{ T_SHELLHEAD	, "#!"		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		}
		}
	},

	{ 1,{
		{ T_QUESTION	, "?"		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		}
		}
	},

	{ 1,{
		{ T_PARENL		, "("		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		}
		}
	},

	{ 1,{
		{ T_PARENR		, ")"		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		}
		}
	},

	{ 1,{
		{ T_BRACKETL	, "["		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		}
		}
	},

	{ 1,{
		{ T_BRACKETR	, "]"		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		}
		}
	},

	{ 1,{
		{ T_CURLYL		, "{"		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		}
		}
	},

	{ 1,{
		{ T_CURLYR		, "}"		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		}
		}
	},

	{ 1,{
		{ T_SEMICOLON	, ";"		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		}
		}
	},

	{ 1,{
		{ T_COLON		, ":"		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		}
		}
	},

	{ 1,{
		{ T_DOLLAR		, "$"		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		}
		}
	},

	{ 1,{
		{ T_AT			, "@"		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		}
		}
	},

	{ 1,{
		{ T_ESCAPE		, "\\"		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		},
		{ T_UNKNOWN		, ""		}
		}
	}
};

/** 上記のテーブル位置検索用のキー	*/
static const char* KScript_tokenTypeTableKey = "!<>.&|-+/*=%^~#?()[]{};:$@\\";


/* ---------------------------------------------------------------------
 *  プロトタイプ宣言
 * ---------------------------------------------------------------------
 */
static bool KScript_parseComment(KScriptTokenStatus* status, int lineNum);
static bool KScript_parseCommentStart(KScriptTokenStatus* status, int lineNum);
static bool KScript_parseBlockComment(KScriptTokenStatus* status, int lineNum);
static bool KScript_parseNestingBlockComment(KScriptTokenStatus* status, int lineNum);
static bool KScript_parseSimpleSymbol(KScriptTokenStatus* status, int lineNum);
static bool KScript_parseOtherSymbol(KScriptTokenStatus* status, int lineNum);
static bool KScript_parseStringToken(KScriptTokenStatus* status, int lineNum);
static bool KScript_parseNumberToken(KScriptTokenStatus* status, int lineNum);
static bool KScript_parseKeywordSymbolToken(KScriptTokenStatus* status, int lineNum);

static void KScript_addToken(KScriptTokenStatus* status, KScriptToken* token);
static int  KScript_getTableIndex(char c);
static int  KScript_indexOf(const char* ptr, char c);



/**
 * 指定された行を解析し、トークンを token に追加します。
 *
 * @param token   トークン格納用 
 * @param line    行
 * @param lineNum 行番号
 * @param status  ステータス
 */
void KScript_parseLine(KScriptTokenStatus* status, char* line, int lineNum)
{
	bool  ret;
	status->ptr = line;

	while ((*status->ptr) != '\0')
	{
		/* コメント解析				*/
		ret = KScript_parseComment(status, lineNum);
		if (ret) { continue; }

		/* 単純シンボル解析			*/
		ret = KScript_parseSimpleSymbol(status, lineNum);
		if (ret) { continue; }

		/* その他のシンボル解析		*/
		KScript_parseOtherSymbol(status, lineNum);
	}
}


/**
 * コメント解析。
 * 指定された位置の文字列がコメントであれば true を返す。
 * コメントが終了した場合、tokens にコメントトークンを追加します。
 *
 * @param status  ステータス
 * @param lineNum 行番号
 */
static
bool KScript_parseComment(KScriptTokenStatus* status, int lineNum)
{
	bool isCommentStart;
	if (!status->isNowBlockComment && (status->nestingComment == 0))
	{	/* コメント中ではない⇒コメント開始かどうかチェックする	*/
		isCommentStart = KScript_parseCommentStart(status, lineNum);
		if (!isCommentStart)
		{	/* コメントではない			*/
			return false;
		}
	}

	if (status->isNowBlockComment)
	{	/* ブロックコメント				*/
		KScript_parseBlockComment(status, lineNum);
	}
	else if (status->nestingComment > 0)
	{	/* ネスト可能ブロックコメント	*/
		KScript_parseNestingBlockComment(status, lineNum);
	}
	/*
	else
	{	行コメントだった (上で解析済み)
	}
	*/

	return true;
}


/**
 * 解析位置の文字列がコメント開始かどうかチェックします。
 * コメント開始の場合、
 * 行コメントの場合はコメントトークンを tokens に追加
 * ブロックコメントの場合は、isNowComment を true に設定します。
 *
 * @param status  ステータス
 * @param lineNum 行番号
 * @return true/false (コメント/コメント以外)
 */
static
bool KScript_parseCommentStart(KScriptTokenStatus* status, int lineNum)
{
	bool isComment = false;
	if ((*(status->ptr) == '/') && (*(status->ptr + 1) != '\0'))
	{
		switch (*(status->ptr + 1))
		{
			case '/':	/* 行コメント (本行の解析終了)	*/
				*(status->ptr) = '\0';
				isComment = true;
				break;
			case '*':	/* ブロックコメント開始				*/
				status->isNowBlockComment = true;
				status->ptr += 2;
				isComment = true;
			case '+':	/* ネスト可能なブロックコメント開始	*/
				status->nestingComment = 1;
				status->ptr += 2;
				isComment = true;
				break;
			default:
				isComment = false;
		}
	}
	return isComment;
}


/**
 * ブロックコメント解析。
 * ブロックコメントの終了位置を探し、見つかれば、
 * isNowBlocking を解除します。
 *
 * @param status  ステータス
 * @param lineNum 行番号
 * @return true 固定
 */
static
bool KScript_parseBlockComment(KScriptTokenStatus* status, int lineNum)
{
	char* ptr = strstr(status->ptr, "*/");
	if (ptr == NULL)
	{	/* 見つからない (本行の解析は終了) */
		*(status->ptr) = '\0';
	}
	else
	{	/* 見つかった (コメント領域終了) */
		status->ptr = (ptr + 2);
		status->isNowBlockComment = false;
	}
	return true;
}


/**
 * ネスト可能なブロックコメント解析。
 *
 * @param status  ステータス
 * @param lineNum 行番号
 * @return true 固定
 */
static
bool KScript_parseNestingBlockComment(KScriptTokenStatus* status, int lineNum)
{
	char* startPtr = strstr(status->ptr, "/+");
	char* endPtr   = strstr(status->ptr, "+/");

	if (endPtr == NULL)
	{	/* コメント終了位置が見つからない	*/
		if (startPtr != NULL)
		{	/* 更なるネスト					*/
			status->nestingComment++;
		}
		/* 本行の解析終了					*/
		*(status->ptr) = '\0';
	}
	else
	{	/* コメント終了位置が見つかった		*/
		if ((startPtr == NULL) || (startPtr > endPtr))
		{	/* とりあえず一旦コメント終了	*/
			status->nestingComment--;
		}
		status->ptr = (endPtr + 2);
	}

	return true;
}


/**
 * 単純シンボルトークンを解析します。
 *
 * @param status  ステータス
 * @param lineNum 行番号
 * @return true/false (単純シンボルトークン/以外)
 */
static
bool KScript_parseSimpleSymbol(KScriptTokenStatus* status, int lineNum)
{
	int                   tblIndex;
	int                   searchIndex;
	int                   tmpRet;
	int                   tmpLen;
	KScriptTokenTypePair* pair;
	KScriptToken          token;

	tblIndex = KScript_getTableIndex(*(status->ptr));
	if (tblIndex < 0)
	{
		return false;
	}
	searchIndex = KScript_tokenTypeTable[tblIndex].count - 1;

	while (searchIndex >= 0)
	{
		pair   = &KScript_tokenTypeTable[tblIndex].typePair[searchIndex];
		tmpLen = strlen(pair->str);
		tmpRet = strncmp(pair->str, status->ptr, tmpLen);
		if (tmpRet == 0)
		{	/* シンボルあり	*/
			token.tokenType    = pair->type;
			token.tokenKeyword = K_UNKNOWN;
			token.tokenString  = NULL;
			token.lineNumber   = lineNum;
			KScript_addToken(status, &token);
			status->ptr += tmpLen;
			return true;
		}
		searchIndex--;
	}

	/* シンボル見つからず	*/
	return false;
}


/**
 * 以下のシンボルトークンを生成します。
 * <pre>
 * 識別子
 * 文字列リテラル
 * 文字リテラル
 * 整数リテラル
 * 浮動小数リテラル
 * キーワード
 * </pre>
 * @param status  ステータス
 * @param lineNum 行番号
 * @return true/false (成功/不正なシンボルを含む)
 */
static
bool KScript_parseOtherSymbol(KScriptTokenStatus* status, int lineNum)
{
	bool ret;
	switch (*status->ptr)
	{
		case '\'':
		case '"':
		case '`':
			/* 文字、文字列トークン		*/
			ret = KScript_parseStringToken(status, lineNum);
			break;
		case '0': case '1': case '2': case '3': case '4':
		case '5': case '6': case '7': case '8': case '9':
			ret = KScript_parseNumberToken(status, lineNum);
			break;
		default:
			/* その他					*/
			ret = KScript_parseKeywordSymbolToken(status, lineNum);
	}
	return ret;
}


/**
 * 文字列トークンを解析します。
 *
 * @param status  ステータス
 * @param lineNum 行番号
 * @return true/false (成功/文字列トークン以外)
 */
static
bool KScript_parseStringToken(KScriptTokenStatus* status, int lineNum)
{
	KScriptToken token;
	int          pos;
	const char*  bkupPtr;
	char qchar = *(status->ptr);

	if (qchar == '\'') {
		token.tokenType = T_CHARACTER;
	} else if (qchar == '`') {
		token.tokenType = T_COMMAND;
	} else {
		token.tokenType = T_STRING;
	}

	/* 区切り位置はtokenStringに含めない	*/
	status->ptr++;
	bkupPtr = status->ptr;
	pos = KScript_indexOf(status->ptr, qchar);
	if (pos < 0)
	{	/* 見つからない。	*/
		/* TODO. log		*/
		return false;
	}

	/* 区切り文字位置を'\0'に置き換える		*/
	status->ptr         += pos;
	*(status->ptr)       = '\0';
	status->ptr++;

	token.tokenKeyword   = K_UNKNOWN;
	token.tokenString    = SC_String_new(pos);
	token.tokenString->append(token.tokenString, bkupPtr);
	/* TODO
	token.tokenString->toNative(token.tokenString);
	*/
	token.lineNumber     = lineNum;
	
	KScript_addToken(status, &token);

	return true;
}


/**
 * 数値を解析します。
 *
 * @param status ステータス
 * @param lineNum 行番号
 * @return true/false (成功/失敗)
 */
static
bool KScript_parseNumberToken(KScriptTokenStatus* status, int lineNum)
{
	/*
	KScriptToken token;
	const char*  bkupPtr = status->ptr;
	bool         isDot = false;

	while (*status->ptr != '\0')
	{
		switch (*(status->ptr))
		{
			case 'x': case
		}
	}
	*/

	return true;
}



/**
 * キーワードまたは、ユーザ定義シンボルを解析します。
 *
 * @param status  ステータス
 * @param lineNum 行番号
 * @return true/false (成功/キーワード、ユーザ定義シンボル以外)
 */
static
bool KScript_parseKeywordSymbolToken(KScriptTokenStatus* status, int lineNum)
{
	KScriptToken token;
	const char*  bkupPtr = status->ptr;
	char         bkupChar;
	int          len;
	while (*(status->ptr) != '\0')
	{
		if ((('a' <= *(status->ptr) && (*(status->ptr) <= 'z')))
		 || (('A' <= *(status->ptr) && (*(status->ptr) <= 'Z')))
		 || (('0' <= *(status->ptr) && (*(status->ptr) <= '9')))
		 ||  ('_' == *(status->ptr)))
		{
			// NOP
		}
		else
		{	/* シンボル以外の文字が含まれる	*/
			break;
		}
		status->ptr++;
	}

	if (bkupPtr == status->ptr)
	{	/* 空白やその他の文字	*/
		status->ptr++;
		return false;
	}

	/* TODO */
	bkupChar           = *(status->ptr);
	*(status->ptr)     = '\0';
	len                = strlen(bkupPtr);

	token.tokenType    = T_KEYWORD;
	token.tokenType    = T_IDENTIFIER;
	token.tokenKeyword = K_UNKNOWN;
	token.tokenString  = SC_String_new(len + 1);
	token.tokenString->append(token.tokenString, bkupPtr);
	token.lineNumber   = lineNum;

	*(status->ptr)     = bkupChar;
	
	KScript_addToken(status, &token);
	return true;
}

/**
 * Tokenを追加します。
 *
 * @param status TokenStatus
 * @param token  追加するトークン
 */
static
void KScript_addToken(KScriptTokenStatus* status, KScriptToken* token)
{
	status->token->add(status->token, -1, token, sizeof(KScriptToken));
}


/**
 * 指定された文字のシンボルテーブル上のインデックスを取得します。
 *
 * @param c 文字
 * @return インデックス
 */
static
int KScript_getTableIndex(char c)
{
	static int max = 0;
	int i;
	if (max == 0)
	{
		max = strlen(KScript_tokenTypeTableKey);
	}
	for (i = 0; i < max; i++)
	{
		if (KScript_tokenTypeTableKey[i] == c)
		{
			return i;
		}
	}
	return -1;
}


/**
 * 文字列 ptr の中より、文字 c を検索し、その位置を返します。
 *
 * @param ptr 文字列
 * @param c   検索文字
 * @return 位置、見つからない場合-1
 */
static
int KScript_indexOf(const char* ptr, char c)
{
	const char* tmpPtr = ptr;
	while (*tmpPtr != '\0')
	{
		if (*tmpPtr == c)
		{
			return (tmpPtr - ptr);
		}
		tmpPtr++;
	}
	return -1;
}
