var TOKENS;
var IMPORTS = []
var escapedChars = {"\\n":"\n","\\r":"\r","\\'":"'",'\\"':'\"'}
var blankToken = new TOKEN(-1,"",-1,-1)
var COUNT_STAT = 0, COUNT_INST = 0
var lastComment;

function PARSE(tokens){
	IMPORTS = []
	PACK = []
	TOKENS = tokens
	var program = ruleRoot()
	program.each(function (_,item){item.imports = IMPORTS})
	return program
}
function ruleRoot(){ // (public)? class <Name> extends <Name2> { OR import ruleImport()
	var prog = []
	while(moreTokens()){
		var cl_v = lookAhead(1)
		var cl_c, cl_n,  cl_ext, cl_b
		var cl_imps = []
		var gen = null
		var hasVisibility = isMatch(TOK_VIS, cl_v)
		
		if(isMatch(TOK_IMPORT, cl_v)) {ruleImport();continue}
		else if(cl_v.data == "package") {ruleImport(true);continue}
		else if(isMatch(TOK_CLASS, cl_v) || isMatch(TOK_INTER, cl_v)) cl_v = new TOKEN(TOK_VIS,"public",cl_v.line)
		else cl_v = nextToken()
		var comment = lastComment;
		cl_c = nextToken()
		if(!isMatch(TOK_CLASS, cl_c) && !isMatch(TOK_INTER, cl_c)) unToken(cl_c,[TOK_CLASS, TOK_INTER])

		cl_n = nextToken()
		
		if(CURRENT_FILENAME && hasVisibility && (cl_n.data + ".java") != CURRENT_FILENAME) compileError({msg:"Class '"+cl_n.data+"' should not have visibility for class not matching file name ", token:cl_v})
		if(!isMatch(TOK_TYPE, cl_n)) unToken(cl_n,[TOK_TYPE])
		if(lookAhead(1).data == "<"){
			gen = ruleGeneric(true)
		}
		
		if(isMatch(TOK_EXT, lookAhead(1))){
			nextToken()
			cl_ext = nextToken()
		}else cl_ext = new TOKEN(TOK_TYPE,"Object",cl_n.line)
		
		var com = false
		if(isMatch(TOK_IMP, lookAhead(1))){
			nextToken()
			while(!isMatch(TOK_ME_L, lookAhead(1))){
				if(com){
					var split = nextToken()
					if(!isMatch(TOK_CM, split)) {
						unToken(split,[TOK_CM])
						break
					}
				}
				var next = nextToken()
				cl_imps.push(next.data)
				com = true
			}
			if(!com) unToken(lookAhead(1),"expected interface name")
		}
		var cl_b = nextToken()
		if(isMatch(TOK_ME_L,cl_b)){
			if(cl_n.data == cl_ext.data && cl_n.data != "Object") semError("class '"+cl_n.data+"' cant extend itself.",cl_ext)
			var cl = new J_CLASS(cl_n.data,cl_ext.data,(cl_v.data=="public"?true:false),[],{},true)
			cl.implement = cl_imps
			cl.tok = cl_n
			cl.generic = gen
			cl.comment = comment
			if(cl_c.data == "interface") cl.interface = true
			prog.push(cl)
			ruleClass(cl)
		}
		else compileError("C004:: (LINE "+cl_v.line+") Expected \"public class [NAME] extends [CLASS]{\"")
		
		var cl_b2 = nextToken()
		if(!isMatch(TOK_ME_R,cl_b2)) compileError("C005:: Expected }, instead got "+cl_b2.data)
		cl.range = new rangeDef(cl_b.ch, cl_b2.ch)
	}
	return prog
}
function ruleImport(pack){
	nextToken()
	var com = false
	var i, sub;
	while(!isMatch(TOK_END,lookAhead(1))){
		if(com){
			var split = nextToken()
			if(!isMatch(TOK_DOT, split)) unToken(split,[TOK_DOT,TOK_END])
		}
		var c = nextToken()
		if(!isMatch(TOK_TYPE, c) && c.data != "*") unToken(c,[TOK_TYPE])
		com = true
		var tmp = new IMP(c.data)
		if(!i) {i = tmp;sub=i}
		else{
			sub.sub = tmp
			sub = tmp
		}
	}
	var eol = nextToken()
	if(!isMatch(TOK_END, eol)) unToken(split,[TOK_END])
	if(pack) PACK.push(i)
	else IMPORTS.push(i)
}
function ruleClass(cl){ //<vis>?
	cl.vardef = new J_ASSIGNS()
	while(moreTokens() && !isMatch(TOK_ME_R,lookAhead(1))){ //functions and variable definitions
		var p = new J_VARIABLE()
		var v = lookAhead(1)
		switch(v.type){
			case TOK_VIS: ruleAfterVis(setObj(p,["vis","comment"],[nextToken().data,lastComment]), cl);break;
			case TOK_STATIC: nextToken();ruleDefType(setObj(p,["static","vis","comment"],[true,"public",lastComment]), cl);break;
			case TOK_FINAL: nextToken();ruleDefType(setObj(p,["fin","vis","comment"],[true,"public",lastComment]), cl);break;
			case TOK_PRIM: ruleDefType(setObj(p,"vis","public"), cl);break;
			case TOK_TYPE: ruleDefType(setObj(p,"vis","public"), cl);break;
			default: unToken(v,[TOK_VIS,TOK_STATIC,TOK_PRIM,TOK_ME_R])
		}
	}
	if(cl.interface) return;
	cl.static = false
	var con = funSearch(cl,cl.name,false,cl.name,[])
	if(!con){
		if(!cl.funs['f_'+cl.name]) cl.funs['f_'+cl.name] = []
		var s = new J_CALL("super",[],false)
		var f = new J_FUNCTION(cl.name,cl.name,true,true,[],[],(cl.name!="Object"?[s]:[]))
		cl.funs['f_'+cl.name].unshift(f)
	}
	cl.static = true
}
function ruleAfterVis (p,cl) { // static 
  	var v = lookAhead(1)
	switch(v.type){
		case TOK_STATIC: nextToken();ruleDefType(setObj(p,"static",true), cl);break;
		case TOK_FINAL: nextToken();ruleDefType(setObj(p,"fin",true), cl);break;
		case TOK_PRIM: ruleDefType(p, cl);break;
		case TOK_TYPE: ruleDefType(p, cl);break;
		default: unToken(v, [TOK_STATIC,TOK_PRIM])
	}
}
function ruleDefType (p,cl) { // void|int|...
  	var v = nextToken()
  	if(isMatch(TOK_AR_L,lookAhead(1))) v.data = ("[]").repeat(ruleArray(false).arr.length)+v.data
	switch(v.type){
		case TOK_FINAL: ruleDefType(setObj(p,"fin",true), cl);break;
		case TOK_PRIM: ruleDefName(setObj(p,"type",v.data), cl);break;
		case TOK_TYPE: ruleDefName(setObj(p,"type",v.data), cl);break;
		default: unToken(v, [TOK_PRIM,TOK_TYPE])
	}
}
function ruleDefName (p,cl) { // test...
  	var v = lookAhead(1)
	if(v.data == "<"){
		p.generic = ruleGeneric(true)
		v = lookAhead(1)
	}
	if(isMatch(TOK_AR_L,v)) {
		if(p.type && p.type.indexOf("[") > -1) unToken(lookAhead(1),"array already defined for variable "+p.name)
		p.type = ("[]").repeat(ruleArray(false).arr.length)+p.type
		v = lookAhead(1)
	}
	switch(v.type){
		case TOK_TYPE: nextToken(); ruleDefWhat(setObj(p,["name","tok"],[v.data,v]), cl);break;
		case TOK_BR_L: ruleDefWhat(p, cl);break;
		default: unToken(v, [TOK_TYPE])
	}
}
function ruleDefWhat (p,cl,va) { // (... | ; | = ...
	if(isMatch(TOK_AR_L,lookAhead(1))) {
		if(p.type && p.type.indexOf("[") > -1) unToken(lookAhead(1),"array already defined for variable "+p.name)
		p.type.data = ("[]").repeat(ruleArray(false).arr.length)+p.type.data
	}
  	var v = nextToken()
  	if(p.static==null) p.static=false
	
	if(!va && isMatch(TOK_BR_L,v)){ //method definition
		var args = ruleDefArgs()
		if(p.name==null) {
			if(p.type != cl.name) compileError("invalid function, missing return type: "+p.type,true)
			p.name=p.type//if a class constructor rearrange structure
		}
		var f = new J_FUNCTION(p.name, p.type, (p.vis=="public"?true:false), p.static,args,[],[],p.tok)
		f.comment = p.comment
		var key = "f_"+f.name
		if(!cl.funs[key]) cl.funs[key] = []
		cl.funs[key].push(f)
		
		if(cl.interface){
			if(f.vis == false) semError("private method '"+f.name+"' cannot be defined in interface '"+cl.name+"'",p)
			ruleEOL()
			return;
		}
		
		ruleMethod(f)
		if(f.name==f.type && cl.name != "Object"){ //check constructor superclass
			if(f.stats.length > 0 && f.stats[0].fun != "super"){
					f.stats.unshift(new J_CALL("super",[],false))
			}else if(f.stats.length==0) f.stats.unshift(new J_CALL("super",[],false))
		}
	}else if(isMatch(TOK_END,v) || isMatch(TOK_CM,v)){
		if(p.type == "void") compileError("cannot define variable of type void")
		p.vis = (p.vis=="public"?true:false)
		if(cl.interface){
			if(p.vis == false) semError("private variable '"+p.name+"' cannot be defined in interface '"+cl.name+"'",p)
			p.static = true
			p.fin = true
		}
		cl.vars.push(p)
		p.dependents = [];
		
		cl.vardef[(p.static?'stat':'inst')].push(null);
		if(isMatch(TOK_CM, v)){
			var next = nextToken()
			p = new J_VARIABLE(next.data,p.type,p.vis,null,next,p.static,p.fin)
			if(!isMatch(TOK_TYPE,next)) unToken(next,[TOK_TYPE])
			ruleDefWhat(p,cl,true)
		}
	}else if(isMatch(TOK_EQ,v)){
		if(p.type == "void") compileError("cannot define variable of type void")
		p.vis = (p.vis=="public"?true:false)
		if(cl.interface){
			if(p.vis == false) semError("private variable '"+p.name+"' cannot be defined in interface '"+cl.name+"'",p)
			p.static = true
			p.fin = true
		}
		cl.vars.push(p)
		p.dependents = [];
		
		var r = ruleStatement(false)
		cl.vardef[(p.static?'stat':'inst')].push(r)
		
		v = nextToken()
		if(isMatch(TOK_CM, v)){
			var next = nextToken()
			p = new J_VARIABLE(next.data,p.type,p.vis,null,next,p.static,p.fin)
			if(!isMatch(TOK_TYPE,next)) unToken(next,[TOK_TYPE])
			ruleDefWhat(p,cl,true)
		}
	}else if(isMatch(TOK_IS,v)){ //EM: is for class variables
		if(p.type == "void") compileError("cannot define variable of type void")
		p.vis = (p.vis=="public"?true:false)
		if(cl.interface){
			if(p.vis == false) semError("private variable '"+p.name+"' cannot be defined in interface '"+cl.name+"'",p)
			p.static = true
			p.fin = true
		}
		cl.vars.push(p)
		
		var r = ruleStatement(false)
		cl.vardef[(p.static?'stat':'inst')].push(r)
		
		p.dependents = [];
		p.emdef = r;
		function getDependencies(statement, args){
		    var variables = [];
		    _.each(statement, function(val, key){
			if(val == null || typeof val != "object") return;
	
			if(val.is == IS_CALL){
			    if(!args) variables.push(val);
			    
			    if(val.args){
				variables = variables.concat(getDependencies(val.args));
			    }else if(val.subcall){
				variables = variables.concat(getDependencies(val.subcall));
			    }
			}else{
			    variables = variables.concat(getDependencies(val))
			}
		    });
		    return variables;
		}
		v = nextToken()
		p.dependencies = getDependencies([r]);
		_.each(p.dependencies, function(val, key){
			var vv = cl.vars.each(function (i,item){if(item.name==val.fun ) return item})
			
			if(vv && vv.dependents.indexOf(p) == -1){
				vv.dependents.push(p);
			}
		})
		/*
		if(isMatch(TOK_CM, v)){
			var next = nextToken()
			p = new J_VARIABLE(next.data,p.type,p.vis,null,next,p.static,p.fin)
			if(!isMatch(TOK_TYPE,next)) unToken(next,[TOK_TYPE])
			ruleDefWhat(p,cl,true)
		}*/
	}else unToken(v, [TOK_BR_L,TOK_END,TOK_EQ, TOK_IS])
}
function ruleDefArgs () { // int a, boolean b, ... )
  	var args = []
  	if(!isMatch(TOK_BR_R,lookAhead(1))) args.push(ruleDefArgType())
  	while(!isMatch(TOK_BR_R,lookAhead(1))){
  		var c = nextToken()
  		if(!isMatch(TOK_CM,c)) unToken(c,[TOK_CM,TOK_BR_R])
  		args.push(ruleDefArgType())
  	}
  	nextToken()
  	return args
}
function ruleDefArgType (noname) { // int []* a []*
	var arr = false
  	var ty = nextToken()
	//if(!isMatch(TOK_PRIM, ty) && !isMatch(TOK_TYPE, ty)) unToken(ty,[TOK_PRIM,TOK_TYPE])
	if(lookAhead(1).data == "<"){
		var gen = ruleGeneric()
		var str = "<"
		for(var i = 0; i < gen.length; i++){
			str+=gen[i].type
			if(i < gen.length - 1) str+=","
		}
		ty.data+=str+">"
	}
	if(isMatch(TOK_AR_L,lookAhead(1))) {
		ty.data = ("[]").repeat(ruleArray().arr.length)+ty.data
		arr=true
	}
	var na = blankToken
	if(!noname){
		na = nextToken()
		if(!isMatch(TOK_TYPE, na)) unToken(na,[TOK_TYPE])
		if(isMatch(TOK_AR_L,lookAhead(1))){
			if(arr) unToken(lookAhead(1),[])
			ty.data = ("[]").repeat(ruleArray().arr.length)+ty.data
		}
	}
	return new J_VARIABLE(na.data,ty.data,false,null,na)
}

function ruleMethod(f){
	var a = matchToken(TOK_ME_L)
	while(!isMatch(TOK_ME_R,lookAhead(1))){
		var s = ruleStatement(true,f)
		if(s!=null) f.stats.push(s)
		if(s!=null && eolCheck(s.is)){
			var eol = lookAhead(1)
			if(!isMatch(TOK_END, eol)) unToken(eol,[TOK_END],true)
			else nextToken()
		}
	}
	var b = matchToken(TOK_ME_R)
	f.range = new rangeDef(a.ch, b.ch)
}
function ruleArray(size){
	var arrays = []
	var result = new J_ARR(arrays)
	while(isMatch(TOK_AR_L,lookAhead(1))){
		nextToken()
		var stat = null
		if(size) stat = ruleStatement(false)
		var br = nextToken()
		if(!isMatch(TOK_AR_R,br)) unToken(br,[TOK_AR_R])
		arrays.push(stat)
	}
	return result
}
function ruleGeneric(notype){
	nextToken()
	var gen = []
	var com = false
	while(lookAhead(1).data != ">"){
		if(com){
			var c = nextToken()
			if(!isMatch(TOK_CM,c)) unToken(c,[TOK_CM,TOK_BR_R])
		}
		if(notype){
			var n = nextToken()
			if(!isMatch(TOK_TYPE,n)) unToken(n,[TOK_TYPE])
			gen.push(n.data)
		}else gen.push(ruleDefArgType(true))
		com = true
	}
	nextToken()
	return gen
}
function ruleArrayActual(size){
	nextToken()
	TOKENS.unshift(new TOKEN(TOK_BR_L))
	var arrays = ruleCallArgs(false,true)
	return new J_NEW(null,new J_ARR_ACT(arrays,"[]"+arrays[0].type),null,"[]"+arrays[0].type)
}
function eolCheck(is){
	switch(is){
		case IS_IF: return false; break;
		case IS_WHILE: return false; break;
		case IS_FOR: return false; break;
		case IS_SWITCH: return false; break;
		case IS_TRY: return false; break;
		case IS_BLOCK: return false; break;
	}
	return true
}
function ruleStatement(addr,f,subop,single){
	var r;
	var vardef = false
	var s = lookAhead(1)
	var s2 = lookAhead(2)
	if(addr && isMatch(TOK_END,s)) {nextToken();return null}
	else if(addr && isMatch(TOK_IF,s)) r = ruleIf()
	else if(addr && isMatch(TOK_WHILE,s)) r = ruleWhile()
	else if(addr && isMatch(TOK_DO,s)) r = ruleDo()
	else if(addr && isMatch(TOK_FOR,s)) r = ruleFor()
	else if(addr && isMatch(TOK_SWITCH,s)) r = ruleSwitch()
	else if(addr && isMatch(TOK_TRY,s)) r = ruleTry()
	else if(addr && isMatch(TOK_ME_L,s)){
		var m = new J_FUNCTION("", "", true, true,[],[],[])
		ruleMethod(m)
		r = new J_BLOCK(m.vars,m.stats)
	}else if(isMatch(TOK_RET,s)) {
		var t = nextToken()
		var ret
		if(!isMatch(TOK_END,lookAhead(1))) ret = ruleStatement(false)
		r = new J_RETURN(ret,t)
	}else if(isMatch(TOK_BR_L,s)) {
		nextToken()
		var ret = ruleStatement(false)
		var br = nextToken()
		if(!isMatch(TOK_BR_R, br)) unToken(br,[TOK_BR_R])
		r = ret
		if(!isMatch(TOK_QU,lookAhead(1)) && !isMatch(TOK_CM,lookAhead(1)) && !isMatch(TOK_END,lookAhead(1)) && !isMatch(TOK_OP,lookAhead(1)) && !isMatch(TOK_OP_BOOL,lookAhead(1)) && !isMatch(TOK_EQ,lookAhead(1)) && !isMatch(TOK_BR_R,lookAhead(1)) && !isMatch(TOK_DOT,lookAhead(1)))
			r = new J_CONV(r.fun,ruleStatement(false,null,null,true))
		else {
			r.br = true
			if(isMatch(TOK_DOT,lookAhead(1))){
				nextToken()
				r.subcall = ruleCallPart()
			}
		}
	}else if(isMatch(TOK_OP,s) && isMatch(TOK_OP,s2) && s.data == s2.data && (s.data == "+" || s.data == "-")) {
		nextToken();nextToken()
		r = ruleStatement(false)
		r = new J_LR(r,new J_OP(s.data,r,new J_VALUE("int",1)))
	}else if(isMatch(TOK_OP,s) && s.data == "-") {
		nextToken()
		var ret = ruleStatement(false,true)
		r = new J_OP("-",new J_VALUE("int","0"),ret)
		r.br = true;
	}else if(isMatch(TOK_NOT,s)) {
		nextToken()
		var ret = ruleStatement(false,true,false)
		r = new J_OP("!",ret,ret)
		r.br = true;
	}else if(isMatch(TOK_NUM,s)) {
		var n = nextToken()
		r = new J_VALUE("int",parseInt(n.data),n)
	}else if(isMatch(TOK_DEC,s)) {
		var n = nextToken()
		r = new J_VALUE("double",parseFloat(n.data),n)
		if(n.data.indexOf("l")>-1){
			r = new J_VALUE("long",n.data.substring(0,n.data.length-1),n)
		}
		else if(n.data.indexOf("f")>-1) r.type = "float"
	}else if(isMatch(TOK_BOOL,s)) {
		var n = nextToken()
		r = new J_VALUE("boolean",(n.data=="true"?true:false),n)
	}else if(isMatch(TOK_STR,s)) {
		var n = nextToken()
		r = new J_NEW(null,null,n.data,"String")
	}else if(isMatch(TOK_CHAR,s)) {
		var n = nextToken()
		var ch = n.data;
		var err=false
		if(ch.charAt(0) == "\\"){
			if(ch.length > 2)  err = true
			else ch = escapedChars[ch] || ch
		}else if(ch.length > 1) err = true
		if(err) compileError({msg:"line "+n.line.toString().pad(3," ",true)+":: PARSE ERROR: badly formatted char, length > 1: '"+ch, token:n},true)
		//eval("ch='"+n.data+"'")
		r = new J_VALUE("char",ch.charCodeAt(0))
	}else if((isMatch(TOK_PRIM,s) || isMatch(TOK_TYPE,s)) && isMatch(TOK_BR_R,s2)) {
		nextToken()
		r = new J_CALL(s.data,null,false,null)
	}else if(isMatch(TOK_PRIM,s)) {
		vardef = true
		var v = ruleDefArgType()
		f.vars.push(v)
		f.stats.push(new J_DEF(v.name,true))
		r = new J_CALL(v.name,null,false,null)
	}else if(isMatch(TOK_NEW,s)) r = ruleNew()
	else if(isMatch(TOK_ME_L,s)) r = ruleArrayActual()
	else if(isMatch(TOK_BREAK,s)) r = ruleBreak()
	else if(isMatch(TOK_THROW,s)) r = ruleThrow()
	else if(isMatch(TOK_NULL,s)) {
		nextToken()
		r = new J_VALUE("Object",JNULL)
	}else if(addr && isMatch(TOK_TYPE,s) && (s2.data=="<" || isMatch(TOK_TYPE,s2) || (isMatch(TOK_AR_L,s2) && isMatch(TOK_AR_R,s2)))) {
		vardef = true
		var v = ruleDefArgType()
		f.vars.push(v)
		f.stats.push(new J_DEF(v.name,true))
		r = new J_CALL(v.name,null,false,null)
	}else if(isMatch(TOK_TYPE,s)) r = ruleCall()
	else if(isMatch(TOK_CUSTOM,s) || isMatch(TOK_BYTECODE,s)) {
		r = new J_CALL(nextToken().data,[],true,null)
		if(isMatch(TOK_BYTECODE,s)) r.bcode = true
	}else {
		r = new J_CALL("",null,false)
		if(addr) nextToken();
		unToken(s,"statement expected",true)
	}
	if(single) return r
	
	s = lookAhead(1)
	s2 = lookAhead(2)
	if(s.type == TOK_OP && s2.type == TOK_EQ) {nextToken();nextToken();r=ruleOpAssign(r,s.data)}
	else if(s.type == TOK_EQ) {
		var debug=nextToken();r=ruleAssign(r);r.tok = debug
		if(!vardef) return r
		var type = f.vars[f.vars.length-1].type
		r = ruleChainedDefine(f,r,type)
	}else if(s.type == TOK_IS) { //EM: is token parsing
		var debug=nextToken();r=ruleAssign(r);r.tok = debug
		if(!vardef) return r
		var type = f.vars[f.vars.length-1].type
		r = ruleChainedDefine(f,r,type)
	}
	else if(isMatch(TOK_OP,s) && isMatch(TOK_OP,lookAhead(2)) && s.data == lookAhead(2).data && (s.data == "+" || s.data == "-")) {
		nextToken();nextToken()
		r = new J_LR(r,new J_OP(s.data,r,new J_VALUE("int",1)),true)
	}
	else if(!f && (s.type == TOK_OP || s.type == TOK_OP_BOOL)) {nextToken();r=ruleOp(r,s.data,subop)}
	else if(s.type==TOK_QU) {nextToken();r=ruleTernaryOp(r)}
	else if(f && f.vars && isMatch(TOK_CM, s))  r = ruleChainedDefine(f,r,f.vars[f.vars.length-1].type)
	else if(isMatch(TOK_DOT,s)) r.subcall = ruleStatement(false)
	return r;
}
function ruleChainedDefine(f,r,type){ //continues a chained defined ie int x = 0, a, b, c = 3;
	while(isMatch(TOK_CM, lookAhead(1))){
		nextToken()
		f.stats.push(r)
		if(isMatch(TOK_PRIM,lookAhead(1)) || isMatch(TOK_TYPE,lookAhead(1))){
			var next = nextToken()
			f.vars.push(new J_VARIABLE(next.data,type,true))
			f.stats.push(new J_DEF(next.data,true))
			r = new J_CALL(next.data,null,false,null)
			if(isMatch(TOK_EQ, lookAhead(1))){
				debug=nextToken();r=ruleAssign(r);r.tok = debug
			}
		}
	}
	return r
}
function ruleOp(l,op,subop){ // +-/* <statement>
	var r = ruleStatement(false,null,true)
	var result = new J_OP(op,l,r)
	//REORDER USING BIDMAS :)
	var stats = []
	var ops = []
	getOpStats(stats,ops,result)
	while(ops.length > 0){
		var index = getMaxOp(ops)
		var o = ops.splice(index,1)[0]
		var l = stats[index]
		var r = stats[index+1]
		stats.splice(index,2,new J_OP(o,l,r))
	}
	//END
	return stats[0]
} 
function getOpStats(s,o,f){
	if(f.is != IS_OP || f.br){
		s.push(f)
		return ;
	}
	s.push(f.left)
	o.push(f.op)
	getOpStats(s,o,f.right)
}
function getMaxOp(ops){
	var max = -1
	var index = -1;
	ops.each(function (i,item){
		if(bidmas(item) > max){
			max = bidmas(item)
			index = i
		}
	})
	return index
}
function bidmas(sym){
	if(sym == "|" || sym == "&" || sym == "||" || sym == "&&") return 0
	if(sym == "<" || sym == ">" || sym == "<=" || sym == ">=" || sym == "!=" || sym == "==") return 1
	if(sym=="%") return 2
	if(sym == "-" || sym == "+") return 3
	if(sym == "*") return 4
	if(sym == "/") return 5
}
function ruleAssign(l){ // l = <statement>
	var r = ruleStatement(false)
	return new J_LR(l,r)
}
function ruleOpAssign(l,op){return new J_LR(l,ruleOp(l,op))}
function ruleTernaryOp(c){ // l = <statement>
	var l = ruleStatement(false)
	var col = nextToken()
	if(!isMatch(TOK_COL,col)) unToken(col,[TOK_COL])
	var r = ruleStatement(false)
	var node = new J_IF(c,l,r)
	node.ternary = true
	node.tok = l.tok
	return node
}
function ruleNew(){ //new <name> <generic>? (<args>*)
	nextToken()
	var n = ruleCallPart(true,true)
	if(n.arr) {
		var arr = n.arr
		arr.type = n.fun
		return new J_NEW(null, arr)
	}
	return new J_NEW(n,null,null,null,n.tok)
}
function ruleBreak(){var b = nextToken();return new J_BREAK(b.data=="break")}
function ruleThrow(){ //throw <ruleNew()>
	nextToken()
	if(!isMatch(TOK_NEW, lookAhead(1))) unToken(lookAhead(1),[TOK_NEW]);
	var r = new J_RETURN(ruleNew())
	r.thr = true
	return r
}
function ruleIf(){ //if (<statement()>) <statement()> (else <statement()>)?
	var sem = nextToken()
	var args = ruleCallArgs(true)[0]
	var thenf = ruleStatement(true)
	if(thenf && eolCheck(thenf.is)) ruleEOL()
	
	var elsef=null;
	if(isMatch(TOK_ELSE,lookAhead(1))){
		nextToken()
		elsef = ruleStatement(true)
		if(eolCheck(elsef.is)) ruleEOL()
	}
	return new J_IF(args,thenf,elsef,sem)
}
function ruleFor(){
	nextToken()
	var br = nextToken()
	if(!isMatch(TOK_BR_L, br)) unToken(br,[TOK_BR_L])

	var dof = new J_FUNCTION("then","",true,false,[],[],[])

	var init = [ruleStatement(false,dof)]
	if(dof.stats.length > 0){
		init.unshift(dof.stats.pop())
		dof.stats = []
	}
	ruleEOL()

	var con = ruleStatement(false)
	ruleEOL()
	
	var inc = ruleStatement(false)
	br = nextToken()
	if(!isMatch(TOK_BR_R, br)) unToken(br,[TOK_BR_R])
	
	if(isMatch(TOK_ME_L,lookAhead(1))) ruleMethod(dof) //for(...) {<statement>*}
	else{  //for(...) <statement>
		dof.stats=[ruleStatement()]
		ruleEOL()
	}
	return new J_FOR([],init,con,inc,dof)
}
function ruleWhile(){ //while (<statement()>) <statement()>
	nextToken()
	var args = ruleCallArgs(true)[0]
	var dof = ruleStatement(true)
	if(dof && dof.is != IS_BLOCK) ruleEOL()
	return new J_WHILE(args,dof)
}
function ruleDo(){
	nextToken()
	var dof = new J_FUNCTION("then","",true,false,[],[],[])
	if(isMatch(TOK_ME_L,lookAhead(1))) ruleMethod(dof) //if(...) {<statement>*}
	else{  //if(...) <statement>
		dof.stats=[ruleStatement()]
		ruleEOL()
	}
	var wh = nextToken();
	if(!isMatch(TOK_WHILE,wh)) unToken(wh,[TOK_WHILE])
	var args = ruleCallArgs(true)[0]
	ruleEOL()
	return new J_WHILE(args,dof,true)
}
function ruleTry(){
	nextToken()
	var tryf = new J_FUNCTION("try","",true,false,[],[],[])
	ruleMethod(tryf) //try{...}
	var catches = []
	while(isMatch(TOK_CATCH,lookAhead(1))){ //catch(type name){...}
		nextToken();nextToken()
		var args = ruleDefArgs()
		var f = new J_FUNCTION("catch", "", true, true,args,[],[])
		ruleMethod(f)
		catches.push(f)
	}
	if(catches.length == 0) unToken(lookAhead(1),"No catches defined")
	var finf;
	if(isMatch(TOK_FIN,lookAhead(1))){ //finally{...}
		nextToken()
		finf = new J_FUNCTION("finally","",true,false,[],[],[])
		ruleMethod(finf) 
	}
	return new J_TRY(tryf,catches,finf)
}
function ruleSwitch(){
	var sem = nextToken()
	var args = ruleCallArgs(true)[0]
	var cases = []
	var CONS = []
	var last;
	var br = nextToken()
	if(!isMatch(TOK_ME_L, br)) unToken(br,[TOK_ME_L])
	while(!isMatch(TOK_ME_R, lookAhead(1))){
		var c = nextToken();
		var tok = c
		if(!isMatch(TOK_CASE, c) && !isMatch(TOK_DEF, c)) unToken(c,[TOK_CASE, TOK_DEF])
		var stat=null
		if(isMatch(TOK_CASE, c)) {
			var stat = ruleStatement(false)
			//if(!isMatch(TOK_NUM, num)) unToken(num,[TOK_NUM])
			//stat = new J_VALUE("int",parseInt(num.data),num)
			
			//if(CONS.indexOf(num.data) != -1) semError("duplicate case in switch statement - '"+num.data+"'",stat)
			//else CONS.push(num.data)
		}
		c = nextToken();
		if(!isMatch(TOK_COL, c)) unToken(c,[TOK_COL])
		cases.push(new J_CASE(stat,ruleMultiStat(),tok))
		
		if(last && !last.con) semError("only default case should defined and at the end of switch block",last)
		last = cases[cases.length-1]
	}
	br = nextToken()
	if(!isMatch(TOK_ME_R, br)) unToken(br,[TOK_ME_R])
	return new J_SWITCH(args,cases,sem)
}
function ruleMultiStat(){
	var stats = []
	while(!isMatch(TOK_CASE, lookAhead(1)) && !isMatch(TOK_DEF, lookAhead(1)) && !isMatch(TOK_ME_R, lookAhead(1))){
		var s = ruleStatement(false)
		stats.push(s)
		ruleEOL()
	}
	return stats
}
function ruleEOL(){
	var eol = lookAhead(1)
	if(!isMatch(TOK_END, eol)) unToken(eol,[TOK_END],true)
	else nextToken()
}
function ruleCall(){
	var r = ruleCallPart()
	var c = r
	while(isMatch(TOK_DOT,lookAhead(1))){
		nextToken()
		c.subcall = ruleCallPart()
		c = c.subcall
	}
	return r
}
function ruleCallPart(fun,allowgen){ // (stat,stat) OR stat,stat} 
	var c = nextToken()
	var gen
	if(allowgen && lookAhead(1).data == "<"){
			var g = ruleGeneric()
			gen = new Array(g.length)
			var i = g.length
			while(i--) gen[i] = g[i].type
	}
	var args = null
	if(isMatch(TOK_BR_L,lookAhead(1))){
		nextToken()
		args=[]
		var com = false
		while(!isMatch(TOK_BR_R,lookAhead(1))){
			if(com){
				var split = nextToken()
				if(!isMatch(TOK_CM, split)) unToken(split,[TOK_CM,TOK_BR_R])
			}
			args.push(ruleStatement(false))
			com = true
		}
		var br = nextToken()
		if(!isMatch(TOK_BR_R, br)) unToken(br,[TOK_BR_R])
	}else if(isMatch(TOK_AR_L,lookAhead(1))){
		var arrs = ruleArray(true)
		return new J_CALL(c.data,null,false,null,arrs)
	}else if(fun!=null) unToken(lookAhead(1),[TOK_BR_L])
	return new J_CALL(c.data,args,false,null,null,c,gen)
}
function ruleCallArgs(single,arr){
	var br = nextToken()
	if(!isMatch(TOK_BR_L,br)) unToken(br,[TOK_BR_L])
	var args=[]
	var com = false
	
	while((!arr && !isMatch(TOK_BR_R,lookAhead(1))) || (arr && !isMatch(TOK_ME_R,lookAhead(1)))){
		if(com){
			var split = nextToken()
			if(!isMatch(TOK_CM, split)) unToken(split,[TOK_CM,(arr?TOK_ME_R:TOK_BR_R)])
		}
		args.push(ruleStatement(false))
		com = true
		if(single) break;
	}
	if(single && args.length == 0) unToken(lookAhead(1),"statement expected",true)
	br = nextToken()
	if(!arr && !isMatch(TOK_BR_R, br)) unToken(br,[TOK_BR_R])
	else if(arr && !isMatch(TOK_ME_R, br)) unToken(br,[TOK_ME_R])
	return args
}

//TOKEN FUNCTIONS
function nextToken(){
	lastComment = null
	if(TOKENS.length == 0) compileError("run out of tokens...")
	while(true){
		var result = TOKENS.splice(0,1)
		if(result[0].type == TOK_COMMENT) lastComment = result[0].data
		else if(result[0].type != TOK_WHITE && result[0].type != TOK_ERR) break
	}
	return result[0]
}
function lookAhead(n){
	var count = 0;
	for(var i = 0; i < TOKENS.length; i++){
		if(TOKENS[i].type != TOK_WHITE && TOKENS[i].type != TOK_COMMENT && TOKENS[i].type != TOK_ERR) count++
		if(count == n) return TOKENS[i]
	}
	compileError("can not lookahead "+n)
}
function isMatch(id,t){return t.type == id}
function moreTokens(){
	for(var i = 0; i < TOKENS.length; i++){
		if(TOKENS[i].type != TOK_WHITE) return true
	}
	return false
}
function matchToken(type){
	var tok = lookAhead(1)
	if(!isMatch(type, tok)) unToken(tok,[type],true)
	else nextToken()
	return tok
}
function unToken(t,ex,warn){
	debugger
	var msg = ""
	if((typeof ex) != "string"){
		ex.each(function (_,item) {
			msg += " , "+J_TOKENS[item].name
		})
		msg = "expected: [ "+msg.substring(3)+" ]"
	}else msg = ex
	compileError({msg:"unexpected token '"+J_TOKENS[t.type].name+"' ("+t.data+"), "+msg, token:t},warn)
}
function stringToArray(str){
	var array = []
	var chars = new J_ARR_ACT(array,"[]char")
	for(var i = 0; i < str.length; i++){
		array.push(new J_VALUE("char",str.charCodeAt(i)))
	}
	return new J_NEW(null,chars)
}
function setObj(o,a,v){
	if(a.each!=null){
		a.each(function (i,item) {o[item]=v[i]})
	}else o[a]=v
	return o
}
function IMP(name){
	this.name = name
	this.all = (name=="*"?true:false)
}
function rangeDef(f,t){this.from = f; this.to = t;}

/*
var statement = PROGRAM[0].vardef.stat[4];
var cls = PROGRAM[0];

console.log(statement)

function getDependencies(statement){
    var variables = [];
    _.each(statement, function(val, key){
        if(typeof val != "object") return;
        
        if(val.is == IS_CALL){
            variables.push(val);
        }else{
            variables = variables.concat(getDependencies(val))
        }
    });
    return variables;
}

var dependencies = getDependencies(statement);
dependencies
//var vars = cls.vars.each(function (i,item){if(item.name=="a" ) return item})*/
//PROGRAM
//PROGRAM[0].vars.each(function (i,item){if(item.name=="p" ) return item})


