/*
 *	lexer.c
 *
 *	JoyToyインタプリタ
 *
 *	* Sun Jan 01 00:00:00 JST 2006 Naoyuki Sawa
 *	- 作成開始。
 *	* Tue Jan 31 00:00:00 JST 2006 Naoyuki Sawa
 *	- 1st リリース。
 *	* Sat Jun 17 05:26:02 JST 2006 Naoyuki Sawa
 *	- FIELD_OFFSET()→offsetof()に変更。
 */
#include "app.h"

/*****************************************************************************
 *	LEXER
 *****************************************************************************/

typedef struct _LEXER {
	int refcnt;
	LEXER_INTERFACE* lexer_interface;
	//
	int line;			/* これまにでに読み込んだ行数 */
	FILE* in;			/* 入力ストリーム */
	TOKEN_INTERFACE** token;	/* 次のトークン、または、NULL */
} LEXER;

IMPLEMENT_LEXER_INTERFACE(LEXER, lexer)

const INTERFACE_MAP lexer_interface_map[] = {
	{ LEXER_INTERFACE_ID, offsetof(LEXER, lexer_interface), &lexer_interface },
	{ 0/*終端*/ },
};

LEXER_INTERFACE**
lexer_create(FILE* in)
{
	LEXER* this = calloc(1, sizeof(LEXER));
	if(!this) {
		DIE(); /* メモリ不足 */
	}
	this->refcnt = 1;
	apply_interface_map(this, lexer_interface_map);

	/* 入力ストリームを格納します。 */
	this->in = in;

	return &this->lexer_interface;
}

static void
lexer_delete(LEXER* this)
{
	/* 次のトークンを保持していたら、解放します。 */
	safe_release(this->token);

	free(this);
}

/*****************************************************************************
 *	LEXER_INTERFACE
 *****************************************************************************/

static int
lexer_getc(LEXER* this)
{
	int c = getc(this->in);
	if(c == '\n') {
		this->line++;
	}
	return c;
}

static void
lexer_ungetc(LEXER* this, int c)
{
	if(ungetc(c, this->in) == EOF) {
		DIE(); /* 二重unget、または、EOFを戻そうとした、等のエラー */
	}
	if(c == '\n') {
		this->line--;
	}
}

static int
lexer_get_line(LEXER_INTERFACE** intf)
{
	LEXER* this = CONTAINING_RECORD(intf, LEXER, lexer_interface);

	return this->line;
}

static TOKEN_INTERFACE**
lexer_get_token(LEXER_INTERFACE** intf)
{
	LEXER* this = CONTAINING_RECORD(intf, LEXER, lexer_interface);
	//
	TOKEN_INTERFACE** token = NULL;
	JTCODE_INTERFACE** code = NULL;
	STRING_BUFFER_INTERFACE** string_buffer = NULL;
	//
	int c;
	int num;
	int type;
	const char* str;

	/* 次のトークンを保持していたら、呼び出し元に所有権を譲ります。 */
	if(this->token) {
		return safe_detach(this->token);
	}

	/* 先行する空白文字を読み飛ばします。 */
	do {
		c = lexer_getc(this);
		if(c == EOF) {
			return NULL; /* プログラム終了 */
		}
	} while(isspace(c));

	/* トークンを読み出します。 */
	switch(c) {
	case ';':
	case '+':
	case '-':
	case '*':
	case '(':
	case ')':
	case '{':
	case '}':
	case ',':
	case '.':
		/* 単純なトークンを作成します。 */
		safe_attach(token, token_create(c, NULL));
		goto L_EXIT;

	case '/':
		/* コメントをスキップ、または、'/'トークンを作成します。 */
		c = lexer_getc(this);
		if(c != EOF) {
			if(c == '*') { /* Cコメント */
				for(;;) {
					c = lexer_getc(this);
					if(c == '*') {
						c = lexer_getc(this);
						if(c == '/') {
							break; /* Cコメント終了 */
						}
					}
					if(c == EOF) {
						DIE(); /* Cコメントの中でファイル終端に遭遇するのはNGです */
					}
				}
				safe_attach(token, lexer_get_token(intf));
				goto L_EXIT;
			}
			if(c == '/') { /* C++コメント */
				for(;;) {
					c = lexer_getc(this);
					if(c == '\n') {
						break; /* C++コメント行終了 */
					}
					if(c == EOF) {
						break; /* C++コメント行でファイル終端に遭遇するのはOKです */
					}
				}
				safe_attach(token, lexer_get_token(intf));
				goto L_EXIT;
			}
			lexer_ungetc(this, c); /* 忘れないで!! */
		}
		safe_attach(token, token_create('/', NULL)); /* '/' */
		goto L_EXIT;

	case '=':
		/* '=='、または、'='トークンを生成します。 */
		c = lexer_getc(this);
		if(c != EOF) {
			if(c == '=') {
				safe_attach(token, token_create(TOKEN_TYPE_EQ, NULL)); /* '==' */
				goto L_EXIT;
			}
			lexer_ungetc(this, c); /* 忘れないで!! */
		}
		safe_attach(token, token_create('=', NULL)); /* '=' */
		goto L_EXIT;

	case '!':
		/* '!='、または、'!'トークンを生成します。 */
		c = lexer_getc(this);
		if(c != EOF) {
			if(c == '=') {
				safe_attach(token, token_create(TOKEN_TYPE_NE, NULL)); /* '!=' */
				goto L_EXIT;
			}
			lexer_ungetc(this, c); /* 忘れないで!! */
		}
		safe_attach(token, token_create('!', NULL)); /* '!' */
		goto L_EXIT;

	case '<':
		/* '<='、または、'<'トークンを生成します。 */
		c = lexer_getc(this);
		if(c != EOF) {
			if(c == '=') {
				safe_attach(token, token_create(TOKEN_TYPE_LE, NULL)); /* '<=' */
				goto L_EXIT;
			}
			lexer_ungetc(this, c); /* 忘れないで!! */
		}
		safe_attach(token, token_create('<', NULL)); /* '<' */
		goto L_EXIT;

	case '>':
		/* '>='、または、'>'トークンを生成します。 */
		c = lexer_getc(this);
		if(c != EOF) {
			if(c == '=') {
				safe_attach(token, token_create(TOKEN_TYPE_GE, NULL)); /* '>=' */
				goto L_EXIT;
			}
			lexer_ungetc(this, c); /* 忘れないで!! */
		}
		safe_attach(token, token_create('>', NULL)); /* '>' */
		goto L_EXIT;

	case '&':
		/* '&&'トークンを生成します。 */
		c = lexer_getc(this);
		if(c == '&') {
			safe_attach(token, token_create(TOKEN_TYPE_AND, NULL)); /* '&&' */
			goto L_EXIT;
		}
		DIE(); /* '&'は使えません */

	case '|':
		/* '||'トークンを生成します。 */
		c = lexer_getc(this);
		if(c == '|') {
			safe_attach(token, token_create(TOKEN_TYPE_OR, NULL)); /* '||' */
			goto L_EXIT;
		}
		DIE(); /* '|'は使えません */

	case '"':
		/* 文字列トークンを作成します。 */
		safe_attach(string_buffer, string_buffer_create());
		for(;;) {
			c = lexer_getc(this);
			if(c == '\\') {
				c = lexer_getc(this);
			}
			if(c == EOF) {
				DIE(); /* 予期しないファイル終端 */
			}
			if(c == '"') {
				break;
			}
			(*string_buffer)->append_char(string_buffer, c);
		}
		str = (*string_buffer)->lock(string_buffer, 0);
		safe_attach(code, jtstring_create(str));
		safe_attach(token, token_create(TOKEN_TYPE_STRING, code));
		goto L_EXIT;

	default:
		/* 数値トークンを作成します。 */
		if(isdigit(c)) {
			num = 0;
			do {
				num = (num * 10) + (c - '0');
				c = lexer_getc(this);
				if(c == EOF) {
					DIE(); /* 予期しないファイル終端 */
				}
			} while(isdigit(c));
			lexer_ungetc(this, c); /* 忘れないで!! */
			safe_attach(code, jtint_create(num));
			safe_attach(token, token_create(TOKEN_TYPE_INT, code));
			goto L_EXIT;
		}

		/* 予約語、または、シンボルトークンを作成します。 */
		if(iscsymf(c)) {
			safe_attach(string_buffer, string_buffer_create());
			do {
				(*string_buffer)->append_char(string_buffer, c);
				c = lexer_getc(this);
				if(c == EOF) {
					DIE(); /* 予期しないファイル終端 */
				}
			} while(iscsym(c));
			lexer_ungetc(this, c); /* 忘れないで!! */
			str = (*string_buffer)->lock(string_buffer, 0);
			type = joytoy_get_reserved(str);
			if(type >= 0) {
				safe_attach(token, token_create(type, NULL)); /* 予約語 */
			} else {
				safe_attach(code, jtsymbol_intern(str));
				safe_attach(token, token_create(TOKEN_TYPE_SYMBOL, code)); /* シンボル */
			}
			goto L_EXIT;
		}

		DIE(); /* 不明なトークン */
	}
L_EXIT:

	/* 不要になったインターフェイスを解放します。 */
	safe_release(code);
	safe_release(string_buffer);

	return token;
}

static void
lexer_unget_token(LEXER_INTERFACE** intf, TOKEN_INTERFACE** token)
{
	LEXER* this = CONTAINING_RECORD(intf, LEXER, lexer_interface);

	/* 戻されたトークンを、次のトークンとして保持します。 */
	if(!token) {
		DIE(); /* ファイル終端を戻すことはできない */
	}
	if(this->token) {
		DIE(); /* 二重unget禁止 */
	}
	safe_assign(this->token, token);
}

