Homework 1
This commit is contained in:
parent
49bca7f856
commit
12f678a924
43 changed files with 3703 additions and 0 deletions
148
src/cd/Config.java
Normal file
148
src/cd/Config.java
Normal file
|
@ -0,0 +1,148 @@
|
|||
package cd;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class Config {
|
||||
|
||||
public static enum SystemKind {
|
||||
LINUX,
|
||||
WINDOWS,
|
||||
MACOSX
|
||||
}
|
||||
|
||||
/**
|
||||
* What kind of system we are on
|
||||
*/
|
||||
public static final SystemKind systemKind;
|
||||
|
||||
/**
|
||||
* Defines the extension used for assembler files on this platform.
|
||||
* Currently always {@code .s}.
|
||||
*/
|
||||
public static final String ASMEXT = ".s";
|
||||
|
||||
/** Defines the extension used for binary files on this platform. */
|
||||
public static final String BINARYEXT;
|
||||
|
||||
/** Defines the name of the main function to be used in .s file */
|
||||
public static final String MAIN;
|
||||
|
||||
/** Defines the name of the printf function to be used in .s file */
|
||||
public static final String PRINTF;
|
||||
|
||||
/** Defines the name of the scanf function to be used in .s file */
|
||||
public static final String SCANF;
|
||||
|
||||
/** Defines the name of the calloc function to be used in .s file */
|
||||
public static final String CALLOC;
|
||||
|
||||
/** Defines the name of the exit function to be used in .s file */
|
||||
public static final String EXIT;
|
||||
|
||||
/** The assembler directive used to define a constant string */
|
||||
public static final String DOT_STRING;
|
||||
|
||||
/** The assembler directive used to define a constant int */
|
||||
public static final String DOT_INT;
|
||||
|
||||
/** The assembler directive used to start the text section */
|
||||
public static final String TEXT_SECTION;
|
||||
|
||||
/** The assembler directive used to start the section for integer data */
|
||||
public static final String DATA_INT_SECTION;
|
||||
|
||||
/**
|
||||
* The assembler directive used to start the section for string data (if
|
||||
* different from {@link #DATA_INT_SECTION}
|
||||
*/
|
||||
public static final String DATA_STR_SECTION;
|
||||
|
||||
/** Comment separator used in assembly files */
|
||||
public static final String COMMENT_SEP;
|
||||
|
||||
/**
|
||||
* Defines the assembler command to use. Should be a string array where each
|
||||
* entry is one argument. Use the special string "$0" to refer to the output
|
||||
* file, and $1 to refer to the ".s" file.
|
||||
*/
|
||||
public static final String[] ASM;
|
||||
|
||||
/**
|
||||
* The directory from which to run the assembler. In a CYGWIN installation,
|
||||
* this can make a big difference!
|
||||
*/
|
||||
public static final File ASM_DIR;
|
||||
|
||||
/**
|
||||
* sizeof a pointer in bytes in the target platform.
|
||||
*/
|
||||
public static final int SIZEOF_PTR = 4;
|
||||
|
||||
/**
|
||||
* Name of java executable in JRE path
|
||||
*/
|
||||
public static final String JAVA_EXE;
|
||||
|
||||
static {
|
||||
|
||||
final String os = System.getProperty("os.name").toLowerCase();
|
||||
|
||||
if(os.contains("windows") || os.contains("nt")) {
|
||||
systemKind = SystemKind.WINDOWS;
|
||||
BINARYEXT = ".exe";
|
||||
MAIN = "_main";
|
||||
PRINTF = "_printf";
|
||||
SCANF = "_scanf";
|
||||
CALLOC = "_calloc";
|
||||
EXIT = "_exit";
|
||||
// These are set up for a Cygwin installation on C:,
|
||||
// you can change as registersNeeded.
|
||||
ASM = new String[]{"gcc", "-o", "$0", "$1"};
|
||||
ASM_DIR = new File("C:\\CYGWIN\\BIN");
|
||||
JAVA_EXE = "javaw.exe";
|
||||
DOT_STRING = ".string";
|
||||
DOT_INT = ".int";
|
||||
TEXT_SECTION = ".section .text";
|
||||
DATA_INT_SECTION = ".section .data";
|
||||
DATA_STR_SECTION = ".section .data";
|
||||
COMMENT_SEP = "#";
|
||||
}
|
||||
else if(os.contains("mac os x") || os.contains("darwin")) {
|
||||
systemKind = SystemKind.MACOSX;
|
||||
BINARYEXT = ".bin";
|
||||
MAIN = "_main";
|
||||
PRINTF = "_printf";
|
||||
SCANF = "_scanf";
|
||||
CALLOC = "_calloc";
|
||||
EXIT = "_exit";
|
||||
ASM = new String[]{"gcc", "-m32", "-o", "$0", "$1"};
|
||||
ASM_DIR = new File(".");
|
||||
JAVA_EXE = "java";
|
||||
DOT_STRING = ".asciz";
|
||||
DOT_INT = ".long";
|
||||
TEXT_SECTION = ".text";
|
||||
DATA_INT_SECTION = ".data";
|
||||
DATA_STR_SECTION = ".cstring";
|
||||
COMMENT_SEP = "#";
|
||||
}
|
||||
else {
|
||||
systemKind = SystemKind.LINUX;
|
||||
BINARYEXT = ".bin";
|
||||
MAIN = "main";
|
||||
PRINTF = "printf";
|
||||
SCANF = "scanf";
|
||||
CALLOC = "calloc";
|
||||
EXIT = "exit";
|
||||
ASM = new String[]{"gcc", "-m32", "-o", "$0", "$1"};
|
||||
ASM_DIR = new File(".");
|
||||
JAVA_EXE = "java";
|
||||
DOT_STRING = ".string";
|
||||
DOT_INT = ".int";
|
||||
TEXT_SECTION = ".section .text";
|
||||
DATA_INT_SECTION = ".section .data";
|
||||
DATA_STR_SECTION = ".section .data";
|
||||
COMMENT_SEP = "#";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
126
src/cd/Main.java
Normal file
126
src/cd/Main.java
Normal file
|
@ -0,0 +1,126 @@
|
|||
package cd;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.antlr.v4.runtime.ANTLRInputStream;
|
||||
import org.antlr.v4.runtime.BailErrorStrategy;
|
||||
import org.antlr.v4.runtime.CommonTokenStream;
|
||||
import org.antlr.v4.runtime.misc.ParseCancellationException;
|
||||
|
||||
import cd.backend.codegen.AstCodeGenerator;
|
||||
import cd.frontend.parser.JavaliAstVisitor;
|
||||
import cd.frontend.parser.JavaliLexer;
|
||||
import cd.frontend.parser.JavaliParser;
|
||||
import cd.frontend.parser.JavaliParser.UnitContext;
|
||||
import cd.frontend.parser.ParseFailure;
|
||||
import cd.ir.Ast.ClassDecl;
|
||||
import cd.util.debug.AstDump;
|
||||
|
||||
/**
|
||||
* The main entrypoint for the compiler. Consists of a series
|
||||
* of routines which must be invoked in order. The main()
|
||||
* routine here invokes these routines, as does the unit testing
|
||||
* code. This is not the <b>best</b> programming practice, as the
|
||||
* series of calls to be invoked is duplicated in two places in the
|
||||
* code, but it will do for now. */
|
||||
public class Main {
|
||||
|
||||
// Set to non-null to write debug info out
|
||||
public Writer debug = null;
|
||||
|
||||
// Set to non-null to write dump of control flow graph (Advanced Compiler Design)
|
||||
public File cfgdumpbase;
|
||||
|
||||
public void debug(String format, Object... args) {
|
||||
if (debug != null) {
|
||||
String result = String.format(format, args);
|
||||
try {
|
||||
debug.write(result);
|
||||
debug.write('\n');
|
||||
debug.flush();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Parse command line, invoke compile() routine */
|
||||
public static void main(String args[]) throws IOException {
|
||||
Main m = new Main();
|
||||
|
||||
for (String arg : args) {
|
||||
if (arg.equals("-d"))
|
||||
m.debug = new OutputStreamWriter(System.err);
|
||||
else {
|
||||
FileReader fin = new FileReader(arg);
|
||||
|
||||
// Parse:
|
||||
List<ClassDecl> astRoots = m.parse(fin);
|
||||
|
||||
// Run the semantic check:
|
||||
m.semanticCheck(astRoots);
|
||||
|
||||
// Generate code:
|
||||
String sFile = arg + Config.ASMEXT;
|
||||
try (FileWriter fout = new FileWriter(sFile)) {
|
||||
m.generateCode(astRoots, fout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Parses an input stream into an AST
|
||||
* @throws IOException */
|
||||
public List<ClassDecl> parse(Reader reader) throws IOException {
|
||||
List<ClassDecl> result = new ArrayList<ClassDecl>();
|
||||
|
||||
try {
|
||||
JavaliLexer lexer = new JavaliLexer(new ANTLRInputStream(reader));
|
||||
JavaliParser parser = new JavaliParser(new CommonTokenStream(lexer));
|
||||
parser.setErrorHandler(new BailErrorStrategy());
|
||||
UnitContext unit = parser.unit();
|
||||
|
||||
JavaliAstVisitor visitor = new JavaliAstVisitor();
|
||||
visitor.visit(unit);
|
||||
result = visitor.classDecls;
|
||||
} catch (ParseCancellationException e) {
|
||||
ParseFailure pf = new ParseFailure(0, "?");
|
||||
pf.initCause(e);
|
||||
throw pf;
|
||||
}
|
||||
|
||||
debug("AST Resulting From Parsing Stage:");
|
||||
dumpAst(result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
public void semanticCheck(List<ClassDecl> astRoots) {
|
||||
{
|
||||
// Not registersNeeded until later. Ignore.
|
||||
}
|
||||
}
|
||||
|
||||
public void generateCode(List<ClassDecl> astRoots, Writer out) {
|
||||
{
|
||||
AstCodeGenerator cg = AstCodeGenerator.createCodeGenerator(this, out);
|
||||
cg.go(astRoots);
|
||||
}
|
||||
}
|
||||
|
||||
/** Dumps the AST to the debug stream */
|
||||
private void dumpAst(List<ClassDecl> astRoots) throws IOException {
|
||||
if (this.debug == null) return;
|
||||
this.debug.write(AstDump.toString(astRoots));
|
||||
}
|
||||
}
|
14
src/cd/ToDoException.java
Normal file
14
src/cd/ToDoException.java
Normal file
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
164
src/cd/backend/codegen/AssemblyEmitter.java
Normal file
164
src/cd/backend/codegen/AssemblyEmitter.java
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
14
src/cd/backend/codegen/AssemblyFailedException.java
Normal file
14
src/cd/backend/codegen/AssemblyFailedException.java
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
58
src/cd/backend/codegen/AstCodeGenerator.java
Normal file
58
src/cd/backend/codegen/AstCodeGenerator.java
Normal file
|
@ -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.
|
||||
*
|
||||
* <p>
|
||||
* The generated file will be divided into two sections:
|
||||
* <ol>
|
||||
* <li>Prologue: Generated by {@link #emitPrefix()}. This contains any
|
||||
* introductory declarations and the like.
|
||||
* <li>Body: Generated by {@link ExprGenerator}. This contains the main
|
||||
* method definitions.
|
||||
* </ol>
|
||||
*/
|
||||
public void go(List<? extends ClassDecl> astRoots) {
|
||||
for (ClassDecl ast : astRoots) {
|
||||
sg.gen(ast);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected void initMethodData() {
|
||||
}
|
||||
}
|
307
src/cd/backend/codegen/ExprGenerator.java
Normal file
307
src/cd/backend/codegen/ExprGenerator.java
Normal file
|
@ -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<Register, Void> {
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
97
src/cd/backend/codegen/Interrupts.java
Normal file
97
src/cd/backend/codegen/Interrupts.java
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
123
src/cd/backend/codegen/RegisterManager.java
Normal file
123
src/cd/backend/codegen/RegisterManager.java
Normal file
|
@ -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<Register> registers = new ArrayList<Register>();
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
206
src/cd/backend/codegen/StmtGenerator.java
Normal file
206
src/cd/backend/codegen/StmtGenerator.java
Normal file
|
@ -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<Register, Void> {
|
||||
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<Register> auxRegs = Arrays.asList(Register.EDI, Register.ESI);
|
||||
List<Register> divRegs = Arrays.asList(Register.EAX, Register.EDX);
|
||||
Map<Register, Boolean> 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;
|
||||
}
|
||||
}
|
6
src/cd/frontend/parser/.gitignore
vendored
Normal file
6
src/cd/frontend/parser/.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
/Javali.tokens
|
||||
/JavaliBaseVisitor.java
|
||||
/JavaliLexer.java
|
||||
/JavaliLexer.tokens
|
||||
/JavaliParser.java
|
||||
/JavaliVisitor.java
|
9
src/cd/frontend/parser/ParseFailure.java
Normal file
9
src/cd/frontend/parser/ParseFailure.java
Normal file
|
@ -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));
|
||||
}
|
||||
}
|
752
src/cd/ir/Ast.java
Normal file
752
src/cd/ir/Ast.java
Normal file
|
@ -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.
|
||||
*
|
||||
* <p><b>Note:</b> this list may contain null pointers!
|
||||
*/
|
||||
public final List<Ast> rwChildren;
|
||||
final String VAR_LABEL_FORMAT = "var_%s";
|
||||
|
||||
protected Ast(int fixedCount) {
|
||||
if (fixedCount == -1)
|
||||
this.rwChildren = new ArrayList<Ast>();
|
||||
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<Ast> children() {
|
||||
ArrayList<Ast> result = new ArrayList<Ast>();
|
||||
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 <A> List<A> childrenOfType(Class<A> C) {
|
||||
List<A> res = new ArrayList<A>();
|
||||
for (Ast c : children()) {
|
||||
if (C.isInstance(c))
|
||||
res.add(C.cast(c));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
/** Accept method for the pattern Visitor. */
|
||||
public abstract <R,A> R accept(AstVisitor<R, A> 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, A> R accept(AstVisitor<R, A> visitor, A arg) {
|
||||
return this.accept((ExprVisitor<R, A>) visitor, arg);
|
||||
}
|
||||
|
||||
public abstract <R, A> R accept(ExprVisitor<R, A> 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 extends Expr> 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, A> R accept(ExprVisitor<R, A> 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 <code>true</code> iff <code>A op B == B op A</code> 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, A> R accept(ExprVisitor<R, A> 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, A> R accept(ExprVisitor<R, A> 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, A> R accept(ExprVisitor<R, A> 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, A> R accept(ExprVisitor<R, A> visitor, A arg) {
|
||||
return visitor.booleanConst(this, arg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class NullConst extends LeafExpr {
|
||||
|
||||
@Override
|
||||
public <R, A> R accept(ExprVisitor<R, A> 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, A> R accept(ExprVisitor<R, A> 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, A> R accept(ExprVisitor<R, A> 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, A> R accept(ExprVisitor<R, A> 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, A> R accept(ExprVisitor<R, A> 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, A> R accept(ExprVisitor<R, A> 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, A> R accept(ExprVisitor<R, A> visitor, A arg) {
|
||||
return visitor.var(this, arg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class BuiltInRead extends LeafExpr {
|
||||
|
||||
@Override
|
||||
public <R, A> R accept(ExprVisitor<R, A> visitor, A arg) {
|
||||
return visitor.builtInRead(this, arg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class MethodCallExpr extends Expr {
|
||||
|
||||
public String methodName;
|
||||
|
||||
public MethodCallExpr(Expr rcvr, String methodName, List<Expr> 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, <b>including the receiver.</b>
|
||||
* i.e, for a method call {@code a.b(c,d)} returns {@code [a, c, d]} */
|
||||
public List<Expr> allArguments()
|
||||
{
|
||||
ArrayList<Expr> result = new ArrayList<Expr>();
|
||||
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<Expr> argumentsWithoutReceiver()
|
||||
{
|
||||
ArrayList<Expr> result = new ArrayList<Expr>();
|
||||
for (int i = 1; i < this.rwChildren.size(); i++)
|
||||
result.add((Expr) this.rwChildren.get(i));
|
||||
return Collections.unmodifiableList(result);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public <R, A> R accept(ExprVisitor<R, A> 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, A> R accept(AstVisitor<R, A> 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, A> R accept(AstVisitor<R, A> 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, A> R accept(AstVisitor<R, A> 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, A> R accept(AstVisitor<R, A> 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, A> R accept(AstVisitor<R, A> visitor, A arg) {
|
||||
return visitor.builtInWrite(this, arg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class BuiltInWriteln extends Stmt {
|
||||
|
||||
public BuiltInWriteln() {
|
||||
super(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R, A> R accept(AstVisitor<R, A> 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, A> R accept(AstVisitor<R, A> 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, A> R accept(AstVisitor<R, A> 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, A> R accept(AstVisitor<R, A> 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<Ast> nodes) {
|
||||
super(-1);
|
||||
if (nodes != null) this.rwChildren.addAll(nodes);
|
||||
}
|
||||
|
||||
/** Grant access to the raw list of children for seq nodes */
|
||||
public List<Ast> rwChildren() {
|
||||
return this.rwChildren;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R, A> R accept(AstVisitor<R, A> visitor, A arg) {
|
||||
return visitor.seq(this, arg);
|
||||
}
|
||||
}
|
||||
|
||||
public static class MethodDecl extends Decl {
|
||||
|
||||
public String returnType;
|
||||
public String name;
|
||||
public List<String> argumentTypes;
|
||||
public List<String> argumentNames;
|
||||
public MethodDecl(
|
||||
String returnType,
|
||||
String name,
|
||||
List<Pair<String>> formalParams,
|
||||
Seq decls,
|
||||
Seq body) {
|
||||
this(returnType, name, Pair.unzipA(formalParams), Pair.unzipB(formalParams), decls, body);
|
||||
}
|
||||
public MethodDecl(
|
||||
String returnType,
|
||||
String name,
|
||||
List<String> argumentTypes,
|
||||
List<String> 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, A> R accept(AstVisitor<R, A> 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<Ast> members() {
|
||||
return Collections.unmodifiableList(this.rwChildren);
|
||||
}
|
||||
|
||||
public List<VarDecl> fields() {
|
||||
return childrenOfType(VarDecl.class);
|
||||
} // includes constants!
|
||||
|
||||
public List<MethodDecl> methods() {
|
||||
return childrenOfType(MethodDecl.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R, A> R accept(AstVisitor<R, A> visitor, A arg) {
|
||||
return visitor.classDecl(this, arg);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
106
src/cd/ir/AstVisitor.java
Normal file
106
src/cd/ir/AstVisitor.java
Normal file
|
@ -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<R,A> extends ExprVisitor<R,A> {
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
92
src/cd/ir/ExprVisitor.java
Normal file
92
src/cd/ir/ExprVisitor.java
Normal file
|
@ -0,0 +1,92 @@
|
|||
package cd.ir;
|
||||
|
||||
import cd.ir.Ast.Expr;
|
||||
|
||||
/**
|
||||
* A visitor that only visits {@link Expr} nodes.
|
||||
*/
|
||||
public class ExprVisitor<R,A> {
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
121
src/cd/util/FileUtil.java
Normal file
121
src/cd/util/FileUtil.java
Normal file
|
@ -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<Object[]> result) {
|
||||
for (File testFile : testDir.listFiles()) {
|
||||
if (testFile.getName().endsWith(".javali"))
|
||||
result.add(new Object[] { testFile });
|
||||
else if (testFile.isDirectory())
|
||||
findJavaliFiles(testFile, result);
|
||||
}
|
||||
}
|
||||
|
||||
/** Finds all .javali under directory {@code testDir} and returns them. */
|
||||
public static List<File> findJavaliFiles(File testDir) {
|
||||
List<File> result = new ArrayList<File>();
|
||||
for (File testFile : testDir.listFiles()) {
|
||||
if (testFile.getName().endsWith(".javali"))
|
||||
result.add(testFile);
|
||||
else if (testFile.isDirectory())
|
||||
result.addAll(findJavaliFiles(testFile));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
53
src/cd/util/Pair.java
Normal file
53
src/cd/util/Pair.java
Normal file
|
@ -0,0 +1,53 @@
|
|||
package cd.util;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/** Simple class for joining two objects of the same type */
|
||||
public class Pair<T> {
|
||||
public T a;
|
||||
public T b;
|
||||
|
||||
public Pair(T a, T b) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
public static <T> List<Pair<T>> zip(List<T> listA, List<T> listB) {
|
||||
List<Pair<T>> res = new ArrayList<Pair<T>>();
|
||||
for (int i = 0; i < Math.min(listA.size(), listB.size()); i++) {
|
||||
res.add(new Pair<T>(listA.get(i), listB.get(i)));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public static <T> List<T> unzipA(List<Pair<T>> list) {
|
||||
List<T> res = new ArrayList<T>();
|
||||
for (Pair<T> p : list)
|
||||
res.add(p.a);
|
||||
return res;
|
||||
}
|
||||
|
||||
public static <T> List<T> unzipB(List<Pair<T>> list) {
|
||||
List<T> res = new ArrayList<T>();
|
||||
for (Pair<T> p : list)
|
||||
res.add(p.b);
|
||||
return res;
|
||||
}
|
||||
|
||||
public static String join(
|
||||
List<Pair<?>> pairs,
|
||||
String itemSep,
|
||||
String pairSep) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
boolean first = true;
|
||||
for (Pair<?> pair : pairs) {
|
||||
if (!first) sb.append(pairSep);
|
||||
sb.append(pair.a);
|
||||
sb.append(itemSep);
|
||||
sb.append(pair.b);
|
||||
first = false;
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
16
src/cd/util/Tuple.java
Normal file
16
src/cd/util/Tuple.java
Normal file
|
@ -0,0 +1,16 @@
|
|||
package cd.util;
|
||||
|
||||
/** Simple class for joining two objects of different type */
|
||||
public class Tuple<A,B> {
|
||||
public A a;
|
||||
public B b;
|
||||
|
||||
public Tuple(A a, B b) {
|
||||
this.a = a;
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "(" + a.toString() + ", " + b.toString() + ")";
|
||||
}
|
||||
}
|
125
src/cd/util/debug/AstDump.java
Normal file
125
src/cd/util/debug/AstDump.java
Normal file
|
@ -0,0 +1,125 @@
|
|||
package cd.util.debug;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import cd.ir.Ast;
|
||||
import cd.ir.AstVisitor;
|
||||
import cd.util.Pair;
|
||||
|
||||
public class AstDump {
|
||||
|
||||
public static String toString(Ast ast) {
|
||||
return toString(ast, "");
|
||||
}
|
||||
|
||||
public static String toString(List<? extends Ast> astRoots) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Ast a : astRoots) {
|
||||
sb.append(toString(a));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String toString(Ast ast, String indent) {
|
||||
AstDump ad = new AstDump();
|
||||
ad.dump(ast, indent);
|
||||
return ad.sb.toString();
|
||||
}
|
||||
|
||||
public static String toStringFlat(Ast ast) {
|
||||
AstDump ad = new AstDump();
|
||||
ad.dumpFlat(ast);
|
||||
return ad.sb.toString();
|
||||
}
|
||||
|
||||
private StringBuilder sb = new StringBuilder();
|
||||
private Visitor vis = new Visitor();
|
||||
|
||||
protected void dump(Ast ast, String indent) {
|
||||
// print out the overall class structure
|
||||
sb.append(indent);
|
||||
String nodeName = ast.getClass().getSimpleName();
|
||||
List<Pair<?>> flds = vis.visit(ast, null);
|
||||
sb.append(String.format("%s (%s)\n",
|
||||
nodeName,
|
||||
Pair.join(flds, ": ", ", ")));
|
||||
|
||||
// print out any children
|
||||
String newIndent = indent + "| ";
|
||||
for (Ast child : ast.children()) {
|
||||
dump(child, newIndent);
|
||||
}
|
||||
}
|
||||
|
||||
protected void dumpFlat(Ast ast) {
|
||||
String nodeName = ast.getClass().getSimpleName();
|
||||
List<Pair<?>> flds = vis.visit(ast, null);
|
||||
sb.append(String.format("%s(%s)[",
|
||||
nodeName,
|
||||
Pair.join(flds, ":", ",")));
|
||||
|
||||
// print out any children
|
||||
for (int child = 0; child < ast.children().size(); child++) {
|
||||
dumpFlat(ast.children().get(child));
|
||||
if (child < ast.children().size() - 1)
|
||||
sb.append(",");
|
||||
}
|
||||
|
||||
sb.append("]");
|
||||
}
|
||||
|
||||
protected class Visitor extends AstVisitor<List<Pair<?>>, Void> {
|
||||
|
||||
@Override
|
||||
protected List<Pair<?>> dflt(Ast ast, Void arg) {
|
||||
ArrayList<Pair<?>> res = new ArrayList<Pair<?>>();
|
||||
|
||||
// Get the list of fields and sort them by name:
|
||||
java.lang.Class<? extends Ast> rclass = ast.getClass();
|
||||
List<java.lang.reflect.Field> rflds =
|
||||
Arrays.asList(rclass.getFields());
|
||||
Collections.sort(rflds, new Comparator<java.lang.reflect.Field> () {
|
||||
public int compare(Field o1, Field o2) {
|
||||
return o1.getName().compareTo(o2.getName());
|
||||
}
|
||||
});
|
||||
|
||||
// Create pairs for each one that is not of type Ast:
|
||||
for (java.lang.reflect.Field rfld : rflds) {
|
||||
rfld.setAccessible(true);
|
||||
|
||||
// ignore various weird fields that show up from
|
||||
// time to time:
|
||||
if (rfld.getName().startsWith("$"))
|
||||
continue;
|
||||
|
||||
// ignore fields of AST type, and rwChildren (they should be
|
||||
// uncovered using the normal tree walk)
|
||||
if (rfld.getType().isAssignableFrom(Ast.class))
|
||||
continue;
|
||||
if (rfld.getName().equals("rwChildren"))
|
||||
continue;
|
||||
|
||||
// ignore NULL fields, but add others to our list of pairs
|
||||
try {
|
||||
Object value = rfld.get(ast);
|
||||
if (value != null)
|
||||
res.add(new Pair<Object>(rfld.getName(), value));
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new RuntimeException(e);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
160
src/cd/util/debug/AstOneLine.java
Normal file
160
src/cd/util/debug/AstOneLine.java
Normal file
|
@ -0,0 +1,160 @@
|
|||
package cd.util.debug;
|
||||
|
||||
import cd.ir.Ast;
|
||||
import cd.ir.Ast.Assign;
|
||||
import cd.ir.Ast.BinaryOp;
|
||||
import cd.ir.Ast.BooleanConst;
|
||||
import cd.ir.Ast.BuiltInRead;
|
||||
import cd.ir.Ast.BuiltInWrite;
|
||||
import cd.ir.Ast.BuiltInWriteln;
|
||||
import cd.ir.Ast.Cast;
|
||||
import cd.ir.Ast.ClassDecl;
|
||||
import cd.ir.Ast.Field;
|
||||
import cd.ir.Ast.IfElse;
|
||||
import cd.ir.Ast.Index;
|
||||
import cd.ir.Ast.IntConst;
|
||||
import cd.ir.Ast.MethodDecl;
|
||||
import cd.ir.Ast.NewArray;
|
||||
import cd.ir.Ast.NewObject;
|
||||
import cd.ir.Ast.Nop;
|
||||
import cd.ir.Ast.NullConst;
|
||||
import cd.ir.Ast.Seq;
|
||||
import cd.ir.Ast.ThisRef;
|
||||
import cd.ir.Ast.UnaryOp;
|
||||
import cd.ir.Ast.Var;
|
||||
import cd.ir.Ast.VarDecl;
|
||||
import cd.ir.Ast.WhileLoop;
|
||||
import cd.ir.AstVisitor;
|
||||
|
||||
public class AstOneLine {
|
||||
|
||||
public static String toString(Ast ast) {
|
||||
return new Visitor().visit(ast, null);
|
||||
}
|
||||
|
||||
protected static class Visitor extends AstVisitor<String, Void> {
|
||||
|
||||
public String str(Ast ast) {
|
||||
return ast.accept(this, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String assign(Assign ast, Void arg) {
|
||||
return String.format("%s = %s", str(ast.left()), str(ast.right()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String binaryOp(BinaryOp ast, Void arg) {
|
||||
return String.format("(%s %s %s)",
|
||||
str(ast.left()), ast.operator.repr, str(ast.right()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String booleanConst(BooleanConst ast, Void arg) {
|
||||
return Boolean.toString(ast.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String builtInRead(BuiltInRead ast, Void arg) {
|
||||
return String.format("read()");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String builtInWrite(BuiltInWrite ast, Void arg) {
|
||||
return String.format("write(%s)", str(ast.arg()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String builtInWriteln(BuiltInWriteln ast, Void arg) {
|
||||
return String.format("writeln()");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String cast(Cast ast, Void arg) {
|
||||
return String.format("(%s)(%s)", ast.typeName, str(ast.arg()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String classDecl(ClassDecl ast, Void arg) {
|
||||
return String.format("class %s {...}", ast.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String field(Field ast, Void arg) {
|
||||
return String.format("%s.%s", str(ast.arg()), ast.fieldName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String ifElse(IfElse ast, Void arg) {
|
||||
return String.format("if (%s) {...} else {...}", str(ast.condition()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String index(Index ast, Void arg) {
|
||||
return String.format("%s[%s]", str(ast.left()), str(ast.right()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String intConst(IntConst ast, Void arg) {
|
||||
return Integer.toString(ast.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String methodDecl(MethodDecl ast, Void arg) {
|
||||
return String.format("%s %s(...) {...}", ast.returnType, ast.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String newArray(NewArray ast, Void arg) {
|
||||
return String.format("new %s[%s]", ast.typeName, str(ast.arg()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String newObject(NewObject ast, Void arg) {
|
||||
return String.format("new %s()", ast.typeName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String nop(Nop ast, Void arg) {
|
||||
return "nop";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String nullConst(NullConst ast, Void arg) {
|
||||
return "null";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String seq(Seq ast, Void arg) {
|
||||
return "(...)";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String thisRef(ThisRef ast, Void arg) {
|
||||
return "this";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String unaryOp(UnaryOp ast, Void arg) {
|
||||
return String.format("%s(%s)", ast.operator.repr, str(ast.arg()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String var(Var ast, Void arg) {
|
||||
{
|
||||
return ast.name;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String varDecl(VarDecl ast, Void arg) {
|
||||
return String.format("%s %s", ast.type, ast.name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String whileLoop(WhileLoop ast, Void arg) {
|
||||
return String.format("while (%s) {...}", str(ast.condition()));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue