
function strtrim(s){return s.replace(/^\s+|\s+$/g, "")}


/**
 * MiMicILAsm
 MiMicILテキストから、MiMicBCを生成します。
 @param i_def
 定数値を格納した連想配列です。
 */
function MiMicILAsm(i_def)
{
	this._def=i_def;
}
MiMicILAsm.prototype=
{
	version:"MiMicILAsm/1.1.0",
	_exception:new Object(),
	_def:undefined,
	OPR_WM	:0x0001,
	OPR_INT	:0x0002,
	OPRSET_NONE		:0x0101,
	OPRSET_WM		:0x0102,
	OPRSET_INT		:0x0103,
	OPRSET_WM_WM	:0x0104,
	OPRSET_WM_INT	:0x0105,

	_log:new String(),
	/*
	 * MiMicILのニーモニックを記述したテキストデータをアセンブルします。
	 * 
	 */
	assemble:function(i_asm)
	{
		var is_error=false;
		var ret=new String();
		this._log=new String();
		var lines=i_asm.replace(/\r/g,"\n").split("\n");
		for(var i=0;i<lines.length;i++){
			var l=lines[i];
			if(l.length>0){
				var bc=this._line2bc(i,l);
				//エラーが無いときだけ
				if(ret==null || bc==null){
					ret=null;
				}else{
					ret=ret.concat(this._line2bc(i,l));
				}
			}
		}
		return ret;
	},
	getErrors:function(){return this._log;},

	//インストラクションをバイトコードへ変換する。
	//i_ist:トリミング済のインストラクションライン
	_line2bc:function(i_ln,i_ist)
	{
		var _t=this;
		//ログの追記
		function logMsg(i_level,i_str)
		{
			_t._log+="["+i_level+"] Line("+i_ln+")"+i_str+"\r\n";
		}
		//桁数を指定してHEXを出力
		function hexout(i_val,i_digit)
		{
			var s=(i_val>>>0).toString(16).toLowerCase();
			if(s.length>i_digit){
				logMsg("ERROR","Range Error. value="+i_val+" digit="+i_digit);
				throw _t._exception;
			}
			while(s.length<i_digit){
				s="0"+s;
			}
			return s;
		};
		//WM型を出力する。
		function wmout(i_val)
		{
			return hexout(i_val,2);
		};
		function constName2Value(i_str)
		{
			//i_sの.分割
			var list=i_str.split(".");
			var obj=_t._def;
			for(var i=0;i<list.length;i++){
				if(obj[list[i]]==undefined){
					return null;
				}
				obj=obj[list[i]];
			}
			//数値か文字列ならOK
			switch(typeof obj){
			case "number":
			case "string":
				return obj;
			}
			return null;
		}
		//オペランドをパースする。
		//@param opr トリミングしたニーモニックのオペランド部
		//@return -1 ERROR, 0:none, 1:WM,2:HEX32 10: WM WM,11:WM HEXn
		function opf2opa(i_oprs){
			if(i_oprs==null){
				return new Array();
			}
			//,で分割
			var list=i_oprs.split(",");
			//オペランドの配列化
			var r=new Array();
			for(var i=0;i<list.length;i++)
			{
				var s=strtrim(list[i]);
				//マクロ名なら置換処理(ネスト対応してない)
				if(s.match(/^[a-zA-Z]{1}[0-9a-zA-Z._]+$/)){
					var v=constName2Value(s);
					if(v==null){
						logMsg("ERROR","Unknown constant name '"+s+"'");
						throw _t._exception;
					}
					s=v.toString();
				}
				if(s.match(/^0x[0-9a-fA-F]{0,7}[0-9a-fA-F]$/)){
					//数値は全てINTに変換する。
					r.push({t:_t.OPR_INT,v:parseInt(s,16)});
				}else if(s.match(/^[0-9]+$/)){
					//10進数も全部INT
					r.push({t:_t.OPR_INT,v:parseInt(s,10)});
				}else if(s.match(/^#[0-9a-fA-F]{0,1}[0-9a-fA-F]$/)){
					//#から始まってるのはレジスタID
					r.push({t:_t.OPR_WM,v:parseInt(s.substring(1),16)});
				}
			}
			return r;
		};
		//オペレータの内容から、オペレータセットの値を出力
		function getOPRSET(code){
			switch(code.opr.length){
			case 0:
				return _t.OPRSET_NONE;
			case 1:
				switch(code.opr[0].t){
				case _t.OPR_WM:
					return _t.OPRSET_WM;
				case _t.OPR_INT:
					return _t.OPRSET_INT;
				}
				break;
			case 2:
				switch(code.opr[0].t){
				case _t.OPR_WM:
					switch(code.opr[1].t){
					case _t.OPR_WM:
						return _t.OPRSET_WM_WM; 	
					case _t.OPR_INT:
						return _t.OPRSET_WM_INT;
					}
				}
			}
			logMsg("ERROR","Invalid operand type");
			throw _t._exception;
		};
		//BCを出力する。
		function oprset_WM_WMHEXn(code,wmwm,wmhex32,c)
		{
			switch(getOPRSET(code)){
			case _t.OPRSET_WM_WM:
				return wmwm+wmout(code.opr[0].v)+wmout(code.opr[1].v);
			case _t.OPRSET_WM_INT:
				return wmhex32+wmout(code.opr[0].v)+hexout(code.opr[1].v,Math.floor(c/4));
			}
			logMsg("ERROR","Invalid operand. 'OP [WM],[WM|INT]'");
			throw _t._exception;
		};
		function oprset_WM_WM(code,wmwm)
		{
			switch(getOPRSET(code)){
			case _t.OPRSET_WM_WM:
				return wmwm+wmout(code.opr[0].v)+wmout(code.opr[1].v);
			}
			logMsg("ERROR","Invalid operand. 'OP [WM],[WM]'");
			throw _t._exception;
		};
		function oprset_WM_HEXn(code,wmhex32,c)
		{
			switch(getOPRSET(code)){
			case _t.OPRSET_WM_INT:
				return wmhex32+wmout(code.opr[0].v)+hexout(code.opr[1].v,Math.floor(c/4));
			}
			logMsg("ERROR","Invalid operand. 'OP [WM],[INT]'");
			throw _t._exception;
		};
		function oprset_NONEHEX8(code,none,hex8)
		{
			switch(getOPRSET(code)){
			case _t.OPRSET_NONE:
				return none;
			case _t.OPRSET_INT:
				return hex8+hexout(code.opr[0].v,2);
			}
			logMsg("ERROR","Invalid operand. 'OP [|INT]'");
			throw _t._exception;
		};
		function oprset_WM(code,wm)
		{
			switch(getOPRSET(code)){
			case _t.OPRSET_WM:
				return wm+wmout(code.opr[0].v);
			}
			logMsg("ERROR","Invalid operand. 'OP [WM]'");
			throw _t._exception;
		};		
		function oprset_HEXn(code,hexn,c)
		{
			switch(getOPRSET(code)){
			case _t.OPRSET_INT:
				return hexn+hexout(code.opr[0].v,Math.floor(c/4));
			}
			logMsg("ERROR","Invalid operand. 'OP [INT]'");
			throw _t._exception;
		};
		function oprset_NONE(code,none)
		{
			switch(getOPRSET(code)){
			case _t.OPRSET_NONE:
				return none;
			}
			logMsg("ERROR","Invalid operand. 'OP'");
			throw _t._exception;
		};
		function oprset_WMHEXn(code,wm,hexn,c)
		{
			switch(getOPRSET(code)){
			case _t.OPRSET_WM:
				return wm+wmout(code.opr[0].v);
			case _t.OPRSET_INT:
				return hexn+hexout(code.opr[0].v,Math.floor(c/4));
			}
			logMsg("ERROR","Invalid operand. 'OP [WM|INT]'");
			throw _t._exception;
		}
		var opr_tbl=[
		//bit operation
		["AND",function(code){
			return oprset_WM_WMHEXn(code,"AA","AB",32);
		}],
		["OR",function(code){
			return oprset_WM_WMHEXn(code,"AE","AF",32);
		}],
		["XOR",function(code){
			return oprset_WM_WMHEXn(code,"AI","AJ",32);
		}],
		["NOT",function(code){
			return oprset_WM(code,"AM");
		}],
		//bit shift
		["SHL",function(code){
			return oprset_WM_WMHEXn(code,"BB","BA",8);
		}],
		["SHR",function(code){
			return oprset_WM_WMHEXn(code,"BF","BE",8);
		}],
		//Calculation
		["ADD",function(code){
			return oprset_WM_WMHEXn(code,"CA","CB",32);
		}],
		["SUB",function(code){
			return oprset_WM_WMHEXn(code,"CE","CF",32);
		}],
		["MUL",function(code){
			return oprset_WM_WMHEXn(code,"CI","CJ",32);
		}],
		//Memory Interface
		["MGET",function(code){
			return oprset_WM_WMHEXn(code,"DB","DA",32);
		}],
		["MPUT",function(code){
			return oprset_WM_WMHEXn(code,"DF","DE",32);
		}],
		//Stream Interface
		["SGET",function(code){
			//[WM]のみ
			return oprset_WM(code,"EA");
		}],
		["SPUT",function(code){
			return oprset_WMHEXn(code,"EE","EF",32);
		}],
		["LD",function(code){
			return oprset_WM_WMHEXn(code,"FA","FB",32);
		}],
		//Control
		["NOP",function(code){
			return oprset_NONEHEX8(code,"ZA","ZB");
		}],
		["CALL",function(code){
			return oprset_WMHEXn(code,"ZE","ZF",32);
		}],
		["EXIT",function(code){
			return oprset_NONE(code,"ZZ");
		}],
		//擬似命令
		[".END",function(code){
			return oprset_NONE(code,".E");
		}],
		[".D32",function(code){
			return oprset_HEXn(code,"",32);
		}]
		];
		//ニーモニックをBCへ変換
		try{		
			//ニーモニックの検出
			var code=
				function(v){
					var m=strtrim(v.split(";")[0]);
					if(strtrim(m).length==0){
						return null;
					}
					var idx=m.indexOf(" ");
					//OPR無し
					if(idx<0){
						return{op:m,opr:[]};
					}
					//op文字列と、変換済みのOPR配列を格納したオブジェクト。
					var ret={
						op:m.substr(0,idx),
						opr:opf2opa(strtrim(m.substr(idx+1)))
					};
					return ret;
				}(i_ist);
			if(code==null){
				return "";
			}

			for(var i=0;i<opr_tbl.length;i++){
				if(opr_tbl[i][0]==code.op){
					//オペコードの解析
					return opr_tbl[i][1].call(this,code);
				}
			}
			//インストラクション一致無し
			logMsg("ERROR","Unknown instruction \'"+i_ist+"'");
			throw this._exception;
		}catch(e){
			//内部例外なら処理継続
			if(e!==this._exception){
				throw e;
			}
			//エラー
		}
		return null;
	}
}
