astRoots) throws IOException {
+ if (this.debug == null) return;
+ this.debug.write(AstDump.toString(astRoots));
+ }
+}
diff --git a/src/cd/ToDoException.java b/src/cd/ToDoException.java
new file mode 100644
index 0000000..615a148
--- /dev/null
+++ b/src/cd/ToDoException.java
@@ -0,0 +1,14 @@
+package cd;
+
+/** TAs insert this to mark code that students need to write */
+public class ToDoException extends RuntimeException {
+ private static final long serialVersionUID = 4054810321239901944L;
+
+ public ToDoException() {
+ }
+
+ public ToDoException(String message) {
+ super(message);
+ }
+
+}
diff --git a/src/cd/backend/codegen/AssemblyEmitter.java b/src/cd/backend/codegen/AssemblyEmitter.java
new file mode 100644
index 0000000..f9c8c01
--- /dev/null
+++ b/src/cd/backend/codegen/AssemblyEmitter.java
@@ -0,0 +1,164 @@
+package cd.backend.codegen;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import cd.Config;
+import cd.backend.codegen.RegisterManager.Register;
+
+public class AssemblyEmitter {
+ public Writer out;
+ public StringBuilder indent = new StringBuilder();
+ public int counter = 0;
+
+ public AssemblyEmitter(Writer out) {
+ this.out = out;
+ }
+
+ /** Creates an constant operand. */
+ static String constant(int i) {
+ return "$" + i;
+ }
+
+ /** Creates an constant operand with the address of a label. */
+ static String labelAddress(String lbl) {
+ return "$" + lbl;
+ }
+
+ /** Creates an operand relative to another operand. */
+ static String registerOffset(int offset, Register reg) {
+ return String.format("%d(%s)", offset, reg);
+ }
+
+ /** Creates an operand addressing an item in an array */
+ static String arrayAddress(Register arrReg, Register idxReg) {
+ final int offset = Config.SIZEOF_PTR * 2; // one word each in front for
+ // vptr and length
+ final int mul = Config.SIZEOF_PTR; // assume all arrays of 4-byte elem
+ return String.format("%d(%s,%s,%d)", offset, arrReg, idxReg, mul);
+ }
+
+ void increaseIndent(String comment) {
+ indent.append(" ");
+ if (comment != null)
+ emitComment(comment);
+ }
+
+ void decreaseIndent() {
+ indent.setLength(indent.length() - 2);
+ }
+
+ void emitCommentSection(String name) {
+ int indentLen = indent.length();
+ int breakLen = 68 - indentLen - name.length();
+ StringBuffer sb = new StringBuffer();
+ sb.append(Config.COMMENT_SEP).append(" ");
+ for (int i = 0; i < indentLen; i++)
+ sb.append("_");
+ sb.append(name);
+ for (int i = 0; i < breakLen; i++)
+ sb.append("_");
+
+ try {
+ out.write(sb.toString());
+ out.write("\n");
+ } catch (IOException e) {
+ }
+ }
+
+ void emitComment(String comment) {
+ emitRaw(Config.COMMENT_SEP + " " + comment);
+ }
+
+ void emit(String op, Register src, String dest) {
+ emit(op, src.repr, dest);
+ }
+
+ void emit(String op, String src, Register dest) {
+ emit(op, src, dest.repr);
+ }
+
+ void emit(String op, Register src, Register dest) {
+ emit(op, src.repr, dest.repr);
+ }
+
+ void emit(String op, String src, String dest) {
+ emitRaw(String.format("%s %s, %s", op, src, dest));
+ }
+
+ void emit(String op, int src, Register dest) {
+ emit(op, constant(src), dest);
+ }
+
+ void emit(String op, String dest) {
+ emitRaw(op + " " + dest);
+ }
+
+ void emit(String op) {
+ emitRaw(op);
+ }
+
+ void emit(String op, Register reg) {
+ emit(op, reg.repr);
+ }
+
+ void emit(String op, int dest) {
+ emit(op, constant(dest));
+ }
+
+ void emitMove(Register src, String dest) {
+ emitMove(src.repr, dest);
+ }
+
+ void emitMove(Register src, Register dest) {
+ emitMove(src.repr, dest.repr);
+ }
+
+ void emitMove(String src, Register dest) {
+ emitMove(src, dest.repr);
+ }
+
+ void emitMove(String src, String dest) {
+ if (!src.equals(dest))
+ emit("movl", src, dest);
+ }
+
+ void emitLoad(int srcOffset, Register src, Register dest) {
+ emitMove(registerOffset(srcOffset, src), dest.repr);
+ }
+
+ void emitStore(Register src, int destOffset, Register dest) {
+ emitStore(src.repr, destOffset, dest);
+ }
+
+ void emitStore(String src, int destOffset, Register dest) {
+ emitMove(src, registerOffset(destOffset, dest));
+ }
+
+ void emitConstantData(String data) {
+ emitRaw(String.format("%s %s", Config.DOT_INT, data));
+ }
+
+ String uniqueLabel() {
+ String labelName = "label" + counter++;
+ return labelName;
+ }
+
+ void emitLabel(String label) {
+ try {
+ out.write(label + ":" + "\n");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ void emitRaw(String op) {
+ try {
+ out.write(indent.toString());
+ out.write(op);
+ out.write("\n");
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/cd/backend/codegen/AssemblyFailedException.java b/src/cd/backend/codegen/AssemblyFailedException.java
new file mode 100644
index 0000000..112fee9
--- /dev/null
+++ b/src/cd/backend/codegen/AssemblyFailedException.java
@@ -0,0 +1,14 @@
+package cd.backend.codegen;
+
+public class AssemblyFailedException extends RuntimeException {
+ private static final long serialVersionUID = -5658502514441032016L;
+
+ public final String assemblerOutput;
+ public AssemblyFailedException(
+ String assemblerOutput) {
+ super("Executing assembler failed.\n"
+ + "Output:\n"
+ + assemblerOutput);
+ this.assemblerOutput = assemblerOutput;
+ }
+}
diff --git a/src/cd/backend/codegen/AstCodeGenerator.java b/src/cd/backend/codegen/AstCodeGenerator.java
new file mode 100644
index 0000000..cdbb510
--- /dev/null
+++ b/src/cd/backend/codegen/AstCodeGenerator.java
@@ -0,0 +1,58 @@
+package cd.backend.codegen;
+
+import cd.Main;
+import cd.ir.Ast.ClassDecl;
+
+import java.io.Writer;
+import java.util.List;
+
+public class AstCodeGenerator {
+
+ protected ExprGenerator eg;
+ protected StmtGenerator sg;
+
+ protected final Main main;
+
+ protected final AssemblyEmitter emit;
+ protected final RegisterManager rm = new RegisterManager();
+
+ AstCodeGenerator(Main main, Writer out) {
+ this.emit = new AssemblyEmitter(out);
+ this.main = main;
+ this.eg = new ExprGenerator(this);
+ this.sg = new StmtGenerator(this);
+ }
+
+ protected void debug(String format, Object... args) {
+ this.main.debug(format, args);
+ }
+
+ public static AstCodeGenerator createCodeGenerator(Main main, Writer out) {
+ return new AstCodeGenerator(main, out);
+ }
+
+
+ /**
+ * Main method. Causes us to emit x86 assembly corresponding to {@code ast}
+ * into {@code file}. Throws a {@link RuntimeException} should any I/O error
+ * occur.
+ *
+ *
+ * The generated file will be divided into two sections:
+ *
+ * - Prologue: Generated by {@link #emitPrefix()}. This contains any
+ * introductory declarations and the like.
+ *
- Body: Generated by {@link ExprGenerator}. This contains the main
+ * method definitions.
+ *
+ */
+ public void go(List extends ClassDecl> astRoots) {
+ for (ClassDecl ast : astRoots) {
+ sg.gen(ast);
+ }
+ }
+
+
+ protected void initMethodData() {
+ }
+}
\ No newline at end of file
diff --git a/src/cd/backend/codegen/ExprGenerator.java b/src/cd/backend/codegen/ExprGenerator.java
new file mode 100644
index 0000000..7632727
--- /dev/null
+++ b/src/cd/backend/codegen/ExprGenerator.java
@@ -0,0 +1,307 @@
+package cd.backend.codegen;
+
+import cd.backend.codegen.RegisterManager.Register;
+import cd.ir.Ast.*;
+import cd.ir.Ast.BinaryOp.BOp;
+import cd.ir.ExprVisitor;
+import cd.util.debug.AstOneLine;
+
+/**
+ * Generates code to evaluate expressions. After emitting the code, returns a
+ * String which indicates the register where the result can be found.
+ */
+class ExprGenerator extends ExprVisitor {
+ protected final AstCodeGenerator cg;
+
+ ExprGenerator(AstCodeGenerator astCodeGenerator) {
+ cg = astCodeGenerator;
+ }
+
+ public Register gen(Expr ast) {
+ return visit(ast, null);
+ }
+
+ @Override
+ public Register visit(Expr ast, Void arg) {
+ try {
+ cg.emit.increaseIndent("Emitting " + AstOneLine.toString(ast));
+ return super.visit(ast, null);
+ } finally {
+ cg.emit.decreaseIndent();
+ }
+
+ }
+
+ @Override
+ public Register binaryOp(BinaryOp ast, Void arg) {
+ String op;
+ if (ast.operator == BOp.B_TIMES)
+ op = "imul";
+ else if (ast.operator == BOp.B_PLUS)
+ op = "add";
+ else if (ast.operator == BOp.B_MINUS)
+ op = "sub";
+ else if (ast.operator == BOp.B_DIV)
+ op = "div";
+ else
+ throw new RuntimeException("Not required (optional)");
+ op += "l"; // long = 4B = 32b
+
+ Register left, right;
+ int leftNeeded = ast.left().registersNeeded();
+ int rightNeeded = ast.right().registersNeeded();
+ if (leftNeeded > rightNeeded) {
+ left = cg.eg.visit(ast.left(), arg);
+ right = cg.eg.visit(ast.right(), arg);
+ } else {
+ right = cg.eg.visit(ast.right(), arg);
+ left = cg.eg.visit(ast.left(), arg);
+ }
+
+ if (ast.operator == BOp.B_DIV) {
+ // The dividend (left) will be replaced with the quotient
+ // So free the divisor register (right) and return the quotient (left)
+ division(left, right);
+ cg.rm.releaseRegister(right);
+ return left;
+ } else {
+ // All operators take the form OP A, B --> B = B OP A --> B is left, A is right
+ // Therefore the result is stored in B (left) and A (right) can be freed
+ cg.emit.emit(op, right, left);
+ cg.rm.releaseRegister(right);
+ return left;
+ }
+ }
+
+ /**
+ * Division with divide by zero check incorporated
+ */
+ private void division(Register dividend, Register divisor) {
+ division(dividend, divisor, true);
+ }
+
+ /**
+ * Special implementation for divisions, as it requires specific registers to be used
+ * @param dividend Maximum 32bit (even though the division is implemented as 64bit)
+ * @param divisor Maximum 32bit
+ * @param checkZero Whether to generate code that checks for division by zero or not
+ * @return The quotient will be in place of the dividend and the remainder in the divisor
+ */
+ protected void division(Register dividend, Register divisor, boolean checkZero) {
+ if (checkZero) {
+ String beginDivision = cg.emit.uniqueLabel();
+ cg.emit.emit("cmp", 0, divisor);
+ cg.emit.emit("jne", beginDivision);
+ // Exit with error code in case of div/0
+ Interrupts.exit(cg, Interrupts.EXIT_DIV_0);
+ cg.emit.emitLabel(beginDivision);
+ }
+
+ // D = d * q + r (dividend, divisor, quotient, remainder)
+ // EDX:EAX = d * EAX + EDX (before and after the div operation)
+
+ // Take care of register use: move EAX to memory if necessary
+ // The stack is not needed because the division operator is not recursive
+ // After the division is performed, move the data back to dividend and divisor
+ Register d, D = Register.EAX;
+ if (divisor.equals(D)) {
+ d = cg.rm.getRegister();
+ cg.emit.emitMove(divisor, d);
+ cg.emit.emitMove(dividend, Register.EAX);
+ } else {
+ if (!dividend.equals(D) && cg.rm.isInUse(D))
+ cg.emit.emitMove(D, "(" + Interrupts.Mem.EAX.pos + ")");
+ cg.emit.emitMove(dividend, D);
+ if (divisor.equals(Register.EDX)) {
+ d = cg.rm.getRegister();
+ cg.emit.emitMove(divisor, d);
+ }
+ }
+
+ // Division
+ cg.emit.emit("cdq");
+ cg.emit.emit("idiv", divisor);
+ cg.emit.emitMove(Register.EAX, dividend);
+ cg.emit.emitMove(Register.EDX, divisor);
+
+ Register q = Register.EAX, r = Register.EDX;
+ if (divisor.equals(D)) {
+ cg.emit.emitMove(q, dividend);
+ cg.emit.emitMove(r, divisor);
+ } else {
+ if (!dividend.equals(D) && cg.rm.isInUse(D))
+ cg.emit.emitMove(Interrupts.Mem.EAX.pos, D);
+ cg.emit.emitMove(dividend, D);
+ if (!divisor.equals(r)) {
+ cg.emit.emitMove(r, divisor);
+ }
+ }
+ }
+
+ @Override
+ public Register booleanConst(BooleanConst ast, Void arg) {
+ {
+ throw new RuntimeException("Not required");
+ }
+ }
+
+ @Override
+ public Register builtInRead(BuiltInRead ast, Void arg) {
+ // Holds the first digit and then is used to accumulate the total value
+ Register value = cg.rm.getRegister();
+ // Used to mark for positive (0) or negative (1) value
+ Register negative = cg.rm.getRegister();
+ // Holds the next digit read, once the first has been read
+ Register nextDigit = cg.rm.getRegister();
+
+ // Labels for the read structure
+ String removeWhitespace = cg.emit.uniqueLabel();
+ String negativeFirstDigit = cg.emit.uniqueLabel();
+ String inputError = cg.emit.uniqueLabel();
+ String moreDigits = cg.emit.uniqueLabel();
+ String endRead = cg.emit.uniqueLabel();
+ String checkSign = cg.emit.uniqueLabel();
+
+ // By default, variable is positive (negative == 0)
+ cg.emit.emitMove(AssemblyEmitter.constant(0), negative);
+
+ // Skip whitespace ( \t\n\r) at the beginning
+ cg.emit.emitLabel(removeWhitespace);
+ Interrupts.readChar(cg, value);
+ cg.emit.emit("cmp", '\n', value);
+ cg.emit.emit("je", removeWhitespace);
+ cg.emit.emit("cmp", ' ', value);
+ cg.emit.emit("je", removeWhitespace);
+ cg.emit.emit("cmp", '\t', value);
+ cg.emit.emit("je", removeWhitespace);
+ cg.emit.emit("cmp", '\r', value);
+ cg.emit.emit("je", removeWhitespace);
+
+ // Recognize first digit, if negative read another digit to place at value
+ cg.emit.emit("cmp", '-', value);
+ cg.emit.emit("je", negativeFirstDigit);
+ charToIntOrJump(value, inputError);
+ cg.emit.emit("jmp", moreDigits);
+
+ // Negative first digit
+ cg.emit.emitLabel(negativeFirstDigit);
+ Interrupts.readChar(cg, value);
+ cg.emit.emitMove(AssemblyEmitter.constant(1), negative);
+ charToIntOrJump(value, inputError);
+
+ // All other digits:
+ // while ( (nextDigit = read()).isDigit())
+ // value = value * 10 + nextDigit
+ cg.emit.emitLabel(moreDigits);
+ Interrupts.readChar(cg, nextDigit);
+ charToIntOrJump(nextDigit, checkSign);
+ cg.emit.emit("imul", 10, value);
+ cg.emit.emit("add", nextDigit, value);
+ cg.emit.emit("jmp", moreDigits);
+
+ // No number can be read (non-digit first or following the '-')
+ cg.emit.emitLabel(inputError);
+ Interrupts.exit(cg, Interrupts.EXIT_INPUT_MISMATCH);
+
+ // Sign check and end
+ cg.emit.emitLabel(checkSign);
+ cg.emit.emit("cmp", 0, negative);
+ cg.emit.emit("je", endRead);
+ cg.emit.emit("negl", value);
+ cg.emit.emitLabel(endRead);
+
+ // Register bookkeeping and return read value
+ cg.rm.releaseRegister(negative);
+ cg.rm.releaseRegister(nextDigit);
+ return value;
+ }
+
+ private void charToIntOrJump(Register reg, String jumpLabel) {
+ cg.emit.emit("cmp", '0', reg);
+ cg.emit.emit("jl", jumpLabel);
+ cg.emit.emit("cmp", '9', reg);
+ cg.emit.emit("jg", jumpLabel);
+ cg.emit.emit("sub", '0', reg);
+ }
+
+ @Override
+ public Register cast(Cast ast, Void arg) {
+ {
+ throw new RuntimeException("Not required");
+ }
+ }
+
+ @Override
+ public Register index(Index ast, Void arg) {
+ {
+ throw new RuntimeException("Not required");
+ }
+ }
+
+ @Override
+ public Register intConst(IntConst ast, Void arg) {
+ Register r = cg.rm.getRegister();
+ cg.emit.emitMove(AssemblyEmitter.constant(ast.value), r);
+ return r;
+ }
+
+ @Override
+ public Register field(Field ast, Void arg) {
+ {
+ throw new RuntimeException("Not required");
+ }
+ }
+
+ @Override
+ public Register newArray(NewArray ast, Void arg) {
+ {
+ throw new RuntimeException("Not required");
+ }
+ }
+
+ @Override
+ public Register newObject(NewObject ast, Void arg) {
+ {
+ throw new RuntimeException("Not required");
+ }
+ }
+
+ @Override
+ public Register nullConst(NullConst ast, Void arg) {
+ {
+ throw new RuntimeException("Not required");
+ }
+ }
+
+ @Override
+ public Register thisRef(ThisRef ast, Void arg) {
+ {
+ throw new RuntimeException("Not required");
+ }
+ }
+
+ @Override
+ public Register unaryOp(UnaryOp ast, Void arg) {
+ Register aux = cg.eg.visit(ast.arg(), arg);
+ switch (ast.operator) {
+ case U_PLUS:
+ return aux;
+ case U_MINUS:
+ cg.emit.emit("negl", aux);
+ return aux;
+ case U_BOOL_NOT:
+ cg.emit.emit("notl", aux);
+ return aux;
+ default:
+ throw new RuntimeException("Not implemented");
+ }
+ }
+
+ @Override
+ public Register var(Var ast, Void arg) {
+ Register r = cg.rm.getRegister();
+ cg.emit.emitMove(ast.getLabel(), r);
+ return r;
+ }
+
+}
diff --git a/src/cd/backend/codegen/Interrupts.java b/src/cd/backend/codegen/Interrupts.java
new file mode 100644
index 0000000..c1c2992
--- /dev/null
+++ b/src/cd/backend/codegen/Interrupts.java
@@ -0,0 +1,97 @@
+package cd.backend.codegen;
+
+import cd.backend.codegen.RegisterManager.Register;
+
+import static cd.backend.codegen.Interrupts.Mem.BUFFER;
+
+public class Interrupts {
+
+ protected static final int EXIT_OK = 0, EXIT_DIV_0 = 7, EXIT_INPUT_MISMATCH = 8;
+ protected static final int STDIN = 0, STDOUT = 1, STDERR = 2;
+ protected static final int I_EXIT = 1, I_READ = 3, I_WRITE = 4;
+
+ protected enum Mem {
+ BUFFER("io_buffer"), EAX("eax");
+
+ String pos;
+ Mem(String pos) {
+ this.pos = pos;
+ }
+
+ @Override
+ public String toString() {
+ return pos;
+ }
+ }
+
+ /**
+ * Write auxiliary data variables needed for I/O and division
+ * @param cg AstCodeGenerator to print instructions
+ */
+ protected static void reserveMemory(AstCodeGenerator cg) {
+ for (Mem m : Mem.values()) {
+ cg.emit.emitLabel(m.pos);
+ cg.emit.emitConstantData("0");
+ }
+ }
+
+ /**
+ * Writes a single character passed as parameter
+ * @param cg AstCodeGenerator to print instructions
+ * @param i Character code in ASCII (or single casted character)
+ */
+ protected static void printChar(AstCodeGenerator cg, int i) {
+ cg.emit.emitMove(AssemblyEmitter.constant(i), BUFFER.pos);
+ printChar(cg);
+ }
+
+ /**
+ * Writes a single character from the BUFFER memory pos to stdout
+ * @param cg AstCodeGenerator to print instructions
+ */
+ protected static void printChar(AstCodeGenerator cg) {
+ Register[] needed = {Register.EAX, Register.EBX, Register.ECX, Register.EDX};
+ for (int i = 0; i < needed.length; i++)
+ if (cg.rm.isInUse(needed[i]))
+ cg.emit.emit("push", needed[i]);
+ cg.emit.emitMove(AssemblyEmitter.constant(I_WRITE), Register.EAX);
+ cg.emit.emitMove(AssemblyEmitter.constant(STDOUT), Register.EBX);
+ cg.emit.emitMove("$" + BUFFER.pos, Register.ECX);
+ cg.emit.emitMove(AssemblyEmitter.constant(1), Register.EDX);
+ cg.emit.emit("int", 0x80);
+ for (int i = needed.length - 1; i >= 0; i--)
+ if (cg.rm.isInUse(needed[i]))
+ cg.emit.emit("pop", needed[i]);
+ }
+
+ /**
+ * Reads a single character from stdin and places it in the
+ * memory pos BUFFER
+ */
+ protected static void readChar(AstCodeGenerator cg, Register destination) {
+ Register[] needed = {Register.EAX, Register.EBX, Register.ECX, Register.EDX};
+ for (int i = 0; i < needed.length; i++)
+ if (cg.rm.isInUse(needed[i]))
+ cg.emit.emit("push", needed[i]);
+ cg.emit.emitMove(AssemblyEmitter.constant(I_READ), Register.EAX);
+ cg.emit.emitMove(AssemblyEmitter.constant(STDIN), Register.EBX);
+ cg.emit.emitMove("$" + BUFFER.pos, Register.ECX);
+ cg.emit.emitMove(AssemblyEmitter.constant(1), Register.EDX);
+ cg.emit.emit("int", 0x80);
+ for (int i = needed.length - 1; i >= 0; i--)
+ if (cg.rm.isInUse(needed[i]))
+ cg.emit.emit("pop", needed[i]);
+ cg.emit.emitMove(BUFFER.pos, destination);
+ }
+
+ /**
+ * Generates a exit interrupt with the code provided
+ * @param cg AstCodeGenerator to print instructions
+ * @param exitCode Number to use as exit code (can use constants in this class)
+ */
+ protected static void exit(AstCodeGenerator cg, int exitCode) {
+ cg.emit.emitMove(AssemblyEmitter.constant(I_EXIT), Register.EAX);
+ cg.emit.emitMove(AssemblyEmitter.constant(exitCode), Register.EBX);
+ cg.emit.emit("int", 0x80);
+ }
+}
diff --git a/src/cd/backend/codegen/RegisterManager.java b/src/cd/backend/codegen/RegisterManager.java
new file mode 100644
index 0000000..02c96b9
--- /dev/null
+++ b/src/cd/backend/codegen/RegisterManager.java
@@ -0,0 +1,123 @@
+package cd.backend.codegen;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Simple class that manages the set of currently used
+ * and unused registers
+ */
+public class RegisterManager {
+ private List registers = new ArrayList();
+
+ // lists of register to save by the callee and the caller
+ public static final Register CALLEE_SAVE[] = new Register[]{Register.ESI,
+ Register.EDI, Register.EBX};
+ public static final Register CALLER_SAVE[] = new Register[]{Register.EAX,
+ Register.ECX, Register.EDX};
+
+ // list of general purpose registers
+ public static final Register GPR[] = new Register[]{Register.EAX, Register.EBX,
+ Register.ECX, Register.EDX, Register.ESI, Register.EDI};
+
+ // special purpose registers
+ public static final Register BASE_REG = Register.EBP;
+ public static final Register STACK_REG = Register.ESP;
+
+ public static final int SIZEOF_REG = 4;
+
+
+ public enum Register {
+ EAX("%eax", ByteRegister.EAX), EBX("%ebx", ByteRegister.EBX), ECX(
+ "%ecx", ByteRegister.ECX), EDX("%edx", ByteRegister.EDX), ESI(
+ "%esi", null), EDI("%edi", null), EBP("%ebp", null), ESP(
+ "%esp", null);
+
+ public final String repr;
+ private final ByteRegister lowByteVersion;
+
+ private Register(String repr, ByteRegister bv) {
+ this.repr = repr;
+ this.lowByteVersion = bv;
+ }
+
+ @Override
+ public String toString() {
+ return repr;
+ }
+
+ /**
+ * determines if this register has an 8bit version
+ */
+ public boolean hasLowByteVersion() {
+ return lowByteVersion != null;
+ }
+
+ /**
+ * Given a register like {@code %eax} returns {@code %al}, but doesn't
+ * work for {@code %esi} and {@code %edi}!
+ */
+ public ByteRegister lowByteVersion() {
+ assert hasLowByteVersion();
+ return lowByteVersion;
+ }
+ }
+
+ public enum ByteRegister {
+ EAX("%al"), EBX("%bl"), ECX("%cl"), EDX("%dl");
+
+ public final String repr;
+
+ private ByteRegister(String repr) {
+ this.repr = repr;
+ }
+
+ @Override
+ public String toString() {
+ return repr;
+ }
+ }
+
+ /**
+ * Reset all general purpose registers to free
+ */
+ public void initRegisters() {
+ registers.clear();
+ registers.addAll(Arrays.asList(GPR));
+ }
+
+ /**
+ * returns a free register and marks it as used
+ */
+ public Register getRegister() {
+ int last = registers.size() - 1;
+ if (last < 0)
+ throw new AssemblyFailedException(
+ "Program requires too many registers");
+
+ return registers.remove(last);
+ }
+
+ /**
+ * marks a currently used register as free
+ */
+ public void releaseRegister(Register reg) {
+ assert !registers.contains(reg);
+ registers.add(reg);
+ }
+
+ /**
+ * Returns whether the register is currently non-free
+ */
+ public boolean isInUse(Register reg) {
+ return !registers.contains(reg);
+ }
+
+ /**
+ * returns the number of free registers
+ */
+ public int availableRegisters() {
+ return registers.size();
+ }
+}
\ No newline at end of file
diff --git a/src/cd/backend/codegen/StmtGenerator.java b/src/cd/backend/codegen/StmtGenerator.java
new file mode 100644
index 0000000..acfa300
--- /dev/null
+++ b/src/cd/backend/codegen/StmtGenerator.java
@@ -0,0 +1,206 @@
+package cd.backend.codegen;
+
+import cd.backend.codegen.RegisterManager.Register;
+import cd.ir.Ast;
+import cd.ir.Ast.*;
+import cd.ir.AstVisitor;
+import cd.util.debug.AstOneLine;
+
+import java.util.*;
+
+/**
+ * Generates code to process statements and declarations.
+ */
+class StmtGenerator extends AstVisitor {
+ protected final AstCodeGenerator cg;
+
+ StmtGenerator(AstCodeGenerator astCodeGenerator) {
+ cg = astCodeGenerator;
+ }
+
+ public void gen(Ast ast) {
+ visit(ast, null);
+ }
+
+ @Override
+ public Register visit(Ast ast, Void arg) {
+ try {
+ cg.emit.increaseIndent("Emitting " + AstOneLine.toString(ast));
+ return super.visit(ast, arg);
+ } finally {
+ cg.emit.decreaseIndent();
+ }
+ }
+
+ /**
+ * Declares a variable in the data segment. This method was implemented because it was necessary
+ * to create the data segment
+ * @param ast Variable declaration node
+ * @param arg Extra arguments
+ * @return null
+ */
+ @Override
+ public Register varDecl(Ast.VarDecl ast, Void arg) {
+ cg.emit.emitLabel(ast.getLabel());
+ cg.emit.emitConstantData("0");
+ return null;
+ }
+
+ @Override
+ public Register methodCall(MethodCall ast, Void dummy) {
+ {
+ throw new RuntimeException("Not required");
+ }
+ }
+
+ @Override
+ public Register methodDecl(MethodDecl ast, Void arg) {
+ // Prologue: in future version a more complex prologue that
+ // takes care of parameters and return values is necessary
+ // Method code
+ cg.rm.initRegisters();
+ cg.emit.emitRaw(".section .data");
+ Interrupts.reserveMemory(cg);
+ // Variables
+ cg.sg.visit(ast.decls(), arg);
+ cg.emit.emitRaw(".section .text");
+ cg.emit.emitRaw(".globl " + ast.name);
+ cg.emit.emitLabel(ast.name);
+ Register result = cg.sg.visit(ast.body(), arg);
+ // Epilogue: not needed in the simple HW1
+ return null;
+ }
+
+ @Override
+ public Register ifElse(IfElse ast, Void arg) {
+ {
+ throw new RuntimeException("Not required");
+ }
+ }
+
+ @Override
+ public Register whileLoop(WhileLoop ast, Void arg) {
+ {
+ throw new RuntimeException("Not required");
+ }
+ }
+
+ @Override
+ public Register assign(Assign ast, Void arg) {
+ // Because we only handle very simple programs in HW1,
+ // you can just emit the prologue here!
+ Register value = cg.eg.visit(ast.right(), arg);
+ Ast.Var variable = (Var) ast.left();
+ cg.emit.emitMove(value, variable.getLabel());
+ cg.rm.releaseRegister(value);
+ return null;
+ }
+
+ @Override
+ public Register builtInWrite(BuiltInWrite ast, Void arg) {
+ String printNonZero = cg.emit.uniqueLabel();
+ String end = cg.emit.uniqueLabel();
+ String printPositive = cg.emit.uniqueLabel();
+ String stackLoop = cg.emit.uniqueLabel();
+ String printLoop = cg.emit.uniqueLabel();
+
+ Register value = cg.eg.visit(ast.arg(), arg);
+ // Begin: decide if print 0 or print number
+ cg.emit.emit("cmp", 0, value);
+ cg.emit.emit("jne", printNonZero); // value != 0
+
+ // Print 0
+ Interrupts.printChar(cg, '0'); // print 0
+ cg.emit.emit("jmp", end);
+
+ // Not 0: positive or negative?
+ cg.emit.emitLabel(printNonZero);
+ cg.emit.emit("jg", printPositive); // value > 0
+
+ // Number is negative, print '-' then number (with sign changed)
+ Interrupts.printChar(cg, '-');
+ cg.emit.emit("negl", value);
+
+ // Print number: extract and print all digits of number
+ cg.emit.emitLabel(printPositive);
+
+ // In order to avoid using EAX and EDX, we are going to guarantee
+ // that there will be at least 2 other registers available
+ List auxRegs = Arrays.asList(Register.EDI, Register.ESI);
+ List divRegs = Arrays.asList(Register.EAX, Register.EDX);
+ Map isPushed = new HashMap<>();
+ boolean usingAuxRegs = cg.rm.availableRegisters() < 4;
+ if (usingAuxRegs)
+ for (int i = 0; i < auxRegs.size(); i++)
+ isPushed.put(auxRegs.get(i), cg.rm.isInUse(auxRegs.get(i)));
+
+ // Free EAX and EDX for div operand use
+ isPushed.put(Register.EAX, cg.rm.isInUse(Register.EAX) && !value.equals(Register.EAX));
+ isPushed.put(Register.EDX, cg.rm.isInUse(Register.EDX));
+
+ // Push all elements
+ for (Register r : isPushed.keySet())
+ if (isPushed.get(r))
+ cg.emit.emit("push", r);
+
+ // Force counter and divisor to not be EDX/EAX, as they need to coexist
+ Register counter = cg.rm.getRegister();
+ Register divisor = cg.rm.getRegister();
+ while (divRegs.contains(counter)) {
+ Register aux = cg.rm.getRegister();
+ cg.rm.releaseRegister(counter);
+ counter = aux;
+ }
+ while (divRegs.contains(divisor)) {
+ Register aux = cg.rm.getRegister();
+ cg.rm.releaseRegister(divisor);
+ divisor = aux;
+ }
+
+ // Divide by 10 to extract the remainders and push them to the stack
+ // Registers used: EAX is the remaining digits (initially the value)
+ // in div it is used for the lower half of the dividend and for quotient
+ // divisor
+ // EDX is used as the high half of the dividend and as remainder
+ cg.emit.emitMove(value, Register.EAX);
+ cg.rm.releaseRegister(value);
+ cg.emit.emitMove(AssemblyEmitter.constant(0), counter);
+
+ cg.emit.emitLabel(stackLoop);
+ cg.emit.emitMove(AssemblyEmitter.constant(0), Register.EDX);
+ cg.emit.emitMove(AssemblyEmitter.constant(10), divisor);
+ cg.emit.emit("div", divisor);
+ cg.emit.emit("add", '0', Register.EDX);
+ cg.emit.emit("push", Register.EDX);
+ cg.emit.emit("inc", counter);
+ cg.emit.emit("cmp", 0, Register.EAX);
+ cg.emit.emit("je", printLoop);
+ cg.emit.emit("jmp", stackLoop);
+ // Release divisor register
+ cg.rm.releaseRegister(divisor);
+
+ // Print digits from the stack
+ cg.emit.emitLabel(printLoop);
+ cg.emit.emit("cmp", 0, counter);
+ cg.emit.emit("jz", end);
+ cg.emit.emit("dec", counter);
+ cg.emit.emit("pop", Interrupts.Mem.BUFFER.pos);
+ Interrupts.printChar(cg);
+ cg.emit.emit("jmp", printLoop);
+ cg.emit.emitLabel(end);
+
+ cg.rm.releaseRegister(counter);
+
+ // Restore original registers
+ for (Register r : isPushed.keySet())
+ if (isPushed.get(r))
+ cg.emit.emit("pop", r);
+ return null;
+ }
+
+ @Override
+ public Register builtInWriteln(BuiltInWriteln ast, Void arg) {
+ Interrupts.printChar(cg, '\n');
+ return null;
+ }
+}
diff --git a/src/cd/frontend/parser/.gitignore b/src/cd/frontend/parser/.gitignore
new file mode 100644
index 0000000..3713151
--- /dev/null
+++ b/src/cd/frontend/parser/.gitignore
@@ -0,0 +1,6 @@
+/Javali.tokens
+/JavaliBaseVisitor.java
+/JavaliLexer.java
+/JavaliLexer.tokens
+/JavaliParser.java
+/JavaliVisitor.java
diff --git a/src/cd/frontend/parser/ParseFailure.java b/src/cd/frontend/parser/ParseFailure.java
new file mode 100644
index 0000000..71ae266
--- /dev/null
+++ b/src/cd/frontend/parser/ParseFailure.java
@@ -0,0 +1,9 @@
+package cd.frontend.parser;
+
+public class ParseFailure extends RuntimeException {
+ private static final long serialVersionUID = -949992757679367939L;
+ public ParseFailure(int line, String string) {
+ super(String.format("Parse failure on line %d: %s",
+ line, string));
+ }
+}
diff --git a/src/cd/ir/Ast.java b/src/cd/ir/Ast.java
new file mode 100644
index 0000000..300fb97
--- /dev/null
+++ b/src/cd/ir/Ast.java
@@ -0,0 +1,752 @@
+package cd.ir;
+
+import cd.util.Pair;
+import cd.util.debug.AstOneLine;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public abstract class Ast {
+
+ /**
+ * The list of children AST nodes. Typically, this list is of a fixed size: its contents
+ * can also be accessed using the various accessors defined on the Ast subtypes.
+ *
+ * Note: this list may contain null pointers!
+ */
+ public final List rwChildren;
+ final String VAR_LABEL_FORMAT = "var_%s";
+
+ protected Ast(int fixedCount) {
+ if (fixedCount == -1)
+ this.rwChildren = new ArrayList();
+ else
+ this.rwChildren = Arrays.asList(new Ast[fixedCount]);
+ }
+
+ /**
+ * Returns a copy of the list of children for this node. The result will
+ * never contain null pointers.
+ */
+ public List children() {
+ ArrayList result = new ArrayList();
+ for (Ast n : rwChildren) {
+ if (n != null) result.add(n);
+ }
+ return result;
+ }
+
+ /**
+ * Returns a new list containing all children AST nodes
+ * that are of the given type.
+ */
+ public List childrenOfType(Class C) {
+ List res = new ArrayList();
+ for (Ast c : children()) {
+ if (C.isInstance(c))
+ res.add(C.cast(c));
+ }
+ return res;
+ }
+
+
+ /** Accept method for the pattern Visitor. */
+ public abstract R accept(AstVisitor visitor, A arg);
+
+ /** Convenient debugging printout */
+ @Override
+ public String toString() {
+ return String.format(
+ "(%s)@%x",
+ AstOneLine.toString(this),
+ System.identityHashCode(this));
+ }
+
+ // _________________________________________________________________
+ // Expressions
+
+ /** Base class for all expressions */
+ public static abstract class Expr extends Ast {
+
+ int needed = -1;
+
+ protected Expr(int fixedCount) {
+ super(fixedCount);
+ }
+
+ @Override
+ public R accept(AstVisitor visitor, A arg) {
+ return this.accept((ExprVisitor) visitor, arg);
+ }
+
+ public abstract R accept(ExprVisitor visitor, A arg);
+
+ /**
+ * Registers needed to perform the operation represented by this Expr node
+ * If not already run, the cost is visiting all subnodes, else the cost is constant
+ * @return Minimum number of registers needed
+ */
+ public abstract int registersNeeded();
+
+ /** Copies any non-AST fields. */
+ protected E postCopy(E item) {
+ return item;
+ }
+ }
+
+ /** Base class used for exprs with left/right operands.
+ * We use this for all expressions that take strictly two operands,
+ * such as binary operators or array indexing. */
+ public static abstract class LeftRightExpr extends Expr {
+
+ public LeftRightExpr(Expr left, Expr right) {
+ super(2);
+ assert left != null;
+ assert right != null;
+ setLeft(left);
+ setRight(right);
+ }
+
+ @Override
+ public int registersNeeded() {
+ if (needed != -1)
+ return needed;
+ int leftNeed = left().registersNeeded();
+ int rightNeed = right().registersNeeded();
+ if (leftNeed > rightNeed)
+ return needed = leftNeed;
+ else if (leftNeed < rightNeed)
+ return needed = rightNeed;
+ else
+ return needed = ++rightNeed;
+ }
+
+ public Expr left() { return (Expr) this.rwChildren.get(0); }
+ public void setLeft(Expr node) { this.rwChildren.set(0, node); }
+
+ public Expr right() { return (Expr) this.rwChildren.get(1); }
+ public void setRight(Expr node) { this.rwChildren.set(1, node); }
+ }
+
+ /** Base class used for expressions with a single argument */
+ public static abstract class ArgExpr extends Expr {
+
+ public ArgExpr(Expr arg) {
+ super(1);
+ assert arg != null;
+ setArg(arg);
+ }
+
+ @Override
+ public int registersNeeded() {
+ return arg().registersNeeded();
+ }
+
+ public Expr arg() { return (Expr) this.rwChildren.get(0); }
+ public void setArg(Expr node) { this.rwChildren.set(0, node); }
+
+ }
+
+ /** Base class used for things with no arguments */
+ protected static abstract class LeafExpr extends Expr {
+ public LeafExpr() {
+ super(0);
+ }
+
+ @Override
+ public int registersNeeded() {
+ return 1;
+ }
+ }
+
+ /** Represents {@code this}, the current object */
+ public static class ThisRef extends LeafExpr {
+
+ @Override
+ public R accept(ExprVisitor visitor, A arg) {
+ return visitor.thisRef(this, arg);
+ }
+
+ }
+
+ /** A binary operation combining a left and right operand,
+ * such as "1+2" or "3*4" */
+ public static class BinaryOp extends LeftRightExpr {
+
+ public static enum BOp {
+
+ B_TIMES("*"),
+ B_DIV("/"),
+ B_MOD("%"),
+ B_PLUS("+"),
+ B_MINUS("-"),
+ B_AND("&&"),
+ B_OR("||"),
+ B_EQUAL("=="),
+ B_NOT_EQUAL("!="),
+ B_LESS_THAN("<"),
+ B_LESS_OR_EQUAL("<="),
+ B_GREATER_THAN(">"),
+ B_GREATER_OR_EQUAL(">=");
+
+ public String repr;
+ private BOp(String repr) { this.repr = repr; }
+
+ /**
+ * Note that this method ignores short-circuit evaluation of boolean
+ * AND/OR.
+ *
+ * @return true
iff A op B == B op A
for this
+ * operator.
+ */
+ public boolean isCommutative() {
+ switch(this) {
+ case B_PLUS:
+ case B_TIMES:
+ case B_AND:
+ case B_OR:
+ case B_EQUAL:
+ case B_NOT_EQUAL:
+ return true;
+ default:
+ return false;
+ }
+ }
+ };
+
+ public BOp operator;
+
+ public BinaryOp(Expr left, BOp operator, Expr right) {
+ super(left, right);
+ this.operator = operator;
+ }
+
+ @Override
+ public R accept(ExprVisitor visitor, A arg) {
+ return visitor.binaryOp(this, arg);
+ }
+
+ }
+
+ /** A Cast from one type to another: {@code (typeName)arg} */
+ public static class Cast extends ArgExpr {
+
+ public String typeName;
+
+ public Cast(Expr arg, String typeName) {
+ super(arg);
+ this.typeName = typeName;
+ }
+
+ @Override
+ public R accept(ExprVisitor visitor, A arg) {
+ return visitor.cast(this, arg);
+ }
+
+ }
+
+ public static class IntConst extends LeafExpr {
+
+ public final int value;
+ public IntConst(int value) {
+ this.value = value;
+ }
+
+ @Override
+ public R accept(ExprVisitor visitor, A arg) {
+ return visitor.intConst(this, arg);
+ }
+
+ }
+
+ public static class BooleanConst extends LeafExpr {
+
+ public final boolean value;
+ public BooleanConst(boolean value) {
+ this.value = value;
+ }
+
+ @Override
+ public R accept(ExprVisitor visitor, A arg) {
+ return visitor.booleanConst(this, arg);
+ }
+
+ }
+
+ public static class NullConst extends LeafExpr {
+
+ @Override
+ public R accept(ExprVisitor visitor, A arg) {
+ return visitor.nullConst(this, arg);
+ }
+
+ }
+
+ public static class Field extends ArgExpr {
+
+ public final String fieldName;
+
+ public Field(Expr arg, String fieldName) {
+ super(arg);
+ assert arg != null && fieldName != null;
+ this.fieldName = fieldName;
+ }
+
+ @Override
+ public R accept(ExprVisitor visitor, A arg) {
+ return visitor.field(this, arg);
+ }
+
+ }
+
+ public static class Index extends LeftRightExpr {
+
+ public Index(Expr array, Expr index) {
+ super(array, index);
+ }
+
+ @Override
+ public R accept(ExprVisitor visitor, A arg) {
+ return visitor.index(this, arg);
+ }
+
+ }
+
+ public static class NewObject extends LeafExpr {
+
+ /** Name of the type to be created */
+ public String typeName;
+
+ public NewObject(String typeName) {
+ this.typeName = typeName;
+ }
+
+ @Override
+ public R accept(ExprVisitor visitor, A arg) {
+ return visitor.newObject(this, arg);
+ }
+
+ }
+
+ public static class NewArray extends ArgExpr {
+
+ /** Name of the type to be created: must be an array type */
+ public String typeName;
+
+ public NewArray(String typeName, Expr capacity) {
+ super(capacity);
+ this.typeName = typeName;
+ }
+
+ @Override
+ public R accept(ExprVisitor visitor, A arg) {
+ return visitor.newArray(this, arg);
+ }
+
+ }
+
+ public static class UnaryOp extends ArgExpr {
+
+ public static enum UOp {
+ U_PLUS("+"),
+ U_MINUS("-"),
+ U_BOOL_NOT("!");
+ public String repr;
+ private UOp(String repr) { this.repr = repr; }
+ };
+
+ public final UOp operator;
+
+ public UnaryOp(UOp operator, Expr arg) {
+ super(arg);
+ this.operator = operator;
+ }
+
+ @Override
+ public R accept(ExprVisitor visitor, A arg) {
+ return visitor.unaryOp(this, arg);
+ }
+
+ }
+
+ public static class Var extends LeafExpr {
+
+ public String name;
+
+ /**
+ * Use this constructor to build an instance of this AST
+ * in the parser.
+ */
+ public Var(String name) {
+ this.name = name;
+ }
+
+ public String getLabel() {
+ return String.format(VAR_LABEL_FORMAT, name);
+ }
+
+ @Override
+ public R accept(ExprVisitor visitor, A arg) {
+ return visitor.var(this, arg);
+ }
+
+ }
+
+ public static class BuiltInRead extends LeafExpr {
+
+ @Override
+ public R accept(ExprVisitor visitor, A arg) {
+ return visitor.builtInRead(this, arg);
+ }
+
+ }
+
+ public static class MethodCallExpr extends Expr {
+
+ public String methodName;
+
+ public MethodCallExpr(Expr rcvr, String methodName, List arguments) {
+ super(-1);
+ assert rcvr != null && methodName != null && arguments != null;
+ this.methodName = methodName;
+ this.rwChildren.add(rcvr);
+ this.rwChildren.addAll(arguments);
+ }
+
+ /** Returns the receiver of the method call.
+ * i.e., for a method call {@code a.b(c,d)} returns {@code a}. */
+ public Expr receiver() { return (Expr) this.rwChildren.get(0); }
+
+ /** Changes the receiver of the method call.
+ * i.e., for a method call {@code a.b(c,d)} changes {@code a}. */
+ public void setReceiver(Expr rcvr) { this.rwChildren.set(0, rcvr); }
+
+ /** Returns all arguments to the method, including the receiver.
+ * i.e, for a method call {@code a.b(c,d)} returns {@code [a, c, d]} */
+ public List allArguments()
+ {
+ ArrayList result = new ArrayList();
+ for (Ast chi : this.rwChildren)
+ result.add((Expr) chi);
+ return Collections.unmodifiableList(result);
+ }
+
+ /** Returns all arguments to the method, without the receiver.
+ * i.e, for a method call {@code a.b(c,d)} returns {@code [c, d]} */
+ public List argumentsWithoutReceiver()
+ {
+ ArrayList result = new ArrayList();
+ for (int i = 1; i < this.rwChildren.size(); i++)
+ result.add((Expr) this.rwChildren.get(i));
+ return Collections.unmodifiableList(result);
+ }
+
+
+ @Override
+ public R accept(ExprVisitor visitor, A arg) {
+ return visitor.methodCall(this, arg);
+ }
+
+ @Override
+ public int registersNeeded() {
+ throw new RuntimeException("Not implemented");
+ }
+
+ }
+
+ // _________________________________________________________________
+ // Statements
+
+ /** Interface for all statements */
+ public static abstract class Stmt extends Ast {
+ protected Stmt(int fixedCount) {
+ super(fixedCount);
+ }
+ }
+
+ /** Represents an empty statement: has no effect. */
+ public static class Nop extends Stmt {
+
+ public Nop() {
+ super(0);
+ }
+
+ @Override
+ public R accept(AstVisitor visitor, A arg) {
+ return visitor.nop(this, arg);
+ }
+
+ }
+
+ /** An assignment from {@code right()} to the location
+ * represented by {@code left()}.
+ */
+ public static class Assign extends Stmt {
+
+ public Assign(Expr left, Expr right) {
+ super(2);
+ assert left != null && right != null;
+ setLeft(left);
+ setRight(right);
+ }
+
+ public Expr left() { return (Expr) this.rwChildren.get(0); }
+ public void setLeft(Expr node) { this.rwChildren.set(0, node); }
+
+ public Expr right() { return (Expr) this.rwChildren.get(1); }
+ public void setRight(Expr node) { this.rwChildren.set(1, node); }
+
+ @Override
+ public R accept(AstVisitor visitor, A arg) {
+ return visitor.assign(this, arg);
+ }
+
+ }
+
+ public static class IfElse extends Stmt {
+
+ public IfElse(Expr cond, Ast then, Ast otherwise) {
+ super(3);
+ assert cond != null && then != null && otherwise != null;
+ setCondition(cond);
+ setThen(then);
+ setOtherwise(otherwise);
+ }
+
+ public Expr condition() { return (Expr) this.rwChildren.get(0); }
+ public void setCondition(Expr node) { this.rwChildren.set(0, node); }
+
+ public Ast then() { return this.rwChildren.get(1); }
+ public void setThen(Ast node) { this.rwChildren.set(1, node); }
+
+ public Ast otherwise() { return this.rwChildren.get(2); }
+ public void setOtherwise(Ast node) { this.rwChildren.set(2, node); }
+
+ @Override
+ public R accept(AstVisitor visitor, A arg) {
+ return visitor.ifElse(this, arg);
+ }
+
+ }
+
+
+ public static class ReturnStmt extends Stmt {
+
+ public ReturnStmt(Expr arg) {
+ super(1);
+ setArg(arg);
+ }
+
+ public Expr arg() { return (Expr) this.rwChildren.get(0); }
+ public void setArg(Expr node) { this.rwChildren.set(0, node); }
+
+ @Override
+ public R accept(AstVisitor visitor, A arg) {
+ return visitor.returnStmt(this, arg);
+ }
+
+ }
+
+ public static class BuiltInWrite extends Stmt {
+
+ public BuiltInWrite(Expr arg) {
+ super(1);
+ assert arg != null;
+ setArg(arg);
+ }
+
+ public Expr arg() { return (Expr) this.rwChildren.get(0); }
+ public void setArg(Expr node) { this.rwChildren.set(0, node); }
+
+ @Override
+ public R accept(AstVisitor visitor, A arg) {
+ return visitor.builtInWrite(this, arg);
+ }
+
+ }
+
+ public static class BuiltInWriteln extends Stmt {
+
+ public BuiltInWriteln() {
+ super(0);
+ }
+
+ @Override
+ public R accept(AstVisitor visitor, A arg) {
+ return visitor.builtInWriteln(this, arg);
+ }
+
+ }
+
+ public static class MethodCall extends Stmt {
+
+ public MethodCall(MethodCallExpr mce) {
+ super(1);
+ this.rwChildren.set(0, mce);
+ }
+
+ public MethodCallExpr getMethodCallExpr() {
+ return (MethodCallExpr)this.rwChildren.get(0);
+ }
+
+ @Override
+ public R accept(AstVisitor visitor, A arg) {
+ return visitor.methodCall(this, arg);
+ }
+
+ }
+
+ public static class WhileLoop extends Stmt {
+
+ public WhileLoop(Expr condition, Ast body) {
+ super(2);
+ assert condition != null && body != null;
+ setCondition(condition);
+ setBody(body);
+ }
+
+ public Expr condition() { return (Expr) this.rwChildren.get(0); }
+ public void setCondition(Expr cond) { this.rwChildren.set(0, cond); }
+
+ public Ast body() { return this.rwChildren.get(1); }
+ public void setBody(Ast body) { this.rwChildren.set(1, body); }
+
+ @Override
+ public R accept(AstVisitor visitor, A arg) {
+ return visitor.whileLoop(this, arg);
+ }
+ }
+
+ // _________________________________________________________________
+ // Declarations
+
+ /** Interface for all declarations */
+ public static abstract class Decl extends Ast {
+ protected Decl(int fixedCount) {
+ super(fixedCount);
+ }
+ }
+
+ public static class VarDecl extends Decl {
+
+ public String type;
+ public String name;
+ public VarDecl(String type, String name) {
+ this(0, type, name);
+ }
+
+ protected VarDecl(int num, String type, String name) {
+ super(num);
+ this.type = type;
+ this.name = name;
+ }
+
+ public String getLabel() {
+ return String.format(VAR_LABEL_FORMAT, name);
+ }
+
+ @Override
+ public R accept(AstVisitor visitor, A arg) {
+ return visitor.varDecl(this, arg);
+ }
+
+ }
+
+ /** Used in {@link MethodDecl} to group together declarations
+ * and method bodies. */
+ public static class Seq extends Decl {
+
+ public Seq(List nodes) {
+ super(-1);
+ if (nodes != null) this.rwChildren.addAll(nodes);
+ }
+
+ /** Grant access to the raw list of children for seq nodes */
+ public List rwChildren() {
+ return this.rwChildren;
+ }
+
+ @Override
+ public R accept(AstVisitor visitor, A arg) {
+ return visitor.seq(this, arg);
+ }
+ }
+
+ public static class MethodDecl extends Decl {
+
+ public String returnType;
+ public String name;
+ public List argumentTypes;
+ public List argumentNames;
+ public MethodDecl(
+ String returnType,
+ String name,
+ List> formalParams,
+ Seq decls,
+ Seq body) {
+ this(returnType, name, Pair.unzipA(formalParams), Pair.unzipB(formalParams), decls, body);
+ }
+ public MethodDecl(
+ String returnType,
+ String name,
+ List argumentTypes,
+ List argumentNames,
+ Seq decls,
+ Seq body) {
+ super(2);
+ this.returnType = returnType;
+ this.name = name;
+ this.argumentTypes = argumentTypes;
+ this.argumentNames = argumentNames;
+ setDecls(decls);
+ setBody(body);
+ }
+
+ public Seq decls() { return (Seq) this.rwChildren.get(0); }
+ public void setDecls(Seq decls) { this.rwChildren.set(0, decls); }
+
+ public Seq body() { return (Seq) this.rwChildren.get(1); }
+ public void setBody(Seq body) { this.rwChildren.set(1, body); }
+
+ @Override
+ public R accept(AstVisitor visitor, A arg) {
+ return visitor.methodDecl(this, arg);
+ }
+
+ }
+
+ public static class ClassDecl extends Decl {
+
+ public String name;
+ public String superClass;
+ public ClassDecl(
+ String name,
+ String superClass,
+ List extends Ast> members) {
+ super(-1);
+ this.name = name;
+ this.superClass = superClass;
+ this.rwChildren.addAll(members);
+ }
+
+ public List members() {
+ return Collections.unmodifiableList(this.rwChildren);
+ }
+
+ public List fields() {
+ return childrenOfType(VarDecl.class);
+ } // includes constants!
+
+ public List methods() {
+ return childrenOfType(MethodDecl.class);
+ }
+
+ @Override
+ public R accept(AstVisitor visitor, A arg) {
+ return visitor.classDecl(this, arg);
+ }
+
+ }
+}
diff --git a/src/cd/ir/AstVisitor.java b/src/cd/ir/AstVisitor.java
new file mode 100644
index 0000000..0576c24
--- /dev/null
+++ b/src/cd/ir/AstVisitor.java
@@ -0,0 +1,106 @@
+package cd.ir;
+
+import cd.ir.Ast.Decl;
+import cd.ir.Ast.Expr;
+import cd.ir.Ast.Stmt;
+
+/** A visitor that visits any kind of node */
+public class AstVisitor extends ExprVisitor {
+
+ /**
+ * Recurse and process {@code ast}. It is preferred to
+ * call this rather than calling accept directly, since
+ * it can be overloaded to introduce memoization,
+ * for example. */
+ public R visit(Ast ast, A arg) {
+ return ast.accept(this, arg);
+ }
+
+ /**
+ * A handy function which visits the children of {@code ast},
+ * providing "arg" to each of them. It returns the result of
+ * the last child in the list. It is invoked by the method
+ * {@link #dflt(Ast, Object)} by default.
+ */
+ public R visitChildren(Ast ast, A arg) {
+ R lastValue = null;
+ for (Ast child : ast.children())
+ lastValue = visit(child, arg);
+ return lastValue;
+ }
+
+ /**
+ * The default action for default actions is to call this,
+ * which simply recurses to any children. Also called
+ * by seq() by default. */
+ protected R dflt(Ast ast, A arg) {
+ return visitChildren(ast, arg);
+ }
+
+ /**
+ * The default action for statements is to call this */
+ protected R dfltStmt(Stmt ast, A arg) {
+ return dflt(ast, arg);
+ }
+
+ /**
+ * The default action for expressions is to call this */
+ protected R dfltExpr(Expr ast, A arg) {
+ return dflt(ast, arg);
+ }
+
+ /**
+ * The default action for AST nodes representing declarations
+ * is to call this function */
+ protected R dfltDecl(Decl ast, A arg) {
+ return dflt(ast, arg);
+ }
+
+ public R assign(Ast.Assign ast, A arg) {
+ return dfltStmt(ast, arg);
+ }
+
+ public R builtInWrite(Ast.BuiltInWrite ast, A arg) {
+ return dfltStmt(ast, arg);
+ }
+
+ public R builtInWriteln(Ast.BuiltInWriteln ast, A arg) {
+ return dfltStmt(ast, arg);
+ }
+
+ public R classDecl(Ast.ClassDecl ast, A arg) {
+ return dfltDecl(ast, arg);
+ }
+
+ public R methodDecl(Ast.MethodDecl ast, A arg) {
+ return dfltDecl(ast, arg);
+ }
+
+ public R varDecl(Ast.VarDecl ast, A arg) {
+ return dfltDecl(ast, arg);
+ }
+
+ public R ifElse(Ast.IfElse ast, A arg) {
+ return dfltStmt(ast, arg);
+ }
+
+ public R returnStmt(Ast.ReturnStmt ast, A arg) {
+ return dfltStmt(ast, arg);
+ }
+
+ public R methodCall(Ast.MethodCall ast, A arg) {
+ return dfltStmt(ast, arg);
+ }
+
+ public R nop(Ast.Nop ast, A arg) {
+ return dfltStmt(ast, arg);
+ }
+
+ public R seq(Ast.Seq ast, A arg) {
+ return dflt(ast, arg);
+ }
+
+ public R whileLoop(Ast.WhileLoop ast, A arg) {
+ return dfltStmt(ast, arg);
+ }
+}
diff --git a/src/cd/ir/ExprVisitor.java b/src/cd/ir/ExprVisitor.java
new file mode 100644
index 0000000..121fd57
--- /dev/null
+++ b/src/cd/ir/ExprVisitor.java
@@ -0,0 +1,92 @@
+package cd.ir;
+
+import cd.ir.Ast.Expr;
+
+/**
+ * A visitor that only visits {@link Expr} nodes.
+ */
+public class ExprVisitor {
+ /**
+ * Recurse and process {@code ast}. It is preferred to
+ * call this rather than calling accept directly, since
+ * it can be overloaded to introduce memoization,
+ * for example. */
+ public R visit(Expr ast, A arg) {
+ return ast.accept(this, arg);
+ }
+
+ /**
+ * Visits all children of the expression. Relies on the fact
+ * that {@link Expr} nodes only contain other {@link Expr} nodes.
+ */
+ public R visitChildren(Expr ast, A arg) {
+ R lastValue = null;
+ for (Ast child : ast.children())
+ lastValue = visit((Expr)child, arg);
+ return lastValue;
+ }
+
+ /**
+ * The default action for default actions is to call this,
+ * which simply recurses to any children. Also called
+ * by seq() by default. */
+ protected R dfltExpr(Expr ast, A arg) {
+ return visitChildren(ast, arg);
+ }
+
+ public R binaryOp(Ast.BinaryOp ast, A arg) {
+ return dfltExpr(ast, arg);
+ }
+
+ public R booleanConst(Ast.BooleanConst ast, A arg) {
+ return dfltExpr(ast, arg);
+ }
+
+ public R builtInRead(Ast.BuiltInRead ast, A arg) {
+ return dfltExpr(ast, arg);
+ }
+
+ public R cast(Ast.Cast ast, A arg) {
+ return dfltExpr(ast, arg);
+ }
+
+ public R field(Ast.Field ast, A arg) {
+ return dfltExpr(ast, arg);
+ }
+
+ public R index(Ast.Index ast, A arg) {
+ return dfltExpr(ast, arg);
+ }
+
+ public R intConst(Ast.IntConst ast, A arg) {
+ return dfltExpr(ast, arg);
+ }
+
+ public R methodCall(Ast.MethodCallExpr ast, A arg) {
+ return dfltExpr(ast, arg);
+ }
+
+ public R newObject(Ast.NewObject ast, A arg) {
+ return dfltExpr(ast, arg);
+ }
+
+ public R newArray(Ast.NewArray ast, A arg) {
+ return dfltExpr(ast, arg);
+ }
+
+ public R nullConst(Ast.NullConst ast, A arg) {
+ return dfltExpr(ast, arg);
+ }
+
+ public R thisRef(Ast.ThisRef ast, A arg) {
+ return dfltExpr(ast, arg);
+ }
+
+ public R unaryOp(Ast.UnaryOp ast, A arg) {
+ return dfltExpr(ast, arg);
+ }
+
+ public R var(Ast.Var ast, A arg) {
+ return dfltExpr(ast, arg);
+ }
+}
diff --git a/src/cd/util/FileUtil.java b/src/cd/util/FileUtil.java
new file mode 100644
index 0000000..bf5cc60
--- /dev/null
+++ b/src/cd/util/FileUtil.java
@@ -0,0 +1,121 @@
+package cd.util;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.List;
+
+public class FileUtil {
+
+ public static String readAll(Reader ubReader) throws IOException {
+ try (BufferedReader bReader = new BufferedReader(ubReader)) {
+ StringBuilder sb = new StringBuilder();
+
+ while (true) {
+ int ch = bReader.read();
+ if (ch == -1)
+ break;
+
+ sb.append((char) ch);
+ }
+ return sb.toString();
+ }
+ }
+
+ public static String read(File file) throws IOException {
+ return readAll(new FileReader(file));
+ }
+
+ public static void write(File file, String text) throws IOException {
+ try (FileWriter writer = new FileWriter(file)) {
+ writer.write(text);
+ }
+ }
+
+ public static String runCommand(File dir, String[] command,
+ String[] substs, String input, boolean detectError)
+ throws IOException {
+ // Substitute the substitution strings $0, $1, etc
+ String newCommand[] = new String[command.length];
+ for (int i = 0; i < command.length; i++) {
+ String newItem = command[i];
+ for (int j = 0; j < substs.length; j++)
+ newItem = newItem.replace("$" + j, substs[j]);
+ newCommand[i] = newItem;
+ }
+
+ // Run the command in the specified directory
+ ProcessBuilder pb = new ProcessBuilder(newCommand);
+ pb.directory(dir);
+ pb.redirectErrorStream(true);
+ final Process p = pb.start();
+ if (input != null && !input.equals("")) {
+ try (OutputStreamWriter osw = new OutputStreamWriter(p.getOutputStream())) {
+ osw.write(input);
+ }
+ }
+
+ try {
+ final StringBuffer result = new StringBuffer();
+ // thread to read stdout from child so that p.waitFor() is interruptible
+ // by JUnit's timeout mechanism. Otherwise it would block in readAll()
+ Thread t = new Thread () {
+ public void run() {
+ try {
+ result.append(readAll(new InputStreamReader(p.getInputStream())));
+ } catch (IOException e) {
+ }
+ }
+ };
+ t.start();
+
+ if (detectError) {
+ int err = p.waitFor();
+
+ // hack: same as ReferenceServer returns when
+ // a dynamic error occurs running the interpreter
+ if (err != 0)
+ return "Error: " + err + "\n";
+ }
+
+ t.join();
+ return result.toString();
+ } catch (InterruptedException e) {
+ return "Error: execution of " + command[0] + " got interrupted (probably timed out)\n";
+ } finally {
+ // in case child is still running, destroy
+ p.destroy();
+ }
+ }
+
+ /**
+ * Finds all .javali under directory {@code testDir}, adding File objects
+ * into {@code result} for each one.
+ */
+ public static void findJavaliFiles(File testDir, List