import java.util.*;

/* Copyright Andrew Tolmach 2002-2004. All rights reserved. */

class Ast {
  
  public abstract static class Node {
    int line;
    Node (int line) {
      this.line = line;
    }
    private static final String newline =  System.getProperty("line.separator");
    private static String tabs(int tab) {
      String r = newline;
      for (int i = 0; i< tab; i++)
        r += "  ";
      return r;
    }
    abstract String toPretty(int tab);     
    String format(int tab,String desc) {
      return tabs(tab) + "(" + line + " " + desc + ")";
    }
    String formatList(int tab, Node[] nodes) {
      String r = "(";
      for (int i = 0; i < nodes.length; i++)
        r += nodes[i].toPretty(tab);
      r += ")";
      return r;
    }
    public String toString() {
      String r = this.toPretty(0);
      return r.substring(newline.length());
    }
  }


  public static class Program extends Node {
    Body body;
    Program (int line, Body body) {
      super(line);
      this.body = body;
    }
    String toPretty(int tab) {
      return format(tab,"Program" + body.toPretty(tab+1));
    }
    void check() throws CheckError {
      Env env = Env.empty;
      env = new Env(new TypeDec(0, "INTEGER", new BuiltinType()), 0, env);
      env = new Env(new TypeDec(0, "REAL", new BuiltinType()), 0, env);
      env = new Env(new TypeDec(0, "?RECORD", new BuiltinType()), 0, env);  // hidden type for polymorphic records (i.e. NIL)
      env = new Env(new TypeDec(0, "?STRING", new BuiltinType()), 0, env);  // hidden type for string literals
      env = new Env(new ConstDec("NIL", "?RECORD"), 0, env); 
      body.check(1, null, env);
    }
  }

  public static class Body extends Node {
    Decs[] decslist;
    St statement;
    Body (int line, Decs[] decslist, St statement) {
      super(line); 
      this.decslist = decslist; this.statement = statement;
    }
    Body (int line, List decslist, St statement) {
	this(line,(Decs[]) decslist.toArray(new Decs[0]),statement);
    }
    public String toPretty(int tab) {
      return format(tab, "BodyDef " + formatList(tab+1,decslist) + statement.toPretty(tab+1));
    }
    void check(int level, String expectedReturnType, Env env) throws CheckError {
      for (int i = 0; i < decslist.length; i++)
	env = decslist[i].check(level, env);
      statement.check(expectedReturnType, level, env);
    }
  }

  public abstract static class Decs extends Node {
    Decs(int line) {
      super(line);
    }
    abstract Env check(int level, Env env) throws CheckError;
  }

  public static class VarDecs extends Decs {
    VarDec[] vardeclist;
    VarDecs (int line, VarDec[] vardeclist) {
      super(line);
      this.vardeclist = vardeclist;
    }
    VarDecs (int line, List vardeclist) {
      this(line,(VarDec[]) vardeclist.toArray(new VarDec[0]));
    }
    String toPretty(int tab) {
      return format(tab, "VarDecs " + formatList(tab+1,vardeclist));
    }
    Env check(int level, Env env) throws CheckError {
      for (int i = 0; i< vardeclist.length; i++) 
	env = vardeclist[i].check(level, env);
      return env;
    }
  }

  public static class TypeDecs extends Decs {
    TypeDec[] typedeclist;
    TypeDecs (int line,TypeDec[] typedeclist) {
      super(line);
      this.typedeclist = typedeclist;
    }
    TypeDecs (int line, List typedeclist) {
      this(line,(TypeDec[]) typedeclist.toArray(new TypeDec[0]));
    }
    String toPretty(int tab) {
      return format(tab, "TypeDecs " + formatList(tab+1,typedeclist));
    }
    Env check(int level, Env env) throws CheckError {
      for (int i = 0; i< typedeclist.length; i++) 
	env = typedeclist[i].preCheck(level, env);
      for (int i = 0; i< typedeclist.length; i++) 
	typedeclist[i].check(env);
      return env;
    }
  }

  public static class ProcDecs extends Decs {
    ProcDec[] procdeclist;
    ProcDecs (int line, ProcDec[] procdeclist) {
      super(line);
      this.procdeclist = procdeclist;
    }
    ProcDecs (int line, List procdeclist) {
      this(line,(ProcDec[]) procdeclist.toArray(new ProcDec[0]));
    }
    String toPretty(int tab) {
      return format(tab, "ProcDecs " + formatList(tab+1,procdeclist));
    }
    Env check(int level, Env env) throws CheckError {
      for (int i = 0; i< procdeclist.length; i++) 
	env = procdeclist[i].preCheck(level, env);
      for (int i = 0; i< procdeclist.length; i++) 
	procdeclist[i].check(level, env);
      return env;
    }
  }

  public abstract static class Dec extends Node {
    String name;
    Dec(int line, String name) {
      super(line);
      this.name = name;
    }
  }

  public static class VarDec extends Dec {
    String type;   // can be null on construction, but should be filled in by checker
    Exp initializer;
    VarDec(int line, String name, String type, Exp initializer) {
      super(line,name); 
      this.type = type; this.initializer = initializer;
    }
    String toPretty(int tab) {
      return format(tab,"VarDec " + name + " " + 
		    (type != null ? type : "-") + initializer.toPretty(tab+1));
    }
    Env check(int level, Env env) throws CheckError {
      String t = initializer.check(env);
      if (type == null) { // no type constraint specified
	if (t.equals("?RECORD"))
	  throw new CheckError(line,"Variable initialized to NIL must have explicit type constraint");
	type = t;  
      } else
	checkTypeName(line,type,env);
      t = promoteType(type,t,env);
      if (!type.equals(t)) {
	throw new CheckError(line,"Type of initializing expression ('" + t + "') does not match declared type ('" + type + "')");
      }
      return installBinding(this,level,env);
    }
  }

  public static class TypeDec extends Dec {
    Type defn;
    TypeDec(int line, String name, Type defn) {
      super(line,name); 
      this.defn = defn;
    }
    String toPretty(int tab) {
      return format(tab, "TypeDec " + name + defn.toPretty(tab+1));
    }
    Env preCheck(int level, Env env) throws CheckError {
      // just install the binding without checking it
      //  (this allows for recursive type bindings)
      return installBinding(this,level,env);
    }
    void check(Env env) throws CheckError {
      defn.check(env);
    }
  }

  public static class ProcDec extends Dec {
    String resultType;  // can be null
    FormalParam[] formals;
    Body body;
    ProcDec(int line, String name, String resultType, FormalParam[] formals, Body body) {
      super(line,name); 
      this.resultType = resultType; this.formals = formals; this.body = body;
    }
    ProcDec(int line, String name, String resultType, List formals, Body body) {
      this(line, name, resultType, (FormalParam[]) formals.toArray(new FormalParam[0]), body);
    }
    String toPretty(int tab) {
      return format(tab, "ProcDec " + name + " " + (resultType != null ? resultType : "-") + 
		          " " + formatList(tab+1,formals) + body.toPretty(tab+1));
    }
    Env preCheck(int level, Env env) throws CheckError {
      // just install the binding without checking it
      // leave proper checking to the 'check' method
      return installBinding(this,level,env);
    }
    void check (int level, Env env) throws CheckError {
      // NOT IMPLEMENTED
    }
  }


  public static class FormalParam extends Dec {
    String type;
    FormalParam (int line, String name, String type) {
      super(line,name);
      this.type = type;
    }
    String toPretty(int tab) {
      return format(tab, "Param " + name + " " + type);
    }
    Env check(int level, Env env) throws CheckError {
      // NOT IMPLEMENTED
      return env;
    }
  }

  public static class ConstDec extends Dec { // builtins only
    String type;
    ConstDec(String name,String type) {
      super(0,name);
      this.type = type;
    }
    String toPretty(int tab) {
      return format(tab, "Const " + name + " " + type);
    }
  }

  public abstract static class Type extends Node {
    Type(int line) {
      super(line);
    }
    abstract void check(Env env) throws CheckError;
  }

  public static class ArrayType extends Type {
    String elementType;
    ArrayType (int line, String elementType) {
      super(line); 
      this.elementType = elementType;
    }
    String toPretty(int tab) {
      return format(tab, "ArrayTyp " + elementType);
    }
    void check(Env env) throws CheckError {
      // NOT IMPLEMENTED
    }
  }

  public static class RecordType extends Type {
    Component[] components;
    RecordType (int line, Component[] components) {
      super(line); 
      this.components = components;
    }
    RecordType (int line, List components) {
      this(line, (Component[]) components.toArray(new Component[0]));
    }
    String toPretty(int tab) {
      return format(tab, "RecordTyp " + formatList(tab+1,components));
    }
    void check(Env env) throws CheckError {
      Set names = new TreeSet(); // keep track of names to avoid duplicates
      for (int i = 0; i < components.length; i++)
	components[i].check(env,names);
    }
  }

  public static class BuiltinType extends Type {
    BuiltinType() {
      super(0);
    }
    String toPretty(int tab) {
      return format(tab, "BuiltinTyp");
    }
    void check(Env env) throws CheckError {}
  }

  public static class Component extends Node {
    String name;
    String type;
    Component (int line, String name, String type) {
      super(line);
      this.name = name; this.type = type;
    }
    String toPretty(int tab) {
      return format(tab, "Comp " + name + " " + type);
    }
    void check(Env env, Set names) throws CheckError {  // names contains fields already checked
      checkTypeName(line,type,env);
      if (!names.add(name))
	throw new CheckError(line, "Duplicate field name '" + name + "' in record declaration");
    }
  }
  

  public abstract static class St extends Node {
    St (int line) {
      super(line);
    }
    abstract void check(String expectedReturnType, int level, Env env) throws CheckError;
  }


  public static class AssignSt extends St {
    Lvalue lhs;
    Exp rhs;
    AssignSt(int line, Lvalue lhs, Exp rhs) {
      super(line); 
      this.lhs = lhs; this.rhs = rhs;
    }
    String toPretty(int tab) {
      return format(tab,"AssignSt" + lhs.toPretty(tab+1) + rhs.toPretty(tab+1));
    }
    void check(String expectedReturnType, int level, Env env) throws CheckError {
      // NOT IMPLEMENTED
    }
  }

  public static class CallSt extends St {
    String procName;
    Exp[] args;
    CallSt(int line, String procName, Exp[] args) {
      super(line); 
      this.procName = procName; this.args = args;
    }
    CallSt(int line, String procName, List args) {
      this(line, procName, (Exp[]) args.toArray(new Exp[0]));
    }
    String toPretty(int tab) {
      return format(tab, "CallSt " + procName + " " + formatList(tab+1,args));
    }
    void check(String expectedReturnType, int level, Env env) throws CheckError {
      Dec d = checkBinding(line,procName,env);
      if (!(d instanceof ProcDec))
	throw new CheckError(line,"Identifier '" + procName + "' is not a procedure");
      ProcDec pd = (ProcDec) d;
      if (args.length != pd.formals.length) 
	throw new CheckError(line,"Wrong number of arguments provided");
      for (int i = 0; i < args.length; i++) {
	String t1 = args[i].check(env);
	String t0 = pd.formals[i].type;
	t1 = promoteType(t0,t1,env);
	if (!t0.equals(t1))
	  throw new CheckError(line, "Argument type ('" + t1 + "') does not match declared type ('" + 
			       t0 + "') for parameter '" + pd.formals[i].name + "'");
      }
      if (pd.resultType != null) 
	throw new CheckError(line,"Procedure '" + procName + "' is a function procedure");
    }
  }

  public static class ReadSt extends St {
    Lvalue[] targets;
    ReadSt(int line, Lvalue[] targets) {
      super(line); 
      this.targets = targets;
    }
    ReadSt(int line, List targets) {
      this(line, (Lvalue[]) targets.toArray(new Lvalue[0]));
    }
    String toPretty(int tab) {
      return format(tab, "ReadSt " + formatList(tab+1,targets));
    }
    void check(String expectedReturnType, int level, Env env) throws CheckError {
      // NOT IMPLEMENTED
    }
  }

  public static class WriteSt extends St {
    Exp[] exps;
    WriteSt(int line, Exp[] exps) {
      super(line); 
      this.exps = exps;
    }
    WriteSt(int line, List exps) {
      this(line,(Exp[]) exps.toArray(new Exp[0]));
    }
    String toPretty(int tab) {
      return format(tab, "WriteSt " + formatList(tab+1,exps));
    }
    void check(String expectedReturnType, int level, Env env) throws CheckError {
      for (int i = 0; i < exps.length; i++) {
	String t = exps[i].check(env);
	if (!(t.equals("INTEGER") || t.equals("REAL") ||
	      t.equals("BOOLEAN") || t.equals("?STRING")))
	  throw new CheckError(line,"WRITE statement argument has type '" + t + "'; must be 'INTEGER', 'REAL', 'BOOLEAN', or string");
      }
    }
  }

  public static class IfSt extends St {
    Exp test;
    St ifTrue;
    St ifFalse;
    IfSt(int line, Exp test, St ifTrue, St ifFalse) {
      super(line); 
      this.test = test; this.ifTrue = ifTrue; this.ifFalse = ifFalse;
    }
    String toPretty(int tab) {
      return format(tab,"IfSt" + test.toPretty(tab+1) + ifTrue.toPretty(tab+1) + ifFalse.toPretty(tab+1));
    }
    void check(String expectedReturnType, int level, Env env) throws CheckError {
      // NOT IMPLEMENTED
    }
  }

  public static class WhileSt extends St {
    Exp test;
    St body;
    WhileSt(int line, Exp test, St body) {
      super(line); 
      this.test = test; this.body = body;
    }
    String toPretty(int tab) {
      return format(tab, "WhileSt" + test.toPretty(tab+1) + body.toPretty(tab+1));
    }
    void check(String expectedReturnType, int level, Env env) throws CheckError {
      // NOT IMPLEMENTED
    }
  }

  public static class LoopSt extends St {
    St body;
    LoopSt(int line, St body) {
      super(line); 
      this.body = body;
    }
    String toPretty(int tab) {
      return format(tab,"LoopSt" + body.toPretty(tab+1));
    }
    void check(String expectedReturnType, int level, Env env) throws CheckError {
      body.check(expectedReturnType, level, env);
    }
  }
  
  public static class ForSt extends St {
    String loopVar;
    Exp start;
    Exp stop;
    Exp step;
    St body;
    ForSt (int line, String loopVar, Exp start, Exp stop, Exp step, St body) {
      super(line); 
      this.loopVar = loopVar; this.start = start;
      this.stop = stop; this.step = step; this.body = body;
    }
    String toPretty(int tab) {
      return format(tab, "ForSt " + loopVar + start.toPretty(tab+1) + 
		          stop.toPretty(tab+1) + step.toPretty(tab+1) + body.toPretty(tab+1));
    }
    void check(String expectedReturnType, int level, Env env) throws CheckError {
      Dec d = checkBinding(line,loopVar,env);
      String t;
      if (d instanceof VarDec)
	t = ((VarDec) d).type;
      else if (d instanceof FormalParam)
	t = ((FormalParam) d).type;
      else
	throw new CheckError(line,"Index '" + loopVar + "' of FOR statement is not a variable");
      if (!t.equals("INTEGER"))
	throw new CheckError(line,"Index '" + loopVar + "' of FOR statement has type '" + t + "'; must be 'INTEGER'");
      t = start.check(env);
      if (!t.equals("INTEGER"))
	  throw new CheckError(line,"Expression in FOR statement has type '" + t + "'; must be 'INTEGER'");
      t = stop.check(env);
      if (!t.equals("INTEGER"))
	  throw new CheckError(line,"Expression in FOR statement has type '" + t + "'; must be 'INTEGER'");
      t = step.check(env);
      if (!t.equals("INTEGER"))
	  throw new CheckError(line,"Expression in FOR statement has type '" + t + "'; must be 'INTEGER'");
      body.check(expectedReturnType, level, env);
    }
  }

  public static class ExitSt extends St {
    ExitSt(int line) {
      super(line);
    }
    String toPretty(int tab) {
      return format(tab,"ExitSt");
    }
    void check(String expectedReturnType, int level, Env env) throws CheckError {
      // NOT IMPLEMENTED
    }
  }

  public static class ReturnSt extends St {
    Exp returnValue; // can be null
    ReturnSt(int line, Exp returnValue) {
      super(line); 
      this.returnValue = returnValue;
    }
    String toPretty(int tab) {
      return format(tab,"RetSt" + (returnValue != null ? returnValue.toPretty(tab+1) : " - "));
    }
    void check(String expectedReturnType, int level, Env env) throws CheckError {
      if (level == 1)
	  throw new CheckError(line,"RETURN statement not allowed in Main program body");
      if (expectedReturnType != null) {
	if (returnValue != null) {
	  String t = returnValue.check(env);
	  t = promoteType(expectedReturnType,t,env);
	  if (!expectedReturnType.equals(t)) 
	    throw new CheckError(line,"RETURN expression type ('" + t + 
				 "') does not match declared procedure return type ('" + expectedReturnType + "')");
	} else
	  throw new CheckError(line,"RETURN from function procedure must have result expression");
      } else 
	if (returnValue != null) 
	  throw new CheckError(line,"RETURN from proper procedure does not allow result expression");
    }
  }

  public static class SequenceSt extends St {
    St[] statements;
    SequenceSt (int line, St[] statements) {
      super(line);
      this.statements = statements;
    }
    SequenceSt (int line, List statements) {
      this(line,(St[]) statements.toArray(new St[0]));
    }
    String toPretty(int tab) {
      return format(tab, "SeqSt " + formatList(tab+1,statements));
    }
    void check(String expectedReturnType, int level, Env env) throws CheckError {
      for (int i = 0; i < statements.length; i++)
	statements[i].check(expectedReturnType, level,env);
    }
  }

  public abstract static class Exp extends Node {
    Exp (int line) {
      super(line);
    }
    abstract String check(Env env) throws CheckError ;
  }

  // BinOp codes
  public static final int  
    LT = 0,
    LEQ = 1,
    GT = 2,
    GEQ = 3,
    EQ = 4,
    NEQ = 5,
    PLUS = 6,
    MINUS = 7,
    TIMES = 8,
    SLASH = 9,
    DIV = 10,
    MOD = 11,
    AND = 12,
    OR = 13;
  
  public static class BinOpExp extends Exp {
    int binOp;
    Exp left;
    Exp right;
    BinOpExp(int line, int binOp, Exp left, Exp right) {
      super(line);
      this.binOp = binOp; this.left = left; this.right = right;
    }
    private static String[] binOpName = {"LT","LEQ","GT","GEQ","EQ","NEQ","PLUS","MINUS","TIMES","SLASH",
				  "DIV","MOD","AND","OR"};
    String toPretty(int tab) {
      return format(tab,"BinOpExp " + binOpName[binOp] + left.toPretty(tab+1) + right.toPretty(tab+1));
    }
    String check(Env env) throws CheckError {
      String t1 = left.check(env);
      String t2 = right.check(env);
      switch (binOp) {
      case PLUS:
      case MINUS:
      case TIMES:
	if (t1.equals("INTEGER") && t2.equals("INTEGER")) 
	  return "INTEGER";
	// fall through
      case SLASH:
	if (!numericType(t1))
	  throw new CheckError(line, "Operand has type '" + t1 + "'; must be 'INTEGER' or 'REAL'");
	if (!numericType(t2))
	  throw new CheckError(line, "Operand has type '" + t2 + "'; must be 'INTEGER' or 'REAL'");
	return "REAL";
      default:
	// NOT IMPLEMENTED
	return "?"; /* shouldn't happen */
      }
    }
  }

  // UnOp codes
  public static final int
    UMINUS = 0,
    NOT = 1;

  public static class UnOpExp extends Exp {
    int unOp;
    Exp operand;
    UnOpExp(int line, int unOp, Exp operand) {
      super(line);
      this.unOp = unOp; this.operand = operand;
    }
    private static String[] unOpName = {"UMINUS","NOT"};
    String toPretty(int tab) {
      return format(tab, "UnOpExp " + unOpName[unOp] + operand.toPretty(tab+1));
    }
    String check(Env env) throws CheckError {
      // NOT IMPLEMENTED
      return "?"; /* shouldn't happen */
    }
  }

  public static class LvalExp extends Exp {
    Lvalue lval;
    LvalExp(int line, Lvalue lval) {
      super(line);
      this.lval = lval;
    }
    String toPretty(int tab) {
      return format(tab, "LvalExp" + lval.toPretty(tab+1));
    }
    String check(Env env) throws CheckError {
      return lval.check(false,env);
    }
  }

  public static class CallExp extends Exp {
    String procName;
    Exp[] args;
    CallExp(int line, String procName, Exp[] args) {
      super(line);
      this.procName = procName; this.args = args;
    }
    CallExp(int line, String procName, List args) {
      this(line, procName, (Exp[]) args.toArray(new Exp[0]));
    }
    String toPretty(int tab) {
      return format(tab, "CallExp " + procName + " " + formatList(tab+1,args));
    }
    String check(Env env) throws CheckError {
      // NOT IMPLEMENTED
      return "?";
    }
  }


  public static class ArrayExp extends Exp {
    String typeName;
    ArrayInit[] initializers;
    ArrayExp(int line, String typeName, ArrayInit[] initializers) {
      super(line);
      this.typeName = typeName; this.initializers = initializers;
    }
    ArrayExp(int line, String typeName, List initializers) {
      this(line, typeName, (ArrayInit[]) initializers.toArray(new ArrayInit[0]));
    }
    String toPretty(int tab) {
      return format(tab, "ArrayExp " + typeName + " " + formatList(tab+1,initializers));
    }
    String check(Env env) throws CheckError {
      // NOT IMPLEMENTED
      return "?";
    }
  }

  public static class RecordExp extends Exp {
    String typeName;
    RecordInit[] initializers;
    RecordExp(int line, String typeName, RecordInit[] initializers) {
      super(line);
      this.typeName = typeName; this.initializers = initializers;
    }
    RecordExp(int line, String typeName, List initializers) {
      this(line, typeName, (RecordInit[]) initializers.toArray(new RecordInit[0]));
    }
    String toPretty(int tab) {
      return format(tab, "RecordExp " + typeName + " " + formatList(tab+1,initializers));
    }
    String check(Env env) throws CheckError {
      Type t0 = checkTypeName(line,typeName,env);
      if (!(t0 instanceof RecordType)) 
	throw new CheckError(line,"Identifier '" + typeName +"' is not a record type name");
      Component[] comps = ((RecordType) t0).components;
      boolean[] present = new boolean[comps.length];
      for (int i = 0; i < present.length; i++)
	present[i] = false;
      for (int i = 0; i < initializers.length; i++)
	initializers[i].check(comps,present,env);
      for (int i = 0; i < present.length; i++)
	if (!present[i])
	  throw new CheckError(line,"Record initializer expression has missing field(s)");
      return typeName;
    }
  }

  public static class IntLitExp extends Exp {
    int lit;
    IntLitExp(int line, int lit) {
      super(line);
      this.lit = lit;
    }
    IntLitExp(int line, Integer lit) {
      this(line, lit.intValue());
    }
    String toPretty(int tab) {
      return format(tab, "IntLit " + lit);
    }
    String check(Env env) throws CheckError {
      return "INTEGER";
    }
  }

  public static class RealLitExp extends Exp {
    String lit;
    RealLitExp(int line, String lit) {
      super(line);
      this.lit = lit;
    }
    String toPretty(int tab) {
      return format(tab, "RealLit \"" + lit + "\"");
    }
    String check(Env env) throws CheckError {
      return "REAL";
    }
  }

  public static class StringLitExp extends Exp {
    String lit;
    StringLitExp(int line, String lit) {
      super(line);
      this.lit = lit;
    }
    String toPretty(int tab) {
      return format(tab, "StringLit \"" + lit + "\"");
    }
    String check(Env env) throws CheckError {
      return "?STRING";
    }
  }

  public static class ArrayInit extends Node {
    Exp count;
    Exp value;
    ArrayInit(int line, Exp count, Exp value) {
      super(line); 
      this.count = count; this.value = value;
    }
    String toPretty(int tab) {
      return format(tab, "ArrayInit" + count.toPretty(tab+1) + value.toPretty(tab+1));
    }
    void check(String t,Env env) throws CheckError {
      // NOT IMPLEMENTED
    }
  }

  public static class RecordInit extends Node {
    String name;
    Exp value;
    RecordInit(int line, String name, Exp value) {
      super(line);
      this.name = name; this.value = value;
    }
    String toPretty(int tab) {
      return format(tab, "RecordInit " + name + value.toPretty(tab+1));
    }
    void check(Component[] comps,boolean[] present,Env env) throws CheckError {  
      for (int i = 0; i < comps.length; i++) 
	if (comps[i].name.equals(name)) {
	  if (present[i]) 
	    throw new CheckError(line,"Repeated field '" + name + "' in record initializer");
	  present[i] = true;
	  String t0 = comps[i].type;
	  String t = value.check(env);
	  t = promoteType(t0,t,env);
	  if (!t0.equals(t))
	    throw new CheckError(line,"Type of expression ('" +  t + "') does not match type of field '" + 
				 name + "' ('" + t0 + "')");
	  return;
	}
      throw new CheckError(line,"Undefined field '" + name + "' in record initializer");
    }
  }

  public abstract static class Lvalue extends Node {
    Lvalue(int line) {
      super(line);
    }
    abstract String check(boolean assignable, Env env) throws CheckError;
  }

  public static class VarLvalue extends Lvalue {
    String name;
    VarLvalue(int line, String name) {
      super(line);
      this.name = name;
    }
    String toPretty(int tab) {
      return format(tab, "Var " + name);
    }
    String check(boolean assignable, Env env) throws CheckError {
      Dec d = checkBinding(line,name,env);
      if (d instanceof VarDec)
	return ((VarDec) d).type;
      if (d instanceof FormalParam)
	return ((FormalParam) d).type;
      if (assignable)
	throw new CheckError(line,"Identifier '" + name + "' is not a variable");
      if (d instanceof ConstDec)
	return ((ConstDec) d).type;
      throw new CheckError(line,"Identifier '" + name + "' is not a variable or constant");
    }
  }

  public static class ArrayDerefLvalue extends Lvalue {
    Lvalue array;
    Exp index;
    ArrayDerefLvalue(int line, Lvalue array, Exp index) {
      super(line);
      this.array = array; this.index = index;
    }
    String toPretty(int tab) {
      return format(tab, "ArrayDeref" + array.toPretty(tab+1) + index.toPretty(tab+1));
    }
    String check(boolean assignable, Env env) throws CheckError {
      // NOT IMPLEMENTED
      return "?";
    }
  }
      
  public static class RecordDerefLvalue extends Lvalue {
    Lvalue record;
    String name;
    RecordDerefLvalue(int line, Lvalue record, String name) {
      super(line);
      this.record = record; this.name = name;
    }
    String toPretty(int tab) {
      return format(tab, "RecordDeref" + record.toPretty(tab+1) + " " + name);
    }
    String check(boolean assignable, Env env) throws CheckError {
      String t = record.check(true,env);
      TypeDec d = (TypeDec) find(t,env).dec; // assume ok
      if (d.defn instanceof RecordType) {
	Component[] comps = ((RecordType)(d.defn)).components;
	for (int i = 0; i < comps.length; i++) 
	  if (comps[i].name.equals(name))
	    return comps[i].type;
	throw new CheckError(line,"Field '" + name + "' does not appear in this record");
      }
      throw new CheckError(line,"Dereferenced expression is not a record");
    }
  }

  // Utility classes and functions for type-checking

  public static class CheckError extends Exception {
    CheckError(int line, String text) {
      super("Error at line " + line + ": " + text);
    }
  }

  public static class Env {
    Dec dec;
    int level;
    Env next;

    /** Empty environment is just a null Env. */
    static final Env empty = null;

    /** Creates new Env by extending an existing Env with a new declaration binding.
     */
    Env(Dec dec, int level, Env next) {
      this.dec = dec; this.level = level; this.next = next;
    }

    public String toString() {
      String r = "[ ";
      for (Env env = this; env != null; env = env.next) {
	r += env.dec;
	r += level + "\n";
      }
      r += "]";
      return r;
    }
  }

  static Env find(String name,Env env) {
    for (; env != null; env = env.next)
      if (env.dec.name.equals(name))
	return env;
    return null;
    }

  static Dec checkBinding(int line, String name, Env env) throws CheckError {
    Env e = find(name,env);
    if (e == null)
      throw new CheckError(line,"Identifier '" + name + "' is not defined");
    return e.dec;
  }

  static Env installBinding(Dec d, int level, Env env) throws CheckError {
    Env e = find(d.name,env);
    if (e != null) {
      if (e.level == level) 
	throw new CheckError(d.line,"Identifier '" + d.name + "' is already defined (at line " + e.dec.line + ")");
      if (e.dec instanceof TypeDec)
	throw new CheckError(d.line,"Identifier '" + d.name + "' is a type name and cannot be redefined");
    }
    return new Env(d,level,env);
  }

  static Type checkTypeName(int line, String name, Env env) throws CheckError {
    Dec d = checkBinding(line,name,env);
    if (d instanceof TypeDec) 
      return ((TypeDec) d).defn;
    else
      throw new CheckError(line,"Identifier '" + name + "' is not a type name");
  }

  static String promoteType(String t0, String t, Env env) {
    if (t0.equals("REAL") && t.equals("INTEGER"))
      return t0;
    if (t.equals("?RECORD")) {
      TypeDec d = (TypeDec) (find(t0,env).dec);  // assume ok
      if (d.defn instanceof RecordType)
	return t0;
    }
    return t;
  }

  static boolean numericType(String t) {
    return t.equals("INTEGER") || t.equals("REAL");
  }
}

