Newer
Older
zweic / sources / zweic / Analyzer.scala
/*  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);

}