public enum OpType {
	MOV,MOVN,MOVS,MOVL,MOVA,CALL,RAWEQ,NEW,
	BEGIN,END,PROP,NOP,DEF,
	IF,ELSE,FOR,ITER,DO,WHILE,REP,
	RET,BRK,CONT,
	ENDB,NATIVE,
	LABEL,GOTO,GOTOT,GOTOF,TRUE,FALSE;
		
	public boolean hasLVar() {
		if (this.isBlockStarter()) {
			return false;
		}
		switch (this) {
			case ELSE:
			case RET:
			case BRK:
			case CONT:
			case PROP:
			case BEGIN:
			case END:
			case ENDB:
			case DO:
			case LABEL:
			case GOTO:
			case GOTOT:
			case GOTOF:
			case NATIVE:
			case DEF:
			case ITER:
				return false;
			default:
				return true;
		}
	}
		
	public boolean isBlockStarter() {
		switch (this) {
			case IF:
			case WHILE:
			case REP:
			case FOR:
				return true;
			default:
				return false;
		}
	}
} public class Element implements IDirectable {
	public IElementType type;
	public Object[] args = new Object[10];
	public Element dataType = null;
	public ArrayList<String> directives = new ArrayList<>();
	public Range range;

	public Element(Range range, IElementType type) {
		this.range = range;
		this.type = type;
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder("{");
		sb.append(type).append(" as ").append(dataType).append(": [").append(args[0]).append(" ").append(args[1]).append(" ").append(args[2]).append(" ").append(args[3]).append("]");
		return sb.append("}").toString();
	}

	@Override
	public ArrayList<String> getDirectives() {
		return directives;
	}
} public enum Rule implements IElementType {
	BLOCK_COMMENT(null,(a,i)->{
		if (a.get(i).type==TokenRule.SYMBOL && "/*".equals(a.get(i).args[0])) {
			int j = i;
			int depth = 1;
			ArrayList<Element> a2 = new ArrayList<>();
			Range r1 = a.get(i).range;
			while (true) {
				j++;
				if (j>a.size()) {
					return null;
				}
				if (a.get(j).type==TokenRule.SYMBOL && "/*".equals(a.get(j).args[0])) {
					depth++;
				}
				if (a.get(j).type==TokenRule.SYMBOL && "*/".equals(a.get(j).args[0])) {
					depth--;
				}
				if (depth==0) {
					return new RuleResult(null,a2.size()+2);
				}
				a2.add(a.get(j));
			}
		}
		return null;
	}),
	PAREN("p",(a,i)->{
		if (a.get(i).type==TokenRule.SYMBOL && "(".equals(a.get(i).args[0])) {
			int j = i;
			int depth = 1;
			ArrayList<Element> a2 = new ArrayList<>();
			Range r1 = a.get(i).range;
			while (true) {
				j++;
				if (j>a.size()) {
					return null;
				}
				if (a.get(j).type==TokenRule.SYMBOL && "(".equals(a.get(j).args[0])) {
					depth++;
				}
				if (a.get(j).type==TokenRule.SYMBOL && ")".equals(a.get(j).args[0])) {
					depth--;
				}
				if (depth==0) {
					Element e = new Element(Range.from(r1, a.get(j).range),Rule.PAREN);
					ArrayList<Element> a3 = Parser.parse((ArrayList<Element>) a2.clone());
					e.args[0] = a3;
					return new RuleResult(e,a2.size()+2);
				}
				a2.add(a.get(j));
			}
		}
		return null;
	}),
	INDEX("i",(a,i)->{
		if (a.get(i).type==TokenRule.SYMBOL && "[".equals(a.get(i).args[0])) {
			int j = i;
			int depth = 1;
			ArrayList<Element> a2 = new ArrayList<>();
			Range r1 = a.get(i).range;
			while (true) {
				j++;
				if (j>a.size()) {
					return null;
				}
				if (a.get(j).type==TokenRule.SYMBOL && "[".equals(a.get(j).args[0])) {
					depth++;
				}
				if (a.get(j).type==TokenRule.SYMBOL && "]".equals(a.get(j).args[0])) {
					depth--;
				}
				if (depth==0) {
					Element e = new Element(Range.from(r1, a.get(j).range),Rule.INDEX);
					ArrayList<Element> a3 = Parser.parse((ArrayList<Element>) a2.clone());
					e.args[0] = a3;
					return new RuleResult(e,a2.size()+2);
				}
				a2.add(a.get(j));
			}
		}
		return null;
	}),
	DYN_INDEX(null,"i0!':'?"),
	CODE("c",(a,i)->{
		if (a.get(i).type==TokenRule.SYMBOL && "{".equals(a.get(i).args[0])) {
			int j = i;
			int depth = 1;
			ArrayList<Element> a2 = new ArrayList<>();
			Range r1 = a.get(i).range;
			while (true) {
				j++;
				if (j>a.size()) {
					return null;
				}
				if (a.get(j).type==TokenRule.SYMBOL && "{".equals(a.get(j).args[0])) {
					depth++;
				}
				if (a.get(j).type==TokenRule.SYMBOL && "}".equals(a.get(j).args[0])) {
					depth--;
				}
				if (depth==0) {
					Element e = new Element(Range.from(r1, a.get(j).range),Rule.CODE);
					ArrayList<Element> a3 = Parser.parse((ArrayList<Element>) a2.clone());
					e.args[0] = a3;
					return new RuleResult(e,a2.size()+2);
				}
				a2.add(a.get(j));
			}
		}
		return null;
	}),
	DIR(null,(a,i)->{ //local directive rule
		ArrayList<String> dirs = new ArrayList<>();
		int j = i;
		while (j < a.size()) {
			if (a.get(j).type == TokenRule.DIRECTIVE && !((Token)a.get(j)).string().startsWith("@")) {
				dirs.add((String) a.get(j).args[0]);
			} else {
				a.get(j).getDirectives().addAll(dirs);
				return new RuleResult(null, dirs.size());
			}
			j++;
		}
		return null;
	}),
	GLOBAL_DIR(null,"r0"),
	FCALL("F","w0!p1?"),
	ICALL("I","w0!i1?"),
	FCALL2(null,"I02!p1?"),
	FCALL_HAX(null,(a,i)->{
		if (a.get(i).type!=FCALL2) {
			return null;
		}
		Element e = new Element(a.get(i).range, FCALL);
		e.args[0] = a.get(i).args[0];
		e.args[1] = a.get(i).args[1];
		e.args[2] = a.get(i).args[2];
		e.directives.addAll(a.get(i).directives);
		e.dataType = a.get(i).dataType;
		return new RuleResult(e, 1);
	}),
	TO(null,"a@0'to'a@1"),
	CAST(null,(a,i)->{
		if (i>a.size()-2 || a.get(i+1).type != TokenRule.RESWORD || !((Token)a.get(i+1)).string().equals("as")) {
			return null;
		}
		Element e1 = a.get(i);
		Element e2 = a.get(i+2);
		e1.dataType = e2;
		return new RuleResult(e1,3);
	}),
	EXTEND("E","w0'extends'a@1"),
	CHAIN("C", new com.iconmaster.source.element.ISpecialRule() {
		@Override
		public RuleResult match(ArrayList<Element> a, int i) {
			if (i+2>a.size() || a.get(i+1).type!=TokenRule.SYMBOL || !".".equals(a.get(i+1).args[0])) {
				return null;
			}
			int j = i;
			ArrayList<Element> a2 = new ArrayList<>();
			Range r1 = a.get(i).range;
			while (true) {
				a2.add(a.get(j));
				if (j+2>a.size() || a.get(j+1).type!=TokenRule.SYMBOL || !".".equals(a.get(j+1).args[0])) {
					Element e = new Element(Range.from(r1, a.get(j).range),Rule.CHAIN);
					e.args[0] = a2;
					return new RuleResult(e,a2.size()*2-1);
				}
				j+=2;
			}
		}
	}),
	NEW(null,"'new'!F01?"),
	TRUE(null,"'true'?!"),
	FALSE(null,"'false'?!"),
	NOT(null,"'not'a@0"),
	BIT_NOT(null,"'!'a@0"),
	POW(null,"a@0'^'a@1"),
	MUL(null,"a@0'*'a@1"),
	DIV(null,"a@0'/'a@1"),
	MOD(null,"a@0'%'a@1"),
	ADD(null,"a@0'+'a@1"),
	NEG("_","'-'a@0"),
	SUB(null,"A@0_1"),
	CONCAT(null,"a@0'~'a@1"),
	SLL(null,"a@0'<<'a@1"),
	SRL(null,"a@0'>>'a@1"),
	SRA(null,"a@0'>>>'a@1"),
	BIT_AND(null,"a@0'&'a@1"),
	BIT_OR(null,"a@0'|'a@1"),
	RAW_EQ(null,"a@0'==='a@1"),
	EQ(null,"a@0'=='a@1"),
	NEQ(null,"a@0'!='a@1"),
	LT(null,"a@0'<'a@1"),
	GT(null,"a@0'>'a@1"),
	LTE(null,"a@0'<='a@1"),
	GTE(null,"a@0'>='a@1"),
	AND(null,"a@0'and'a@1"),
	OR(null,"a@0'or'a@1"),
	TUPLE("T", new com.iconmaster.source.element.ISpecialRule() {
		@Override
		public RuleResult match(ArrayList<Element> a, int i) {
			if (i+2>a.size() || a.get(i+1).type!=TokenRule.SYMBOL || !",".equals(a.get(i+1).args[0])) {
				return null;
			}
			int j = i;
			ArrayList<Element> a2 = new ArrayList<>();
			Range r1 = a.get(i).range;
			while (true) {
				a2.add(a.get(j));
				if (j+2>a.size() || a.get(j+1).type!=TokenRule.SYMBOL || !",".equals(a.get(j+1).args[0])) {
					Element e = new Element(Range.from(r1, a.get(j).range),Rule.TUPLE);
					e.args[0] = a2;
					return new RuleResult(e,a2.size()*2-1);
				}
				j+=2;
			}
		}
	}),
	LOCAL("L","'local'!t0?"),
	LOCAL_ASN(null,"L0!?'='t1"),
	FIELD("G","'field'!t0?"),
	FIELD_ASN(null,"G0!?'='t1"),
	ICALL_REF(null,"A@0i1"),
	ADD_ASN(null,"a@0'+='a@1"),
	SUB_ASN(null,"a@0'-='a@1"),
	MUL_ASN(null,"a@0'*='a@1"),
	DIV_ASN(null,"a@0'/='a@1"),
	ASSIGN("=","t0'='t1"),
	IF(null,"'if'!a@0c2"),
	ELSEIF(null,"'elseif'!a@0c2"),
	ELSE(null,"'else'!c2"),
	IFBLOCK(null,(a,i)->{
		if (a.get(i).type!=Rule.IF) {
			return null;
		}
		Element e = new Element(null, IFBLOCK);
		e.args[0] = a.get(i);
		ArrayList<Element> es = new ArrayList<>(); 
		e.args[1] = es;
		Range endr = e.range;
		int n = 1;
		for (int j=i+1;j<a.size();j++) {
			if (a.get(j).type==Rule.ELSEIF) {
				es.add(a.get(j));
				n++;
				endr = a.get(j).range;
			} else if (a.get(j).type==Rule.ELSE) {
				e.args[2] = a.get(j);
				n++;
				endr = a.get(j).range;
			} else {
				break;
			}
		}
		e.range = Range.from(a.get(i).range, endr);
		return new RuleResult(e, n);
	}),
	FOR(null,"'for'!t0'in'a@1c2"),
	WHILE(null,"'while'!a@0c2"),
	REPEAT(null,"'repeat'!c2'until'a@0"),
	RETURN_NULL("R","'return'!"),
	RETURN(null,"R!A@0?"),
	BREAK(null,"'break'!"),
	CONTINUE(null,"'continue'!"),
	PACKAGE(null,"'package'!a@0"),
	IMPORT(null,"'import'!a@0?"),
	FUNC(null,"'function'!F013?c2"),
	ITERATOR(null,"'iterator'!F01?c2"),
	TYPE(null,"'type'!w0c2"),
	TYPE_EXT(null,"'type'!E01c2"),
	STRUCT(null,"'struct'!w@0c2"),
	STRUCT_EXT(null,"'struct'!E01c2"),
	ENUM(null,"'enum'!w0c1"),
	SEP(null,(a,i)->{
		if (a.get(i).type != TokenRule.SEP) {
			return null;
		}
		return new RuleResult(null,1);
	});

	private boolean isCompound() {
		switch (this) {
			default:
				return false;
		}
	}
	
	public class Clause {
		public String toMatch;
		public int[] toEnter = new int[0];
		public boolean isTyped = false;
		public boolean isDired = false;
		public boolean literal;
		private boolean[] isRef = new boolean[0];

		public Clause(String toMatch, boolean literal) {
			this.toMatch = toMatch;
			this.literal = literal;
		}

		@Override
		public String toString() {
			return "CLAUSE("+toMatch+" "+Arrays.toString(toEnter)+" "+isTyped+" "+isDired+" "+literal+")";
		}
	}
	
	public class ClauseMatcher implements ISpecialRule {
		
		public final Clause[] clauses;
		public final IElementType type;
		
		public ClauseMatcher(Clause[] clauses, IElementType type) {
			this.clauses = clauses;
			this.type = type;
		}

		@Override
		public RuleResult match(ArrayList<Element> a, int i) {
			if (i+clauses.length>a.size()) {
				return null;
			}
			Element e = new Element(null,type);
			Range low = null;
			Range high = null;
			for (int j=0;j<clauses.length;j++) {
				Element v = a.get(i+j);
				if ((clauses[j].literal && v.type instanceof TokenRule && ((Token)v).string().equals(clauses[j].toMatch)) || (v.type==Parser.getAlias(clauses[j].toMatch)) || (clauses[j].toMatch.equals("a")) || (clauses[j].toMatch.equals("t")) || (clauses[j].toMatch.equals("A")) && isValidOperationToken(v)) {
					if (clauses[j].toMatch.equals("t") && v.type!=Rule.TUPLE) {
						if (v.type==Rule.PAREN) {
							Element oldv = v;
							v = new Element(v.range,Rule.TUPLE);
							v.args[0] = oldv.args[0];
						} else {
							ArrayList a2 = new ArrayList();
							a2.add(v);
							v = new Token(v.range,Rule.TUPLE,a2);
						}
					}
					for (int k=0;k<clauses[j].toEnter.length;k++) {
						if (clauses[j].isRef[k]) {
							e.args[clauses[j].toEnter[k]] = v;
						} else {
							e.args[clauses[j].toEnter[k]] = v.args[k];
						}
					}
					if (clauses[j].isTyped) {
						e.dataType = v.dataType;
					}
					if (clauses[j].isDired) {
						e.directives.addAll(v.directives);
					}
					if (j==0) {
						low = v.range;
					}
					high = v.range;
				} else {
					return null;
				}
			}
			e.range=Range.from(low, high);
			return new RuleResult(e,clauses.length);
		}
	}
	
	public final Clause[] clauses;
	public String alias;
	public ISpecialRule rule;
	
	Rule(String alias,String match) {
		List<String> cstr = new ArrayList<>();
		if (match!=null) {
			Matcher m = Pattern.compile("'[^']+'|.").matcher(match);
			while (m.find()) {
				cstr.add(m.group());
			}

			//System.out.println(cstr);

			ArrayList<Clause> a = new ArrayList<>();

			Clause clause = null;
			boolean ref = false;
			for (String line : cstr) {
				if (line.matches("'[^']+'|[^\\d\\!\\?\\@]")) {
					if (clause != null) {
						a.add(clause);
					}
					if (line.startsWith("'")) {
						clause = new Clause(line.substring(1, line.length()-1),true);
					} else {
						clause = new Clause(line,false);
					}
				} else {
					if (clause != null) {
						if (line.matches("\\d")) {
							clause.toEnter = Arrays.copyOf(clause.toEnter, clause.toEnter.length+1);
							clause.isRef = Arrays.copyOf(clause.isRef, clause.isRef.length+1);
							clause.toEnter[clause.toEnter.length-1] = Integer.parseInt(line);
							clause.isRef[clause.isRef.length-1] = ref;
							ref = false;
						} else if (line.equals("!")) {
							clause.isDired = true;
						} else if (line.equals("?")) {
							clause.isTyped = true;
						} else if (line.matches("@")) {
							ref = true;
						}
					}
				}
			}

			if (clause != null) {
				a.add(clause);
			}

			this.clauses = a.toArray(new Clause[0]);
			this.rule = new ClauseMatcher(clauses,this);
		} else {
			this.clauses = new Clause[0];
		}

		this.alias = alias;
	}
	
	Rule(String match) {
		this(null,match);
	}
	
	Rule(String alias, ISpecialRule rule) {
		this(alias,(String)null);
		this.rule = rule;
	}
	
	@Override
	public String getAlias() {
		return alias;
	}
	
	public static boolean isValidOperationToken(Element e) {
		return !(e.type==TokenRule.SYMBOL || e.type==TokenRule.RESWORD || e.type==TokenRule.SEP);
	}
} public class SourceException extends Exception {
	private final Range range;
	
	public SourceException(Range range) {
		super(range.toString()+": Unknown Source exception");
		this.range = range;
	}
	
	public SourceException(Range range, String message) {
		super((range==null?"null: ":range.toString()+": ")+message);
		this.range = range;
	}
	
	@Override
	public String getMessage() {
		return super.getMessage();
	}
	
	public Range getRange() {
		return range;
	}
} public class SourceUndefinedVariableException extends SourceException {
	public String var;
	
	public SourceUndefinedVariableException(Range range, String message, String var) {
		super(range,message);
		this.var = var;
	}
	
	@Override
	public String getMessage() {
		return super.getMessage();
	}
} public class SourceUninitializedVariableException extends SourceException {
	public String var;
	
	public SourceUninitializedVariableException(Range range, String message, String var) {
		super(range,message);
		this.var = var;
	}
	
	@Override
	public String getMessage() {
		return super.getMessage();
	}
} public class Field implements IDirectable {
	protected String name;
	public Element rawType;
	public Element rawValue;
	protected ArrayList<String> directives = new ArrayList<>();;
	
	protected boolean compiled;
	protected boolean library;
	protected Expression value;
	private DataType type;
	
	public String pkgName;
	
	/**
	 * A map used to store item information for the platform.
	 */
	public HashMap data = new HashMap<>();

	public Field(String name) {
		this(name,null);
	}

	public Field(String name, Element type) {
		this.name = name;
		this.rawType = type;
	}
	
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder(name+"="+getType());
		if (value!=null) {
			sb.append(". CODE:");
			
			for (Operation op : value) {
				sb.append("\n\t");
				sb.append(op.toString());
			}
		}
		return sb.toString();
	}

	public String getName() {
		return name;
	}
	
	public void setCompiled(Expression code) {
		this.compiled = true;
		this.value = code;
	}

	public Element rawData() {
		return rawValue;
	}

	public boolean isCompiled() {
		return compiled;
	}

	public Expression getValue() {
		return value;
	}
	
	@Override
	public ArrayList<String> getDirectives() {
		return directives;
	}
	
	public boolean isLibrary() {
		return library;
	}

	public Element getRawType() {
		return rawType;
	}

	public DataType getType() {
		return type;
	}

	public void setType(DataType type) {
		this.type = type;
	}
	
	public static Field libraryField(String name, TypeDef type) {
		Field f = new Field(name);
		if (type!=null) {
			f.setType(new DataType(type,false));
		}
		f.library = true;
		return f;
	}
	
	public static interface OnCompile {
		public String compile(SourcePackage pkg, boolean isGet, Object... args);
	}
	
	public static interface OnRun {
		public Object run(SourcePackage pkg, boolean isGet, Object... args);
	}
} public class Function implements IDirectable {
	protected String name;
	protected ArrayList<Field> args;
	public Element rawReturns;
	protected ArrayList<String> directives = new ArrayList<>();
	public ArrayList<Element> rawCode;
	protected boolean library = false;
	
	protected boolean compiled = false;
	protected ArrayList<Operation> code;
	private DataType returns;
	
	public String pkgName;
	
	public int order = 0;
	public int references = 0;
	
	public ArrayList<Field> rawParams;
	
	/**
	 * A map used to store item information for the platform.
	 */
	public HashMap data = new HashMap<>();

	public Function(String name, ArrayList<Field> args, Element returns) {
		this.name = name;
		this.args = args;
		this.rawReturns = returns;
	}

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder(getFullName()+args+" as "+returns);
		if (code!=null) {
			sb.append(". CODE:");
			
			for (Operation op : code) {
				sb.append("\n\t");
				sb.append(op.toString());
			}
		}
		return sb.toString();
	}
	
	public static Function libraryFunction(String name, String[] args, Object[] argTypes, Object ret) {
		ArrayList<Field> argList = new ArrayList<>();
		ArrayList<DataType> retList = new ArrayList<>();
		
		int i = 0;
		for (String arg : args) {
			Element e = null;
			Field f = new Field(arg, null);
			if (i<argTypes.length) {
				DataType dt = null;
				if (argTypes[i] instanceof TypeDef) {
					dt = new DataType((TypeDef)argTypes[i],false);
				} else if (argTypes[i] instanceof DataType) {
					dt = (DataType) argTypes[i];
				}
				f.setType(dt);
			}
			argList.add(f);
			i++;
		}
		

		Function fn = new Function(name, argList, null);
		DataType dt = null;
		if (ret instanceof TypeDef) {
			dt = new DataType((TypeDef)ret,false);
		} else if (ret instanceof DataType) {
			dt = (DataType) ret;
		}
		fn.setReturnType(dt);
		fn.library = true;
		fn.compiled = true;
		return fn;
	}

	public void setCompiled(ArrayList<Operation> code) {
		this.compiled = true;
		this.code = code;
	}
	
	public ArrayList<Element> rawData() {
		return rawCode;
	}

	public boolean isCompiled() {
		return compiled;
	}
	
	public String getName() {
		return name;
	}
	
	public String getFullName() {
		return ((pkgName==null || pkgName.isEmpty())?"":(pkgName+"."))+name+(order==0?"":("%"+order));
	}

	public ArrayList<Operation> getCode() {
		return code;
	}
	
	@Override
	public ArrayList<String> getDirectives() {
		return directives;
	}

	public ArrayList<Field> getArguments() {
		return args;
	}

	public boolean isLibrary() {
		return library;
	}

	public Element getReturn() {
		return rawReturns;
	}

	public DataType getReturnType() {
		return returns;
	}

	public void setReturnType(DataType returns) {
		this.returns = returns;
	}

	public static interface OnCompile {
		public String compile(SourcePackage pkg, Object... args);
	}
	
	public static interface OnRun {
		public Object run(SourcePackage pkg, Object... args);
	}
} public class FunctionCall {
	public String name;
	public ArrayList<DataType> args;
	public DataType ret;
	public ArrayList<String> dirs;
	public boolean dirsMatter = true;
	
	public boolean isIter = false;
	public ArrayList<DataType> rets;

	public FunctionCall(String name, ArrayList<DataType> args, DataType ret, ArrayList<String> dirs) {
		this.name = name;
		this.args = args;
		this.ret = ret;
		this.dirs = dirs;
	}
	
	public FunctionCall(String name, ArrayList<DataType> args, ArrayList<DataType> rets, ArrayList<String> dirs) {
		this.name = name;
		this.args = args;
		this.isIter = true;
		this.ret = new DataType(true);
		this.rets = rets;
		this.dirs = dirs;
	}
} public class Iterator extends Function {
	public ArrayList<DataType> iterReturns;
	public boolean shouldInline = true;
	
	public Iterator(String name, ArrayList<Field> args, Element returns) {
		super(name, args, returns);
	}
	
	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder(getFullName()+args+" as "+iterReturns);
		if (code!=null) {
			sb.append(". CODE:");
			
			for (Operation op : code) {
				sb.append("\n\t");
				sb.append(op.toString());
			}
		}
		return sb.toString();
	}
	
	public static Iterator libraryIterator(String name, String[] args, Object[] argTypes, Object[] rets) {
		ArrayList<Field> argList = new ArrayList<>();
		ArrayList<DataType> retList = new ArrayList<>();
		
		int i = 0;
		for (String arg : args) {
			Element e = null;
			Field f = new Field(arg, null);
			if (i<argTypes.length) {
				DataType dt = null;
				if (argTypes[i] instanceof TypeDef) {
					dt = new DataType((TypeDef)argTypes[i],false);
				} else if (argTypes[i] instanceof DataType) {
					dt = (DataType) argTypes[i];
				}
				f.setType(dt);
			}
			argList.add(f);
			i++;
		}
		

		Iterator fn = new Iterator(name, argList, null);
		for (Object ret : rets) {
			DataType dt = null;
			if (ret instanceof TypeDef) {
				dt = new DataType((TypeDef)ret,false);
			} else if (ret instanceof DataType) {
				dt = (DataType) ret;
			}
			retList.add(dt);
		}
		fn.iterReturns = retList;
		fn.library = true;
		fn.compiled = true;
		return fn;
	}
} public class TypeDef {
	public static final TypeDef UNKNOWN = new TypeDef();
	public static final TypeDef STRING = new TypeDef("string");
	public static final TypeDef ARRAY = new TypeDef("array");
	public static final TypeDef LIST = new TypeDef("list");
	public static final TypeDef BOOLEAN = new TypeDef("bool");
	public static final TypeDef MAP = new TypeDef("map");
	public static final TypeDef PTR = new TypeDef("ptr");
	public static final TypeDef FPTR = new TypeDef("fptr");
	
	public static final TypeDef INT8 = new TypeDef("int8");
	public static final TypeDef INT16 = new TypeDef("int16");
	public static final TypeDef INT32 = new TypeDef("int32");
	public static final TypeDef INT64 = new TypeDef("int64");
	
	public static final TypeDef REAL32 = new TypeDef("real32");
	public static final TypeDef REAL64 = new TypeDef("real64");
	
	public static final TypeDef REAL = new TypeDef("real", TypeDef.REAL32);
	public static final TypeDef INT = new TypeDef("int", TypeDef.INT32);
	public static final TypeDef CHAR = new TypeDef("char", TypeDef.INT8);
	
	public String name;
	public String pkgName;
	
	public TypeDef parent = null;
	
	public TypeDef() {
		this.name = "?";
	}

	public TypeDef(String name) {
		this(name,TypeDef.UNKNOWN);
	}
	
	public TypeDef(String name, TypeDef parent) {
		this.name = name;
		this.parent = parent;
	}
	
	@Override
	public String toString() {
		return name;
	}
	
	public static TypeDef getCommonParent(TypeDef type1,TypeDef type2) {
		if (type1==null || type2==null) {
			return TypeDef.UNKNOWN;
		}
		if (type1==type2) {
			return type1;
		}
		if (type1.parent==type2) {
			return type2;
		}
		if (type1==type2.parent) {
			return type1;
		}
		return getCommonParent(type1.parent,type2.parent);
	}
	
	public static void addBaseTypes(SourcePackage pkg) {
		pkg.addType(TypeDef.UNKNOWN);
		pkg.addType(TypeDef.INT);
		pkg.addType(TypeDef.REAL);
		pkg.addType(TypeDef.STRING);
		pkg.addType(TypeDef.ARRAY);
		pkg.addType(TypeDef.LIST);
		pkg.addType(TypeDef.CHAR);
		pkg.addType(TypeDef.BOOLEAN);
		pkg.addType(TypeDef.MAP);
		pkg.addType(TypeDef.INT8);
		pkg.addType(TypeDef.INT16);
		pkg.addType(TypeDef.INT32);
		pkg.addType(TypeDef.INT64);
		pkg.addType(TypeDef.REAL32);
		pkg.addType(TypeDef.REAL64);
	}
} public enum TokenRule implements IElementType {
	COMMENT(null,"\\/\\/[^\n]*\n?"),
	SPACE(" ","[\\s]+"),
	RESWORD(null,"(local|function|and|or|not|for|in|as|return|break|struct|if|else|elseif|while|repeat|until|field|import|package|enum|true|false|iterator|new|extends|to|continue|type|class)\\b"),
	WORD("w","[\\w\\?&&[^\\d]][\\w\\?\\.]*"),
	NUMBER("n","[\\d\\.]+"),
	STRING("s","\"(\\\\.|[^\"])*\""),
	CHAR(null,"\'(\\\\.|[^\'])\'"),
	SEP(";",";+"),
	DIRECTIVE("r","@[\\S]*"),
	SYMBOL("y","([\\Q+-*/=<>~:!&|%^\\E]+|\\(|\\)|\\[|\\]|\\{|\\}|,)");
	
	public final String match;
	public String alias;
	
	TokenRule(String alias, String match) {
		this.match = "^"+match;
		this.alias = alias;
	}
	
	TokenRule(String match) {
		this(null,match);
	}
	
	TokenRule() {
		this(null,"[^.]");
	}
	
	public String format(String input) {
		switch (this) {
			case SPACE:
			case COMMENT:
				return null;
			case CHAR:
			case STRING:
				return StringUtils.unescape(input.substring(1, input.length()-1));
			case DIRECTIVE:
				return input.substring(1);
			default:
				return input;
		}
	}
	
	@Override
	public String getAlias() {
		return alias;
	}
} public class Directives {
	public static boolean has(ArrayList<String> dirs, String dir) {
		boolean neg = false;
		if (dir.startsWith("!")) {
			dir = dir.substring(1);
			neg = true;
		}
		int count = 0;
		for (String dir2 : dirs) {
			if (dir2.startsWith(dir)) {
				count++;
			} else if (dir2.startsWith("!"+dir)) {
				count--;
			}
		}
		return neg?count<0:count>0;
	}
	
	public static boolean has(IDirectable obj, String dir) {
		return obj==null ? false : has(obj.getDirectives(),dir);
	}
	
	public static ArrayList<String> getAll(IDirectable obj) {
		return obj.getDirectives();
	}
	
	public static ArrayList<String> getValues(IDirectable obj, String dir) {
		return getValues(obj.getDirectives(),dir);
	}
	
	public static ArrayList<String> getValues(ArrayList<String> dirs, String dir) {
		ArrayList<String> a = new ArrayList<>();
		for (String dir2 : dirs) {
			if (dir2.startsWith(dir+"=")) {
				a.add(dir2.substring(dir2.indexOf("=")+1));
			}
		}
		return a;
	}
} public class Range {
	public final int low;
	public final int high;

	public Range(int low, int high) {
		this.low = Math.min(low, high);
		this.high = Math.max(low, high);
	}

	@Override
	public String toString() {
		return low+"~"+high;
	}
	
	public static Range from(Range r1, Range r2) {
		return new Range(r1==null?0:r1.low,r2==null?0:r2.high);
	}
	
	
} public class SourceDecompiler {
	public static String elementsToString(ArrayList<Element> es) {
		StringBuilder sb = new StringBuilder();
		for (Element e : es) {
			sb.append(elementToString(e));
		}
		return sb.toString();
	}
	
	public static String elementToString(Element e) {
		StringBuilder sb = new StringBuilder();
		for (String dir : e.directives) {
			sb.append("@");
			sb.append(dir);
			sb.append("\n");
		}
		if (e.type instanceof TokenRule) {
			switch ((TokenRule)e.type) {
				case COMMENT:
					sb.append("//");
					sb.append(e.args[0]);
					sb.append("\n");
					break;
				case DIRECTIVE:
					sb.append("@");
					sb.append(e.args[0]);
					break;
				case STRING:
					sb.append("\"");
					sb.append(e.args[0]);
					sb.append("\"");
					break;
				case NUMBER:
				case RESWORD:
				case SYMBOL:
				case WORD:
					sb.append(e.args[0]);
					break;
			}
		} else {
			switch ((Rule)e.type) {
				case PAREN:
					sb.append("(");
					sb.append(elementsToString((ArrayList<Element>) e.args[0]));
					sb.append(")");
					break;
				case INDEX:
					sb.append("[");
					sb.append(elementsToString((ArrayList<Element>) e.args[0]));
					sb.append("]");
					break;
				case CODE:
					sb.append("{\n\t");
					sb.append(elementsToString((ArrayList<Element>) e.args[0]).replace("\n", "\n\t"));
					sb.append("}\n");
					break;
				case GLOBAL_DIR:
					sb.append("@@");
					sb.append(e.args[0]);
					break;
				case FCALL:
					sb.append(e.args[0]);
					sb.append("(");
					sb.append(elementsToString((ArrayList<Element>) e.args[0]));
					sb.append(")");
					break;
				case ICALL:
					sb.append(e.args[0]);
					sb.append("[");
					sb.append(elementsToString((ArrayList<Element>) e.args[1]));
					sb.append("]");
					break;
				case TO:
					sb.append(elementToString((Element) e.args[0]));
					sb.append(" to ");
					sb.append(elementToString((Element) e.args[1]));
					break;
				case EXTEND:
					sb.append(elementToString((Element) e.args[0]));
					sb.append(" extends ");
					sb.append(elementToString((Element) e.args[1]));
					break;
				case CHAIN:
					for (Element e2 :(ArrayList<Element>)e.args[0]) {
						sb.append(elementToString(e2));
						sb.append(".");
					}
					sb.deleteCharAt(sb.length()-1);
					break;
				case TRUE:
					sb.append("true");
					break;
				case FALSE:
					sb.append("false");
					break;
				case NOT:
					sb.append("not ");
					sb.append(elementToString((Element) e.args[0]));
					break;
				case BIT_NOT:
					sb.append("!");
					sb.append(elementToString((Element) e.args[0]));
					break;
				case POW:
					sb.append(elementToString((Element) e.args[0]));
					sb.append("^");
					sb.append(elementToString((Element) e.args[1]));
					break;
				case NEG:
					sb.append("-");
					sb.append(elementToString((Element) e.args[0]));
					break;
				case MUL:
					sb.append(elementToString((Element) e.args[0]));
					sb.append("*");
					sb.append(elementToString((Element) e.args[1]));
					break;
				case DIV:
					sb.append(elementToString((Element) e.args[0]));
					sb.append("/");
					sb.append(elementToString((Element) e.args[1]));
					break;
				case MOD:
					sb.append(elementToString((Element) e.args[0]));
					sb.append("%");
					sb.append(elementToString((Element) e.args[1]));
					break;
				case ADD:
					sb.append(elementToString((Element) e.args[0]));
					sb.append(" + ");
					sb.append(elementToString((Element) e.args[1]));
					break;
				case SUB:
					sb.append(elementToString((Element) e.args[0]));
					sb.append("-");
					sb.append(elementToString((Element) e.args[1]));
					break;
				case CONCAT:
					sb.append(elementToString((Element) e.args[0]));
					sb.append("~");
					sb.append(elementToString((Element) e.args[1]));
					break;
				case BIT_AND:
					sb.append(elementToString((Element) e.args[0]));
					sb.append("&");
					sb.append(elementToString((Element) e.args[1]));
					break;
				case BIT_OR:
					sb.append(elementToString((Element) e.args[0]));
					sb.append("|");
					sb.append(elementToString((Element) e.args[1]));
					break;
				case EQ:
					sb.append(elementToString((Element) e.args[0]));
					sb.append("==");
					sb.append(elementToString((Element) e.args[1]));
					break;
				case NEQ:
					sb.append(elementToString((Element) e.args[0]));
					sb.append("!=");
					sb.append(elementToString((Element) e.args[1]));
					break;
				case LT:
					sb.append(elementToString((Element) e.args[0]));
					sb.append("<");
					sb.append(elementToString((Element) e.args[1]));
					break;
				case GT:
					sb.append(elementToString((Element) e.args[0]));
					sb.append(">");
					sb.append(elementToString((Element) e.args[1]));
					break;
				case LTE:
					sb.append(elementToString((Element) e.args[0]));
					sb.append("<=");
					sb.append(elementToString((Element) e.args[1]));
					break;
				case GTE:
					sb.append(elementToString((Element) e.args[0]));
					sb.append(">=");
					sb.append(elementToString((Element) e.args[1]));
					break;
				case AND:
					sb.append(elementToString((Element) e.args[0]));
					sb.append(" and ");
					sb.append(elementToString((Element) e.args[1]));
					break;
				case OR:
					sb.append(elementToString((Element) e.args[0]));
					sb.append(" or ");
					sb.append(elementToString((Element) e.args[1]));
					break;
				case TUPLE:
					sb.append(tupleToString((ArrayList<Element>) e.args[0]));
					break;
				case LOCAL:
					sb.append("local ");
					sb.append(tupleToString((ArrayList<Element>) e.args[0]));
					sb.append("\n");
					break;
				case LOCAL_ASN:
					sb.append("local ");
					sb.append(tupleToString((ArrayList<Element>) e.args[0]));
					sb.append(" = ");
					sb.append(tupleToString((ArrayList<Element>) e.args[1]));
					sb.append("\n");
					break;
				case FIELD:
					sb.append("field ");
					sb.append(tupleToString((ArrayList<Element>) e.args[0]));
					sb.append("\n\n");
					break;
				case FIELD_ASN:
					sb.append("field ");
					sb.append(tupleToString((ArrayList<Element>) e.args[0]));
					sb.append(" = ");
					sb.append(tupleToString((ArrayList<Element>) e.args[1]));
					sb.append("\n\n");
					break;
//				case REF_INDEX:
//					break;
//				case REF_ICALL:
//					break;
				case ADD_ASN:
					sb.append(elementToString((Element) e.args[0]));
					sb.append(" += ");
					sb.append(elementToString((Element) e.args[1]));
					break;
				case SUB_ASN:
					sb.append(elementToString((Element) e.args[0]));
					sb.append(" -= ");
					sb.append(elementToString((Element) e.args[1]));
					break;
				case MUL_ASN:
					sb.append(elementToString((Element) e.args[0]));
					sb.append(" *= ");
					sb.append(elementToString((Element) e.args[1]));
					break;
				case DIV_ASN:
					sb.append(elementToString((Element) e.args[0]));
					sb.append(" /= ");
					sb.append(elementToString((Element) e.args[1]));
					break;
				case ASSIGN:
					sb.append(elementToString((Element) e.args[0]));
					sb.append(" = ");
					sb.append(elementToString((Element) e.args[1]));
					break;
				case IF:
					sb.append("if ");
					sb.append(elementToString((Element) e.args[0]));
					sb.append("{\n\t");
					sb.append(elementsToString((ArrayList<Element>) e.args[2]).replace("\n", "\n\t"));
					sb.append("\n}\n");
					break;
				case ELSEIF:
					sb.append("elseif ");
					sb.append(elementToString((Element) e.args[0]));
					sb.append(" {\n\t");
					sb.append(elementsToString((ArrayList<Element>) e.args[2]).replace("\n", "\n\t"));
					sb.append("\n}\n");
					break;
				case ELSE:
					sb.append("else {\n\t");
					sb.append(elementsToString((ArrayList<Element>) e.args[2]).replace("\n", "\n\t"));
					sb.append("\n}\n");
					break;
				case FOR:
					sb.append("for ");
					sb.append(elementToString((Element) e.args[0]));
					sb.append(" in ");
					sb.append(elementToString((Element) e.args[1]));
					sb.append(" {\n\t");
					sb.append(elementsToString((ArrayList<Element>) e.args[2]).replace("\n", "\n\t"));
					sb.append("\n}\n");
					break;
				case WHILE:
					sb.append("while ");
					sb.append(elementToString((Element) e.args[0]));
					sb.append(" {\n\t");
					sb.append(elementsToString((ArrayList<Element>) e.args[2]).replace("\n", "\n\t"));
					sb.append("\n}\n");
					break;
				case REPEAT:
					sb.append("");
					sb.append("repeat {\n\t");
					sb.append(elementsToString((ArrayList<Element>) e.args[2]).replace("\n", "\n\t"));
					sb.append("\n} until ");
					sb.append(elementToString((Element) e.args[0]));
					sb.append("\n");
				case RETURN_NULL:
					sb.append("return");
					break;
				case RETURN:
					sb.append("return ");
					sb.append(elementToString((Element) e.args[0]));
					sb.append("\n");
					break;
				case BREAK:
					sb.append("break");
					break;
				case PACKAGE:
					sb.append("package ");
					sb.append(elementToString((Element) e.args[0]));
					sb.append("\n\n");
					break;
				case IMPORT:
					sb.append("import ");
					sb.append(elementToString((Element) e.args[0]));
					sb.append("\n\n");
					break;
				case FUNC:
					sb.append("function ");
					sb.append(e.args[0]);
					sb.append("(");
					sb.append(elementsToString((ArrayList<Element>) e.args[1]));
					sb.append(")");
					if (e.dataType!=null) {
						sb.append(" as ").append(elementToString(e.dataType));
					}
					sb.append(" {\n\t");
					sb.append(elementsToString((ArrayList<Element>) e.args[2]).replace("\n", "\n\t"));
					sb.append("\n}\n\n");
					break;
//				case ITERATOR:
//					break;
//				case STRUCT:
//					break;
//				case STRUCT_EXT:
//					break;
//				case ENUM:
//					break;
			}
		}
		if (e.dataType!=null && e.type!=Rule.FUNC) {
			sb.append(" as ").append(elementToString(e.dataType));
		}
		return sb.toString();
	}

	private static String tupleToString(ArrayList<Element> es) {
		StringBuilder sb = new StringBuilder();
		for (Element e2 : es) {
			sb.append(elementToString(e2));
			sb.append(",");
		}
		sb.deleteCharAt(sb.length()-1);
		return sb.toString();
	}
}
import com.iconmaster.source.compile.Operation.OpType;
import com.iconmaster.source.element.Element;
import com.iconmaster.source.element.Rule;
import com.iconmaster.source.exception.SourceException;
import com.iconmaster.source.exception.SourceUndefinedVariableException;
import com.iconmaster.source.exception.SourceUninitializedVariableException;
import com.iconmaster.source.prototype.Field;
import com.iconmaster.source.prototype.Function;
import com.iconmaster.source.prototype.FunctionCall;
import com.iconmaster.source.prototype.Iterator;
import com.iconmaster.source.prototype.TypeDef;
import com.iconmaster.source.tokenize.TokenRule;
import com.iconmaster.source.util.Directives;
import com.iconmaster.source.util.Range;
import com.iconmaster.source.util.SourceDecompiler;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
			e.retVar = sargs[sarg];
			if (arg instanceof DataType) {
				e.type = (DataType) arg;
			} else if (arg instanceof TypeDef) {
				e.type = new DataType((TypeDef) arg);
			}
			a.add(e);
			sarg++;
		}
		
		DataType rt = null;
		if (ret instanceof DataType) {
			rt = (DataType) ret;
		} else if (ret instanceof TypeDef) {
			rt = new DataType((TypeDef) ret);
		}
		
		return rvalLookup(cd, retVar, new LookupFunction(name, a, rt, new ArrayList<>()));
	}
	
	public static LookupNode getLookupTree(CompileData cd) {
		LookupNode tree = getLookupTree(cd, LookupNode.root(null));
		for (String v : cd.frame.getAllVars()) {
			LookupNode tree2 = LookupNode.addFromFullName(cd,LookupType.VAR, tree, v, v, null, true);
			getLookupTree(cd, tree2);
		} 
		return tree;
	}
	
	public static LookupNode getLookupTree(CompileData cd, LookupNode node) {
		switch (node.type) {
			case ROOT:
				for (Function fn : cd.pkg.getFunctions()) {
					LookupNode tree = LookupNode.addFromFullName(cd,LookupType.FUNC, node, fn, fn.getName(), null, false);
					getLookupTree(cd, tree);
					tree = LookupNode.addFromFullName(cd,LookupType.FUNC, node, fn, fn.pkgName+"."+fn.getName(), null, false);
					getLookupTree(cd, tree);
				}
				for (Field fn : cd.pkg.getFields()) {
					LookupNode tree = LookupNode.addFromFullName(cd,LookupType.GLOBAL, node, fn, fn.getName(), null, false);
					getLookupTree(cd, tree);
					tree = LookupNode.addFromFullName(cd,LookupType.GLOBAL, node, fn, fn.pkgName+"."+fn.getName(), null, false);
					getLookupTree(cd, tree);
				}
				for (TypeDef td : cd.pkg.getTypes()) {
					LookupNode tree = LookupNode.addFromFullName(cd,LookupType.TYPE, node, td, td.name, null, false);
					getLookupTree(cd, tree);
				}
				for (Iterator iter : cd.pkg.getIterators()) {
					LookupNode tree = LookupNode.addFromFullName(cd,LookupType.ITER, node, iter, iter.getName(), null, false);
					getLookupTree(cd, tree);
					tree = LookupNode.addFromFullName(cd,LookupType.ITER, node, iter, iter.pkgName+"."+iter.getName(), null, false);
					getLookupTree(cd, tree);
				}
				break;
			case VAR:
				String varName = (String) node.data;
				DataType type = cd.frame.getVarType(varName);
				if (type!=null) {
					for (Function fn : cd.pkg.getFunctions()) {
						if (fn.getName().startsWith(type.type.name+".")) {
							String methodName = fn.getName();
							methodName = methodName.substring(methodName.indexOf(".")+1);
							LookupNode tree = LookupNode.addFromFullName(cd,LookupType.METHOD, node, fn, methodName, null, false);
							getLookupTree(cd, tree);
						}
					}
					for (Iterator iter : cd.pkg.getIterators()) {
						if (iter.getName().startsWith(type.type.name+".")) {
							String methodName = iter.getName();
							methodName = methodName.substring(methodName.indexOf(".")+1);
							LookupNode tree = LookupNode.addFromFullName(cd,LookupType.ITER, node, iter, methodName, null, false);
							getLookupTree(cd, tree);
						}
					}
				}
				break;
			case TYPE:
				for (Function fn : cd.pkg.getFunctions()) {
					TypeDef td = (TypeDef) node.data;
					while (td!=null) {
						if (fn.getName().startsWith(td.name+".")) {
							String methodName = fn.getName();
							methodName = methodName.substring(td.name.length()+1);
							LookupNode tree = LookupNode.addFromFullName(cd,LookupType.METHOD, node, fn, methodName, null, false);
							getLookupTree(cd, tree);
						}
						td = td.parent;
					}
				}
				
				for (Iterator iter : cd.pkg.getIterators()) {
					TypeDef td = (TypeDef) node.data;
					while (td!=null) {
						if (iter.getName().startsWith(td.name+".")) {
							String methodName = iter.getName();
							methodName = methodName.substring(td.name.length()+1);
							LookupNode tree = LookupNode.addFromFullName(cd,LookupType.MITER, node, iter, methodName, null, false);
							getLookupTree(cd, tree);
						}
						td = td.parent;
					}
				}
				break;
		}
		return node;
	}
	
	public static LookupNode parseArgs(CompileData cd, Object... args) {
		LookupNode tree = LookupNode.root(null);
		LookupNode node = tree;
		for (Object arg : args) {
			if (arg instanceof LookupNode) {
				node.c.add((LookupNode)arg);
				((LookupNode)arg).p = node;
				node = (LookupNode) arg;
			} else if (arg instanceof String) {
				node = LookupNode.addFromFullName(cd, LookupType.RAWSTR, node, arg, (String) arg, null, false);
			} else if (arg instanceof LookupFunction) {
				node = LookupNode.addFromFullName(cd, LookupType.RAWCALL, node, arg, ((LookupFunction) arg).name, null, false);
			} else if (arg instanceof Element) {