/* zweic -- a compiler for Zwei * * Stephane Micheloud & LAMP * * $Id$ */ package zweic; /** * This class implements the semantic analyzer of zweic. */ class Analyzer { import Operators._; import scala.collection.immutable.{Map, ListMap}; // Global scope of classes type ClassScope = Map[String, ClassSymbol]; var classScope: ClassScope = ListMap.Empty; // For local scopes of variables type VarScope = Map[String, VarSymbol]; val emptyVarScope: VarScope = ListMap.Empty; // Analyze a program def analyzeProgram(tree: Program): Unit = tree match { case Program(classes, main) => classes.foreach(analyzeClass); analyzeExpr(emptyVarScope, main); () } // Analyze a class private def analyzeClass(tree: ClassDef): Unit = tree match { case ClassDef(name, None, members) => classScope.get(name.name) match { case Some(c) => Report.error(tree.pos, "class '" + name + "' already defined") case None => val cs = ClassSymbol(tree.pos, name.name, None); classScope = classScope + name.name -> cs; name.sym = cs; members.foreach(x => analyzeMember(cs,x)) } case ClassDef(name, extend, members) => classScope.get(name.name) match { case Some(c) => Report.error(tree.pos, "class '" + name + "' already defined"); case None => classScope.get(extend.get.name) match { case None => Report.error(tree.pos, "superclass '" + extend.get.name + "' not defined") case _ => () } val cs = ClassSymbol(tree.pos, name.name, classScope.get(extend.get.name)); classScope = classScope + name.name -> cs; name.sym = cs; members.foreach(x => analyzeMember(cs,x)) } } // Analyze a member private def analyzeMember(ownerClass: ClassSymbol, tree: Member): Unit = tree.match { case FieldDecl(Formal(name, typ)) => ownerClass.lookupField(name.name) match { case Some(v) => Report.error(tree.pos, "class variable '" + name + "' already defined"); case None => name.sym = FieldSymbol(tree.pos, name.name, analyzeType(typ)); ownerClass.enterField(name.sym.asInstanceOf[FieldSymbol]); } case MethodDef(name, args, rtype, expr) => var myVarScope = emptyVarScope; //XXX tree.asInstanceOf[MethodDef].self = Name(ownerClass.name); myVarScope = myVarScope + "this" -> VarSymbol(ownerClass.pos, "this", IClassType(ownerClass)); val paramtypes = for ( val f <- args ) yield { //TODO: check that there isn't already a class field with the same name myVarScope.get(f.name.name) match { case Some(m) => Report.error(f.pos, "an argument with the name '" + f.name.name + "' exists already"); case None => () } val at = analyzeType(f.typ); f.name.sym = VarSymbol(f.pos, f.name.name, at); myVarScope = myVarScope + f.name.name -> f.name.sym.asInstanceOf[VarSymbol]; at } val rt = analyzeType(rtype); name.sym = MethodSymbol(tree.pos, name.name, paramtypes, rt); ownerClass.lookupMethod(name.name) match { case Some(m) => //TODO: check that old returntype is subtype of new returntype and args are subtypes of old args checkSubtype(tree.pos, "redefinition of class method '" + name + "' has incompatible returntype", rt, m.restype); if (paramtypes.length != m.paramtypes.length) { Report.error(tree.pos, "wrong number of arguments for class method '" + name + "' redefinition"); } if ( !m.paramtypes.zip(paramtypes).forall(x:Pair[Type,Type] => x._1.isSubtype(x._2)) ) { Report.error(tree.pos, "incompatible argument types for class method redefinition: \n required: " + paramtypes.foldLeft("")((a,b)=>a + " " + b) + "\n found: " + m.paramtypes.foldLeft("")((a,b)=>a + " " + b) ); } ownerClass.enterMethod(name.sym.asInstanceOf[MethodSymbol]); case None => ownerClass.enterNewMethod(name.sym.asInstanceOf[MethodSymbol]); } checkSubtype(tree.pos, "returntype mismatch", analyzeExpr(myVarScope, expr), rt); } // Analyze a statement private def analyzeStat(varScope: VarScope, tree: Stat): VarScope = tree match { case While(cond, stats) => checkIntType(cond.pos, analyzeExpr(varScope, cond)); stats.foldLeft(varScope)(analyzeStat); varScope case Var(name, typ, expr) => varScope.get(name.name) match { case Some(v) => Report.error(tree.pos, "variable '" + name + "' already defined"); varScope case None => val mt = analyzeType(typ); checkSubtype(tree.pos, "incompatible types", analyzeExpr(varScope, expr), mt); name.sym = VarSymbol(tree.pos, name.name, mt); varScope + name.name -> name.sym.asInstanceOf[VarSymbol]; } case Set(ident, expr) => varScope.get(ident.name) match { case Some(v) => ident.sym = v; checkSubtype(tree.pos, "incompatible types", analyzeExpr(varScope, expr), v.vartype); case None => Report.error(tree.pos, "unknown variable '" + ident.name + "'"); } varScope case Do(expr) => analyzeExpr(varScope, expr); varScope case PrintInt(expr) => checkIntType(tree.pos, analyzeExpr(varScope, expr)); varScope case PrintChar(expr) => checkIntType(tree.pos, analyzeExpr(varScope, expr)); varScope } // Analyze a type private def analyzeType(tree: TypeTree): Type = tree match { case IntType() => IIntType case NullType() => INullType case ClassType(name) => classScope.get(name.name) match { case Some(c) => name.sym = c; IClassType(c) case None => Report.error(tree.pos, "type '" + name + "' is unknown"); IBadType } } // Analyze an expression private def analyzeExpr(varScope: VarScope, tree: Expr): Type = tree match { // ... à compléter ... case Ident(name) => varScope.get(name.name) match { case Some(v) => name.sym = v; v.vartype case None => Report.error(tree.pos, "unknown variable '" + name + "'"); IBadType } case Select(expr, name) => val ct = analyzeExpr(varScope, expr); ct match { case IClassType(c) => c.lookupField(name.name) match { case Some(v) => name.sym = v; v.fieldtype case None => Report.error(tree.pos, "unknown class variable '" + name + "' in " + c); IBadType; } case _ => Report.error(tree.pos, "class variable '" + name.name + "' does not exist"); IBadType; } case Call(expr, name, args) => val ct = analyzeExpr(varScope, expr); //XXX: this implies checking if class exists ct match { case IClassType(c) => c.lookupMethod(name.name) match { case Some(v) => if (args.length != v.paramtypes.length) { Report.error(tree.pos, "wrong number of arguments for method '" + name + "' of " + c); IBadType } val argtypes = args.map(x => analyzeExpr(varScope, x)); if ( !argtypes.zip(v.paramtypes).forall( x:Pair[Type,Type] => x._1.isSubtype(x._2)) ) { Report.error(tree.pos, "incompatible argument types for class method: \n required: " + v.paramtypes.foldLeft("")((a,b)=>a + " " + b) + "\n found: " + argtypes.foldLeft("")((a,b)=>a + " " + b) ); IBadType; } name.sym = v; v.restype case None => Report.error(tree.pos, "unknown method '" + name + "' in " + c); IBadType } case _ => Report.error(tree.pos, "method '" + name.name + "' does not exist"); IBadType; } case New(name, args) => classScope.get(name.name) match { case Some(v) => if (args.length != v.allFields.length) { Report.error(tree.pos, "wrong number of arguments for constructor of " + v); IBadType } val argtypes = args.map(x => analyzeExpr(varScope, x)); val fieldtypes = v.allFields.map(y => y.fieldtype); if ( !argtypes.zip(fieldtypes).forall( x:Pair[Type, Type] => x._1.isSubtype(x._2)) ) { Report.error(tree.pos, "incompatible argument types for class constructor: \n required: " + fieldtypes.foldLeft("")((a,b) => a + " " + b) + "\n found: " + argtypes.foldLeft("")((a,b) => a + " " + b)); IBadType; } name.sym = v; IClassType(v) case None => Report.error(tree.pos, "class '" + name + "' is unknown"); IBadType } case NullLit() => INullType case IntLit(_) => IIntType case Unop(op, expr) => checkIntType(tree.pos, analyzeExpr(varScope, expr)); IIntType case Binop(op, left, right) => val t1 = analyzeExpr(varScope, left); val t2 = analyzeExpr(varScope, right); if ( op == EQ || op == NE ) { if (!( t1.isSubtype(t2) || t2.isSubtype(t1) )) { Report.error(tree.pos, "incompatible types: " + t1 + " <=> " + t2); } } else { checkIntType(left.pos, t1); checkIntType(right.pos, t2); } IIntType case ReadInt() => IIntType case ReadChar() => IIntType case If(cond, stata, statb) => checkIntType(cond.pos, analyzeExpr(varScope, cond)); val ta = analyzeExpr(varScope, stata); val tb = analyzeExpr(varScope, statb); ta.lub(tb) match { case Some(v) => v case None => Report.error(tree.pos, "if...else block has incompatible return types: " + ta + " <=> " + tb); IBadType } case Block(stats, expr) => analyzeExpr(stats.foldLeft(varScope)(analyzeStat), expr) } /** * Generates an error if the expected type is not equal * to the found type. */ private def checkSametype(pos: Int, found: Type, expected: Type): Unit = if (!found.isSametype(expected)) error(pos, "type mismatch", found, expected); /** * Generates an error if the found type is not equal to IIntType. */ private def checkIntType(pos: Int, found: Type): Unit = checkSametype(pos, found, IIntType); /** * Generates an error if the expected type is not a super type * of the found type. */ private def checkSubtype(pos: Int, header: String, found: Type, expected: Type) = if (!found.isSubtype(expected)) error(pos, header, found, expected); /** * Generates an error message for unexpected types. */ private def error(pos: Int, header: String, found: Type, expected: Type) = Report.error(pos, header + "\n expected type: " + expected + "\n" + " actual type: " + found); }