import java.io.*;
import java.util.*;

class IRGen {

  // Support class for describing IR operands

  static abstract class Operand {
  };

  static class RegTemp extends Operand {   // Temporary register %ti
    int i;
    RegTemp(int i) {
      this.i = i;
    }
    public String toString () {
      return "%t" + i;
    }
  }
  static class RegO0 extends Operand {  // Register %o0 (place to get function return value)
    public String toString () {
      return"%o0";
    }
  }
  static final RegO0 REGO0 = new RegO0();
  static class RegI0 extends Operand {  // Register %i0 (place to set function return value)
    public String toString () {
      return "%i0";
    }
  }
  static final RegI0 REGI0 = new RegI0();
  static class IntLit extends Operand { // Integer literal
    int i;
    IntLit(int i) {
      this.i = i;
    }
    public String toString () {
      return "" + i;
    }
  }
  static final IntLit ZERO = new IntLit(0);
  static final IntLit ONE = new IntLit(1);
  static final IntLit MINUSONE = new IntLit(-1);
  static class AddrString extends Operand { // Address of string literal
    String s;
    AddrString(String s) {
      this.s = s;
    }
    public String toString () {
      return "\"" + s + "\"";
    }
  }
  static class AddrArg extends Operand { // Address of call argument $ai
    int i;
    AddrArg(int i) {
      this.i = i;
    }
    public String toString () {
      return "$a" + i;
    }
  }
  static class AddrName extends Operand { // Named address
    String s;
    AddrName(String s) {
      this.s = s;
    }
    public String toString () {
      return s;
    }
  }
  static class AddrNull extends Operand { // Null address
    public String toString () {
      return "$null";
    }
  }
  static final AddrNull ADDRNULL = new AddrNull();
  static class Label extends Operand { // Numeric label
    int i;
    Label(int i) {
      this.i = i;
    }
    public String toString () {
      return "L" + i;
    }
  }

  // Support methods for emiting code

  static void emit0(String op)
  {
    System.out.println("\t" + op);
  }

  static void emit1(String op, Operand rand1)
  {
    System.out.println("\t" + op + " " + rand1);
  }

  static void emit2(String op, Operand rand1, Operand rand2) {
    System.out.println("\t" + op + " " + rand1 + "," + rand2);
  }

  static void emitSt(Operand rand1, Operand rand2)
  {
    System.out.println("\tst " + rand1 + ",[" + rand2 + "]");
  }

  static void emitLd(Operand rand1, Operand rand2)
  {
    System.out.println("\tld [" + rand1 + "]," + rand2);
  }

  static void emit3(String op, Operand rand1, Operand rand2, Operand rand3) {
    System.out.println("\t" + op + " " + rand1 + "," + rand2 + "," + rand3);
  }

  static void emitLabel(int i)
  {
    System.out.println("L" + i + ":");
  }

  // Handy globals for numbering temporaries and albels.

  static int nextTemp = 0;
  static int nextLabel = 0;

  static void gen(Ast.Program p) {
    gen(p.body);
  }

  static void gen(Ast.Body b) {
    nextLabel = 0;
    nextTemp = 0;
    for (int i = 0; i < b.decslist.length; i++)
      gen(b.decslist[i]);
    System.out.println("CODE");
    gen(b.statement,0);
    emit2("mov",ZERO,REGI0);
    emit0("return");
    System.out.println("ENDCODE");
  }

  static void gen(Ast.Decs d) {
    class DecsVisitor implements Ast.DecsVisitor {
      public Object visit(Ast.VarDecs d) {
	for (int i = 0; i< d.vardeclist.length; i++) {
	  System.out.println("VAR " + d.vardeclist[i].name);
	  System.out.println("CODE");
	  Operand t = gen(d.vardeclist[i].initializer);
	  emitSt(t, new AddrName(d.vardeclist[i].name));
	  System.out.println("ENDCODE");
	}
	return null;
      }

      public Object visit(Ast.TypeDecs d) {
	// nothing to do
	return null;
      }

      public Object visit(Ast.ProcDecs d) {
	int saveTemp = nextTemp;
	int saveLabel = nextLabel;
	System.out.println("PROCS");
	// ...
	nextTemp = saveTemp;
	nextLabel = saveLabel;
	return null;
      }
    }
    try {
      d.accept(new DecsVisitor());
    } catch (Ast.Error exn) {
    };
  }

  static void gen(Ast.St s, final int lexit) {
    class StVisitor implements Ast.StVisitor {

      public Object visit(Ast.AssignSt s) {
	// ...
	return null;
      }

      public Object visit(Ast.CallSt s) {
	// ...
	return null;
      }

      public Object visit(Ast.ReadSt s) {
	// ...
	return null;
      }

      public Object visit(Ast.WriteSt s) {
	for (int i = 0; i < s.exps.length; i++) {
	  Operand t = gen(s.exps[i]);
 	  Operand f = new AddrName((s.exps[i] instanceof Ast.StringLitExp) ? "write_string" : "write_int");
 	  emit2("scall",f,t);
	}
	emit2("scall", new AddrName("write_newline"), ZERO);
	return null;
      }

      public Object visit(Ast.IfSt s) {
	// ...
	return null;
      }

      public Object visit(Ast.WhileSt s) {
	// ...
	return null;
      }

      public Object visit(Ast.LoopSt s) {
	//...
	return null;
      }

      public Object visit(Ast.ForSt s) {
	// ...
	return null;
      }

      public Object visit(Ast.ExitSt s) {
	emit1("ba",new Label(lexit));
	return null;
      }

      public Object visit(Ast.ReturnSt s) {
	if (s.returnValue != null) {
	  Operand t = gen(s.returnValue);
	  emit2("mov",t,REGI0);
	}
	emit0("return");
	return null;
      }

      public Object visit(Ast.SequenceSt s) {
	for (int i = 0; i < s.statements.length; i++) 
	  gen(s.statements[i],lexit);
	return null;
      }
    }
    try {
      s.accept(new StVisitor());
    } catch (Ast.Error exn) {
    };
  }

  static void genCall(String procName, Ast.Exp[] args) {
    Operand[] operands = new Operand[args.length];
    for (int i = 0; i < args.length; i++) 
      operands[i] = gen(args[i]);
    for (int i = args.length - 1 ; i >= 0; i--) 
      emitSt(operands[i],new AddrArg(i));
    emit1("call", new AddrName(procName));
  }
  

  // Generate expression into an operand.
  static Operand gen(Ast.Exp e) {
    class ExpVisitor implements Ast.ExpVisitor {

      public Object visit(Ast.BinOpExp e) {
	if (e.binOp >= Ast.PLUS && e.binOp <= Ast.MOD) {
	  // arithmetic expression
	  Operand t1 = gen(e.left);
	  Operand t2 = gen(e.right);
	  Operand t = new RegTemp(nextTemp++);
	  switch (e.binOp) { // think about using a table here instead! 
	  // ...
	  case Ast.PLUS:
	    emit3("add", t1, t2, t);
	    break;
	  // ...
	  }
	  return t;
	} else {
	  // boolean-valued expression 
	  int lfalse = nextLabel++;
	  int ltrue = nextLabel++;
	  gen(e,ltrue,lfalse);
	  // ...
	  return null; // just temporary -- can't happen in real version
	}
      }

      public Object visit(Ast.UnOpExp e) {
	// ...
	return null;  // just temporary -- can't happen in real version
      }

      public Object visit(Ast.LvalExp e) {
	// Kludge to handle TRUE, FALSE, NIL without an environment.
	// Only works on the assumption that they haven't been redefined.
	if (e.lval instanceof Ast.VarLvalue) {
	  if (((Ast.VarLvalue) e.lval).name.equals("TRUE")) 
	    return ONE;
	  else if (((Ast.VarLvalue) e.lval).name.equals("FALSE")) 
	    return ZERO;
	  else if (((Ast.VarLvalue) e.lval).name.equals("NIL"))
	    return ADDRNULL;
	} 
	Operand t = new RegTemp(nextTemp++);
	Operand addr = gen(e.lval);
	emitLd(addr,t);
	return t;
      }	

      public Object visit(Ast.CallExp e) {
	Operand t = new RegTemp(nextTemp++);
	genCall(e.procName,e.args);
	emit2("mov", REGO0, t);
	return t;
      }

      public Object visit(Ast.ArrayExp e) {
	Operand[] counts = new Operand[e.initializers.length];
	Operand[] vals = new Operand[e.initializers.length];
	for (int i = 0; i < e.initializers.length; i++) {
	  counts[i] = gen(e.initializers[i].count);
	  if (counts[i] instanceof IntLit) {
	    IntLit il = (IntLit) (counts[i]);
	    il.i = il.i >= 0 ? il.i : 0;
	  } else if (counts[i] instanceof RegTemp) {  
	    int lok = nextLabel++; 
	    emit2("cmp", counts[i], ZERO);
	    emit1("bge", new Label(lok));
	    emit2("mov", ZERO, counts[i]);
	    emitLabel(lok);
	  } else 
	    throw new Error("Impossible class for array initializer count: " + counts[i]);
	  vals[i] = gen(e.initializers[i].value);
	}
	Operand s = new RegTemp(nextTemp++);
	Operand t = new RegTemp(nextTemp++);
	emit2("mov", ZERO, s);
	for (int i = 0; i < e.initializers.length; i++) 
	  emit3("add", s, counts[i], s);
	emit3("add", s, ONE, t);
	emit2("scall", new AddrName("alloc"),t);
	emitSt(s, REGO0);
	emit2("mov", REGO0, s);
	emit3("adda", s, ONE, s);
	for (int i = 0; i < e.initializers.length; i++) {
	  emit3("adda", s, counts[i], t);
	  int ltop = nextLabel++;
	  int ltest = nextLabel++;
	  emit1("ba", new Label(ltest));
	  emitLabel(ltop);
	  emitSt(vals[i], s);
	  emit3("adda", s, ONE, s);
	  emitLabel(ltest);
	  emit2("cmp", s, t);
	  emit1("bl", new Label(ltop));
	}
	emit3("adda", REGO0, ONE, s);
	return s;
      }

      public Object visit(Ast.RecordExp e) {
	// ...
	return null;  // just temporary -- can't happen in real version
      }

      public Object visit(Ast.IntLitExp e) {
	return new IntLit(e.lit);
      }
      
      public Object visit(Ast.RealLitExp e) {
	return null; // can't happen -- this function should never be invoked in real version
      }

      public Object visit(Ast.StringLitExp e) {
	return new AddrString(e.lit);
      }
    }
    Operand r = null;
    try {
      r = (Operand) e.accept(new ExpVisitor());
    } catch (Ast.Error exn) {
    };
    return r;
  }

  // Generate boolean-typed expressions to control-flow form.
  // Note that e must have type boolean.
  static void gen(Ast.Exp e, int ltrue, int lfalse) {
    if (e instanceof Ast.BinOpExp) {
      Ast.BinOpExp e0 = (Ast.BinOpExp) e;
      if (e0.binOp == Ast.AND) {
	int lright = nextLabel++;
	gen(e0.left, lright, lfalse);
	emitLabel(lright);
	gen(e0.right, ltrue, lfalse);
      } else if (e0.binOp == Ast.OR) {
	// ...
      } else if (e0.binOp >= Ast.LT && e0.binOp <= Ast.NEQ) {
	// ...
      }  
      // other cases impossible
    } else if (e instanceof Ast.UnOpExp) {
      Ast.UnOpExp e0 = (Ast.UnOpExp) e;
      if (e0.unOp == Ast.NOT)
	// ...
	;
      // other cases impossible
    } else {
      Operand t = gen(e);  // must already be a value
      emit2("cmp", t, ZERO);
      emit1("be", new Label(lfalse));
      emit1("ba", new Label(ltrue));
    }
  }


  // Generate lvalue address into an operand.
  static Operand gen(Ast.Lvalue l) {
    class LvalueVisitor implements Ast.LvalueVisitor {

      public Object visit(Ast.VarLvalue l) {
	return new AddrName(l.name);
      }      

      public Object visit(Ast.ArrayDerefLvalue l) {
	// ...
	return null;  // just temporary -- can't happen in real version
      }
      
      public Object visit(Ast.RecordDerefLvalue l) {
	Operand addr = gen(l.record);
	Operand t = new RegTemp(nextTemp++);
	emitLd(addr, t);
	int l1 = nextLabel++;
	emit2("cmp", t, ADDRNULL);
	emit1("bne", new Label(l1));
	emit2("scall", new AddrName("nil_pointer"), ZERO);
	emitLabel(l1);
	emit3("adda",t, new IntLit(l.offset), t);
	return t;
      }
    }
    Operand r = null;
    try {
      r = (Operand) l.accept(new LvalueVisitor());
    } catch (Ast.Error exn) {
    };
    return r;
  }

}





