/* * This file is a part of Alchemy OS project. * Copyright (C) 2011-2014, Sergey Basalaev <sbasalaev@gmail.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package alchemy.nec; import alchemy.nec.opt.ConstOptimizer; import alchemy.fs.Filesystem; import alchemy.io.ConnectionInputStream; import alchemy.io.IO; import alchemy.io.UTFReader; import alchemy.nec.syntax.*; import alchemy.nec.syntax.expr.*; import alchemy.nec.syntax.statement.*; import alchemy.nec.syntax.type.*; import alchemy.types.*; import alchemy.util.ArrayList; import alchemy.util.HashMap; import alchemy.util.Strings; import java.io.ByteArrayInputStream; import java.io.IOException; /** * Parser for Ether language revision 2.2. * @author Sergey Basalaev */ public class Parser { private final CompilerEnv env; private final ConstOptimizer constOptimizer; private final FlowAnalyzer flowAnalyzer; private Unit unit; /** Set of all files that were already parsed. */ private ArrayList finishedFiles = new ArrayList(); /** Stack of files we are currently parsing. */ private ArrayList files = new ArrayList(); /** Current tokenizer */ private Tokenizer t; public Parser(CompilerEnv env) { this.env = env; this.constOptimizer = new ConstOptimizer(env); this.flowAnalyzer = new FlowAnalyzer(env); } public Unit parseUnit(String file) { Unit u = new Unit(); this.unit = u; try { u.addType(BuiltinType.ANY); u.addType(BuiltinType.ARRAY); u.addType(BuiltinType.BOOL); u.addType(BuiltinType.BYTE); u.addType(BuiltinType.CHAR); u.addType(BuiltinType.DOUBLE); u.addType(BuiltinType.ERROR); u.addType(BuiltinType.FLOAT); u.addType(BuiltinType.FUNCTION); u.addType(BuiltinType.INT); u.addType(BuiltinType.LONG); u.addType(BuiltinType.SHORT); u.addType(BuiltinType.STRING); parseFile("/inc/builtin.eh"); if (env.hasOption(CompilerEnv.F_COMPAT21)) { if (Filesystem.exists("/inc/compat/")) { env.io.setEnv("INCPATH", "/inc/compat:" + env.io.getEnv("INCPATH")); } else { env.warn("/inc/compat", 1, CompilerEnv.W_ERROR, "Compatibility headers are not installed."); return null; } } parseFile(file); } catch (IOException ioe) { IO.println(env.io.stderr, "I/O error while reading " + files.last() + ": " + ioe.getMessage()); } catch (ParseException pe) { warn(CompilerEnv.W_ERROR, pe.getMessage()); } catch (Exception e) { // bug in compiler String details = "At: " + files.last() + ':' + t.lineNumber() + "\nLast token: " + t; env.exceptionHappened("Parser", details, e); } return unit; } /** * Finds file referenced in 'use' directive. * Checked files are (in that order): * ./name * ./name.eh * $INCPATH/name * $INCPATH/name.eh */ private String resolveFile(String name) throws ParseException { if (name.length() == 0) { throw new ParseException("Empty string in 'use'"); } String f = env.io.toFile(name); if (Filesystem.exists(f) && !Filesystem.isDirectory(f)) return f; f = env.io.toFile(name+".eh"); if (Filesystem.exists(f)) return f; if (name.charAt(0) != '/') { String[] incpath = Strings.split(env.io.getEnv("INCPATH"), ':', true); for (int i=0; i<incpath.length; i++) { f = env.io.toFile(incpath[i]+'/'+name); if (Filesystem.exists(f) && !Filesystem.isDirectory(f)) return f; f = env.io.toFile(incpath[i]+'/'+name+".eh"); if (Filesystem.exists(f)) return f; } } throw new ParseException("File not found: "+name); } private void parseFile(String file) throws ParseException, IOException { // do nothing if this file was already processed if (finishedFiles.contains(file)) return; // if file is already in stack we have cyclic inclusion if (files.contains(file)) { StringBuffer sb = new StringBuffer("Cyclic inclusion"); for (int i=0; i<files.size(); i++) { sb.append("\n from ").append(files.get(i)); } throw new ParseException(sb.toString()); } //push file into stack and set fields Tokenizer oldt = t; String olddir = env.io.getCurrentDirectory(); files.add(file); env.io.setCurrentDirectory(Filesystem.fileParent(file)); //read file ConnectionInputStream filein = new ConnectionInputStream(Filesystem.read(file)); env.io.addConnection(filein); ByteArrayInputStream in = new ByteArrayInputStream(IO.readFully(filein)); filein.close(); env.io.removeConnection(filein); t = new Tokenizer(env, file, new UTFReader(in)); // do parsing while (t.nextToken() != Token.EOF) { switch (t.ttype) { case ';': // skip those break; case Token.TYPE: { parseTypeDef(); break; } case Token.USE: { // include another file if (t.nextToken() != Token.QUOTED) throw new ParseException("String literal expected after 'use'"); parseFile(resolveFile(t.svalue)); } break; case Token.DEF: { // parse function // parse definition and compare with previous definitions Function func = parseFunctionDef(unit); Var fvar = unit.getVar(func.signature); if (fvar == null) { fvar = new Var(func.signature, func.type); fvar.isConstant = true; fvar.defaultValue = func; unit.addVar(fvar); } else if (!fvar.type.equals(func.type)) { if (fvar.type.kind == Type.TYPE_FUNCTION) throw new ParseException("Definition of function "+func.signature+" conflicts with previous definition."); else throw new ParseException("Variable "+func.signature+" is already defined"); } Function oldfunc = (Function) fvar.defaultValue; oldfunc.args = func.args; func = oldfunc; if (func.isConstructor) { // also create .<init>(), it may be used by subclasses Type owner = func.type.returnType; String initName = owner + ".<init>"; Function initFunc; Var initVar = unit.getVar(initName); if (initVar == null) { initFunc = new Function(unit, owner + ".<init>"); initFunc.isPublic = true; initFunc.args = new Var[func.args.length+1]; initFunc.args[0] = new Var("this", owner); System.arraycopy(func.args, 0, initFunc.args, 1, func.args.length); Type[] argtypes = new Type[initFunc.args.length]; for (int i=0; i<initFunc.args.length; i++) { argtypes[i] = initFunc.args[i].type; } initFunc.type = new FunctionType(BuiltinType.NONE, argtypes); initVar = new Var(initFunc.signature, initFunc.type); initVar.isConstant = true; initVar.defaultValue = initFunc; unit.addVar(initVar); } else { initFunc = (Function) initVar.defaultValue; System.arraycopy(func.args, 0, initFunc.args, 1, func.args.length); } // parse body int next = t.nextToken(); t.pushBack(); if (next == '=' || next == '{') { // generate .new() and parse .init() if (func.body != null) throw new ParseException("Function " + func.signature + " is already implemented"); if (!(owner instanceof ObjectType)) throw new ParseException("Cannot create constructor of " + owner); initFunc.source = func.source = Filesystem.fileName((String)files.last()); unit.implementedFunctions.add(func); unit.implementedFunctions.add(initFunc); func.hits++; initFunc.hits++; func.body = generateConstructor((ObjectType)owner, func, initFunc); initFunc.body = parseInitBody(owner, initFunc); // add return if missing if (flowAnalyzer.visitFunction(initFunc) == flowAnalyzer.NEXT) { BlockStatement block = new BlockStatement(initFunc); block.statements.add(initFunc.body); block.statements.add(new ReturnStatement( new ConstExpr(t.lineNumber(), BuiltinType.NULL, Null.NULL))); initFunc.body = block; } } } else { // parse body int next = t.nextToken(); t.pushBack(); if (next == '=' || next == '{') { if (func.body != null) throw new ParseException("Function " + func.signature + " is already implemented"); func.body = parseFunctionBody(func); func.hits++; func.source = Filesystem.fileName((String)files.last()); unit.implementedFunctions.add(func); // add return if missing if (flowAnalyzer.visitFunction(func) == flowAnalyzer.NEXT) { if (func.type.returnType == BuiltinType.NONE) { BlockStatement block = new BlockStatement(func); block.statements.add(func.body); block.statements.add(new ReturnStatement( new ConstExpr(t.lineNumber(), BuiltinType.NULL, Null.NULL))); func.body = block; } else if (func.body.kind == Statement.STAT_BLOCK) { BlockStatement block = (BlockStatement) func.body; if (block.statements.size() > 0) { Statement last = (Statement) block.statements.last(); if (last.kind == Statement.STAT_EXPR) { Expr expr = cast(((ExprStatement)last).expr, func.type.returnType); if (!env.hasOption(CompilerEnv.F_COMPAT21)) { warn(CompilerEnv.W_RETURN, "'return' is not stated explicitly"); } block.statements.set(block.statements.size()-1, new ReturnStatement(expr)); } else { warn(CompilerEnv.W_ERROR, "Missing return statement"); } } } else { warn(CompilerEnv.W_ERROR, "Missing return statement"); } } } } break; } case Token.VAR: case Token.CONST: { // parse global variable or constant boolean isConst = t.ttype == Token.CONST; if (t.nextToken() != Token.WORD) throw new ParseException("Variable name expected"); String varname = t.svalue; if (unit.getVar(varname) != null) warn(CompilerEnv.W_ERROR, "Variable " + varname + " is already defined"); Type vartype = null; Expr varvalue = null; // parsing type if (t.nextToken() == ':') { vartype = parseType(unit); } else { t.pushBack(); } // parsing value if (t.nextToken() == '=') { varvalue = (Expr) parseExpr(unit).accept(constOptimizer, unit); if (varvalue.kind != Expr.EXPR_CONST) throw new ParseException("Constant expression expected"); if (vartype == null) { vartype = varvalue.returnType(); if (vartype == BuiltinType.NULL) vartype = BuiltinType.ANY; } else { varvalue = (Expr)cast(varvalue, vartype).accept(constOptimizer, unit); } } else { t.pushBack(); } // defining variable if (vartype == null) { throw new ParseException("Type of "+varname+" is not defined"); } Var v = new Var(varname, vartype); if (isConst) { v.isConstant = true; if (varvalue == null) { throw new ParseException("Constant "+varname+" is not initialized"); } } if (varvalue != null) { v.defaultValue = ((ConstExpr)varvalue).value; } else { v.defaultValue = defaultValue(vartype); } unit.addVar(v); break; } default: throw new ParseException(t.toString() + " unexpected here"); } } // pop file from stack and revert fields in.close(); t = oldt; env.io.setCurrentDirectory(olddir); finishedFiles.add(files.last()); files.remove(-1); } private void parseTypeDef() throws IOException, ParseException { // reading name and parent of type if (t.nextToken() != Token.WORD) throw new ParseException("Type name expected after 'type'"); String typename = t.svalue; Type parent; if (t.nextToken() == '<') { if (t.nextToken() != Token.WORD) throw new ParseException("Type name expected after '<'"); String parentName = t.svalue; parent = unit.getType(parentName); if (parent == null) throw new ParseException("Type " + parentName + " is not defined"); } else { t.pushBack(); parent = BuiltinType.ANY; } ObjectType newType; if (parent == BuiltinType.ANY) { newType = new ObjectType(typename, null); } else if (parent instanceof ObjectType) { newType = new ObjectType(typename, (ObjectType)parent); } else { throw new ParseException("Cannot create subtype of " + parent); } Type prevType = unit.getType(typename); if (prevType != null) { if (!(prevType instanceof ObjectType) || !parent.equals(prevType.superType())) throw new ParseException("Definition of " + typename + " conflicts with previous definition"); newType = (ObjectType)prevType; } else { unit.addType(newType); } if (t.nextToken() == '{') { if (newType.fields != null) throw new ParseException("Type " + typename + " is already defined"); ArrayList fields = new ArrayList(); ArrayList fieldNames = new ArrayList(); if (newType.parent != null) { Var[] parentFields = newType.parent.fields; if (parentFields == null) throw new ParseException("Cannot extend " + newType.parent + " since its structure is not known"); for (int i=0; i<parentFields.length; i++) { fields.add(parentFields[i]); fieldNames.add(parentFields[i].name); } } boolean first = true; while (t.nextToken() != '}') { t.pushBack(); if (first) first = false; else expect(','); if (t.nextToken() != Token.WORD) throw new ParseException("Field name expected, got " + t); String fieldName = t.svalue; if (fieldNames.contains(t.svalue)) throw new ParseException("Field " + typename + '.' + fieldName + " is already defined"); expect(':'); Type fieldType = parseType(unit); Object fieldValue = null; if (t.nextToken() == '=') { Expr defExpr = (Expr) cast(parseExpr(unit), fieldType).accept(constOptimizer, unit); if (defExpr.kind != Expr.EXPR_CONST) { warn(CompilerEnv.W_ERROR, "Constant expression expected"); } else { fieldValue = ((ConstExpr)defExpr).value; } } else { t.pushBack(); fieldValue = defaultValue(fieldType); } Var field = new Var(fieldName, fieldType); field.defaultValue = fieldValue; fields.add(field); fieldNames.add(fieldName); } newType.fields = new Var[fields.size()]; fields.copyInto(newType.fields); } else { t.pushBack(); } } private Function parseFunctionDef(Scope scope) throws IOException, ParseException { // parse name if (t.nextToken() != Token.WORD && t.ttype != Token.THROW) throw new ParseException("Function name expected, got "+t); String sig = t.svalue; Type owner = null; boolean isConstructor = false; if (t.nextToken() == '.') { owner = scope.getType(sig); if (owner == null) { warn(CompilerEnv.W_ERROR, "Type " + sig + " is not defined"); owner = BuiltinType.ANY; } if (t.nextToken() == Token.NEW) { isConstructor = true; } else if (t.ttype != Token.WORD) { throw new ParseException("Function name expected, got "+t); } sig = owner.name + '.' + t.svalue; } else { t.pushBack(); } Function func = new Function(scope, sig); func.isPublic = true; func.isConstructor = isConstructor; // parse argument list expect('('); ArrayList arglist = new ArrayList(); ArrayList names = new ArrayList(); if (owner != null && !isConstructor) { arglist.add(new Var("this", owner)); names.add("this"); } boolean first = true; boolean defaults = false; while (t.nextToken() != ')') { t.pushBack(); if (first) first = false; else expect(','); if (t.nextToken() != Token.WORD) throw new ParseException("Variable name expected, got "+t); String varname = t.svalue; if (names.contains(varname)) warn(CompilerEnv.W_ERROR, "Variable " + varname + " is already defined"); names.add(varname); expect(':'); Type vartype = parseType(func); Var var = new Var(varname, vartype); arglist.add(var); if (t.nextToken() == '=') { defaults = true; Expr expr = (Expr) cast(parseExpr(scope), vartype).accept(constOptimizer, scope); if (expr instanceof ConstExpr) { var.defaultValue = ((ConstExpr)expr).value; } else { throw new ParseException("Constant expression expected"); } } else if (defaults) { throw new ParseException("No default provided for argument " + varname); } else { t.pushBack(); } } // parse return type Type rettype; if (t.nextToken() == ':') { rettype = parseType(func); } else { t.pushBack(); rettype = BuiltinType.NONE; } if (func.isConstructor) { if (rettype == BuiltinType.NONE) { rettype = owner; } else if (!rettype.equals(owner)) { warn(CompilerEnv.W_ERROR, "Constructor returns value other than " + owner); } } // fill function fields Var[] args = new Var[arglist.size()]; arglist.copyInto(args); func.args = args; FunctionType ftype = new FunctionType(rettype, new Type[args.length]); for (int i=args.length-1; i>=0; i--) { ftype.argtypes[i] = args[i].type; } func.type = ftype; // semantic checks on main function if (sig.equals("main")) { if (args.length != 1) { warn(CompilerEnv.W_MAIN, "main() arguments are incorrect, should be ([String])"); } else { Type argtype = args[0].type; Type shouldbe = new ArrayType(BuiltinType.STRING); if (!argtype.equals(shouldbe)) { warn(CompilerEnv.W_MAIN, "main() arguments are incorrect, should be ([String])"); } } if (rettype != BuiltinType.INT && rettype != BuiltinType.NONE) { warn(CompilerEnv.W_MAIN, "main() return value is incorrect, should be Int or <none>"); } } // semantic checks on certain methods if (owner != null) { String methodname = sig.substring(sig.lastIndexOf('.')+1); if (methodname.equals("eq") && (rettype != BuiltinType.BOOL || args.length != 2 || !ftype.argtypes[1].equals(owner))) warn(CompilerEnv.W_OVERRIDES, "Method " + sig + " cannot be used as override for equality operators"); else if (methodname.equals("cmp") && (rettype != BuiltinType.INT || args.length != 2 || !ftype.argtypes[1].equals(owner))) warn(CompilerEnv.W_OVERRIDES, "Method " + sig + " cannot be used as override for comparison operators"); else if (methodname.equals("tostr") && (rettype != BuiltinType.STRING || args.length != 1)) warn(CompilerEnv.W_OVERRIDES, "Method " + sig + " cannot be used for conversion to a String"); } return func; } /** * Generates body of .new() method. * Body has the following structure: * <pre> * def Type.new(...): Type { * Type this = new Type { } * this.<init>(...) * return this * } * </pre> */ private Statement generateConstructor(ObjectType owner, Function newFunc, Function initFunc) throws ParseException { if (owner.fields == null) throw new ParseException("Cannot create constructor of " + owner + " since its structure is not known"); BlockStatement block = new BlockStatement(newFunc); Var thisVar = new Var("this", owner); thisVar.isConstant = true; thisVar.hits = 2; block.addVar(thisVar); // create object and initialize default fields int line = t.lineNumber(); Expr[] initializers = new Expr[owner.fields.length]; for (int i=0; i<initializers.length; i++) { if (owner.fields[i].defaultValue != null) { initializers[i] = new ConstExpr(line, owner.fields[i].type, owner.fields[i].defaultValue); } } block.statements.add(new AssignStatement(thisVar, new NewArrayInitExpr(line, owner, initializers))); // call .<init>() Expr[] initArgs = new Expr[1 + newFunc.args.length]; initArgs[0] = new VarExpr(line, thisVar); for (int i=0; i<newFunc.args.length; i++) { initArgs[i+1] = new VarExpr(line, newFunc.args[i]); } block.statements.add(new ExprStatement(new CallExpr(line, initFunc, initArgs))); // return block.statements.add(new ReturnStatement(new VarExpr(line, thisVar))); return block; } /** * Parses body of constructor. * The first statement of constructor might be call to super(). */ private Statement parseInitBody(Type owner, Function f) throws IOException, ParseException { Function superInit = findMethod(owner.superType(), "<init>"); if (superInit == null) { return parseFunctionBody(f); } else { boolean autoInit = superInit.args.length == 1; BlockStatement block = new BlockStatement(f); int line = t.lineNumber(); Expr getThis = new VarExpr(line, f.args[0]); if (t.nextToken() == '=') { if (t.nextToken() == Token.SUPER) { expect('('); Expr superCall = parseFunctionCall(block, new ConstExpr(line, superInit.type, superInit), getThis); block.statements.add(new ExprStatement(superCall)); } else if (autoInit) { t.pushBack(); Expr superCall = new CallExpr(line, superInit, new Expr[] { getThis }); block.statements.add(new ExprStatement(superCall)); block.statements.add(new ExprStatement(parseExpr(block))); } else { throw new ParseException("Missing super() call"); } block.statements.add(new ReturnStatement(new ConstExpr(t.lineNumber(), BuiltinType.NULL, Null.NULL))); } else { // token == '{' if (t.nextToken() == Token.SUPER) { expect('('); Expr superCall = parseFunctionCall(block, new ConstExpr(line, superInit.type, superInit), getThis); block.statements.add(new ExprStatement(superCall)); } else if (autoInit) { t.pushBack(); Expr superCall = new CallExpr(line, superInit, new Expr[] { getThis }); block.statements.add(new ExprStatement(superCall)); } else { throw new ParseException("Missing super() call"); } while (t.nextToken() != '}') { t.pushBack(); block.statements.add(parseStatement(block)); } } return block; } } private Statement parseFunctionBody(Function f) throws IOException, ParseException { if (t.nextToken() == '=') { if (f.type.returnType == BuiltinType.NONE) { BlockStatement block = new BlockStatement(f); block.statements.add(new ExprStatement(parseExpr(block))); block.statements.add(new ReturnStatement(new ConstExpr(t.lineNumber(), BuiltinType.NULL, Null.NULL))); return block; } else { return new ReturnStatement(cast(parseExpr(f), f.type.returnType)); } } else { // token == '{' t.pushBack(); return parseStatement(f); } } private Statement parseStatement(Scope scope) throws IOException, ParseException { switch (t.nextToken()) { case ';': return new EmptyStatement(); case '{': { BlockStatement block = new BlockStatement(scope); while (t.nextToken() != '}') { if (t.ttype == Token.VAR || t.ttype == Token.CONST) { boolean isConst = t.ttype == Token.CONST; if (t.nextToken() != Token.WORD) throw new ParseException("Variable name expected, got " + t); String varname = t.svalue; if (block.vars.get(varname) != null) warn(CompilerEnv.W_ERROR, "Variable " + varname + " is already defined"); Type vartype = null; Expr varvalue = null; if (t.nextToken() == ':') { vartype = parseType(block); } else { t.pushBack(); } if (t.nextToken() == '=') { varvalue = parseExpr(block); if (vartype == null) { vartype = varvalue.returnType(); } else { varvalue = cast(varvalue, vartype); } } else { t.pushBack(); if (vartype == null) vartype = BuiltinType.ANY; if (varvalue == null) { Object dflt = defaultValue(vartype); if (dflt == null) dflt = Null.NULL; varvalue = new ConstExpr(t.lineNumber(), vartype, dflt); } } if (vartype == BuiltinType.NULL) vartype = BuiltinType.ANY; else if (vartype == BuiltinType.NONE) throw new ParseException("Cannot create variable of type <none>"); Var var = new Var(varname, vartype); var.isConstant = isConst; if (varvalue.kind == Expr.EXPR_CONST) { var.defaultValue = ((ConstExpr)varvalue).value; } if (block.addVar(var)) warn(CompilerEnv.W_HIDDEN, "Variable " + varname + " hides another variable with the same name"); block.statements.add(new AssignStatement(var, varvalue)); } else { t.pushBack(); block.statements.add(parseStatement(block)); } } if (block.statements.isEmpty()) return new EmptyStatement(); return block; } case Token.BREAK: { if (t.nextToken() != ';') t.pushBack(); return new BreakStatement(t.lineNumber()); } case Token.CONTINUE: { if (t.nextToken() != ';') t.pushBack(); return new ContinueStatement(t.lineNumber()); } case Token.IF: { expect('('); Expr condition = cast(parseExpr(scope), BuiltinType.BOOL); expect(')'); Statement ifstat = parseStatement(scope); if (ifstat.kind == Statement.STAT_EMPTY) warn(CompilerEnv.W_EMPTY, "Empty statement after 'if'"); Statement elsestat; if (t.nextToken() == Token.ELSE) { elsestat = parseStatement(scope); if (elsestat.kind == Statement.STAT_EMPTY) warn(CompilerEnv.W_EMPTY, "Empty statement after 'else'"); } else { t.pushBack(); elsestat = new EmptyStatement(); } return new IfStatement(condition, ifstat, elsestat); } case Token.RETURN: { Type rettype = scope.enclosingFunction().type.returnType; Expr returnExpr; if (rettype.kind == Type.TYPE_NONE) { returnExpr = new ConstExpr(t.lineNumber(), BuiltinType.NULL, Null.NULL); } else { returnExpr = cast(parseExpr(scope), rettype); } if (t.nextToken() != ';') t.pushBack(); return new ReturnStatement(returnExpr); } case Token.DO: { Statement body = parseStatement(scope); expect(Token.WHILE); expect('('); Expr condition = cast(parseExpr(scope), BuiltinType.BOOL); expect(')'); return new LoopStatement(body, condition, new EmptyStatement()); } case Token.WHILE: { // while can define variables in prestat, enclose it in block BlockStatement whileBlock = new BlockStatement(scope); expect('('); Statement prestat; if (t.nextToken() == Token.VAR) { if (t.nextToken() != Token.WORD) throw new ParseException("Variable name expected, got " + t); String varname = t.svalue; Type vartype = null; Expr varvalue = null; if (t.nextToken() == ':') { vartype = parseType(whileBlock); } else { t.pushBack(); } expect('='); varvalue = parseExpr(whileBlock); if (vartype == null) { vartype = varvalue.returnType(); } else { varvalue = cast(varvalue, vartype); } if (vartype == BuiltinType.NULL) vartype = BuiltinType.ANY; else if (vartype == BuiltinType.NONE) throw new ParseException("Cannot create variable of type <none>"); Var var = new Var(varname, vartype); if (whileBlock.addVar(var)) warn(CompilerEnv.W_HIDDEN, "Variable " + varname + " hides another variable with the same name"); prestat = new AssignStatement(var, varvalue); } else { t.pushBack(); prestat = parseExprStatement(whileBlock); } Expr condition; if (t.nextToken() == ',') { condition = cast(parseExpr(whileBlock), BuiltinType.BOOL); } else { t.pushBack(); if (prestat.kind != Statement.STAT_EXPR) throw new ParseException("Bool expression expected"); condition = cast(((ExprStatement)prestat).expr, BuiltinType.BOOL); prestat = new EmptyStatement(); } expect(')'); whileBlock.statements.add(new LoopStatement(prestat, condition, parseStatement(whileBlock))); return whileBlock; } case Token.TRY: { Statement tryStat = parseStatement(scope); Var catchVar = null; Statement catchStat; expect(Token.CATCH); if (t.nextToken() == '(') { if (t.nextToken() == Token.VAR) { // parse as 'catch (var error) { catchBlock }' if (t.nextToken() != Token.WORD) throw new ParseException("Variable name expected"); catchVar = new Var(t.svalue, BuiltinType.ERROR); expect(')'); BlockStatement catchBlock = new BlockStatement(scope); if (catchBlock.addVar(catchVar)) warn(CompilerEnv.W_HIDDEN, "Variable " + catchVar.name + " hides another variable with the same name"); catchStat = parseStatement(catchBlock); catchBlock.statements.add(catchStat); } else { // rewind to 'catch { catchBlock }' t.pushBack(); catchStat = parseStatement(scope); expect(')'); } } else { t.pushBack(); catchStat = parseStatement(scope); } return new TryCatchStatement(tryStat, catchVar, catchStat); } case Token.VAR: case Token.CONST: { throw new ParseException(t.toString() + " not allowed here"); } case Token.FOR: return parseForLoop(scope); case Token.SWITCH: return parseSwitchStatement(scope); case Token.THROW: { expect('('); Expr errCode; Expr errMsg; if (t.nextToken() == ')') { errCode = new ConstExpr(t.lineNumber(), BuiltinType.INT, Int32.ONE); errMsg = new ConstExpr(t.lineNumber(), BuiltinType.STRING, Null.NULL); } else { t.pushBack(); errCode = cast(parseExpr(scope), BuiltinType.INT); if (t.nextToken() == ',') { errMsg = cast(parseExpr(scope), BuiltinType.STRING); } else { t.pushBack(); errMsg = new ConstExpr(t.lineNumber(), BuiltinType.STRING, Null.NULL); } expect(')'); } if (t.nextToken() != ';') t.pushBack(); return new ThrowStatement(errCode, errMsg); } default: { t.pushBack(); Statement stat = parseExprStatement(scope); if (t.nextToken() != ';') t.pushBack(); return stat; } } } /** * Parses for loops. * <br/> * Classic for: * <pre> * for (init, condition, increment) body * </pre> * we parse as * <pre> * BlockStatement { * init * ForLoopStatement { * condition * increment * body * } * } * </pre> * * Loops over collections are translated as: * <pre> * for (var i in from..to) => for (var i=from, i <= to, i+=1) * for (var i in range) => for (var i=range.from, i <= range.to, i+=1) * for (var i in array) => for (var #=0, # < array.len, #+=1) { i = array[#], ... } */ private Statement parseForLoop(Scope scope) throws IOException, ParseException { expect('('); BlockStatement forBlock = new BlockStatement(scope); if (t.nextToken() != Token.VAR) { // old style 'for (init, cond, incr)' t.pushBack(); forBlock.statements.add(parseExprStatement(forBlock)); expect(','); Expr condition = cast(parseExpr(forBlock), BuiltinType.BOOL); expect(','); Statement incr = parseExprStatement(forBlock); expect(')'); Statement forBody = parseStatement(forBlock); forBlock.statements.add(new ForLoopStatement(condition, incr, forBody)); return forBlock; } // starts with 'for (var name ...' if (t.nextToken() != Token.WORD) throw new ParseException("Variable name expected"); String varname = t.svalue; Type vartype = null; if (t.nextToken() == ':') { vartype = parseType(forBlock); } else { t.pushBack(); } if (t.nextToken() == '=') { // still classic for loop Expr initExpr = parseExpr(forBlock); if (vartype == null) { vartype = initExpr.returnType(); } else { initExpr = cast(initExpr, vartype); } Var var = new Var(varname, vartype); if (forBlock.addVar(var)) { warn(CompilerEnv.W_HIDDEN, "Variable " + varname + " hides another variable with the same name"); } forBlock.statements.add(new AssignStatement(var, initExpr)); expect(','); Expr condition = cast(parseExpr(forBlock), BuiltinType.BOOL); expect(','); Statement incr = parseStatement(forBlock); expect(')'); forBlock.statements.add(new ForLoopStatement(condition, incr, parseStatement(forBlock))); return forBlock; } t.pushBack(); // for loop over collection expect(Token.IN); Expr collection = parseExpr(forBlock); Type collType = collection.returnType(); expect(')'); switch (collType.kind) { case Type.TYPE_INTRANGE: case Type.TYPE_LONGRANGE: { // init is 'loopVar = from; var #to = to' Type itemType = (collType.kind == Type.TYPE_INTRANGE) ? BuiltinType.INT : BuiltinType.LONG; if (vartype == null) vartype = itemType; Var loopVar = new Var(varname, vartype); loopVar.hits = 2; // in comparison and increment if (forBlock.addVar(loopVar)) { warn(CompilerEnv.W_HIDDEN, "Variable " + varname + " hides another variable with the same name"); } Var toVar = new Var("#to", vartype); toVar.isConstant = true; toVar.hits = 1; // in comparison forBlock.addVar(toVar); boolean includingHigh = true; if (collection.kind == Expr.EXPR_RANGE) { forBlock.statements.add(new AssignStatement(loopVar, cast(((RangeExpr)collection).fromExpr, vartype))); Expr high = ((RangeExpr)collection).toExpr; if (high.kind == Expr.EXPR_BINARY) { BinaryExpr binary = (BinaryExpr) high; if (binary.operator == '-' && binary.rhs.kind == Expr.EXPR_CONST) { Object cnst = ((ConstExpr)binary.rhs).value; if (cnst.equals(Int32.ONE) || cnst.equals(new Int64(1))) { high = binary.lhs; includingHigh = false; } } } forBlock.statements.add(new AssignStatement(toVar, cast(high, vartype))); } else { Var rangeVar = new Var("#range", collType); rangeVar.isConstant = true; rangeVar.hits = 2; // to load .from and .to Expr rangeLoad = new VarExpr(-1, rangeVar); Expr getFrom = new ArrayElementExpr(rangeLoad, new ConstExpr(-1, BuiltinType.INT, Int32.ZERO), itemType); Expr getTo = new ArrayElementExpr(rangeLoad, new ConstExpr(-1, BuiltinType.INT, Int32.ONE), itemType); // create subBlock in which #range will live BlockStatement subBlock = new BlockStatement(forBlock); subBlock.addVar(rangeVar); subBlock.statements.add(new AssignStatement(rangeVar, collection)); subBlock.statements.add(new AssignStatement(loopVar, cast(getFrom, vartype))); subBlock.statements.add(new AssignStatement(toVar, cast(getTo, vartype))); forBlock.statements.add(subBlock); } Expr loadVar = new VarExpr(-1, loopVar); Expr loadTo = new VarExpr(-1, toVar); // condition is 'var <= toConst' or 'var < toConst' Expr condition = new ComparisonExpr(loadVar, includingHigh ? Token.LTEQ : '<', loadTo); // increment is 'var += 1' Expr plusOne; switch (vartype.kind) { case Type.TYPE_INT: plusOne = new ConstExpr(-1, BuiltinType.INT, Int32.ONE); break; case Type.TYPE_LONG: plusOne = new ConstExpr(-1, BuiltinType.LONG, new Int64(1L)); break; default: throw new ParseException("Variable " + varname + " must be Int or Long"); } Statement increment = new CompoundAssignStatement(loopVar, Token.PLUSEQ, plusOne); forBlock.statements.add(new ForLoopStatement(condition, increment, parseStatement(forBlock))); return forBlock; } case Type.TYPE_ARRAY: { Type itemType = ((ArrayType)collType).elementType; if (vartype == null) vartype = itemType; // init is 'var #array = collection; var #index = 0; var #len = #array.len' Var arrayVar = new Var("#array", collection.returnType()); Var indexVar = new Var("#index", BuiltinType.INT); Var lenVar = new Var("#len", BuiltinType.INT); arrayVar.isConstant = true; lenVar.isConstant = true; arrayVar.hits = 2; // in init and body indexVar.hits = 2; // in body and condition lenVar.hits = 1; // in condition forBlock.addVar(arrayVar); forBlock.addVar(indexVar); forBlock.addVar(lenVar); Expr getArray = new VarExpr(-1, arrayVar); Expr getIndex = new VarExpr(-1, indexVar); Expr getLen = new VarExpr(-1, lenVar); forBlock.statements.add(new AssignStatement(arrayVar, collection)); forBlock.statements.add(new AssignStatement(indexVar, new ConstExpr(-1, BuiltinType.INT, Int32.ZERO))); if (collection.kind == Expr.EXPR_NEWARRAY_INIT) { arrayVar.hits = 1; int len = ((NewArrayInitExpr)collection).initializers.length; forBlock.statements.add(new AssignStatement(lenVar, new ConstExpr(-1, BuiltinType.INT, Int32.toInt32(len)))); } else { forBlock.statements.add(new AssignStatement(lenVar, new ArrayLenExpr(getArray))); } // condition is '#index < #len' Expr condition = new ComparisonExpr(getIndex, '<', getLen); // increment is '#index += 1' Statement increment = new CompoundAssignStatement(indexVar, Token.PLUSEQ, new ConstExpr(-1, BuiltinType.INT, Int32.ONE)); // add 'var loopVar = #array[#index]' to body BlockStatement body = new BlockStatement(forBlock); Var loopVar = new Var(varname, vartype); body.addVar(loopVar); Expr arrayItem = cast(new ArrayElementExpr(getArray, getIndex, itemType), vartype); body.statements.add(new AssignStatement(loopVar, arrayItem)); body.statements.add(parseStatement(body)); forBlock.statements.add(new ForLoopStatement(condition, increment, body)); return forBlock; } default: throw new ParseException("Type " + collType + " is not iterable"); } } private Statement parseSwitchStatement(Scope scope) throws IOException, ParseException { expect('('); Expr keyExpr = parseExpr(scope); Type keyType = keyExpr.returnType(); boolean intSwitch; if (keyType == BuiltinType.STRING) { intSwitch = false; } else if (keyType == BuiltinType.INT || keyType == BuiltinType.BYTE || keyType == BuiltinType.CHAR || keyType == BuiltinType.SHORT) { intSwitch = true; keyExpr = cast(keyExpr, BuiltinType.INT); keyType = BuiltinType.INT; } else { throw new ParseException("Switch over " + keyType + " values is not supported"); } expect(')'); ArrayList keys = new ArrayList(); ArrayList keySets = new ArrayList(); ArrayList statements = new ArrayList(); Statement elseStat = null; expect('{'); while (t.nextToken() != '}') { // reading possible else branch if (t.ttype == Token.ELSE) { expect(':'); if (elseStat != null) warn(CompilerEnv.W_ERROR, "Duplicate else case"); elseStat = parseStatement(scope); continue; } // reading set of keys ArrayList set = new ArrayList(); boolean first = true; do { t.pushBack(); if (first) first = false; else expect(','); Expr caseExpr = parseExpr(scope); if (intSwitch && caseExpr.kind == Expr.EXPR_RANGE) { Expr case1 = (Expr) cast(((RangeExpr)caseExpr).fromExpr, BuiltinType.INT).accept(constOptimizer, scope); Expr case2 = (Expr) cast(((RangeExpr)caseExpr).toExpr, BuiltinType.INT).accept(constOptimizer, scope); if (case1.kind != Expr.EXPR_CONST || case2.kind != Expr.EXPR_CONST) throw new ParseException("Constant expression expected"); int from = ((Int32)((ConstExpr)case1).value).value; int to = ((Int32)((ConstExpr)case1).value).value; if (from > to) warn(CompilerEnv.W_ERROR, "Invalid range " + from + ".." + to); for (int i=from; i<=to; i++) { Int32 I = Int32.toInt32(i); if (keys.contains(I)) warn(CompilerEnv.W_ERROR, "Duplicate switch case " + i); keys.add(I); set.add(I); } } else { caseExpr = (Expr) cast(caseExpr, keyType).accept(constOptimizer, scope); if (caseExpr.kind != Expr.EXPR_CONST) throw new ParseException("Constant expression expected"); Object key = ((ConstExpr)caseExpr).value; if (keys.contains(key)) warn(CompilerEnv.W_ERROR, "Duplicate switch case " + key); keys.add(key); set.add(key); } } while (t.nextToken() != ':'); // reading branch keySets.add(set); statements.add(parseStatement(scope)); } if (elseStat == null) elseStat = new EmptyStatement(); // return switch if (intSwitch) { int branchCount = statements.size(); Statement[] statArray = new Statement[branchCount]; statements.copyInto(statArray); int[][] keySetArray = new int[branchCount][]; for (int i=0; i<branchCount; i++) { ArrayList set = (ArrayList) keySets.get(i); keySetArray[i] = new int[set.size()]; set.copyInto(keySetArray[i]); } return new SwitchStatement(keyExpr, keySetArray, statArray, elseStat); } else { // string switch we implement as two consequent switches // first is switch over hashcodes which returns branch number BlockStatement switchBlock = new BlockStatement(scope); Var strVar = new Var("#string", BuiltinType.STRING); strVar.hits = 1; strVar.isConstant = true; Var indexVar = new Var("#index", BuiltinType.INT); indexVar.hits = 1; switchBlock.addVar(strVar); switchBlock.addVar(indexVar); switchBlock.statements.add(new AssignStatement(strVar, keyExpr)); switchBlock.statements.add(new AssignStatement(indexVar, new ConstExpr(-1, BuiltinType.INT, Int32.M_ONE))); // create first switch Expr strExpr = new VarExpr(-1, strVar); ArrayList hashes = new ArrayList(); ArrayList checkStatements = new ArrayList(); for (int branchIndex = 0; branchIndex < keySets.size(); branchIndex++) { ArrayList set = (ArrayList) keySets.get(branchIndex); for (int keyIdx=0; keyIdx < set.size(); keyIdx++) { String key = (String) set.get(keyIdx); Int32 hash = Int32.toInt32(key.hashCode()); int hashIdx = hashes.indexOf(hash); if (hashIdx < 0) { hashIdx = hashes.size(); hashes.add(hash); checkStatements.add(new EmptyStatement()); } Statement checkStat = (Statement) checkStatements.get(hashIdx); checkStat = new IfStatement( new ComparisonExpr(strExpr, Token.EQEQ, new ConstExpr(-1, BuiltinType.STRING, key)), new AssignStatement(indexVar, new ConstExpr(-1, BuiltinType.INT, Int32.toInt32(branchIndex))), checkStat); strVar.hits++; checkStatements.set(hashIdx, checkStat); } } int[][] keySetArray = new int[hashes.size()][]; for (int i=0; i<keySetArray.length; i++) { keySetArray[i] = new int[] { ((Int32)hashes.get(i)).value }; } Statement[] statArray = new Statement[hashes.size()]; checkStatements.copyInto(statArray); Expr getHash = new CallExpr(-1, unit.getFunction("Any.hash"), new Expr[] { strExpr }); switchBlock.statements.add(new SwitchStatement(getHash, keySetArray, statArray, new EmptyStatement())); // create second switch keySetArray = new int[statements.size()][]; for (int i=0; i<keySetArray.length; i++) { keySetArray[i] = new int[] { i }; } statArray = new Statement[statements.size()]; statements.copyInto(statArray); switchBlock.statements.add(new SwitchStatement(new VarExpr(-1, indexVar), keySetArray, statArray, elseStat)); return switchBlock; } } private Expr parseSwitchExpr(Scope scope) throws IOException, ParseException { expect('('); Expr keyExpr = parseExpr(scope); Type keyType = keyExpr.returnType(); boolean intSwitch; if (keyType == BuiltinType.STRING) { intSwitch = false; } else if (keyType == BuiltinType.INT || keyType == BuiltinType.BYTE || keyType == BuiltinType.CHAR || keyType == BuiltinType.SHORT) { intSwitch = true; keyExpr = cast(keyExpr, BuiltinType.INT); keyType = BuiltinType.INT; } else { throw new ParseException("Switch over " + keyType + " values is not supported"); } expect(')'); ArrayList keys = new ArrayList(); ArrayList keySets = new ArrayList(); ArrayList exprs = new ArrayList(); Expr elseExpr = null; expect('{'); while (t.nextToken() != '}') { // reading possible else branch if (t.ttype == Token.ELSE) { expect(':'); if (elseExpr != null) warn(CompilerEnv.W_ERROR, "Duplicate else case"); elseExpr = parseExpr(scope); if (t.nextToken() != ';') t.pushBack(); continue; } // reading set of keys ArrayList set = new ArrayList(); boolean first = true; do { t.pushBack(); if (first) first = false; else expect(','); Expr caseExpr = parseExpr(scope); if (intSwitch && caseExpr.kind == Expr.EXPR_RANGE) { Expr case1 = (Expr) cast(((RangeExpr)caseExpr).fromExpr, BuiltinType.INT).accept(constOptimizer, scope); Expr case2 = (Expr) cast(((RangeExpr)caseExpr).toExpr, BuiltinType.INT).accept(constOptimizer, scope); if (case1.kind != Expr.EXPR_CONST || case2.kind != Expr.EXPR_CONST) throw new ParseException("Constant expression expected"); int from = ((Int32)((ConstExpr)case1).value).value; int to = ((Int32)((ConstExpr)case1).value).value; if (from > to) warn(CompilerEnv.W_ERROR, "Invalid range " + from + ".." + to); for (int i=from; i<=to; i++) { Int32 I = Int32.toInt32(i); if (keys.contains(I)) warn(CompilerEnv.W_ERROR, "Duplicate switch case " + i); keys.add(I); set.add(I); } } else { caseExpr = (Expr) cast(caseExpr, keyType).accept(constOptimizer, scope); if (caseExpr.kind != Expr.EXPR_CONST) throw new ParseException("Constant expression expected"); Object key = ((ConstExpr)caseExpr).value; if (keys.contains(key)) warn(CompilerEnv.W_ERROR, "Duplicate switch case " + key); keys.add(key); set.add(key); } } while (t.nextToken() != ':'); // reading branch keySets.add(set); exprs.add(parseExpr(scope)); if (t.nextToken() != ';') t.pushBack(); } // calculating return type if (elseExpr == null) { throw new ParseException("Missing else branch"); } Type rettype = elseExpr.returnType(); for (int i=exprs.size()-1; i>=0; i--) { rettype = binaryCastType(rettype, ((Expr)exprs.get(i)).returnType()); } elseExpr = cast(elseExpr, rettype); for (int i=exprs.size()-1; i>=0; i--) { exprs.set(i, cast((Expr)exprs.get(i), rettype)); } // return switch if (intSwitch) { int branchCount = exprs.size(); Expr[] exprArray = new Expr[branchCount]; exprs.copyInto(exprArray); int[][] keySetArray = new int[branchCount][]; for (int i=0; i<branchCount; i++) { ArrayList set = (ArrayList) keySets.get(i); keySetArray[i] = new int[set.size()]; set.copyInto(keySetArray[i]); } return new SwitchExpr(keyExpr, keySetArray, exprArray, elseExpr); } else { // string switch we implement as two consequent switches // first is switch over hashcodes which returns branch number Var strVar = new Var("#string", BuiltinType.STRING); strVar.hits = 1; strVar.isConstant = true; // create first switch Expr strExpr = new VarExpr(-1, strVar); ArrayList hashes = new ArrayList(); ArrayList checkExprs = new ArrayList(); for (int branchIndex = 0; branchIndex < keySets.size(); branchIndex++) { ArrayList set = (ArrayList) keySets.get(branchIndex); for (int keyIdx=0; keyIdx < set.size(); keyIdx++) { String key = (String) set.get(keyIdx); Int32 hash = Int32.toInt32(key.hashCode()); int hashIdx = hashes.indexOf(hash); if (hashIdx < 0) { hashIdx = hashes.size(); hashes.add(hash); checkExprs.add(new ConstExpr(-1, BuiltinType.INT, Int32.M_ONE)); } Expr checkExpr = (Expr) checkExprs.get(hashIdx); checkExpr = new IfElseExpr( new ComparisonExpr(strExpr, Token.EQEQ, new ConstExpr(-1, BuiltinType.STRING, key)), new ConstExpr(-1, BuiltinType.INT, Int32.toInt32(branchIndex)), checkExpr); strVar.hits++; checkExprs.set(hashIdx, checkExpr); } } int[][] keySetArray = new int[hashes.size()][]; for (int i=0; i<keySetArray.length; i++) { keySetArray[i] = new int[] { ((Int32)hashes.get(i)).value }; } Expr[] exprArray = new Expr[hashes.size()]; checkExprs.copyInto(exprArray); Expr getHash = new CallExpr(-1, unit.getFunction("Any.hash"), new Expr[] { strExpr }); Expr innerSwitch = new SwitchExpr(getHash, keySetArray, exprArray, new ConstExpr(-1, BuiltinType.INT, Int32.M_ONE)); // create second switch keySetArray = new int[exprs.size()][]; for (int i=0; i<keySetArray.length; i++) { keySetArray[i] = new int[] { i }; } exprArray = new Expr[exprs.size()]; exprs.copyInto(exprArray); Expr outerSwitch = new SwitchExpr(innerSwitch, keySetArray, exprArray, elseExpr); return new SequentialExpr(new Var[] { strVar }, new Expr[] { keyExpr }, outerSwitch); } } /** Parses assignments and expression statements. */ private Statement parseExprStatement(Scope scope) throws IOException, ParseException { Expr expr = parseExpr(scope); int assignOp = t.nextToken(); if (assignOp == '=') { Expr rhs = parseExpr(scope); switch (expr.kind) { case Expr.EXPR_VAR: { VarExpr lhs = (VarExpr) expr; if (lhs.var.isConstant) throw new ParseException("Cannot assign to constant " + lhs.var.name); rhs = cast(rhs, lhs.var.type); return new AssignStatement(lhs.var, rhs); } case Expr.EXPR_ARRAY_ELEMENT: { ArrayElementExpr lhs = (ArrayElementExpr) expr; rhs = cast(rhs, lhs.returnType()); return new ArraySetStatement(lhs.arrayExpr, lhs.indexExpr, rhs); } case Expr.EXPR_PROPERTY: { // setter(objectExpr, rhs) PropertyLvalue lhs = (PropertyLvalue) expr; rhs = cast(rhs, lhs.setter.type.argtypes[1]); return new ExprStatement(new CallExpr( lhs.lineNumber(), lhs.setter, new Expr[] { lhs.objectExpr, rhs })); } case Expr.EXPR_ARRAYLIKE: { // setter(objectExpr, indexExprs..., rhs ) ArrayLikePropertyLvalue lhs = (ArrayLikePropertyLvalue) expr; int idxsize = lhs.indexExprs.length; Expr[] setterArgs = new Expr[idxsize + 2]; setterArgs[0] = lhs.objectExpr; setterArgs[idxsize+1] = cast(rhs, lhs.setter.type.argtypes[idxsize+1]); for (int i=0; i<idxsize; i++) { setterArgs[i+1] = cast(lhs.indexExprs[i], lhs.setter.type.argtypes[i+1]); } return new ExprStatement(new CallExpr(lhs.lineNumber(), lhs.setter, setterArgs)); } default: throw new ParseException("Cannot assign to given expression"); } } else if (Token.isAssignment(assignOp)) { Expr rhs = parseExpr(scope); switch (expr.kind) { case Expr.EXPR_VAR: { VarExpr lhs = (VarExpr) expr; if (lhs.var.isConstant) throw new ParseException("Cannot assign to constant " + lhs.var.name); Expr binary = makeBinaryExpr(lhs, Token.getBinaryOperator(assignOp), rhs); if (binary.kind == Expr.EXPR_BINARY) { if (assignOp == Token.LTLTEQ || assignOp == Token.GTGTEQ || assignOp == Token.GTGTGTEQ) { rhs = cast(rhs, BuiltinType.INT); } else { rhs = cast(rhs, lhs.var.type); } return new CompoundAssignStatement(lhs.var, assignOp, rhs); } else { return new AssignStatement(lhs.var, cast(binary, lhs.var.type)); } } case Expr.EXPR_ARRAY_ELEMENT: { // var #lvalue = arrayExpr // var #0 = indexExpr // #array[#index] = #array[#index] * rhs ArrayElementExpr lhs = (ArrayElementExpr) expr; Var arrVar = new Var("#array", lhs.arrayExpr.returnType()); arrVar.hits = 2; arrVar.isConstant = true; Var idxVar = new Var("#index", lhs.indexExpr.returnType()); idxVar.hits = 2; idxVar.isConstant = true; Expr arrExpr = new VarExpr(-1, arrVar); Expr idxExpr = new VarExpr(-1, idxVar); rhs = makeBinaryExpr( new ArrayElementExpr(arrExpr, idxExpr, lhs.returnType()), Token.getBinaryOperator(assignOp), rhs); BlockStatement block = new BlockStatement(scope); block.addVar(arrVar); block.addVar(idxVar); block.statements.add(new AssignStatement(arrVar, lhs.arrayExpr)); block.statements.add(new AssignStatement(idxVar, lhs.indexExpr)); block.statements.add(new ArraySetStatement(arrExpr, idxExpr, cast(rhs, lhs.returnType()))); return block; } case Expr.EXPR_PROPERTY: { // var #object = objectExpr // setter (#object, getter(#object) * rhs) PropertyLvalue lhs = (PropertyLvalue) expr; Var objVar = new Var("#object", lhs.objectExpr.returnType()); objVar.isConstant = true; objVar.hits = 2; Expr objExpr = new VarExpr(-1, objVar); Expr getterCall = new CallExpr(lhs.lineNumber(), lhs.getter, new Expr[] { objExpr }); rhs = makeBinaryExpr(getterCall, Token.getBinaryOperator(assignOp), rhs); Expr setterCall = new CallExpr( lhs.lineNumber(), lhs.setter, new Expr[] { objExpr, cast(rhs, lhs.setter.type.argtypes[1]) }); return new ExprStatement(new SequentialExpr( new Var[] { objVar }, new Expr[] { lhs.objectExpr }, setterCall)); } case Expr.EXPR_ARRAYLIKE: { // var #object = objectExpr // var #index0 = indexExprs[0] // ... // var #indexN = indexExprs[N] // setter (#object, #0...#N , getter(#object, #0...#N) * rhs) ArrayLikePropertyLvalue lhs = (ArrayLikePropertyLvalue) expr; int idxsize = lhs.indexExprs.length; Var[] seqVars = new Var[idxsize+1]; Expr[] seqExprs = new Expr[idxsize+1]; seqVars[0] = new Var("#object", lhs.objectExpr.returnType()); seqVars[0].hits = 2; seqExprs[0] = lhs.objectExpr; for (int i=0; i<idxsize; i++) { seqVars[i+1] = new Var("#index" + i, lhs.indexExprs[i].returnType()); seqVars[i+1].isConstant = true; seqVars[i+1].hits = 2; seqExprs[i+1] = lhs.indexExprs[i]; } Expr[] getterArgs = new Expr[idxsize + 1]; for (int i=0; i<idxsize+1; i++) { getterArgs[i] = cast(new VarExpr(-1, seqVars[i]), lhs.getter.type.argtypes[i]); } CallExpr getterCall = new CallExpr(lhs.lineNumber(), lhs.getter, getterArgs); rhs = makeBinaryExpr(getterCall, Token.getBinaryOperator(assignOp), rhs); Expr[] setterArgs = new Expr[idxsize + 2]; for (int i=0; i<idxsize+1; i++) { setterArgs[i] = cast(new VarExpr(-1, seqVars[i]), lhs.setter.type.argtypes[i]); } setterArgs[idxsize+1] = cast(rhs, lhs.setter.type.argtypes[idxsize+1]); CallExpr setterCall = new CallExpr(lhs.lineNumber(), lhs.setter, setterArgs); return new ExprStatement(new SequentialExpr(seqVars, seqExprs, setterCall)); } default: throw new ParseException("Cannot assign to given expression"); } } else { t.pushBack(); return new ExprStatement(expr); } } /** * Parses expression part after '(' (function call). * * <p> * Performs additional type checks for acopy() arguments. */ private Expr parseFunctionCall(Scope scope, Expr fload, Expr firstarg) throws IOException, ParseException { if (fload.returnType().kind != Type.TYPE_FUNCTION) throw new ParseException("Applying () to non-function expression"); FunctionType ftype = (FunctionType)fload.returnType(); // parse arguments ArrayList vargs = new ArrayList(); if (firstarg != null) vargs.add(firstarg); boolean first = true; while (t.nextToken() != ')') { t.pushBack(); if (first) first = false; else expect(','); vargs.add(parseExpr(scope)); } // add default argument values if (vargs.size() < ftype.argtypes.length && fload.kind == Expr.EXPR_CONST) { Function f = (Function) ((ConstExpr)fload).value; for (int i=vargs.size(); i < ftype.argtypes.length; i++) { Var v = f.args[i]; if (v.defaultValue != null) vargs.add(new ConstExpr(-1, v.type, v.defaultValue)); } } if (ftype.argtypes.length != vargs.size()) { if (fload.kind == Expr.EXPR_CONST) { Function f = (Function) ((ConstExpr)fload).value; throw new ParseException("Wrong number of arguments in call to "+f.signature+"()"); } else { throw new ParseException("Wrong number of arguments in function call"); } } // cast arguments to needed types Expr[] args = new Expr[vargs.size()]; for (int i=0; i<args.length; i++) { args[i] = cast((Expr)vargs.get(i), ftype.argtypes[i]); } // special processing for acopy if (fload.kind == Expr.EXPR_CONST) { Function f = (Function) ((ConstExpr)fload).value; if (f.signature.equals("acopy") && args[2].returnType().kind == Type.TYPE_ARRAY) { ArrayType toarray = (ArrayType)args[2].returnType(); if (args[0].returnType().kind == Type.TYPE_ARRAY) { ArrayType fromarray = (ArrayType)args[0].returnType(); if (toarray.elementType.safeToCastTo(fromarray.elementType) && !toarray.elementType.equals(fromarray.elementType)) { warn(CompilerEnv.W_TYPECAST, "Unsafe type cast when copying from "+fromarray+" to "+toarray); } else if (!toarray.elementType.safeToCastTo(fromarray.elementType)) { warn(CompilerEnv.W_ERROR, "Cast to the incompatible type when copying from "+fromarray+" to "+toarray); } } else if (toarray.elementType != BuiltinType.ANY) { warn(CompilerEnv.W_TYPECAST, "Unsafe type cast when copying from Array to "+toarray); } } } return new CallExpr(fload, args); } private Expr parseDot(Scope scope, Expr expr) throws IOException, ParseException { int lnum = t.lineNumber(); // parse value.cast(Type) if (t.nextToken() == Token.CAST) { Type fromType = expr.returnType(); expect('('); Type toType = parseType(scope); expect(')'); if (toType.equals(fromType)) { warn(CompilerEnv.W_CAST, "Unnecessary cast to the same type"); return expr; } if (fromType.safeToCastTo(toType)) { warn(CompilerEnv.W_CAST, "Unnecessary cast from " + fromType + " to " + toType); return expr; } return cast(expr, toType, true); } if (t.ttype != Token.WORD) throw new ParseException("Identifier expected after '.'"); String member = t.svalue; Type type = expr.returnType(); // object field or emulated field switch (type.kind) { case Type.TYPE_ARRAY: { if (member.equals("len")) { return new ArrayLenExpr(expr); } break; } case Type.TYPE_INTRANGE: case Type.TYPE_LONGRANGE: { Type itemType = (type.kind == Type.TYPE_INTRANGE) ? BuiltinType.INT : BuiltinType.LONG; if (member.equals("from")) { return new ArrayElementExpr(expr, new ConstExpr(lnum, BuiltinType.INT, Int32.ZERO), itemType); } else if (member.equals("to")) { return new ArrayElementExpr(expr, new ConstExpr(lnum, BuiltinType.INT, Int32.ONE), itemType); } break; } case Type.TYPE_FUNCTION: { if (member.equals("apply") || (env.hasOption(CompilerEnv.F_COMPAT21) && member.equals("curry"))) { if (member.equals("curry")) { warn(CompilerEnv.W_DEPRECATED, "'curry' keyword is deprecated. Use Function.apply for partial argument application."); } expect('('); ArrayList args = new ArrayList(); boolean first = true; while (t.nextToken() != ')') { t.pushBack(); if (first) first = false; else expect(','); args.add(parseExpr(scope)); } FunctionType ftype = (FunctionType) type; if (args.size() > ftype.argtypes.length) throw new ParseException("Number of arguments in apply() exceeds arity of function"); Expr[] argExprs = new Expr[args.size()]; for (int i=0; i < argExprs.length; i++) { argExprs[i] = cast((Expr)args.get(i), ftype.argtypes[i]); } return new ApplyExpr(expr, argExprs); } break; } case Type.TYPE_OBJECT: { if (type instanceof ObjectType) { // FIXME: get rid of instanceof // searching named field Var[] fields = ((ObjectType)type).fields; int index = -1; if (fields != null) { for (int i=0; i<fields.length; i++) { if (fields[i].name.equals(member)) { index = i; break; } } } if (index >= 0) { ConstExpr indexexpr = new ConstExpr(lnum, BuiltinType.INT, Int32.toInt32(index)); ArrayElementExpr ldexpr = new ArrayElementExpr(expr, indexexpr, fields[index].type); return ldexpr; } } } } // object method Function method = findMethod(type, member); if (method != null) { method.hits++; if (t.nextToken() == '(') { return parseFunctionCall(scope, new ConstExpr(lnum, method.type, method), expr); } else { t.pushBack(); return new ApplyExpr(new ConstExpr(lnum, method.type, method), new Expr[] { expr }); } } // object property Function getter = findGetter(type, member); Function setter = findSetter(type, member); int operator = t.nextToken(); t.pushBack(); if (operator != '=') { if (getter == null) throw new ParseException("Type " + type + " has no member named " + member); if (getter.type.argtypes.length != 1) throw new ParseException("Function " + getter.signature + " cannot be used as property getter."); getter.hits++; } if (Token.isAssignment(operator)) { if (setter == null) throw new ParseException("Type " + type + " has no member named " + member); if (setter.type.argtypes.length != 2) throw new ParseException("Function " + setter.signature + " cannot be used as property setter."); setter.hits++; return new PropertyLvalue(expr, member, getter, setter); } else { return new CallExpr(expr.lineNumber(), getter, new Expr[] { expr }); } } /** * Parses expression part after '['. */ private Expr parseBrackets(Scope scope, Expr arexpr) throws IOException, ParseException { // parse first expression and check if ':' follows int lnum = t.lineNumber(); Type artype = arexpr.returnType(); ArrayList indices = new ArrayList(); int tok = t.nextToken(); t.pushBack(); if (tok == ':') { indices.add(new ConstExpr(0, BuiltinType.INT, Int32.ZERO)); } else { indices.add(parseExpr(scope)); } // parse range expression if (t.nextToken() == ':') { // parse second range argument tok = t.nextToken(); t.pushBack(); if (tok == ']') { // implicit end // FIXME: arexpr used twice here Function lenMethod = findMethod(artype, "len"); if (lenMethod == null || lenMethod.type.argtypes.length != 1 || lenMethod.type.returnType != BuiltinType.INT) throw new ParseException("Operator [:] cannot be applied to " + artype + ", no suitable len()"); lenMethod.hits++; indices.add(new CallExpr(t.lineNumber(), lenMethod, new Expr[] { arexpr })); } else { indices.add(cast(parseExpr(scope), BuiltinType.INT)); } expect(']'); // return range expression Function rangeMethod = findMethod(artype, "range"); if (rangeMethod == null || rangeMethod.type.argtypes.length != 3 || rangeMethod.type.argtypes[1] != BuiltinType.INT || rangeMethod.type.argtypes[2] != BuiltinType.INT) throw new ParseException("Operator [:] cannot be applied to " + artype); rangeMethod.hits++; return new CallExpr(lnum, rangeMethod, new Expr[] {arexpr, (Expr)indices.get(0), (Expr)indices.get(1)}); } t.pushBack(); // parse remaining indices while (t.nextToken() != ']') { t.pushBack(); expect(','); indices.add(parseExpr(scope)); } // if array then convert to a chain of array gets if (artype.kind == Type.TYPE_ARRAY) { Expr getExpr = arexpr; while (indices.size() > 0) { artype = getExpr.returnType(); if (artype.kind != Type.TYPE_ARRAY) { warn(CompilerEnv.W_ERROR, "Number of arguments in [] exceeds dimension of array"); return getExpr; } Expr indexExpr = cast((Expr)indices.first(), BuiltinType.INT); indices.remove(0); getExpr = new ArrayElementExpr(getExpr, indexExpr, ((ArrayType)getExpr.returnType()).elementType); } return getExpr; } // finally use get() / set() Function getter = findMethod(artype, "get"); Function setter = findMethod(artype, "set"); int operator = t.nextToken(); t.pushBack(); if (Token.isAssignment(operator)) { if (setter == null || setter.type.argtypes.length != 2+indices.size()) throw new ParseException("Operator []= cannot be applied to " + artype); setter.hits++; if (operator != '=') { if (getter == null || getter.type.argtypes.length != 1+indices.size()) throw new ParseException("Operator [] cannot be applied to " + artype); for (int i=1; i<getter.type.argtypes.length; i++) { if (!getter.type.argtypes[i].safeToCastTo(setter.type.argtypes[i])) throw new ParseException("argument types of get() and set() are incompatible for " + artype); } getter.hits++; } Expr[] indexExprs = new Expr[indices.size()]; for (int i=0; i<indexExprs.length; i++) { Expr index = (Expr) indices.get(i); indexExprs[i] = cast(index, (getter != null) ? getter.type.argtypes[i+1] : setter.type.argtypes[i+1]); } return new ArrayLikePropertyLvalue(arexpr, indexExprs, getter, setter); } else { if (getter == null || getter.type.argtypes.length != 1+indices.size()) throw new ParseException("Operator [] cannot be applied to " + artype); getter.hits++; Expr[] args = new Expr[1 + indices.size()]; args[0] = arexpr; for (int i=1; i < args.length; i++) { Expr index = (Expr) indices.get(i-1); args[i] = cast(index, getter.type.argtypes[i]); } return new CallExpr(arexpr.lineNumber(), getter, args); } } private Expr parsePostfix(Scope scope, Expr expr) throws IOException, ParseException { while (true) { switch (t.nextToken()) { case '(': expr = parseFunctionCall(scope, expr, null); break; case '[': expr = parseBrackets(scope, expr); break; case '.': expr = parseDot(scope, expr); break; default: t.pushBack(); return expr; } } } /** * Binary operators arranged by priority. In groups of four. */ private static int[] priorops = { '^', 0, 0, 0, Token.BARBAR, '|', 0, 0, Token.AMPAMP, '&', 0, 0, Token.LTEQ, Token.GTEQ, '<', '>', Token.EQEQ, Token.NOTEQ, 0, 0, Token.IN, 0, 0, 0, Token.RANGE, 0, 0, 0, Token.LTLT, Token.GTGT, Token.GTGTGT, 0, '+', '-', 0, 0, '*', '/', '%', 0 }; private int getPriority(Int32 operator) { int op = operator.value; for (int i=0; i<priorops.length; i++) { if (priorops[i] == op) return i/4; } return -1; } private Expr parseExpr(Scope scope) throws IOException, ParseException { ArrayList exprs = new ArrayList(); ArrayList operators = new ArrayList(); while (true) { exprs.add(parsePostfix(scope, parseExprAtom(scope))); int opchar = t.nextToken(); if (Token.isOperator(opchar)) { operators.add(Int32.toInt32(opchar)); } else { t.pushBack(); break; } } while (!operators.isEmpty()) { int index = 0; int priority = 0; for (int i = 0; i < operators.size(); i++) { int pr = getPriority((Int32)operators.get(i)); if (pr > priority) { priority = pr; index = i; } } int op = ((Int32)operators.get(index)).value; Expr left = (Expr)exprs.get(index); Expr right = (Expr)exprs.get(index+1); Expr newexpr = makeBinaryExpr(left, op, right); exprs.set(index, newexpr); exprs.remove(index+1); operators.remove(index); } return (Expr)exprs.first(); } private Expr parseExprAtom(Scope scope) throws IOException, ParseException { int ttype = t.nextToken(); int line = t.lineNumber(); switch (ttype) { case Token.CHAR: return new ConstExpr(line, BuiltinType.CHAR, Int32.toInt32(t.ivalue)); case Token.INT: return new ConstExpr(line, BuiltinType.INT, Int32.toInt32(t.ivalue)); case Token.LONG: return new ConstExpr(line, BuiltinType.LONG, new Int64(t.lvalue)); case Token.FLOAT: return new ConstExpr(line, BuiltinType.FLOAT, new Float32(t.fvalue)); case Token.DOUBLE: return new ConstExpr(line, BuiltinType.DOUBLE, new Float64(t.dvalue)); case Token.QUOTED: return new ConstExpr(line, BuiltinType.STRING, t.svalue); case Token.FALSE: return new ConstExpr(line, BuiltinType.BOOL, Boolean.FALSE); case Token.TRUE: return new ConstExpr(line, BuiltinType.BOOL, Boolean.TRUE); case Token.NULL: return new ConstExpr(line, BuiltinType.NULL, Null.NULL); case Token.THROW: case Token.WORD: { Var var = scope.getVar(t.svalue); if (var == null) throw new ParseException("Variable " + t.svalue + " is not defined"); if (var.isConstant && var.defaultValue != null) { Object cnst = var.defaultValue; if (cnst instanceof Function) { ((Function)cnst).hits++; } return new ConstExpr(line, var.type, var.defaultValue); } else { var.hits++; return new VarExpr(line, var); } } case '(': { Expr expr = parseExpr(scope); expect(')'); return expr; } case '[': { // reading array elements ArrayList exprs = new ArrayList(); boolean first = true; while (t.nextToken() != ']') { t.pushBack(); if (first) first = false; else expect(','); if (t.nextToken() == ']') break; else t.pushBack(); exprs.add(parseExpr(scope)); } // calculating common type Type eltype = BuiltinType.NULL; for (int i=0; i<exprs.size(); i++) { Expr e = (Expr)exprs.get(i); eltype = binaryCastType(eltype, e.returnType()); } if (eltype == BuiltinType.NULL) eltype = BuiltinType.ANY; else if (eltype == BuiltinType.NONE) throw new ParseException("Cannot create array of <none>"); // building expression Expr[] init = new Expr[exprs.size()]; for (int i=0; i<init.length; i++) { init[i] = cast( (Expr)exprs.get(i), eltype); } return new NewArrayInitExpr(line, new ArrayType(eltype), init); } case '+': { Expr sub = parsePostfix(scope, parseExprAtom(scope)); Type type = sub.returnType(); if (type.isNumeric()) return sub; throw new ParseException("Operator "+Token.toString(ttype)+" cannot be applied to "+type); } case '-': { Expr sub = parsePostfix(scope, parseExprAtom(scope)); Type type = sub.returnType(); if (type.isNumeric()) return new UnaryExpr(ttype, sub); Function method = findMethod(type, "minus"); if (method != null && method.type.argtypes.length == 1) { method.hits++; return new CallExpr(sub.lineNumber(), method, new Expr[] { sub }); } throw new ParseException("Operator "+(char)ttype+" cannot be applied to "+type); } case '!': { Expr sub = parsePostfix(scope, parseExprAtom(scope)); Type type = sub.returnType(); if (type == BuiltinType.BOOL) { return new UnaryExpr(ttype, sub); } Function method = findMethod(type, "not"); if (method != null && method.type.argtypes.length == 1) { method.hits++; return new CallExpr(sub.lineNumber(), method, new Expr[] { sub }); } throw new ParseException("Operator "+Token.toString(ttype)+" cannot be applied to "+type); } case '~': { Expr sub = parsePostfix(scope, parseExprAtom(scope)); Type type = sub.returnType(); if (type == BuiltinType.BYTE || type == BuiltinType.SHORT || type == BuiltinType.CHAR) { sub = cast(sub, BuiltinType.INT); type = BuiltinType.INT; } if (type == BuiltinType.INT || type == BuiltinType.LONG) { return new UnaryExpr(ttype, sub); } throw new ParseException("Operator "+Token.toString(ttype)+" cannot be applied to "+type); } case '{': throw new ParseException("Blocks cannot be used as expressions anymore"); case Token.NEW: { Type type = parseType(scope); // parse array constructor if (type.kind == Type.TYPE_ARRAY) { switch (t.nextToken()) { case '(': { ArrayList lengths = new ArrayList(); boolean first = true; do { if (first) { first = false; } else { t.pushBack(); expect(','); } lengths.add(cast(parseExpr(scope), BuiltinType.INT)); } while (t.nextToken() != ')'); int arrayDim = 0; Type elType = type; while (elType.kind == Type.TYPE_ARRAY) { arrayDim++; elType = ((ArrayType)elType).elementType; } if (arrayDim < lengths.size()) warn(CompilerEnv.W_ERROR, "Number of sizes exceeds array dimension"); Expr[] lengthExprs = new Expr[lengths.size()]; lengths.copyInto(lengthExprs); return new NewArrayExpr(line, type, lengthExprs); } case '{': { Type elementType = ((ArrayType)type).elementType; ArrayList initializers = new ArrayList(); boolean first = true; while (t.nextToken() != '}') { t.pushBack(); if (first) first = false; else expect(','); initializers.add(cast(parseExpr(scope), elementType)); } Expr[] initExprs = new Expr[initializers.size()]; initializers.copyInto(initExprs); return new NewArrayInitExpr(line, type, initExprs); } default: throw new ParseException("Expected '(' or '{' in array constructor"); } } // use .new() method Function newMethod = findMethod(type, "new"); if (newMethod != null) { expect('('); return parseFunctionCall(scope, new ConstExpr(line, newMethod.type, newMethod), null); } // use default constructor if (type != BuiltinType.ANY && findMethod(type.superType(), "new") != null) { warn(CompilerEnv.W_ERROR, "Type " + type + " has no constructor but parent type " + type.superType() + " has"); } if (!(type instanceof ObjectType) || ((ObjectType)type).fields == null) { throw new ParseException("Cannot use default constructor, structure of " + type + " is not defined"); } Var[] fields = ((ObjectType)type).fields; Expr[] initializers = new Expr[fields.length]; if (t.nextToken()== '(') { for (int i=0; i<initializers.length; i++) { if (i != 0) expect(','); initializers[i] = cast(parseExpr(scope), fields[i].type); } expect(')'); } else { t.pushBack(); for (int i=0; i<initializers.length; i++) { if (fields[i].defaultValue != null) { initializers[i] = new ConstExpr(line, fields[i].type, fields[i].defaultValue); } } expect('{'); boolean first = true; while (t.nextToken() != '}') { t.pushBack(); if (first) first = false; else expect(','); if (t.nextToken() != Token.WORD) throw new ParseException("Field name expected, got " + t); String fieldName = t.svalue; int i=fields.length-1; while (i >= 0) { if (fields[i].name.equals(fieldName)) break; i--; } if (i < 0) throw new ParseException("Type " + type + " has no field " + fieldName); expect('='); initializers[i] = cast(parseExpr(scope), fields[i].type); } } return new NewArrayInitExpr(line, type, initializers); } case Token.IF: { expect('('); Expr condition = cast(parseExpr(scope), BuiltinType.BOOL); expect(')'); Expr trueExpr = parseExpr(scope); expect(Token.ELSE); Expr falseExpr = parseExpr(scope); Type binaryType = binaryCastType(trueExpr.returnType(), falseExpr.returnType()); trueExpr = cast(trueExpr, binaryType); falseExpr = cast(falseExpr, binaryType); return new IfElseExpr(condition, trueExpr, falseExpr); } case Token.SWITCH: { return parseSwitchExpr(scope); } case Token.CAST: { if (env.hasOption(CompilerEnv.F_COMPAT21)) { warn(CompilerEnv.W_DEPRECATED, "In Ether 2.2 you should use syntax (expr).cast(type)"); } else { warn(CompilerEnv.W_ERROR, "Use (expr).cast(type) for type cast"); } expect('('); Type toType = parseType(scope); expect(')'); return cast(parseExprAtom(scope), toType, true); } case Token.SUPER: { Var thisVar = scope.getVar("this"); Expr superExpr = new VarExpr(line, thisVar); if (thisVar == null) warn(CompilerEnv.W_ERROR, "'this' outside of method"); Type superType = thisVar.type.superType(); if (superType == null) { warn(CompilerEnv.W_ERROR, "Type " + thisVar.type + " does not have a parent type"); } else { superExpr = new CastExpr(superExpr, superType); } return superExpr; } case Token.TRY: { Expr tryExpr = parseExpr(scope); expect(Token.CATCH); Expr catchExpr = parseExpr(scope); return new TryCatchExpr(tryExpr, catchExpr); } case Token.DEF: { // anonymous function Function lambda = new Function(unit, "#lambda" + unit.implementedFunctions.size()); // parse argument list expect('('); ArrayList args = new ArrayList(); ArrayList names = new ArrayList(); boolean first = true; while (t.nextToken() != ')') { t.pushBack(); if (first) first = false; else expect(','); if (t.nextToken() != Token.WORD) throw new ParseException("Argument name expected, got " + t); String argname = t.svalue; if (names.contains(argname)) warn(CompilerEnv.W_ERROR, "Variable " + argname + " is already defined"); names.add(argname); expect(':'); Type argtype = parseType(lambda); Var arg = new Var(argname, argtype); args.add(arg); if (t.nextToken() == '=') { Expr defValue = (Expr) cast(parseExpr(lambda), argtype).accept(constOptimizer, lambda); if (defValue.kind != Expr.EXPR_CONST) throw new ParseException("Constant expression expected"); arg.defaultValue = ((ConstExpr)defValue).value; } else { t.pushBack(); } } lambda.args = new Var[args.size()]; args.copyInto(lambda.args); Type returnType = null; if (t.nextToken() == ':') { returnType = parseType(lambda); } else { t.pushBack(); } // parse function body expect('='); ClosureScope closure = new ClosureScope(lambda, scope); Expr body = parseExpr(closure); if (returnType == null) { returnType = body.returnType(); } else { body = cast(body, returnType); } lambda.body = new ReturnStatement(body); // add closure variables int argsCount = lambda.args.length; int addedVarsCount = closure.enclosedVars.size(); if (addedVarsCount > 0) { Var[] newArgs = new Var[argsCount + addedVarsCount]; Object[] newVarNames = closure.enclosedVars.keys(); for (int i=0; i<addedVarsCount; i++) { newArgs[i] = (Var) closure.enclosedVars.get(newVarNames[i]); } System.arraycopy(lambda.args, 0, newArgs, addedVarsCount, argsCount); lambda.args = newArgs; } // fill remaining function fields lambda.hits = 1; lambda.source = files.last().toString(); Type[] argTypes = new Type[lambda.args.length]; for (int i=0; i<argTypes.length; i++) { argTypes[i] = lambda.args[i].type; } lambda.type = new FunctionType(returnType, argTypes); unit.implementedFunctions.add(lambda); // return value Expr lambdaLoad = new ConstExpr(line, lambda.type, lambda); if (addedVarsCount > 0) { Expr[] varLoaders = new Expr[addedVarsCount]; for (int i=0; i < addedVarsCount; i++) { Var var = scope.getVar(lambda.args[i].name); var.hits++; varLoaders[i] = new VarExpr(line, var); } lambdaLoad = new ApplyExpr(lambdaLoad, varLoaders); } return lambdaLoad; } default: throw new ParseException(t.toString() + " unexpected here"); } } /** Parses type expression. */ private Type parseType(Scope scope) throws IOException, ParseException { switch (t.nextToken()) { case Token.WORD: { // scalar type Type type = scope.getType(t.svalue); if (type == null) { throw new ParseException("Type " + t.svalue + " is not defined"); } return type; } case '(': { // function type ArrayList arglist = new ArrayList(); boolean first = true; while (t.nextToken() != ')') { t.pushBack(); if (first) first = false; else expect(','); arglist.add(parseType(scope)); } Type rettype; if (t.nextToken() == ':') { rettype = parseType(scope); } else { t.pushBack(); rettype = BuiltinType.NONE; } Type[] argtypes = new Type[arglist.size()]; arglist.copyInto(argtypes); return new FunctionType(rettype, argtypes); } case '[': { // array type Type elementType = parseType(scope); expect(']'); return new ArrayType(elementType); } default: throw new ParseException(t.toString() + " unexpected here"); } } private Expr makeBinaryExpr(Expr left, int op, Expr right) throws ParseException { Type ltype = left.returnType(); Type rtype = right.returnType(); Type btype = binaryCastType(ltype, rtype); // operations on primitive types and special cases switch (op) { case Token.GTGT: case Token.LTLT: case Token.GTGTGT: if (btype == BuiltinType.INT || btype == BuiltinType.LONG) { if (ltype == BuiltinType.BYTE || ltype == BuiltinType.SHORT || ltype == BuiltinType.CHAR) { ltype = BuiltinType.INT; left = cast(left, ltype); } if (rtype == BuiltinType.BYTE || rtype == BuiltinType.SHORT || rtype == BuiltinType.CHAR) { rtype = BuiltinType.INT; right = cast(right, rtype); } if (rtype == BuiltinType.INT && (ltype == BuiltinType.LONG || ltype == BuiltinType.INT)) return new BinaryExpr(left, op, right); } break; case '<': case '>': case Token.LTEQ: case Token.GTEQ: if (ltype.isNumeric() && rtype.isNumeric()) { return new ComparisonExpr(cast(left,btype), op, cast(right,btype)); } // ex.: for '<' returns 'left.cmp(right) < 0' Function cmpmethod = findMethod(ltype, "cmp"); if (cmpmethod != null && cmpmethod.type.returnType == BuiltinType.INT && cmpmethod.type.argtypes.length == 2 && rtype.safeToCastTo(cmpmethod.type.argtypes[1])) { cmpmethod.hits++; Expr call = new CallExpr(left.lineNumber(), cmpmethod, new Expr[] {left, right}); return new ComparisonExpr(call, op, new ConstExpr(-1, BuiltinType.INT, Int32.ZERO)); } break; case Token.EQEQ: case Token.NOTEQ: if (btype == BuiltinType.ANY && ltype != BuiltinType.ANY && rtype != BuiltinType.ANY) { throw new ParseException("Incomparable types " + ltype + " and " + rtype); } if (ltype != BuiltinType.NULL && rtype != BuiltinType.NULL) { Function eqmethod = findMethod(ltype, "eq"); if (eqmethod != null && eqmethod.type.returnType == BuiltinType.BOOL && eqmethod.type.argtypes.length == 2 && rtype.safeToCastTo(eqmethod.type.argtypes[1])) { // var #left = left // var #right = right // /* for == */ // if (#left == null) #right == null else ( if (#right == null) false else #left.eq(#right) ) Var[] seqVars = new Var[2]; seqVars[0] = new Var("#left", left.returnType()); seqVars[0].isConstant = true; seqVars[0].hits = 2; seqVars[1] = new Var("#right", right.returnType()); seqVars[1].isConstant = true; seqVars[1].hits = 3; Expr[] seqExprs = new Expr[] { left, right }; left = new VarExpr(-1, seqVars[0]); right = new VarExpr(-1, seqVars[1]); Expr nullExpr = new ConstExpr(-1, BuiltinType.NULL, Null.NULL); Expr leftIsNull = new ComparisonExpr(left, Token.EQEQ, nullExpr); Expr rightIsNull = new ComparisonExpr(right, Token.EQEQ, nullExpr); Expr eqCall = new CallExpr(seqExprs[0].lineNumber(), eqmethod, new Expr[] { left, right }); Expr secondIf = new IfElseExpr(rightIsNull, new ConstExpr(-1, BuiltinType.BOOL, Boolean.FALSE), eqCall); Expr firstIf = new IfElseExpr(leftIsNull, rightIsNull, secondIf); if (op == Token.NOTEQ) { firstIf = new UnaryExpr('!', firstIf); } return new SequentialExpr(seqVars, seqExprs, firstIf); } } return new ComparisonExpr(cast(left,btype), op, cast(right,btype)); case Token.AMPAMP: { // return 'if (left) right else false' if (ltype != BuiltinType.BOOL || rtype != BuiltinType.BOOL) throw new ParseException("Operator "+Token.toString(op)+" cannot be applied to "+ltype+", "+rtype); return new IfElseExpr(left, right, new ConstExpr(-1, BuiltinType.BOOL, Boolean.FALSE)); } case Token.BARBAR: { // return 'if (left) true else right' if (ltype != BuiltinType.BOOL || rtype != BuiltinType.BOOL) throw new ParseException("Operator "+Token.toString(op)+" cannot be applied to "+ltype+", "+rtype); return new IfElseExpr(left, new ConstExpr(-1, BuiltinType.BOOL, Boolean.TRUE), right); } case '+': case '-': case '*': case '/': case '%': if (ltype == BuiltinType.STRING && op == '+' && rtype != BuiltinType.NONE) { right = cast(right, BuiltinType.STRING); ConcatExpr cexpr; if (left.kind == Expr.EXPR_CONCAT) { cexpr = (ConcatExpr) left; } else { cexpr = new ConcatExpr(); cexpr.exprs.add(left); } if (right.kind == Expr.EXPR_CONCAT) { ArrayList exprs = ((ConcatExpr)right).exprs; for (int i=0; i<exprs.size(); i++) { cexpr.exprs.add(exprs.get(i)); } } else { cexpr.exprs.add(right); } return cexpr; } else if (btype.isNumeric()) { return new BinaryExpr(cast(left,btype), op, cast(right,btype)); } break; case '^': case '&': case '|': if (btype == BuiltinType.BOOL || btype == BuiltinType.INT || btype == BuiltinType.LONG) { return new BinaryExpr(cast(left,btype), op, cast(right,btype)); } break; case Token.RANGE: if (btype == BuiltinType.INT || btype == BuiltinType.LONG) { return new RangeExpr(cast(left, btype), cast(right, btype)); } break; case Token.IN: { if (rtype == BuiltinType.INTRANGE || rtype == BuiltinType.LONGRANGE) { left = cast(left, rtype == BuiltinType.INTRANGE ? BuiltinType.INT : BuiltinType.LONG); Var[] seqVars = new Var[] { new Var("#value", BuiltinType.INT) }; seqVars[0].isConstant = true; seqVars[0].hits = 2; Expr[] seqExprs = new Expr[] { left }; Expr leftVar = new VarExpr(-1, seqVars[0]); Expr comparison = new IfElseExpr( new ComparisonExpr(leftVar, Token.GTEQ, ((RangeExpr)right).fromExpr), new ConstExpr(-1, BuiltinType.BOOL, Boolean.TRUE), new ComparisonExpr(leftVar, Token.LTEQ, ((RangeExpr)right).toExpr)); return new SequentialExpr(seqVars, seqExprs, comparison); } if (rtype.kind == Type.TYPE_ARRAY) { Type elementType = ((ArrayType)rtype).elementType; Var[] seqVars = new Var[2]; seqVars[0] = new Var("#item", elementType); seqVars[1] = new Var("#array", rtype); Expr[] seqExprs = new Expr[2]; seqExprs[0] = cast(left, elementType); seqExprs[1] = right; Expr call; if (elementType.isNumeric() || elementType.kind == Type.TYPE_BOOL) { Function contains = unit.getFunction(rtype.toString() + ".contains"); call = new CallExpr(-1, contains, new Expr[] { new VarExpr(-1, seqVars[1]), new VarExpr(-1, seqVars[0]) }); } else { Function contains = unit.getFunction("[Any].contains"); Function eqfunc = findMethod(elementType, "eq"); if (eqfunc == null || eqfunc.args.length != 2) { call = new CallExpr(-1, contains, new Expr[] { new VarExpr(-1, seqVars[1]), new VarExpr(-1, seqVars[0]), new ConstExpr(-1, BuiltinType.FUNCTION, Null.NULL) }); } else { call = new CallExpr(-1, contains, new Expr[] { new VarExpr(-1, seqVars[1]), cast(new VarExpr(-1, seqVars[0]), eqfunc.type.argtypes[1]), new ConstExpr(-1, eqfunc.type, eqfunc) }); } } return new SequentialExpr(seqVars, seqExprs, call); } Function method = findMethod(rtype, "contains"); if (method != null && method.type.argtypes.length == 2) { // using sequential since order of arguments is reversed, // i. e. 'A in B' becomes 'B.contains(A)' Var[] seqVars = new Var[] { new Var("#item", ltype), new Var("#object", rtype)}; Expr[] seqExprs = new Expr[] { left, right }; VarExpr objExpr = new VarExpr(-1, seqVars[1]); VarExpr itemExpr = new VarExpr(-1, seqVars[0]); return new SequentialExpr(seqVars, seqExprs, new CallExpr(left.lineNumber(), method, new Expr[] {objExpr, itemExpr})); } throw new ParseException("Operator "+Token.toString(op)+" cannot be applied to "+ltype+", "+rtype); } } // searching method that overrides operator String methodname = null; switch (op) { case Token.LTLT: methodname = "shl"; break; case Token.GTGT: methodname = "shr"; break; case Token.GTGTGT: methodname = "ushr"; break; case Token.RANGE: methodname = "rangeTo"; break; case '+': methodname = "add"; break; case '-': methodname = "sub"; break; case '*': methodname = "mul"; break; case '/': methodname = "div"; break; case '%': methodname = "mod"; break; case '^': methodname = "xor"; break; case '&': methodname = "and"; break; case '|': methodname = "or"; break; } Function method = null; if (methodname != null) method = findMethod(ltype, methodname); if (method != null && method.type.argtypes.length == 2) { method.hits++; right = cast(right, method.type.argtypes[1]); return new CallExpr(left.lineNumber(), method, new Expr[] { left, right }); } throw new ParseException("Operator "+Token.toString(op)+" cannot be applied to "+ltype+", "+rtype); } /** * Computes type to which operands of binary operator should be cast. */ private Type binaryCastType(Type ltype, Type rtype) { if (ltype == BuiltinType.NONE || rtype == BuiltinType.NONE) return BuiltinType.NONE; if (ltype == BuiltinType.NULL) return rtype; if (rtype == BuiltinType.NULL) return ltype; if (ltype.isNumeric() && rtype.isNumeric()) { Type ctype = BuiltinType.INT; if (ltype == BuiltinType.DOUBLE || rtype == BuiltinType.DOUBLE) ctype = BuiltinType.DOUBLE; else if (ltype == BuiltinType.FLOAT || rtype == BuiltinType.FLOAT) ctype = BuiltinType.FLOAT; else if (ltype == BuiltinType.LONG || rtype == BuiltinType.LONG) ctype = BuiltinType.LONG; return ctype; } return Type.commonSuperType(ltype, rtype); } private Function findGetter(Type ownertype, String name) throws ParseException { String gettername = "get" + Character.toUpperCase(name.charAt(0)) + name.substring(1); Function getter = findMethod(ownertype, gettername); if (getter == null && env.hasOption(CompilerEnv.F_COMPAT21)) { getter = findMethod(ownertype, "get_" + name); } return getter; } private Function findSetter(Type ownertype, String name) throws ParseException { String gettername = "set" + Character.toUpperCase(name.charAt(0)) + name.substring(1); Function getter = findMethod(ownertype, gettername); if (getter == null && env.hasOption(CompilerEnv.F_COMPAT21)) { getter = findMethod(ownertype, "set_" + name); } return getter; } private Function findMethod(Type ownertype, String name) throws ParseException { Type stype = ownertype; while (stype != null) { Var mvar = unit.getVar(stype.name+'.'+name); if (mvar != null) { if (mvar.isConstant && mvar.type.kind == Type.TYPE_FUNCTION) { return (Function) mvar.defaultValue; } else { throw new ParseException("Cannot use variable " + mvar.name + " as method"); } } stype = stype.superType(); } return null; } private Expr cast(Expr expr, Type toType) throws ParseException { return cast(expr, toType, false); } private Expr cast(Expr expr, Type toType, boolean silent) throws ParseException { Type fromType = expr.returnType(); // safe casts if (fromType.equals(toType)) { return expr; } if (fromType == BuiltinType.NONE) { throw new ParseException("Cannot convert from " + fromType + " to " + toType); } if (fromType.safeToCastTo(toType)) { return expr; } if (toType == BuiltinType.ANY) { if (fromType.isNumeric() || fromType.kind == Type.TYPE_BOOL) return new CastExpr(expr, toType); else return expr; } if (fromType.isNumeric() && toType.isNumeric()) { return new CastExpr(expr, toType); } if (fromType == BuiltinType.NULL && !toType.isNumeric() && toType.kind != Type.TYPE_BOOL) { return new CastExpr(expr, toType); } // conversion to string if (toType == BuiltinType.STRING) { Function tostr = findMethod(fromType, "tostr"); if (tostr.args.length != 1 || tostr.type.returnType != BuiltinType.STRING) { tostr = findMethod(BuiltinType.ANY, "tostr"); } return new CallExpr(expr.lineNumber(), tostr, new Expr[] { expr }); } // array transformations if (expr.kind == Expr.EXPR_NEWARRAY_INIT && fromType.kind == Type.TYPE_ARRAY && toType.kind == Type.TYPE_ARRAY) { NewArrayInitExpr newarray = (NewArrayInitExpr) expr; Type elementTo = ((ArrayType)toType).elementType; for (int i=0; i<newarray.initializers.length; i++) { newarray.initializers[i] = cast(newarray.initializers[i], elementTo); } return new NewArrayInitExpr(newarray.lineNumber(), toType, newarray.initializers); } // unsafe casts if (toType.safeToCastTo(fromType) || fromType == BuiltinType.ANY) { if (!silent) { warn(CompilerEnv.W_TYPECAST, "Unsafe type cast from " + fromType + " to " + toType + "\n Use explicit cast() to suppress this message"); } return new CastExpr(expr, toType); } throw new ParseException("Cannot convert from " + fromType + " to " + toType); } private Object defaultValue(Type type) { switch (type.kind) { case Type.TYPE_BOOL: return Boolean.FALSE; case Type.TYPE_BYTE: case Type.TYPE_CHAR: case Type.TYPE_SHORT: case Type.TYPE_INT: return Int32.ZERO; case Type.TYPE_LONG: return new Int64(0); case Type.TYPE_FLOAT: return new Float32(0); case Type.TYPE_DOUBLE: return new Float64(0); default: return null; } } /** Reads next token and if it is not the given, throws exception. */ private void expect(int ttype) throws ParseException, IOException { if (t.nextToken() != ttype) { throw new ParseException("Expected '" + Token.toString(ttype) + "', got " + t.toString()); } } private void warn(int category, String message) { env.warn((String)files.last(), t.lineNumber(), category, message); } private class ClosureScope implements Scope { private Scope parent; private Function lambda; public HashMap enclosedVars = new HashMap(); public ClosureScope(Function lambda, Scope parent) { this.parent = parent; this.lambda = lambda; } public Function enclosingFunction() { return lambda; } public Type getType(String name) { return lambda.getType(name); } public Var getVar(String name) { // trying local/global var Var var = lambda.getVar(name); if (var != null) return var; // trying closure var var = (Var) enclosedVars.get(name); if (var != null) return var; // enclosing var from parent scope var = parent.getVar(name); if (var != null) { if (!var.isConstant) warn(CompilerEnv.W_ERROR, "Variable " + name + " is not constant"); Var newVar = new Var(var.name, var.type); newVar.isConstant = true; newVar.defaultValue = var.defaultValue; enclosedVars.set(name, newVar); return newVar; } return var; } } }