/* 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));
var offset = 4;
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);
offset = offset + 4;
f.name.sym = VarSymbol(f.pos, f.name.name, at);
f.name.sym.offset = offset;
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);
}