public static class LookupFunction {
	public String name;
	public ArrayList<Expression> args;
	public ArrayList<String> dirs;
	public DataType retType;
		
	public boolean index = false;
	public Function fn;

	public LookupFunction(String name, ArrayList<Expression> args, DataType retType, ArrayList<String> dirs) {
		this.name = name;
		this.args = args;
		this.dirs = dirs;
		this.retType = retType;
	}
		
	public LookupFunction(CompileData cd, Range rn, String name, DataType retType, ArrayList<String> dirs, String... args) {
		this.name = name;
		this.dirs = dirs;
		this.retType = retType;
			
		this.args = new ArrayList<>();
		for (String s : args) {
			Expression expr = rvalLookup(cd, cd.frame.newVarName(), rn, s);
			this.args.add(expr);
		}
	}
		
	public FunctionCall toFuncCall() {
		ArrayList<DataType> a = new ArrayList<>();
		for (Expression expr : args) {
			a.add(expr.type);
		}
		return new FunctionCall(name, a, retType, dirs);
	}
		
	public FunctionCall toIterCall() {
		ArrayList<DataType> a = new ArrayList<>();
		for (Expression expr : args) {
			a.add(expr.type);
		}
		ArrayList<DataType> r = new ArrayList<>();
		return new FunctionCall(name, a, r, dirs);
	}
		
	public LookupFunction cloneFunc() {
		LookupFunction fcall = new LookupFunction(name, (ArrayList<Expression>) args.clone(), retType, (ArrayList<String>) dirs.clone());
		fcall.index = index;
		fcall.fn = fn;
		return fcall;
	}
} public static interface CodeTransformer {

	public ArrayList<Operation> transform(SourcePackage pkg, Object workingOn, ArrayList<Operation> code);
} 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 SourceDataTypeException extends SourceException {
	public SourceDataTypeException(Range range, String message) {
		super(range,message);
	}
	
	@Override
	public String getMessage() {
		return super.getMessage();
	}
} 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 SourceSafeModeException extends SourceException {
	public String var;
	
	public SourceSafeModeException(Range range, String message, String var) {
		super(range,message);
		this.var = var;
	}
	
	@Override
	public String getMessage() {
		return super.getMessage();
	}
} public class SourceSyntaxException extends SourceException {
	public SourceSyntaxException(Range range, String message) {
		super(range,message);
	}
	
	@Override
	public String getMessage() {
		return super.getMessage();
	}
} public class SourceUndefinedFunctionException extends SourceException {
	public String var;
	
	public SourceUndefinedFunctionException(Range range, String message, String var) {
		super(range,message);
		this.var = var;
	}
	
	@Override
	public String getMessage() {
		return super.getMessage();
	}
} 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 CustomType extends TypeDef {
	public Element rawType;

	public CustomType(String name, Element type) {
		super(name);
		this.rawType = type;
	}
} 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 ParamTypeDef extends TypeDef {
	public int paramNo;
	public TypeDef baseType;
	
	public ParamTypeDef(String name, int paramNo) {
		this(name,paramNo,TypeDef.UNKNOWN);
	}

	public ParamTypeDef(String name, int paramNo, TypeDef baseType) {
		super(name,baseType);
		this.paramNo = paramNo;
		this.baseType = baseType;
	}
} public class SourcePackage implements IDirectable {	
	protected String name;
	protected ArrayList<Field> fields = new ArrayList<>();
	protected ArrayList<Function> functions = new ArrayList<>();
	protected ArrayList<Import> imports = new ArrayList<>();
	protected ArrayList<TypeDef> types = new ArrayList<>();
	protected ArrayList<Iterator> iters = new ArrayList<>();
	protected ArrayList<CustomType> customTypes = new ArrayList<>();
	private ArrayList<ImportAlias> aliases = new ArrayList<>();
	
	public NameProvider nameProvider = NameProvider.instance;
	
	private ArrayList<String> directives = new ArrayList<>();

	@Override
	public String toString() {
		StringBuilder sb = new StringBuilder("PACKAGE ");
		sb.append(name).append(":\n\tIMPORTS:");
		for (Import imp : imports) {
			sb.append("\n\t\t");
			sb.append(imp);
		}
		sb.append("\n\tTYPES:");
		for (TypeDef t : types) {
			sb.append("\n\t\t");
			sb.append(t.toString().replace("\n", "\n\t"));
		}
		sb.append("\n\tFIELDS:");
		for (Field field : fields) {
			sb.append("\n\t\t");
			sb.append(field.toString().replace("\n", "\n\t"));
		}
		sb.append("\n\tFUNCTIONS:");
		for (Function func : functions) {
			sb.append("\n\t\t");
			sb.append(func.toString().replace("\n", "\n\t"));
		}
		sb.append("\n\tITERATORS:");
		for (Iterator iter : iters) {
			sb.append("\n\t\t");
			sb.append(iter.toString().replace("\n", "\n\t"));
		}
		return sb.toString();
	}
	
	public boolean shouldAdd(ArrayList<String> item) {
		ArrayList<String> defs = Directives.getValues(this, "def");
		ArrayList<String> undefs = Directives.getValues(this, "undef");
		ArrayList<String> ifdefs = Directives.getValues(item, "ifdef");
		ArrayList<String> ifndefs = Directives.getValues(item, "ifndef");
		
		for (String s : undefs) { //hooray for the lazy remove!
			defs.remove(s);
		}
		
		for (String s : ifdefs) {
			if (!defs.contains(s)) {
				return false;
			}
		}
		
		for (String s : ifndefs) {
			if (defs.contains(s)) {
				return false;
			}
		}
		
		return true;
	}
	
	public ArrayList<SourceException> parse(ArrayList<Element> a) {
		ArrayList<SourceException> errors = new ArrayList<>();
		for (Element e : a) {
			if (!shouldAdd(e.directives)) {
				continue;
			}
			switch ((Rule)e.type) {
				case PACKAGE:
					if (name!=null) {
						errors.add(new SourceException(e.range,"Cannot have multiple package declarations"));
					} else {
						name = StringUtils.nameString((Element) e.args[0]);
						if (name == null) {
							errors.add(new SourceException(e.range,"Invalid package name"));
						}
					}
					break;
				case IMPORT:
					String imp = StringUtils.nameString((Element) e.args[0]);
					if (imp == null) {
						errors.add(new SourceException(e.range,"Invalid import package"));
					} else {
						imports.add(new Import(imp, StringUtils.nameString(((Element)e.args[0]).dataType), ((Element)e.args[0]).type==TokenRule.STRING, e.range));
					}
					break;
				case FIELD_ASN:
					int i=0;
					ArrayList<Element> vals = ((ArrayList<Element>) e.args[1]);
					for (Element e2 : (ArrayList<Element>) e.args[0]) {
						Field var = new Field((String)e2.args[0], e2.dataType);
						if (i < vals.size()) {
							var.rawValue = vals.get(i);
						}
						var.getDirectives().addAll(e.directives);
						var.getDirectives().addAll(directives);
						addField(var);
						i++;
					}
					break;
				case FIELD:
					for (Element e2 : (ArrayList<Element>) e.args[0]) {
						Field var = new Field((String)e2.args[0], e2.dataType);
						var.getDirectives().addAll(e.directives);
						var.getDirectives().addAll(directives);
						addField(var);
					}
					break;
				case ITERATOR:
				case FUNC:
					String fname = (String) e.args[0];
					ArrayList<Field> args = new ArrayList<>();
					Element rets = e.dataType;
					for (Element e2 : (ArrayList<Element>) e.args[1]) {
						if (e2.args[0] instanceof ArrayList) {
							for (Element e3 : (ArrayList<Element>) e2.args[0]) {
								args.add(new Field((String)e3.args[0], e3.dataType));
							}
						} else {
							args.add(new Field((String)e2.args[0], e2.dataType));
						}
					}
					
					Function fn;
					if (e.type==Rule.ITERATOR) {
						fn = new Iterator(fname, args,rets);
					} else {
						fn = new Function(fname,args,rets);
					}
					
					fn.getDirectives().addAll(e.directives);
					fn.getDirectives().addAll(directives);
					fn.rawCode = (ArrayList<Element>) e.args[2];
					
					if (e.args[3]!=null) {
						ArrayList<Element> es = (ArrayList<Element>) e.args[3];
						if (es.size()>0 && es.get(0).type==Rule.TUPLE) {
							es = (ArrayList<Element>) es.get(0).args[0];
						}
						fn.rawParams = new ArrayList<>();
						for (Element e2 : es) {
							Field param = new Field((String) e2.args[0], e2.dataType);
							fn.rawParams.add(param);
						}
					}
					
					if (e.type==Rule.ITERATOR) {
						addIterator((Iterator)fn);
					} else {
						addFunction(fn);
					}
					
					if (Directives.has(fn, "get") || Directives.has(fn, "set")) {
						if (this.getField(fn.getName())==null) {
							Field f = new Field(fn.getName(), fn.getReturn());
							f.pkgName = fn.pkgName;
							this.addField(f);
						}
					}
					break;
				case GLOBAL_DIR:
					String s = (String) e.args[0];
					directives.add(s.substring(1));
					break;
				case TYPE_EXT:
					Element typeExtend = (Element) e.args[1];
					String typeName = (String) e.args[0];
					ArrayList<Element> code = (ArrayList<Element>) e.args[2];
					errors.addAll(parseType(code, typeName, typeExtend));
					break;
				case TYPE:
					typeName = (String) e.args[0];
					code = (ArrayList<Element>) e.args[2];
					errors.addAll(parseType(code, typeName, null));
					break;
			}
		}
		return errors;
	}
	
	public ArrayList<SourceException> parseType(ArrayList<Element> code, String name, Element extend) {
		ArrayList<SourceException> errs = new ArrayList<>();
		
		CustomType td = new CustomType(name, extend);
		this.addCustomType(td);
		
		for (Element e : code) {
			if (e.type instanceof Rule) {
				switch ((Rule)e.type) {
					case ITERATOR:
					case FUNC:
						String fname = name+"."+(String) e.args[0];
						ArrayList<Field> args = new ArrayList<>();
						if (!Directives.has(e, "static")) {
							Field f = new Field("this", null);
							f.setType(new DataType(td));
							args.add(f);
						}
						Element rets = e.dataType;
						for (Element e2 : (ArrayList<Element>) e.args[1]) {
							if (e2.args[0] instanceof ArrayList) {
								for (Element e3 : (ArrayList<Element>) e2.args[0]) {
									args.add(new Field((String)e3.args[0], e3.dataType));
								}
							} else {
								args.add(new Field((String)e2.args[0], e2.dataType));
							}
						}

						Function fn;
						if (e.type==Rule.ITERATOR) {
							fn = new Iterator(fname, args,rets);
						} else {
							fn = new Function(fname,args,rets);
						}

						fn.getDirectives().addAll(e.directives);
						fn.getDirectives().addAll(directives);
						fn.rawCode = (ArrayList<Element>) e.args[2];

						if (e.args[3]!=null) {
							ArrayList<Element> es = (ArrayList<Element>) e.args[3];
							if (es.size()>0 && es.get(0).type==Rule.TUPLE) {
								es = (ArrayList<Element>) es.get(0).args[0];
							}
							fn.rawParams = new ArrayList<>();
							for (Element e2 : es) {
								Field param = new Field((String) e2.args[0], e2.dataType);
								fn.rawParams.add(param);
							}
						}

						if (e.type==Rule.ITERATOR) {
							addIterator((Iterator)fn);
						} else {
							addFunction(fn);
						}

						if (Directives.has(fn, "get") || Directives.has(fn, "set")) {
							if (this.getField(fn.getName())==null) {
								Field f = new Field(fn.getName(), fn.getReturn());
								f.pkgName = fn.pkgName;
								this.addField(f);
							}
						}
						break;
				}
			}
		}
		
		return errs;
	}
	
	public void addContents(SourcePackage other) {
		if (other==null) {
			return;
		}
		
		fields.addAll(other.fields);
		functions.addAll(other.functions);
		imports.addAll(other.imports);
		types.addAll(other.types);
		customTypes.addAll(other.customTypes);
		iters.addAll(other.iters);
		directives.addAll(other.directives);
		aliases.addAll(other.aliases);
	}
	
	public void addFunction(Function fn) {
		fn.pkgName = this.getName();
		fn.order = getFunctions(fn.name).size();
		functions.add(fn);
	}
	
	public void addField(Field f) {
		f.pkgName = this.getName();
		fields.add(f);
	}

	public String getName() {
		if (name==null) {
			name = nameProvider.getTempName();
		}
		return name;
	}

	public ArrayList<Import> getImports() {
		return imports;
	}

	public ArrayList<Function> getFunctions() {
		return functions;
	}
	
	public ArrayList<Function> getFunctionsAndIterators() {
		ArrayList<Function> a = new ArrayList<>();
		a.addAll(functions);
		a.addAll(iters);
		return a;
	}
	
	public ArrayList<Function> getFunctions(String name) {
		ArrayList<Function> a = new ArrayList<>();
		for (Function v : functions) {
			if (v.getName().equals(name) || (v.pkgName+"."+v.getName()).equals(name)) {
				a.add(v);
			}
		}
		return a;
	}
	
	public ArrayList<Field> getFields() {
		return fields;
	}
	
	public Field getField(String name) {
		for (Field v : fields) {
			if (v.getName().equals(name) || (v.pkgName+"."+v.getName()).equals(name)) {
				return v;
			}
		}
		return null;
	}

	public Function getFunction(String name) {
		for (Function v : functions) {
			if (v.getName().equals(name) || (v.pkgName+"."+v.getName()).equals(name) || (v.pkgName+"."+v.getName()+"%"+v.order).equals(name) || (v.getName()+"%"+v.order).equals(name)) {
				return v;
			}
		}
		return null;
	}
	
	public boolean isFunctionCallCompatible(Function v, FunctionCall call) {
		if (call.args.size()==v.args.size()) {
			boolean dirsMatch = true;
			if (call.dirsMatter) {
				for (String dir : call.dirs) {
					if (!Directives.has(v, dir)) {
						dirsMatch = false;
						break;
					}
				}
			}
			if (dirsMatch) {
				int i = 0;
				boolean argsMatch = true;
				for (Field arg : v.args) {
					if (call.args.get(i)!= null && !DataType.canCastTo(arg.getType(), call.args.get(i))) {
						argsMatch = false;
						break;
					}
					i++;
				}
				if (argsMatch) {
					if (call.isIter) {
						return true;
					} else {
						return true;
					}
				}
			}
		}
		if (call.dirsMatter) {
			call.dirsMatter = false;
			return isFunctionCallCompatible(v, call);
		}
		return false;
	}
	
	public Function getFunction(String name, FunctionCall call) {
		ArrayList fns = call.isIter?iters:functions;
		for (Object rawv : fns) {
			Function v = (Function) rawv;
			if (v.getName().equals(name) || (v.pkgName+"."+v.getName()).equals(name) || (v.pkgName+"."+v.getName()+"%"+v.order).equals(name) || (v.getName()+"%"+v.order).equals(name)) {
				if (isFunctionCallCompatible(v, call)) {
					return v;
				}
			}
		}
		return null;
	}

	public ArrayList<TypeDef> getTypes() {
		return types;
	}
	
	public void addType(TypeDef def) {
		def.pkgName = this.getName();
		types.add(def);
	}
	
	public TypeDef getType(String name) {
		for (TypeDef v : types) {
			if (v.name.equals(name) || (v.pkgName+"."+v.name).equals(name)) {
				return v;
			}
		}
		return null;
	}
	
	public ArrayList<CustomType> getCustomTypes() {
		return customTypes;
	}
	
	public void addCustomType(CustomType def) {
		def.pkgName = this.getName();
		customTypes.add(def);
		types.add(def);
	}
	
	public TypeDef getCustomType(String name) {
		for (CustomType v : customTypes) {
			if (v.name.equals(name) || (v.pkgName+"."+v.name).equals(name)) {
				return v;
			}
		}
		return null;
	}

	@Override
	public ArrayList<String> getDirectives() {
		return directives;
	}
	
	public ArrayList<Iterator> getIterators(String name) {
		ArrayList<Iterator> a = new ArrayList<>();
		for (Iterator v : iters) {
			if (v.getName().equals(name) || (v.pkgName+"."+v.getName()).equals(name)) {
				a.add(v);
			}
		}
		return a;
	}
	
	public ArrayList<Iterator> getIterators() {
		return iters;
	}
	
	public void addIterator(Iterator def) {
		def.pkgName = this.getName();
		def.order = getIterators(def.name).size();
		iters.add(def);
	}
	
	public Iterator getIterator(String name) {
		for (Iterator v : iters) {
			if (v.getName().equals(name) || (v.pkgName+"."+v.getName()).equals(name) || (v.pkgName+"."+v.getName()+"%"+v.order).equals(name) || (v.getName()+"%"+v.order).equals(name)) {
				return v;
			}
		}
		return null;
	}
	
	public void setName(String name) {
		this.name = name;
	}

	public ArrayList<ImportAlias> getAliases() {
		return aliases;
	}
}  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 interface IDirectable {
	public ArrayList<String> getDirectives();
} public class StringUtils {
	public static String nameString(Element e) {
		if (e==null) {
			return null;
		} else if (e.type==TokenRule.WORD || e.type==TokenRule.STRING) {
			return (String) e.args[0];
		} else if (e.type == Rule.CHAIN) {
			String str = null;
			for (Element e2 : (ArrayList<Element>)e.args[0]) {
				if (e2.type==TokenRule.WORD) {
					if (str==null) {
						str = (String) e2.args[0];
					} else {
						str += "." + (String) e2.args[0];
					}
				}
			}
			return str;
		}
		return null;
	}
	
	public static boolean isReal(String n) {
		return n.contains(".");
	}
	
	public static String unescape(String s) {
		String out = "";
		boolean esc = false;
		for (char c : s.toCharArray()) {
			if (esc) {
				if (c=='n') {
					out += '\n';
				} else if (c=='t') {
					out += '\t';
				} else {
					out += c;
				}
				esc = false;
			} else {
				if (c=='\\') {
					esc = true;
				} else {
					out += c;
				}
			}
		}
		return out;
	}
	
	public static String mathElementToString(Element e) {
		if (e.type instanceof Rule) {
			switch ((Rule)e.type) {
				case ADD:
					return "_add";
				case SUB:
					return "_sub";
				case MUL:
					return "_mul";
				case DIV:
					return "_div";
				case POW:
					return "_pow";
				case MOD:
					return "_mod";
				case CONCAT:
					return "_concat";
				case EQ:
					return "_eq";
				case NEQ:
					return "_neq";
				case LT:
					return "_lt";
				case GT:
					return "_gt";
				case LTE:
					return "_lte";
				case GTE:
					return "_gte";
				case AND:
					return "_and";
				case OR:
					return "_or";
				case BIT_AND:
					return "_band";
				case BIT_OR:
					return "_bor";
				case SLL:
					return "_sll";
				case SRL:
					return "_srl";
				case SRA:
					return "_sra";
			}
		}
		return null;
	}
}
import com.iconmaster.source.compile.CompileLookup.LookupFunction;
import com.iconmaster.source.compile.CompileUtils.CodeTransformer;
import com.iconmaster.source.compile.Operation.OpType;
import com.iconmaster.source.element.Element;
import com.iconmaster.source.element.Rule;
import com.iconmaster.source.exception.SourceDataTypeException;
import com.iconmaster.source.exception.SourceException;
import com.iconmaster.source.exception.SourceSafeModeException;
import com.iconmaster.source.exception.SourceSyntaxException;
import com.iconmaster.source.exception.SourceUndefinedFunctionException;
import com.iconmaster.source.exception.SourceUndefinedVariableException;
import com.iconmaster.source.prototype.CustomType;
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.ParamTypeDef;
import com.iconmaster.source.prototype.SourcePackage;
import com.iconmaster.source.prototype.TypeDef;
import com.iconmaster.source.tokenize.TokenRule;
import com.iconmaster.source.util.Directives;
import com.iconmaster.source.util.IDirectable;
import com.iconmaster.source.util.StringUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
					}
					for (Element e2 : es) {
						DataType param = compileDataType(cd, e2);
						pList.add(param);
					}
					dt.params = pList.toArray(dt.params);
					break;
				default:
					cd.errs.add(new SourceSyntaxException(e.range, "Illegal data type format"));
			}
		}
		return dt;
	}
	
	public static ArrayList<DataType> compileIteratorReturnType(CompileData cd, Element e) {
		ArrayList<DataType> a = new ArrayList<>();
		if (e.type==Rule.TUPLE || e.type==Rule.PAREN) {
			for (Element e2 : (ArrayList<Element>) e.args[0]) {
				a.addAll(compileIteratorReturnType(cd, e2));
			}
		} else {
			a.add(compileDataType(cd, e));
		}
		return a;
	}
	
	public static Expression resolveLValue(CompileData cd, Expression code, Element e, DataType setType) {
		Expression expr = new Expression();
		String name;
		if (e.type instanceof TokenRule) {
			switch ((TokenRule)e.type) {
				case WORD:
					name = (String) e.args[0];
					if (cd.pkg.getField(name)!=null) {
						
					} else if (!cd.frame.isDefined(name)) {
						cd.errs.add(new SourceUndefinedVariableException(e.range,"Variable "+name+" not declared local", name));
					} else if (cd.frame.isConst(name) && cd.frame.getVariable(name)) {
						cd.errs.add(new SourceSafeModeException(e.range,"Cannot assign a new value to constant "+name, name));
					} else if (!cd.frame.getVariable(name)) {
						cd.frame.putVariable(name);
					}
					expr.retVar = name;
					expr.type = cd.frame.getVarType(name);
					break;
				default:
					cd.errs.add(new SourceSyntaxException(e.range,"Illegal L-value"));
			}
		} else {
			switch ((Rule)e.type) {
				case ICALL_REF:
				case ICALL:
					ArrayList<String> names = new ArrayList<>();
					ArrayList<Expression> exprs = new ArrayList<>();
					ArrayList<Element> es = (ArrayList<Element>) e.args[1];
					if (es.size()==1 && es.get(0).type==Rule.TUPLE) {
						es = (ArrayList<Element>) es.get(0).args[0];
					}
					for (Element e2 : es) {
						String name2 = cd.frame.newVarName();
						Expression expr2 = compileExpr(cd, name2, e2);
						exprs.add(expr2);
						names.add(name2);
					}
					
					Expression listExpr;
					if (e.type==Rule.ICALL) {
						Element listE = new Element(e.range, TokenRule.WORD);
						listE.args[0] = e.args[0];
						listExpr = resolveLValue(cd, expr, listE, new DataType(TypeDef.UNKNOWN));
					} else {
						Expression expr2 = SourceCompiler.compileExpr(cd, cd.frame.newVarName(), (Element) e.args[0]);
						code.addAll(expr2);
						listExpr = new Expression();
						listExpr.retVar = expr2.retVar;
						listExpr.type = expr2.type;
					}

					if (listExpr.type==null) {
						listExpr.type = new DataType(true);
					}
					
					expr.retVar = cd.pkg.nameProvider.getTempName();
					
					ArrayList<Expression> arga = new ArrayList<>();
					arga.add(listExpr);
					Expression ex = new Expression();
					ex.type = setType;
					ex.retVar = expr.retVar;
					arga.add(ex);
					for (Expression expr3 : exprs) {
						arga.add(expr3);
					}
					Expression rfn = CompileLookup.rvalLookup(cd, cd.frame.newVarName(), new LookupFunction(listExpr.type.type.name+"._setindex", arga, null, e.directives));
					
					expr.addAll(rfn);
					expr.addAll(listExpr);
					break;
				default:
					cd.errs.add(new SourceSyntaxException(e.range,"Illegal L-value"));
			}
		}
		return expr;
	}
	
	public static String resolveLValueRaw(CompileData cd, Element e) {
		String name = null;
		if (e.type instanceof TokenRule) {
			switch ((TokenRule)e.type) {
				case WORD:
					name = (String) e.args[0];
					break;
			}
		} else {
			switch ((Rule)e.type) {
			}
		}
		return name;
	}

	public static CodeTransformer fnInliner = (pkg, work, code) -> {