/****************************************************************************
 * KONOHA COPYRIGHT, LICENSE NOTICE, AND DISCRIMER  
 * 
 * Copyright (c) 2005-2008, Kimio Kuramitsu <kimio at ynu.ac.jp>
 *           (c) 2008-      Konoha Software Foundation  
 * All rights reserved.
 * 
 * You may choose one of the following two licenses when you use konoha. 
 * See www.konohaware.org/license.html for further information.
 * 
 * (1) GNU General Public License 2.0      (with    KONOHA_UNDER_GPL2)
 * (2) Konoha Software Foundation License 1.0
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *  
 ****************************************************************************/

/* ************************************************************************ */

#include"commons.h"

/* ************************************************************************ */

#ifdef __cplusplus 
extern "C" {
#endif
	
void knh_Object_vmcinc(Ctx *ctx, Object *self);

/* ======================================================================== */
/* [structs] */

void 
knh_Compiler_struct_init(Ctx *ctx, knh_Compiler *b, int init, Object *cs)
{
	b->flag = 0;
	b->this_cid = CLASS_Object;

	b->vars = (knh_cfield_t*)KNH_MALLOC(ctx, KONOHA_LOCALVAR_SIZE * sizeof(knh_cfield_t));
	b->vars_size = 0;
	b->vars_offset = 0;

	knh_int_t i;
	for(i = 0; i < KONOHA_LOCALVAR_SIZE; i++) {
		b->vars[i].flag  = 0;
		b->vars[i].type  = TYPE_any;
		b->vars[i].fn    = FIELDN_NONAME;
		KNH_INITv(b->vars[i].value, KNH_NULL);
	}
	
	KNH_INITv(b->ns, knh_Context_getNameSpace(ctx, TS_main));
	KNH_INITv(b->method,   KNH_NULL);

	b->nastep = 0;
	KNH_INITv(b->elf, new_Bytes(ctx, 1024));
	KNH_INITv(b->dwarf, new_Bytes(ctx, 1024));
	KNH_INITv(b->labelIdDictIdx, new_DictIdx(ctx, 256, 1));
	KNH_INITv(b->labelAddrDictSet, new_DictSet(ctx, 256));
	KNH_INITv(b->lstacks, new_Array(ctx, CLASS_String, 8));
}

/* ------------------------------------------------------------------------ */

#define _knh_Compiler_struct_copy   NULL

/* ------------------------------------------------------------------------ */

#define _knh_Compiler_struct_compare  NULL

/* ------------------------------------------------------------------------ */

void knh_Compiler_struct_traverse(Ctx *ctx, knh_Compiler *b, f_traverse gc)
{
	knh_int_t i;
	for(i = 0; i < KONOHA_LOCALVAR_SIZE; i++) {
		gc(ctx, b->vars[i].value);
	}
	
	if(IS_SWEEP(gc)) {
		KNH_FREE(b->vars, KONOHA_LOCALVAR_SIZE * sizeof(knh_cfield_t));
	}

	gc(ctx, b->ns);
	gc(ctx, b->method);
	gc(ctx, b->elf);
	gc(ctx, b->dwarf);
	gc(ctx, b->labelIdDictIdx);
	gc(ctx, b->labelAddrDictSet);
	gc(ctx, b->lstacks);
}

/* ======================================================================== */
/* [constructor] */

static Compiler *new_Compiler(Ctx *ctx)
{
	knh_Compiler_t *o = 
		(Compiler*)new_Object__RAW(ctx, FLAG_Compiler, CLASS_Compiler, sizeof(knh_Compiler));
	knh_Compiler_struct_init(ctx, DP(o), 0, NULL);
	return o;
}

/* ======================================================================== */
/* [Compiler] */

Compiler* knh_Context_getCompiler(Ctx *ctx)
{
	if(IS_NULL(ctx->cmpr)) {
		KNH_SETv(ctx, ((Context*)ctx)->cmpr, new_Compiler(ctx));
	}
	return ctx->cmpr;
}

/* ======================================================================== */
/* [namespace] */

void knh_Compiler_setNameSpace(Ctx *ctx, Compiler *cpr, String *nsname)
{
	KNH_SETv(ctx, DP(cpr)->ns, knh_Context_getNameSpace(ctx, nsname));
}

/* ------------------------------------------------------------------------ */

NameSpace *knh_Compiler_getNameSpace(Ctx *ctx, Compiler *cpr, String *nsname)
{
	KNH_SETv(ctx, DP(cpr)->ns, knh_Context_getNameSpace(ctx, nsname));
	return DP(cpr)->ns;
}

/* ------------------------------------------------------------------------ */

Script *knh_Compiler_getScript(Ctx *ctx, Compiler *o)
{
	return knh_NameSpace_getScript(ctx, DP(o)->ns);
}

/* ======================================================================== */
/* [line] */

static
void KNH_ASM_SETLINE(Ctx *ctx, Compiler *cpr, int line)
{
	if(line > DP(cpr)->line) {
		char *top = knh_Bytes_tochar(DP(cpr)->elf);
		char *cur = (char*)knh_Bytes_last(DP(cpr)->elf);
		int offset = cur - top;
		//DEBUG("line=%d at=%d", line, offset);
		knh_dwarf_t dw = {offset, line};
		knh_Bytes_write(ctx, DP(cpr)->dwarf, B2((void*)(&dw), sizeof(knh_dwarf_t)));
		DP(cpr)->line = line;
	}
}

/* ------------------------------------------------------------------------ */

void knh_Stmt_decl(Ctx *ctx, Stmt *stmt, Compiler *cpr, NameSpace *ns)
{
	knh_stmt_decl(DP(stmt)->stt)(ctx, stmt, cpr, ns, 0);
}

/* ------------------------------------------------------------------------ */

void knh_Stmt_name(Ctx *ctx, Stmt *stmt, Compiler *cpr, NameSpace *ns, int level)
{
	knh_stmt_name(DP(stmt)->stt)(ctx, stmt, cpr, ns, level);
}

/* ------------------------------------------------------------------------ */

void knh_Stmt_cmpl(Ctx *ctx, Stmt *stmt, Compiler *cpr, NameSpace *ns, knh_type_t t, int level)
{
	Term *tm = knh_Stmt_typing(ctx, stmt, cpr, ns, TYPE_UNMASK(t));
	if(knh_Term_isCONST(tm)) {
		DEBUG3("switch %s to STT_CALL1", knh_stmt_tochar(DP(stmt)->stt));
		DP(stmt)->stt = STT_CALL1;
		if(DP(stmt)->size > 0) {
			KNH_SETv(ctx, DP(stmt)->terms[0], tm);
			DP(stmt)->size = 1;
		}
		else{
			knh_Stmt_add(ctx, stmt, tm);
		}
	}
	KNH_ASM_SETLINE(ctx, cpr, DP(stmt)->line);
	knh_Stmt_cmpl_beforeLABEL(ctx, stmt, cpr, level);
	//DEBUG3("DP(stmt)->stt=%s", knh_stmt_tochar(DP(stmt)->stt));
	knh_stmt_cmpl(DP(stmt)->stt)(ctx, stmt, cpr, ns, t, level);
	knh_Stmt_cmpl_afterLABEL(ctx, stmt, cpr, level);
}

/* ======================================================================== */
/* [compile] */

static
NameSpace *knh_StmtNAMESPACE_exec(Ctx *ctx, Stmt *stmt, Compiler *cpr)
{
	KNH_ASSERT(DP(stmt)->stt == STT_NAMESPACE);
	{
		Token *tk = StmtNAMESPACE_ns(stmt);
		String *nsname = (String*)DP(tk)->data;
		if(IS_String(nsname)) {
			DEBUG3("namespace %s", knh_String_tochar(nsname));
			return knh_Compiler_getNameSpace(ctx, cpr, nsname);
		}
		else {
			return knh_Compiler_getNameSpace(ctx, cpr, KNH_NULL);
		}
	}
}

/* ------------------------------------------------------------------------ */

void knh_Stmt_names(Ctx *ctx, Stmt *stmt, Compiler *cpr, NameSpace *ns, int level)
{
	KNH_ASSERT(level != 0);
	while(IS_NOTNULL(stmt)) {
		knh_stmt_t stt = DP(stmt)->stt;
		if(stt == STT_NAMESPACE) {
			ns = knh_StmtNAMESPACE_exec(ctx, stmt, cpr);
		}
		else if(stt == STT_ERR) {
			DEBUG3("Found ERR!!");
			KNH_SETv(ctx, DP(stmt)->next, KNH_NULL);
			return ;
		}
		else {
			DP(cpr)->line = DP(stmt)->line;
			knh_stmt_name(stt)(ctx, stmt, cpr, ns, level);
		}
		stmt = DP(stmt)->next;
	}
}

/* ------------------------------------------------------------------------ */

void knh_Stmt_cmpls(Ctx *ctx, Stmt *stmt, Compiler *cpr, NameSpace *ns, int level)
{
	while(IS_NOTNULL(stmt)) {
		knh_stmt_t stt = DP(stmt)->stt;
		if(stt == STT_NAMESPACE) {
			ns = knh_StmtNAMESPACE_exec(ctx, stmt, cpr);
		}
		else {
			Term *tm = knh_Stmt_typing(ctx, stmt, cpr, ns, CLASS_Any);
			if(IS_Token(tm)) {
				Token *tk = (Token*)tm;
				knh_perror(ctx, DP(tk)->fileid, DP(tk)->line, KMSG_IGSTMT, NULL);
				knh_Stmt_add(ctx, stmt, tm);
				stmt = DP(stmt)->next;
				continue;
			}
			KNH_ASM_SETLINE(ctx, cpr, DP(stmt)->line);
			knh_Stmt_cmpl_beforeLABEL(ctx, stmt, cpr, level);
			if(knh_stmt_isExpr(stt)) {
				knh_stmt_cmpl(stt)(ctx, stmt, cpr, ns, CLASS_Any, 0);
			}
			else {
				knh_stmt_cmpl(stt)(ctx, stmt, cpr, ns, CLASS_Any, level);
			}
			knh_Stmt_cmpl_afterLABEL(ctx, stmt, cpr, level);
		}
		stmt = DP(stmt)->next;
	}
}

/* ------------------------------------------------------------------------ */

void
knh_konohac_compile(Ctx *ctx, String *nsname, Stmt *stmt_head, int isrun)
{
	Compiler *cpr = knh_Context_getCompiler(ctx);
	NameSpace *ns = knh_Compiler_getNameSpace(ctx, cpr, nsname);
	Stmt *stmt = stmt_head;

	DP(cpr)->fileid = DP(stmt)->fileid;
	DP(cpr)->line = DP(stmt)->line;
	while(IS_NOTNULL(stmt)) {
		DP(cpr)->line = DP(stmt)->line;
		if(DP(stmt)->stt == STT_NAMESPACE) {
			ns = knh_StmtNAMESPACE_exec(ctx, stmt, cpr);
		}
		else {
			knh_Stmt_decl(ctx, stmt, cpr, ns);
		}
		stmt = DP(stmt)->next;
	}

	ns = knh_Compiler_getNameSpace(ctx, cpr, nsname);
	stmt = stmt_head;
	while(IS_NOTNULL(stmt)) {
		if(DP(stmt)->stt == STT_NAMESPACE) {
			ns = knh_StmtNAMESPACE_exec(ctx, stmt, cpr);
		}
		else {
			DP(cpr)->line = DP(stmt)->line;
			knh_Stmt_name(ctx, stmt, cpr, ns, 0);
		}
		stmt = DP(stmt)->next;
	}


	ns = knh_Compiler_getNameSpace(ctx, cpr, nsname);
	stmt = stmt_head;
	while(IS_NOTNULL(stmt)) {
		knh_stmt_t stt = DP(stmt)->stt; 
		if(stt == STT_NAMESPACE) {
			ns = knh_StmtNAMESPACE_exec(ctx, stmt, cpr);
		}
		else if(stt == STT_CLASS || stt == STT_METHOD || stt == STT_FORMAT) {
			DP(cpr)->line = DP(stmt)->line;
			knh_Stmt_cmpl(ctx, stmt, cpr, ns, CLASS_Any, 0);
			knh_Stmt_done(ctx, stmt);
		}
		stmt = DP(stmt)->next;
	}

	if(isrun) {
		ns = knh_Compiler_getNameSpace(ctx, cpr, nsname);
		stmt = stmt_head;
		while(IS_NOTNULL(stmt)) {
			if(DP(stmt)->stt == STT_NAMESPACE) {
				DP(cpr)->line = DP(stmt)->line;
				ns = knh_StmtNAMESPACE_exec(ctx, stmt, cpr);
			}
			else if(DP(stmt)->stt != STT_DONE) {
				KNH_ASM_SETLINE(ctx, cpr, DP(stmt)->line);
				knh_Stmt_run(ctx, stmt, cpr, ns);
				knh_Stmt_done(ctx, stmt);
			}
			stmt = DP(stmt)->next;
		}
	}
}

/* ======================================================================== */
/* [compl] */

void knh_Compiler_startCompilation(Ctx *ctx, Compiler *cpr, Stmt *stmt)
{
	KNH_ASSERT(IS_Method(DP(cpr)->method));
	//DEBUG("%s.(fn=%s)", CLASSN(DP(cpr)->this_cid), METHODN(DP(cpr)->method->mn));
	KNH_ASSERT(DP(DP(cpr)->method)->cid == DP(cpr)->this_cid);
	
	DP(cpr)->flag = 0;
//	KNH_SETv(ctx, DP(cpr)->stmt_label, KNH_NULL);
	
	DP(cpr)->nastep = 0;
	DP(cpr)->llstep = 0;
	DP(cpr)->fileid  = DP(stmt)->fileid;
	DP(cpr)->line   = 0;
	knh_Bytes_clear(DP(cpr)->elf);
	knh_Bytes_clear(DP(cpr)->dwarf);
	knh_DictIdx_clear(ctx, DP(cpr)->labelIdDictIdx);
	knh_DictSet_clear(ctx, DP(cpr)->labelAddrDictSet);
	knh_Array_clear(ctx, DP(cpr)->lstacks);
	knh_Compiler_setCancelled(cpr, 0);

	KNH_ASM_SETLINE(ctx, cpr, DP(stmt)->line);
}

/* ------------------------------------------------------------------------ */

void knh_Compiler_clear(Ctx *ctx, Compiler *cpr)
{
	DP(cpr)->flag = 0;	
	DP(cpr)->nastep = 0;
	DP(cpr)->llstep = 0;
//	DP(cpr)->fileid  = 0;
	DP(cpr)->line   = 0;
	knh_Bytes_clear(DP(cpr)->elf);
	knh_Bytes_clear(DP(cpr)->dwarf);
	knh_DictIdx_clear(ctx, DP(cpr)->labelIdDictIdx);
	knh_DictSet_clear(ctx, DP(cpr)->labelAddrDictSet);
	knh_Array_clear(ctx, DP(cpr)->lstacks);
	knh_Compiler_setCancelled(cpr, 0);
}

/* ------------------------------------------------------------------------ */

void knh_Compiler_stopCompilation(Ctx *ctx, Compiler *cpr)
{
	DEBUG3("stop compilation!!");
	knh_Compiler_setCancelled(cpr, 1);
}

/* ------------------------------------------------------------------------ */

void knh_Compiler_endCompilation(Ctx *ctx, Compiler *cpr)
{
	Method *mtd = (Method*)DP(cpr)->method;
	KNH_ASSERT(IS_Method(mtd));
	
	if(!knh_Compiler_isCancelled(cpr)) {
		knh_Compiler_remapAddress(ctx, cpr);
	}
	
	if(knh_Compiler_isCancelled(cpr)) {
		char bufcm[CLASSNAME_BUFSIZ];
		knh_format_cmethodn(bufcm, sizeof(bufcm), DP(mtd)->cid, DP(mtd)->mn);
		DBG2_P("*** COMPILATION CANCELLED  ***");
		knh_Compiler_perror(ctx, cpr, KMSG_TOABSTRACT, bufcm);
		knh_Method_toAbstract(ctx, mtd);
	}
	else {
		VirtualMachineCode *vmc = new_VirtualMachineCode(ctx, DP(cpr)->fileid, knh_Bytes_tobytes(DP(cpr)->elf), knh_Bytes_tobytes(DP(cpr)->dwarf));
		knh_Method_setVirtualMachineCode(ctx, mtd, vmc);
		DBG2_DUMP(ctx, mtd, KNH_NULL, "Compiled Code");
		if(knh_Compiler_isStopped(cpr)) {
			if(DP(mtd)->mn != METHODN_lambda) {
				char bufcm[CLASSNAME_BUFSIZ];
				knh_format_cmethodn(bufcm, sizeof(bufcm), DP(mtd)->cid, DP(mtd)->mn);
				knh_Compiler_perror(ctx, cpr, KMSG_TOABSTRACT, bufcm);
			}
			knh_Method_toAbstract(ctx, mtd);
		}
	}
	knh_Compiler_clear(ctx, cpr);
}

/* ======================================================================== */
/* [asmmalloc] */

INLINE
void *knh_Compiler_asmmalloc(Ctx *ctx, Compiler *cpr, size_t size)
{
//	DEBUG("size=%d", (int)size);
	size_t off = knh_Bytes_size(DP(cpr)->elf);
	int i ;
	for(i = 0; i < size; i++) {
		knh_Bytes_putc(ctx, DP(cpr)->elf, 0);
	}
	return (void*)(knh_Bytes_value(DP(cpr)->elf) + off);
}

/* ------------------------------------------------------------------------ */

#ifdef __cplusplus
}
#endif
