Homework 4
This commit is contained in:
parent
0afc86ceeb
commit
72cc3206c4
125 changed files with 4200 additions and 1636 deletions
|
@ -10,11 +10,6 @@ public class Config {
|
|||
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}.
|
||||
|
@ -84,11 +79,9 @@ public class Config {
|
|||
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";
|
||||
|
@ -108,7 +101,6 @@ public class Config {
|
|||
COMMENT_SEP = "#";
|
||||
}
|
||||
else if(os.contains("mac os x") || os.contains("darwin")) {
|
||||
systemKind = SystemKind.MACOSX;
|
||||
BINARYEXT = ".bin";
|
||||
MAIN = "_main";
|
||||
PRINTF = "_printf";
|
||||
|
@ -126,7 +118,6 @@ public class Config {
|
|||
COMMENT_SEP = "#";
|
||||
}
|
||||
else {
|
||||
systemKind = SystemKind.LINUX;
|
||||
BINARYEXT = ".bin";
|
||||
MAIN = "main";
|
||||
PRINTF = "printf";
|
||||
|
|
|
@ -1,19 +1,6 @@
|
|||
package cd;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
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;
|
||||
|
@ -24,6 +11,14 @@ import cd.ir.Ast.ClassDecl;
|
|||
import cd.ir.Symbol;
|
||||
import cd.ir.Symbol.TypeSymbol;
|
||||
import cd.util.debug.AstDump;
|
||||
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 java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The main entrypoint for the compiler. Consists of a series
|
||||
|
@ -37,7 +32,7 @@ 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)
|
||||
// Set to non-null to write dump of control flow graph
|
||||
public File cfgdumpbase;
|
||||
|
||||
/** Symbol for the Main type */
|
||||
|
@ -74,6 +69,12 @@ public class Main {
|
|||
|
||||
// Run the semantic check:
|
||||
m.semanticCheck(astRoots);
|
||||
|
||||
// Generate code:
|
||||
String sFile = arg + Config.ASMEXT;
|
||||
try (FileWriter fout = new FileWriter(sFile)) {
|
||||
m.generateCode(astRoots, fout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -111,6 +112,13 @@ public class Main {
|
|||
new SemanticAnalyzer(this).check(astRoots);
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
19
src/cd/backend/ExitCode.java
Normal file
19
src/cd/backend/ExitCode.java
Normal file
|
@ -0,0 +1,19 @@
|
|||
package cd.backend;
|
||||
|
||||
public enum ExitCode {
|
||||
OK(0),
|
||||
INVALID_DOWNCAST(1),
|
||||
INVALID_ARRAY_STORE(2),
|
||||
INVALID_ARRAY_BOUNDS(3),
|
||||
NULL_POINTER(4),
|
||||
INVALID_ARRAY_SIZE(5),
|
||||
INFINITE_LOOP(6),
|
||||
DIVISION_BY_ZERO(7),
|
||||
INTERNAL_ERROR(22);
|
||||
|
||||
public final int value;
|
||||
|
||||
private ExitCode(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
176
src/cd/backend/codegen/AssemblyEmitter.java
Normal file
176
src/cd/backend/codegen/AssemblyEmitter.java
Normal file
|
@ -0,0 +1,176 @@
|
|||
package cd.backend.codegen;
|
||||
|
||||
import cd.Config;
|
||||
import cd.backend.codegen.RegisterManager.Register;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
|
||||
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, int src, String dest) {
|
||||
emit(op, constant(src), dest);
|
||||
}
|
||||
|
||||
void emit(String op, String dest) {
|
||||
emitRaw(op + " " + dest);
|
||||
}
|
||||
|
||||
void emit(String op, Register reg) {
|
||||
emit(op, reg.repr);
|
||||
}
|
||||
|
||||
void emit(String op, int dest) {
|
||||
emit(op, constant(dest));
|
||||
}
|
||||
|
||||
void emit(String op, int src, int dest) {
|
||||
emit(op, constant(src), 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 emitMove(int src, Register dest) {
|
||||
emitMove(constant(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 emitStore(int src, int destOffset, Register dest) {
|
||||
emitStore(constant(src), 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;
|
||||
}
|
||||
}
|
172
src/cd/backend/codegen/AstCodeGenerator.java
Normal file
172
src/cd/backend/codegen/AstCodeGenerator.java
Normal file
|
@ -0,0 +1,172 @@
|
|||
package cd.backend.codegen;
|
||||
|
||||
import cd.Config;
|
||||
import cd.Main;
|
||||
import cd.backend.codegen.RegisterManager.Register;
|
||||
import cd.ir.Ast;
|
||||
import cd.ir.Ast.ClassDecl;
|
||||
import cd.ir.Symbol.*;
|
||||
|
||||
import java.io.Writer;
|
||||
import java.util.*;
|
||||
|
||||
import static cd.Config.MAIN;
|
||||
import static cd.backend.codegen.RegisterManager.BASE_REG;
|
||||
import static cd.backend.codegen.RegisterManager.STACK_REG;
|
||||
|
||||
public class AstCodeGenerator {
|
||||
/** Constant representing the boolean TRUE as integer */
|
||||
static final int TRUE = 1;
|
||||
/** Constant representing the boolean FALSE as integer */
|
||||
static final int FALSE = 0;
|
||||
/** Size of any variable in assembly
|
||||
* Primitive variables take up 4 bytes (booleans are integers)
|
||||
* Reference variables are a 4 byte pointer
|
||||
*/
|
||||
static final int VAR_SIZE = 4;
|
||||
|
||||
RegsNeededVisitor rnv;
|
||||
|
||||
ExprGenerator eg;
|
||||
StmtGenerator sg;
|
||||
|
||||
protected final Main main;
|
||||
|
||||
final AssemblyEmitter emit;
|
||||
final RegisterManager rm = new RegisterManager();
|
||||
|
||||
AstCodeGenerator(Main main, Writer out) {
|
||||
initMethodData();
|
||||
main.allTypeSymbols = new ArrayList<>();
|
||||
|
||||
this.emit = new AssemblyEmitter(out);
|
||||
this.main = main;
|
||||
this.rnv = new RegsNeededVisitor();
|
||||
|
||||
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) {
|
||||
// Find main type and assign to main.mainType
|
||||
// Add array types to the types list to lookup later
|
||||
for (ClassDecl decl : astRoots) {
|
||||
if (decl.name.equals("Main")) {
|
||||
main.mainType = decl.sym;
|
||||
}
|
||||
main.allTypeSymbols.add(new ArrayTypeSymbol(decl.sym));
|
||||
}
|
||||
|
||||
main.allTypeSymbols.add(new ArrayTypeSymbol(PrimitiveTypeSymbol.intType));
|
||||
main.allTypeSymbols.add(new ArrayTypeSymbol(PrimitiveTypeSymbol.booleanType));
|
||||
main.allTypeSymbols.add(new ArrayTypeSymbol(ClassSymbol.objectType));
|
||||
|
||||
emitPrefix();
|
||||
for (ClassDecl ast : astRoots) {
|
||||
sg.gen(ast);
|
||||
}
|
||||
}
|
||||
|
||||
private void emitPrefix() {
|
||||
// Emit some useful string constants (copied from old HW1 method declaration)
|
||||
emit.emitRaw(Config.DATA_STR_SECTION);
|
||||
emit.emitLabel("STR_NL");
|
||||
emit.emitRaw(Config.DOT_STRING + " \"\\n\"");
|
||||
emit.emitLabel("STR_D");
|
||||
emit.emitRaw(Config.DOT_STRING + " \"%d\"");
|
||||
|
||||
// Define Object, Object[], int, int[], boolean and boolean[]
|
||||
List<TypeSymbol> elementTypes = new ArrayList<>();
|
||||
elementTypes.add(ClassSymbol.objectType);
|
||||
elementTypes.add(PrimitiveTypeSymbol.intType);
|
||||
elementTypes.add(PrimitiveTypeSymbol.booleanType);
|
||||
emit.emitRaw(Config.DATA_INT_SECTION);
|
||||
for (TypeSymbol type : elementTypes) {
|
||||
// type vtable
|
||||
emit.emitLabel(Label.type(type));
|
||||
emit.emitConstantData("0"); // Supertype (null)
|
||||
// array vtable
|
||||
emit.emitLabel(Label.type(new ArrayTypeSymbol(type)));
|
||||
emit.emitConstantData(Label.type(ClassSymbol.objectType)); // Supertype
|
||||
emit.emitConstantData(Label.type(type)); // Element type
|
||||
}
|
||||
|
||||
// Emit the new Main().main() code to start the program:
|
||||
|
||||
// 1. Enter TEXT and start the program
|
||||
emit.emitRaw(Config.TEXT_SECTION);
|
||||
emit.emit(".globl", MAIN);
|
||||
emit.emitLabel(MAIN);
|
||||
// 1.1. Prepare first frame
|
||||
emit.emit("push", BASE_REG);
|
||||
emit.emitMove(STACK_REG, BASE_REG);
|
||||
|
||||
// 2. Create main variable
|
||||
Ast.NewObject newMain = new Ast.NewObject("Main");
|
||||
newMain.type = main.mainType;
|
||||
Register mainLocation = eg.visit(newMain, null);
|
||||
|
||||
// 3. Call main()
|
||||
emit.emit("push", mainLocation);
|
||||
for (ClassSymbol sym = main.mainType; sym != ClassSymbol.objectType; sym = sym.superClass)
|
||||
if (sym.methods.getOrDefault("main", null) != null) {
|
||||
emit.emit("call", Label.method(sym, sym.methods.get("main")));
|
||||
break;
|
||||
}
|
||||
emitMethodSuffix(true);
|
||||
}
|
||||
|
||||
void initMethodData() {
|
||||
rm.initRegisters();
|
||||
}
|
||||
|
||||
|
||||
void emitMethodSuffix(boolean returnNull) {
|
||||
if (returnNull)
|
||||
emit.emit("movl", 0, Register.EAX);
|
||||
emit.emitRaw("leave");
|
||||
emit.emitRaw("ret");
|
||||
}
|
||||
|
||||
static class Label {
|
||||
static String type(TypeSymbol symbol) {
|
||||
if (symbol instanceof ClassSymbol)
|
||||
return String.format("type_%s", symbol);
|
||||
else if (symbol instanceof ArrayTypeSymbol)
|
||||
return String.format("array_%s", ((ArrayTypeSymbol) symbol).elementType);
|
||||
else if (symbol instanceof PrimitiveTypeSymbol)
|
||||
return String.format("primive_%s", symbol);
|
||||
throw new RuntimeException("Unimplemented type symbol");
|
||||
}
|
||||
|
||||
static String method(ClassSymbol classSymbol, MethodSymbol methodSymbol) {
|
||||
return String.format("method_%s_%s", classSymbol.name, methodSymbol.name);
|
||||
}
|
||||
|
||||
static String returnMethod(ClassSymbol classSymbol, MethodSymbol methodSymbol) {
|
||||
return String.format("return_%s", method(classSymbol, methodSymbol));
|
||||
}
|
||||
}
|
||||
}
|
613
src/cd/backend/codegen/ExprGenerator.java
Normal file
613
src/cd/backend/codegen/ExprGenerator.java
Normal file
|
@ -0,0 +1,613 @@
|
|||
package cd.backend.codegen;
|
||||
|
||||
import cd.backend.ExitCode;
|
||||
import cd.backend.codegen.AstCodeGenerator.*;
|
||||
import cd.backend.codegen.RegisterManager.Register;
|
||||
import cd.ir.Ast.*;
|
||||
import cd.ir.ExprVisitor;
|
||||
import cd.ir.Symbol.ClassSymbol;
|
||||
import cd.ir.Symbol.MethodSymbol;
|
||||
import cd.ir.Symbol.PrimitiveTypeSymbol;
|
||||
import cd.ir.Symbol.VariableSymbol;
|
||||
import cd.util.debug.AstOneLine;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static cd.Config.SCANF;
|
||||
import static cd.backend.codegen.AstCodeGenerator.*;
|
||||
import static cd.backend.codegen.RegisterManager.BASE_REG;
|
||||
import static cd.backend.codegen.RegisterManager.CALLER_SAVE;
|
||||
import static cd.backend.codegen.RegisterManager.STACK_REG;
|
||||
|
||||
/**
|
||||
* 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,Location> {
|
||||
private final AstCodeGenerator cg;
|
||||
|
||||
ExprGenerator(AstCodeGenerator astCodeGenerator) {
|
||||
cg = astCodeGenerator;
|
||||
}
|
||||
|
||||
public Register gen(Expr ast) {
|
||||
return visit(ast, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register visit(Expr ast, Location arg) {
|
||||
try {
|
||||
cg.emit.increaseIndent("Emitting " + AstOneLine.toString(ast));
|
||||
if (cg.rnv.calc(ast) > cg.rm.availableRegisters()) {
|
||||
Deque<Register> pushed = new ArrayDeque<>();
|
||||
for (Register r : RegisterManager.GPR) {
|
||||
if (cg.rm.isInUse(r)) {
|
||||
cg.emit.emit("push", r);
|
||||
pushed.push(r);
|
||||
cg.rm.releaseRegister(r);
|
||||
}
|
||||
}
|
||||
Register result = super.visit(ast, arg);
|
||||
for (Register r : pushed)
|
||||
cg.rm.useRegister(r);
|
||||
Register finalResult = cg.rm.getRegister();
|
||||
cg.emit.emitMove(result, finalResult);
|
||||
for (Register r = pushed.pop(); !pushed.isEmpty(); r = pushed.pop())
|
||||
cg.emit.emit("pop", r);
|
||||
return finalResult;
|
||||
} else return super.visit(ast, arg);
|
||||
} finally {
|
||||
cg.emit.decreaseIndent();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register binaryOp(BinaryOp ast, Location arg) {
|
||||
// Simplistic HW1 implementation that does
|
||||
// not care if it runs out of registers
|
||||
|
||||
int leftRN = cg.rnv.calc(ast.left());
|
||||
int rightRN = cg.rnv.calc(ast.right());
|
||||
|
||||
Register leftReg, rightReg;
|
||||
if (leftRN > rightRN) {
|
||||
leftReg = visit(ast.left(), arg);
|
||||
rightReg = visit(ast.right(), arg);
|
||||
} else {
|
||||
rightReg = visit(ast.right(), arg);
|
||||
leftReg = visit(ast.left(), arg);
|
||||
}
|
||||
|
||||
cg.debug("Binary Op: %s (%s,%s)", ast, leftReg, rightReg);
|
||||
|
||||
switch (ast.operator) {
|
||||
case B_TIMES:
|
||||
cg.emit.emit("imul", rightReg, leftReg);
|
||||
break;
|
||||
case B_PLUS:
|
||||
cg.emit.emit("add", rightReg, leftReg);
|
||||
break;
|
||||
case B_MINUS:
|
||||
cg.emit.emit("sub", rightReg, leftReg);
|
||||
break;
|
||||
case B_DIV:
|
||||
case B_MOD:
|
||||
// Check division by 0
|
||||
String beginDivision = cg.emit.uniqueLabel();
|
||||
cg.emit.emit("cmp", 0, rightReg);
|
||||
cg.emit.emit("jne", beginDivision);
|
||||
Interrupts.exit(cg, ExitCode.DIVISION_BY_ZERO);
|
||||
cg.emit.emitLabel(beginDivision);
|
||||
|
||||
// Save EAX, EBX, and EDX to the stack if they are not used
|
||||
// in this subtree (but are used elsewhere). We will be
|
||||
// changing them.
|
||||
List<Register> dontBother = Arrays.asList(rightReg, leftReg);
|
||||
Register[] affected = { Register.EAX, Register.EBX, Register.EDX };
|
||||
|
||||
for (Register s : affected)
|
||||
if (!dontBother.contains(s) && cg.rm.isInUse(s))
|
||||
cg.emit.emit("pushl", s);
|
||||
|
||||
// Move the LHS (numerator) into eax
|
||||
// Move the RHS (denominator) into ebx
|
||||
cg.emit.emit("pushl", rightReg);
|
||||
cg.emit.emit("pushl", leftReg);
|
||||
cg.emit.emit("popl", Register.EAX);
|
||||
cg.emit.emit("popl", Register.EBX);
|
||||
cg.emit.emitRaw("cltd"); // sign-extend %eax into %edx
|
||||
cg.emit.emit("idivl", Register.EBX); // division, result into edx:eax
|
||||
|
||||
// Move the result into the LHS, and pop off anything we saved
|
||||
if (ast.operator == BinaryOp.BOp.B_DIV)
|
||||
cg.emit.emitMove(Register.EAX, leftReg);
|
||||
else
|
||||
cg.emit.emitMove(Register.EDX, leftReg);
|
||||
for (int i = affected.length - 1; i >= 0; i--) {
|
||||
Register s = affected[i];
|
||||
if (!dontBother.contains(s) && cg.rm.isInUse(s))
|
||||
cg.emit.emit("popl", s);
|
||||
}
|
||||
break;
|
||||
case B_AND:
|
||||
cg.emit.emit("and", rightReg, leftReg);
|
||||
break;
|
||||
case B_OR:
|
||||
cg.emit.emit("or", rightReg, leftReg);
|
||||
break;
|
||||
case B_EQUAL: // a == b <--> ! (a != b)
|
||||
cg.emit.emit("xor", rightReg, leftReg);
|
||||
// if 'leftReg'==0, set leftReg to '1'
|
||||
String equal = cg.emit.uniqueLabel();
|
||||
String end = cg.emit.uniqueLabel();
|
||||
cg.emit.emit("cmp", 0, leftReg);
|
||||
cg.emit.emit("je", equal);
|
||||
cg.emit.emitMove(AssemblyEmitter.constant(0), leftReg);
|
||||
cg.emit.emit("jmp", end);
|
||||
cg.emit.emitLabel(equal);
|
||||
cg.emit.emitMove(TRUE, leftReg);
|
||||
cg.emit.emitLabel(end);
|
||||
break;
|
||||
case B_NOT_EQUAL:
|
||||
String skipTrue = cg.emit.uniqueLabel();
|
||||
cg.emit.emit("xor", rightReg, leftReg);
|
||||
cg.emit.emit("cmp", 0, leftReg);
|
||||
cg.emit.emit("je", skipTrue);
|
||||
cg.emit.emitMove(TRUE, leftReg);
|
||||
cg.emit.emitLabel(skipTrue);
|
||||
break;
|
||||
default: // Comparison operations
|
||||
String endLabel = cg.emit.uniqueLabel();
|
||||
cg.emit.emit("cmp", rightReg, leftReg);
|
||||
// leftReg - rightReg
|
||||
cg.emit.emitMove(TRUE, leftReg);
|
||||
switch (ast.operator) {
|
||||
case B_LESS_THAN:
|
||||
cg.emit.emit("jl", endLabel);
|
||||
break;
|
||||
case B_LESS_OR_EQUAL:
|
||||
cg.emit.emit("jle", endLabel);
|
||||
break;
|
||||
case B_GREATER_THAN:
|
||||
cg.emit.emit("jg", endLabel);
|
||||
break;
|
||||
case B_GREATER_OR_EQUAL:
|
||||
cg.emit.emit("jge", endLabel);
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("A binary operation wasn't implemented");
|
||||
}
|
||||
cg.emit.emitMove(FALSE, leftReg);
|
||||
cg.emit.emitLabel(endLabel);
|
||||
}
|
||||
|
||||
cg.rm.releaseRegister(rightReg);
|
||||
|
||||
return leftReg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register booleanConst(BooleanConst ast, Location arg) {
|
||||
Register reg = cg.rm.getRegister();
|
||||
cg.emit.emitMove(ast.value ? TRUE : FALSE, reg);
|
||||
return reg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register builtInRead(BuiltInRead ast, Location arg) {
|
||||
Register reg = cg.rm.getRegister();
|
||||
cg.emit.emit("sub", 16, STACK_REG);
|
||||
cg.emit.emit("leal", AssemblyEmitter.registerOffset(8, STACK_REG), reg);
|
||||
cg.emit.emitStore(reg, 4, STACK_REG);
|
||||
cg.emit.emitStore("$STR_D", 0, STACK_REG);
|
||||
cg.emit.emit("call", SCANF);
|
||||
cg.emit.emitLoad(8, STACK_REG, reg);
|
||||
cg.emit.emit("add", 16, STACK_REG);
|
||||
return reg;
|
||||
}
|
||||
|
||||
/**
|
||||
* A Cast from one type to another: {@code (typeName)arg}
|
||||
*/
|
||||
@Override
|
||||
public Register cast(Cast ast, Location arg) {
|
||||
// 1. Obtain a register with the desired type's address
|
||||
Register desiredType = cg.rm.getRegister();
|
||||
cg.emit.emit("lea", Label.type(ast.type), desiredType); // lea copies the label's address to the reg
|
||||
|
||||
// 2. Get a reference to the object to be casted, and push a copy for later
|
||||
Register runtimeType = visit(ast.arg(), arg);
|
||||
cg.emit.emit("push", runtimeType);
|
||||
|
||||
// 3. Go to the type's vtable
|
||||
cg.emit.emitLoad(0, runtimeType, runtimeType);
|
||||
|
||||
// 4. Runtime type check: recursively go to superType until
|
||||
String matchLabel = cg.emit.uniqueLabel();
|
||||
String checkSuperTypeLabel = cg.emit.uniqueLabel();
|
||||
cg.emit.emitLabel(checkSuperTypeLabel);
|
||||
cg.emit.emit("cmpl", desiredType, runtimeType);
|
||||
cg.emit.emit("je", matchLabel); // 4.1 It matches: ok
|
||||
cg.emit.emitLoad(0, runtimeType, runtimeType); // Go to superclass
|
||||
|
||||
cg.emit.emit("cmp", 0, runtimeType); // 4.2 null is reached (super type of Object): error
|
||||
cg.emit.emit("jne", checkSuperTypeLabel);
|
||||
Interrupts.exit(cg, ExitCode.INVALID_DOWNCAST);
|
||||
cg.emit.emitLabel(matchLabel);
|
||||
|
||||
cg.rm.releaseRegister(desiredType);
|
||||
|
||||
// 5. Recover pointer to object and return it
|
||||
cg.emit.emit("pop", runtimeType);
|
||||
return runtimeType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register index(Index ast, Location arg) {
|
||||
boolean obtainReference = arg.isObtainReference();
|
||||
String invalidBoundLabel = cg.emit.uniqueLabel();
|
||||
String validBoundLabel = cg.emit.uniqueLabel();
|
||||
String checkBoundsLabel = cg.emit.uniqueLabel();
|
||||
|
||||
// Obtain pointer to array and index to be accessed
|
||||
Register pointer = visit(ast.left(), arg);
|
||||
Register index = visit(ast.right(), arg);
|
||||
|
||||
// check for null pointer
|
||||
cg.emit.emit("cmp", 0, pointer);
|
||||
cg.emit.emit("jne", checkBoundsLabel); // 0 != array
|
||||
Interrupts.exit(cg, ExitCode.NULL_POINTER);
|
||||
|
||||
// check if index>=0 and index<arraySize
|
||||
Register arraySizeReg = cg.rm.getRegister();
|
||||
cg.emit.emitLabel(checkBoundsLabel);
|
||||
cg.emit.emitLoad(1 * VAR_SIZE, pointer, arraySizeReg);
|
||||
cg.emit.emit("cmp", 0, index);
|
||||
cg.emit.emit("jl", invalidBoundLabel); // 0 > index
|
||||
cg.emit.emit("cmp", index, arraySizeReg);
|
||||
cg.emit.emit("jg", validBoundLabel); // index < array.length
|
||||
cg.emit.emitLabel(invalidBoundLabel);
|
||||
Interrupts.exit(cg, ExitCode.INVALID_ARRAY_BOUNDS);
|
||||
cg.rm.releaseRegister(arraySizeReg);
|
||||
|
||||
// return array element (base + 2 * VAR_SIZE + index * VAR_SIZE)
|
||||
cg.emit.emitLabel(validBoundLabel);
|
||||
cg.emit.emit("imul", VAR_SIZE, index);
|
||||
cg.emit.emit("add", pointer, index);
|
||||
cg.rm.releaseRegister(pointer);
|
||||
if (!obtainReference)
|
||||
cg.emit.emitLoad(2 * VAR_SIZE, index, index);
|
||||
else
|
||||
cg.emit.emit("add", 2 * VAR_SIZE, index);
|
||||
return index;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register intConst(IntConst ast, Location arg) {
|
||||
Register reg = cg.rm.getRegister();
|
||||
cg.emit.emitMove(ast.value, reg);
|
||||
return reg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register field(Field ast, Location arg) {
|
||||
boolean obtainReference = arg.isObtainReference();
|
||||
String accessFieldLabel = cg.emit.uniqueLabel();
|
||||
Register pointer = visit(ast.arg(), arg);
|
||||
cg.emit.emit("cmp", 0, pointer);
|
||||
cg.emit.emit("jne", accessFieldLabel);
|
||||
Interrupts.exit(cg, ExitCode.NULL_POINTER);
|
||||
|
||||
cg.emit.emitLabel(accessFieldLabel);
|
||||
String foundFieldLabel = cg.emit.uniqueLabel();
|
||||
String lookForFieldLabel = cg.emit.uniqueLabel();
|
||||
cg.emit.emit("add", VAR_SIZE, pointer);
|
||||
cg.emit.emitLabel(lookForFieldLabel);
|
||||
cg.emit.emit("cmpl", ast.sym.hashCode(), AssemblyEmitter.registerOffset(0, pointer));
|
||||
cg.emit.emit("je", foundFieldLabel);
|
||||
cg.emit.emit("add", 2 * VAR_SIZE, pointer);
|
||||
cg.emit.emit("jmp", lookForFieldLabel);
|
||||
cg.emit.emitLabel(foundFieldLabel);
|
||||
if (obtainReference)
|
||||
cg.emit.emit("add", VAR_SIZE, pointer);
|
||||
else
|
||||
cg.emit.emitLoad(VAR_SIZE, pointer, pointer);
|
||||
return pointer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Array structure:
|
||||
* <ul>
|
||||
* <li>Type of array (for casts, assignments and equalities)</li>
|
||||
* <li>Size of array (N)</li>
|
||||
* <li>element 0 or pointer to element 0</li>
|
||||
* <li>...</li>
|
||||
* <li>element N or pointer to element N</li>
|
||||
* </ul>
|
||||
* The type of the elements is stored in the vtable for the array (in order to
|
||||
* save space). <br/>
|
||||
*
|
||||
* The pointer that references the array points to the type of the array.
|
||||
* The reason for this is so that all reference types have at pointer + 0
|
||||
* the type, for dynamic type comparisons for assignments, casts and ==/!= operators
|
||||
*/
|
||||
@Override
|
||||
public Register newArray(NewArray ast, Location arg) {
|
||||
String validArraySizeLabel = cg.emit.uniqueLabel();
|
||||
|
||||
// Check size of array is positive
|
||||
Register arraySizeReg = visit(ast.arg(), arg);
|
||||
cg.emit.emit("cmp", 0, arraySizeReg);
|
||||
cg.emit.emit("jns", validArraySizeLabel); // size >= 0
|
||||
Interrupts.exit(cg, ExitCode.INVALID_ARRAY_SIZE);
|
||||
|
||||
// Reserve for length + 2 variables
|
||||
cg.emit.emitLabel(validArraySizeLabel);
|
||||
cg.emit.emit("push", arraySizeReg);
|
||||
cg.emit.emit("add", 2, arraySizeReg);
|
||||
Register arrayPointerReg = calloc(arraySizeReg);
|
||||
|
||||
// Store overhead information
|
||||
// Type reference
|
||||
Register arrayTypeReg = cg.rm.getRegister();
|
||||
cg.emit.emit("lea", Label.type(ast.type), arrayTypeReg);
|
||||
cg.emit.emitStore(arrayTypeReg, 0 * VAR_SIZE, arrayPointerReg);
|
||||
cg.rm.releaseRegister(arrayTypeReg);
|
||||
// Number of elements
|
||||
Register numElemReg = cg.rm.getRegister();
|
||||
cg.emit.emit("pop", numElemReg);
|
||||
cg.emit.emitStore(numElemReg, 1 * VAR_SIZE, arrayPointerReg);
|
||||
cg.rm.releaseRegister(numElemReg);
|
||||
|
||||
return arrayPointerReg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Structure of reference type objects (except arrays)
|
||||
* <ul>
|
||||
* <li>Pointer to type vtable</li>
|
||||
* <li>hashCode0, field0</li>
|
||||
* <li>...</li>
|
||||
* <li>hashCodeN, fieldN</li>
|
||||
* </ul>
|
||||
* The object pointer points to the first element, and every element
|
||||
* is of VAR_SIZE
|
||||
*/
|
||||
@Override
|
||||
public Register newObject(NewObject ast, Location arg) {
|
||||
ClassSymbol sym = (ClassSymbol) ast.type;
|
||||
|
||||
// Obtain from type size of allocated object and allocate memory
|
||||
int size = 1;
|
||||
ClassSymbol auxSym = sym;
|
||||
while (auxSym != ClassSymbol.objectType) {
|
||||
size += auxSym.fields.size() * 2;
|
||||
auxSym = auxSym.superClass;
|
||||
}
|
||||
Register sizeReg = cg.rm.getRegister();
|
||||
cg.emit.emitMove(size, sizeReg);
|
||||
|
||||
Register pointer = calloc(sizeReg);
|
||||
|
||||
// Store the pointer to the type vtable
|
||||
Register auxReg = cg.rm.getRegister();
|
||||
cg.emit.emit("lea", Label.type(sym), auxReg);
|
||||
cg.emit.emitStore(auxReg, 0, pointer);
|
||||
cg.emit.emit("push", pointer); // Save the pointer to the beginning to return later
|
||||
cg.rm.releaseRegister(auxReg);
|
||||
|
||||
// Store the hashes for the fields' variable symbols
|
||||
cg.emit.emit("add", VAR_SIZE, pointer);
|
||||
auxSym = sym;
|
||||
while (auxSym != ClassSymbol.objectType) {
|
||||
for (VariableSymbol field : auxSym.fields.values()) {
|
||||
cg.emit.emitStore(field.hashCode(), 0, pointer);
|
||||
cg.emit.emit("add", 2 * VAR_SIZE, pointer);
|
||||
}
|
||||
auxSym = auxSym.superClass;
|
||||
}
|
||||
|
||||
// Recover the initial address and return
|
||||
cg.emit.emit("pop", pointer);
|
||||
return pointer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register nullConst(NullConst ast, Location arg) {
|
||||
Register reg = cg.rm.getRegister();
|
||||
cg.emit.emitMove(0, reg);
|
||||
return reg;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
public Register thisRef(ThisRef ast, Location arg) {
|
||||
Register register = cg.rm.getRegister();
|
||||
cg.emit.emitLoad(2 * VAR_SIZE, BASE_REG, register);
|
||||
return register;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* implementation of x86 calling convention according to:
|
||||
* http://unixwiz.net/techtips/win32-callconv-asm.html
|
||||
* following the __cdecl calling convention
|
||||
*/
|
||||
@Override
|
||||
public Register methodCall(MethodCallExpr ast, Location arg) {
|
||||
// 0. Save registers to stack
|
||||
List<Register> callerSaved = new ArrayList<>();
|
||||
for (Register reg : CALLER_SAVE) {
|
||||
if (cg.rm.isInUse(reg)) {
|
||||
callerSaved.add(0, reg);
|
||||
cg.emit.emit("push", Register.EAX);
|
||||
}
|
||||
}
|
||||
|
||||
// 1. Evaluate all the arguments left-to-right (according to Java's spec)
|
||||
// and push them to the stack in right-to-left order. We will push them ltr
|
||||
// and then swap them so that the order is reversed
|
||||
for (Expr allArgument : ast.allArguments()) {
|
||||
Register argumentRegister = visit(allArgument, arg);
|
||||
cg.emit.emit("push", argumentRegister);
|
||||
cg.rm.releaseRegister(argumentRegister);
|
||||
}
|
||||
for (int i = 0; i < ast.allArguments().size() / 2; i++) {
|
||||
int offset1 = i * VAR_SIZE;
|
||||
int offset2 = (ast.allArguments().size() - 1 - i) * VAR_SIZE;
|
||||
Register aux1 = cg.rm.getRegister();
|
||||
Register aux2 = cg.rm.getRegister();
|
||||
cg.emit.emitLoad(offset1, STACK_REG, aux1);
|
||||
cg.emit.emitLoad(offset2, STACK_REG, aux2);
|
||||
cg.emit.emitStore(aux1, offset2, STACK_REG);
|
||||
cg.emit.emitStore(aux2, offset1, STACK_REG);
|
||||
cg.rm.releaseRegister(aux1);
|
||||
cg.rm.releaseRegister(aux2);
|
||||
}
|
||||
|
||||
// 2. Call function
|
||||
// 2.1. Search for the method pointer using the hashCode
|
||||
Register thisRef = cg.rm.getRegister();
|
||||
cg.emit.emitLoad(0, STACK_REG, thisRef);
|
||||
int hashCode = ast.methodName.hashCode();
|
||||
String lookForMethod = cg.emit.uniqueLabel();
|
||||
String methodFound = cg.emit.uniqueLabel();
|
||||
cg.emit.emitLoad(0, thisRef, thisRef); // Go to type vtable
|
||||
cg.emit.emit("add", 1 * VAR_SIZE, thisRef); // Skip the reference to superType
|
||||
cg.emit.emitLabel(lookForMethod);
|
||||
cg.emit.emit("cmpl", hashCode, AssemblyEmitter.registerOffset(0, thisRef));
|
||||
cg.emit.emit("je", methodFound); // hashCode == methodName.hashCode
|
||||
cg.emit.emit("add", 2 * VAR_SIZE, thisRef); // Go to next hashCode
|
||||
cg.emit.emit("jmp", lookForMethod);
|
||||
cg.emit.emitLabel(methodFound);
|
||||
// 2.2. Call the function
|
||||
cg.emit.emit("call", AssemblyEmitter.registerOffset(VAR_SIZE, thisRef));
|
||||
cg.rm.releaseRegister(thisRef);
|
||||
|
||||
// 3. Pop arguments from stack
|
||||
cg.emit.emit("add", VAR_SIZE * ast.allArguments().size(), STACK_REG);
|
||||
|
||||
// 4. Return result to caller
|
||||
Register result = null;
|
||||
if (ast.sym.returnType != PrimitiveTypeSymbol.voidType) {
|
||||
result = cg.rm.getRegister();
|
||||
cg.emit.emitMove(Register.EAX, result);
|
||||
}
|
||||
|
||||
// 5. Restore registers
|
||||
for (Register reg : callerSaved) {
|
||||
if (cg.rm.isInUse(reg))
|
||||
cg.emit.emit("pop", Register.EAX);
|
||||
else
|
||||
cg.emit.emit("add", 4, Register.ESP); // Don't overwrite
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register unaryOp(UnaryOp ast, Location arg) {
|
||||
Register argReg = visit(ast.arg(), arg);
|
||||
switch (ast.operator) {
|
||||
case U_PLUS:
|
||||
break;
|
||||
|
||||
case U_MINUS:
|
||||
cg.emit.emit("negl", argReg);
|
||||
break;
|
||||
|
||||
case U_BOOL_NOT:
|
||||
cg.emit.emit("negl", argReg);
|
||||
cg.emit.emit("incl", argReg);
|
||||
break;
|
||||
}
|
||||
return argReg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the value or reference to a variable described by a Var node
|
||||
* and a VariableSymbol.
|
||||
*/
|
||||
@Override
|
||||
public Register var(Var ast, Location arg) {
|
||||
boolean obtainReference = arg.isObtainReference();
|
||||
Register register = cg.rm.getRegister();
|
||||
cg.emit.emitMove(BASE_REG, register);
|
||||
int offset;
|
||||
switch (ast.sym.kind) {
|
||||
case PARAM:
|
||||
offset = 3 + arg.methodSym().parameters.indexOf(ast.sym);
|
||||
break;
|
||||
case LOCAL:
|
||||
offset = -(1 + positionOfLocal(arg.methodSym(), ast.name));
|
||||
break;
|
||||
case FIELD:
|
||||
String foundFieldLabel = cg.emit.uniqueLabel();
|
||||
String lookForFieldLabel = cg.emit.uniqueLabel();
|
||||
cg.emit.emitLoad(2 * VAR_SIZE, register, register);
|
||||
cg.emit.emit("add", VAR_SIZE, register);
|
||||
cg.emit.emitLabel(lookForFieldLabel);
|
||||
cg.emit.emit("cmpl", ast.sym.hashCode(), AssemblyEmitter.registerOffset(0, register));
|
||||
cg.emit.emit("je", foundFieldLabel);
|
||||
cg.emit.emit("add", 2 * VAR_SIZE, register);
|
||||
cg.emit.emit("jmp", lookForFieldLabel);
|
||||
cg.emit.emitLabel(foundFieldLabel);
|
||||
offset = 1; // The next element will be the reference we want
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("VariableSymbol Kind option not implemented");
|
||||
}
|
||||
if (obtainReference)
|
||||
cg.emit.emit("add", offset * VAR_SIZE, register);
|
||||
else
|
||||
cg.emit.emitLoad(offset * VAR_SIZE, register, register);
|
||||
return register;
|
||||
}
|
||||
|
||||
private int positionOfLocal(MethodSymbol sym, String name) {
|
||||
List<String> locals = new ArrayList<>(sym.locals.keySet());
|
||||
locals.sort(Comparator.comparing(o -> o));
|
||||
for (int i = 0; i < locals.size(); i++)
|
||||
if (locals.get(i).equals(name))
|
||||
return i;
|
||||
throw new RuntimeException("The variable could not be found, and should be there");
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function that calls calloc to obtain memory for reference type variables
|
||||
* @param numberOfElements Size in bytes, stored in a register
|
||||
* @return A register with the first address of the memory allocated
|
||||
*/
|
||||
private Register calloc(Register numberOfElements) {
|
||||
cg.emit.emitComment("calloc begin arg=" + numberOfElements);
|
||||
String callocOk = cg.emit.uniqueLabel();
|
||||
|
||||
boolean eaxInUse = cg.rm.isInUse(Register.EAX);
|
||||
if (eaxInUse && Register.EAX != numberOfElements)
|
||||
cg.emit.emit("push", Register.EAX);
|
||||
|
||||
// push arguments to the stack, then call calloc
|
||||
// the size of allocated elements is always four bytes!
|
||||
cg.emit.emit("push", AssemblyEmitter.constant(VAR_SIZE));
|
||||
cg.emit.emit("push", numberOfElements);
|
||||
cg.rm.releaseRegister(numberOfElements);
|
||||
cg.emit.emit("call", "calloc");
|
||||
cg.emit.emit("add", 8, STACK_REG);
|
||||
|
||||
// Check for null pointer (if calloc fails)
|
||||
cg.emit.emit("cmp", 0, Register.EAX);
|
||||
cg.emit.emit("jne", callocOk);
|
||||
Interrupts.exit(cg, ExitCode.INTERNAL_ERROR);
|
||||
|
||||
cg.emit.emitLabel(callocOk);
|
||||
Register result = cg.rm.getRegister();
|
||||
cg.emit.emitMove(Register.EAX, result);
|
||||
|
||||
// pop EAX if needed
|
||||
if (eaxInUse && Register.EAX != numberOfElements)
|
||||
cg.emit.emit("pop", Register.EAX);
|
||||
|
||||
cg.emit.emitComment("calloc end");
|
||||
return result;
|
||||
}
|
||||
}
|
19
src/cd/backend/codegen/Interrupts.java
Normal file
19
src/cd/backend/codegen/Interrupts.java
Normal file
|
@ -0,0 +1,19 @@
|
|||
package cd.backend.codegen;
|
||||
|
||||
import cd.backend.ExitCode;
|
||||
import cd.backend.codegen.RegisterManager.Register;
|
||||
|
||||
public class Interrupts {
|
||||
protected static final int INTERRUPT_EXIT = 1;
|
||||
|
||||
/**
|
||||
* 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, ExitCode exitCode) {
|
||||
cg.emit.emitMove(AssemblyEmitter.constant(INTERRUPT_EXIT), Register.EAX);
|
||||
cg.emit.emitMove(AssemblyEmitter.constant(exitCode.value), Register.EBX);
|
||||
cg.emit.emit("int", 0x80);
|
||||
}
|
||||
}
|
42
src/cd/backend/codegen/Location.java
Normal file
42
src/cd/backend/codegen/Location.java
Normal file
|
@ -0,0 +1,42 @@
|
|||
package cd.backend.codegen;
|
||||
|
||||
import cd.ir.Symbol.ClassSymbol;
|
||||
import cd.ir.Symbol.MethodSymbol;
|
||||
|
||||
public class Location {
|
||||
private ClassSymbol classSymbol;
|
||||
private MethodSymbol methodSymbol = null;
|
||||
private boolean obtainReference = false;
|
||||
|
||||
public Location (ClassSymbol sym) {
|
||||
classSymbol = sym;
|
||||
}
|
||||
|
||||
public ClassSymbol classSym() {
|
||||
return classSymbol;
|
||||
}
|
||||
|
||||
public MethodSymbol methodSym() {
|
||||
assert methodSymbol != null;
|
||||
return methodSymbol;
|
||||
}
|
||||
|
||||
public void enterMethod(MethodSymbol sym) {
|
||||
methodSymbol = sym;
|
||||
}
|
||||
|
||||
public void leaveMethod() {
|
||||
methodSymbol = null;
|
||||
}
|
||||
|
||||
public boolean isObtainReference() {
|
||||
boolean aux = obtainReference;
|
||||
obtainReference = false;
|
||||
return aux;
|
||||
}
|
||||
|
||||
public Location obtainReference() {
|
||||
obtainReference = true;
|
||||
return this;
|
||||
}
|
||||
}
|
130
src/cd/backend/codegen/RegisterManager.java
Normal file
130
src/cd/backend/codegen/RegisterManager.java
Normal file
|
@ -0,0 +1,130 @@
|
|||
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);
|
||||
}
|
||||
|
||||
public void useRegister(Register reg) {
|
||||
assert registers.contains(reg);
|
||||
assert reg != null;
|
||||
registers.remove(reg);
|
||||
}
|
||||
|
||||
/**
|
||||
* marks a currently used register as free
|
||||
*/
|
||||
public void releaseRegister(Register reg) {
|
||||
assert !registers.contains(reg);
|
||||
assert reg != null;
|
||||
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();
|
||||
}
|
||||
}
|
138
src/cd/backend/codegen/RegsNeededVisitor.java
Normal file
138
src/cd/backend/codegen/RegsNeededVisitor.java
Normal file
|
@ -0,0 +1,138 @@
|
|||
package cd.backend.codegen;
|
||||
|
||||
import cd.ir.Ast;
|
||||
import cd.ir.Ast.*;
|
||||
import cd.ir.AstVisitor;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
/**
|
||||
* Determines the maximum number of registers
|
||||
* required to execute one subtree. */
|
||||
public class RegsNeededVisitor extends AstVisitor<Integer, Void> {
|
||||
|
||||
public int calc(Ast ast) {
|
||||
return visit(ast, null);
|
||||
}
|
||||
|
||||
private Map<Ast,Integer> memo = new HashMap<Ast, Integer>();
|
||||
|
||||
/**
|
||||
* Override visit() so as to memorize the results and avoid
|
||||
* unnecessary computation
|
||||
*/
|
||||
@Override
|
||||
public Integer visit(Ast ast, Void arg) {
|
||||
if (memo.containsKey(ast))
|
||||
return memo.get(ast);
|
||||
Integer res = ast.accept(this, null);
|
||||
memo.put(ast, res);
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer dflt(Ast ast, Void arg) {
|
||||
// For a non-expression, it suffices to find the
|
||||
// maximum registers used by any individual expression.
|
||||
int maxReg = 0;
|
||||
for (Ast a : ast.children()) {
|
||||
maxReg = Math.max(calc(a), maxReg);
|
||||
}
|
||||
return maxReg;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Integer dfltExpr(Expr ast, Void arg) {
|
||||
throw new RuntimeException("Should never be used");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer binaryOp(BinaryOp ast, Void arg) {
|
||||
int left = calc(ast.left());
|
||||
int right = calc(ast.right());
|
||||
int ifLeftFirst = max(left, right+1);
|
||||
int ifRightFirst = max(left+1, right);
|
||||
int overall = min(ifLeftFirst, ifRightFirst);
|
||||
return overall;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer assign(Assign ast, Void arg) {
|
||||
return max(calc(ast.left()) + 1, calc(ast.right()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer booleanConst(BooleanConst ast, Void arg) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer builtInRead(BuiltInRead ast, Void arg) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer cast(Cast ast, Void arg) {
|
||||
return calc(ast.arg());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer index(Index ast, Void arg) {
|
||||
return max(calc(ast.left()), calc(ast.right()) + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer field(Field ast, Void arg) {
|
||||
return calc(ast.arg());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer intConst(IntConst ast, Void arg) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer newArray(NewArray ast, Void arg) {
|
||||
return calc(ast.arg());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer newObject(NewObject ast, Void arg) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer nullConst(NullConst ast, Void arg) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer thisRef(ThisRef ast, Void arg) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer methodCall(MethodCallExpr ast, Void arg) {
|
||||
int max = 1;
|
||||
for (Expr argex : ast.allArguments()) {
|
||||
int needed = calc(argex);
|
||||
if (needed > max)
|
||||
max = needed;
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer unaryOp(UnaryOp ast, Void arg) {
|
||||
return calc(ast.arg());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer var(Var ast, Void arg) {
|
||||
return 1;
|
||||
}
|
||||
}
|
288
src/cd/backend/codegen/StmtGenerator.java
Normal file
288
src/cd/backend/codegen/StmtGenerator.java
Normal file
|
@ -0,0 +1,288 @@
|
|||
package cd.backend.codegen;
|
||||
|
||||
import cd.Config;
|
||||
import cd.backend.ExitCode;
|
||||
import cd.backend.codegen.AstCodeGenerator.Label;
|
||||
import cd.backend.codegen.RegisterManager.Register;
|
||||
import cd.frontend.semantic.ReturnCheckerVisitor;
|
||||
import cd.ir.Ast;
|
||||
import cd.ir.Ast.*;
|
||||
import cd.ir.AstVisitor;
|
||||
import cd.ir.Symbol;
|
||||
import cd.ir.Symbol.ClassSymbol;
|
||||
import cd.ir.Symbol.MethodSymbol;
|
||||
import cd.util.debug.AstOneLine;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static cd.backend.codegen.AstCodeGenerator.VAR_SIZE;
|
||||
import static cd.backend.codegen.AstCodeGenerator.TRUE;
|
||||
import static cd.backend.codegen.RegisterManager.BASE_REG;
|
||||
import static cd.backend.codegen.RegisterManager.STACK_REG;
|
||||
|
||||
/**
|
||||
* Generates code to process statements and declarations.
|
||||
*/
|
||||
class StmtGenerator extends AstVisitor<Register,Location> {
|
||||
protected final AstCodeGenerator cg;
|
||||
|
||||
StmtGenerator(AstCodeGenerator astCodeGenerator) {
|
||||
cg = astCodeGenerator;
|
||||
|
||||
}
|
||||
|
||||
public void gen(Ast ast) {
|
||||
visit(ast, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register visit(Ast ast, Location arg) {
|
||||
try {
|
||||
cg.emit.increaseIndent("Emitting " + AstOneLine.toString(ast));
|
||||
if (cg.rnv.calc(ast) > cg.rm.availableRegisters()) {
|
||||
Deque<Register> pushed = new ArrayDeque<>();
|
||||
for (Register r : RegisterManager.GPR) {
|
||||
if (cg.rm.isInUse(r)) {
|
||||
cg.emit.emit("push", r);
|
||||
pushed.push(r);
|
||||
cg.rm.releaseRegister(r);
|
||||
}
|
||||
}
|
||||
Register result = super.visit(ast, arg);
|
||||
for (Register r : pushed)
|
||||
cg.rm.useRegister(r);
|
||||
Register finalResult = cg.rm.getRegister();
|
||||
cg.emit.emitMove(result, finalResult);
|
||||
for (Register r = pushed.pop(); !pushed.isEmpty(); r = pushed.pop())
|
||||
cg.emit.emit("pop", r);
|
||||
return finalResult;
|
||||
} else return super.visit(ast, arg);
|
||||
} finally {
|
||||
cg.emit.decreaseIndent();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register methodCall(MethodCall ast, Location arg) {
|
||||
Register result = cg.eg.visit(ast.getMethodCallExpr(), arg);
|
||||
if (result != null)
|
||||
cg.rm.releaseRegister(result);
|
||||
return null;
|
||||
}
|
||||
|
||||
public Register methodCall(MethodSymbol sym, List<Expr> allArguments) {
|
||||
throw new RuntimeException("Not required");
|
||||
}
|
||||
|
||||
/**
|
||||
* vtable structure for a class type: pointer to superclass, and methods, each with 2 entries, name's hashCode and
|
||||
* pointer to method execution
|
||||
*/
|
||||
@Override
|
||||
public Register classDecl(ClassDecl ast, Location arg) {
|
||||
cg.emit.emitRaw(Config.DATA_INT_SECTION);
|
||||
// Emit vtable for class
|
||||
cg.emit.emitLabel(Label.type(ast.sym)); // Label
|
||||
cg.emit.emitConstantData(Label.type(ast.sym.superClass)); // Superclass
|
||||
// Methods (don't write those that are overridden twice)
|
||||
Set<String> generated = new HashSet<>();
|
||||
ClassSymbol currSym = ast.sym;
|
||||
while (currSym != ClassSymbol.objectType) {
|
||||
ClassSymbol finalCurrSym = currSym;
|
||||
currSym.methods.values().forEach(o -> {
|
||||
if (!generated.add(o.name)) return;
|
||||
cg.emit.emitConstantData(String.valueOf(o.name.hashCode()));
|
||||
cg.emit.emitConstantData(Label.method(finalCurrSym, o));
|
||||
});
|
||||
currSym = currSym.superClass;
|
||||
}
|
||||
// End of class vtable
|
||||
// Array vtable
|
||||
boolean found = false;
|
||||
for (Symbol.TypeSymbol type : cg.main.allTypeSymbols) {
|
||||
if (type instanceof Symbol.ArrayTypeSymbol) {
|
||||
Symbol.ArrayTypeSymbol aType = (Symbol.ArrayTypeSymbol) type;
|
||||
if (aType.elementType == ast.sym) {
|
||||
cg.emit.emitLabel(Label.type(aType)); // Label
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
throw new RuntimeException("The array type could not be found");
|
||||
cg.emit.emitConstantData(Label.type(ClassSymbol.objectType)); // Supertype
|
||||
cg.emit.emitConstantData(Label.type(ast.sym)); // Type of elements
|
||||
// End of array vtable
|
||||
cg.emit.emitRaw(Config.TEXT_SECTION);
|
||||
|
||||
// Method bodies
|
||||
for (Ast method : ast.methods())
|
||||
visit(method, new Location(ast.sym));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register methodDecl(MethodDecl ast, Location arg) {
|
||||
// Bookkeeping for framework
|
||||
arg.enterMethod(ast.sym);
|
||||
cg.initMethodData();
|
||||
|
||||
// Begin method
|
||||
cg.emit.emitLabel(Label.method(arg.classSym(), ast.sym));
|
||||
|
||||
// 1. Save and update the base register, reserve space for local variables
|
||||
cg.emit.emit("enter", ast.sym.locals.size() * VAR_SIZE, 0);
|
||||
for (int i = 0; i < ast.sym.locals.size(); i++)
|
||||
cg.emit.emitStore(0, -(i + 1) * VAR_SIZE, BASE_REG);
|
||||
|
||||
// 2. Save CPU registers
|
||||
Register[] regs = RegisterManager.CALLEE_SAVE;
|
||||
for (int i = 0; i < regs.length; i++)
|
||||
cg.emit.emit("push", regs[i]);
|
||||
|
||||
// 3. Run the code contained in the function
|
||||
Register returnReg = visit(ast.body(), arg);
|
||||
cg.emit.emitLabel(Label.returnMethod(arg.classSym(), ast.sym));
|
||||
|
||||
// 4. Restore saved registers (if there is a mismatch, the stack will be corrupted)
|
||||
for (int i = regs.length - 1; i >= 0; i--)
|
||||
cg.emit.emit("pop", regs[i]);
|
||||
|
||||
// 5 and 6: Restore the old base pointer and return from the function
|
||||
cg.emitMethodSuffix(returnReg == null);
|
||||
|
||||
// Framework bookkeeping
|
||||
arg.leaveMethod();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register ifElse(IfElse ast, Location arg) {
|
||||
// Emit condition check
|
||||
Register conditionRegister = cg.eg.visit(ast.condition(), arg);
|
||||
// If both blocks are empty, no more code need be generated, and the condition can be ignored
|
||||
if (ast.then().children().isEmpty() && ast.otherwise().children().isEmpty()) {
|
||||
// Generate the condition and ignore the result
|
||||
cg.rm.releaseRegister(conditionRegister);
|
||||
return null;
|
||||
}
|
||||
String notIfLabel = cg.emit.uniqueLabel();
|
||||
String endLabel = cg.emit.uniqueLabel();
|
||||
|
||||
cg.emit.emit("cmp", TRUE, conditionRegister);
|
||||
cg.rm.releaseRegister(conditionRegister);
|
||||
if (!ast.then().children().isEmpty()) {
|
||||
cg.emit.emit("jne", notIfLabel);
|
||||
visit(ast.then(), arg);
|
||||
// If there is no otherwise, the jump instruction makes no sense
|
||||
// as the next code executed will be the next instruction
|
||||
if (!ast.otherwise().children().isEmpty())
|
||||
cg.emit.emit("jmp", endLabel);
|
||||
} else {
|
||||
// No if, therefore the else follows the condition immediately
|
||||
cg.emit.emit("je", endLabel);
|
||||
}
|
||||
|
||||
cg.emit.emitLabel(notIfLabel);
|
||||
// Emit otherwise
|
||||
visit(ast.otherwise(), arg);
|
||||
cg.emit.emitLabel(endLabel);
|
||||
|
||||
// Check if the ifElse ast node contains a return statement
|
||||
ReturnCheckerVisitor rc = new ReturnCheckerVisitor();
|
||||
if (rc.ifElse(ast, null)) {
|
||||
return Register.EAX;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register whileLoop(WhileLoop ast, Location arg) {
|
||||
String conditionLabel = cg.emit.uniqueLabel();
|
||||
|
||||
cg.emit.emitLabel(conditionLabel);
|
||||
Register conditionRegister = cg.eg.visit(ast.condition(), arg);
|
||||
cg.emit.emit("cmp", TRUE, conditionRegister);
|
||||
cg.rm.releaseRegister(conditionRegister);
|
||||
if (ast.body().children().isEmpty()) {
|
||||
// Jump structure can be easier if there is no body
|
||||
cg.emit.emit("je", conditionLabel);
|
||||
} else {
|
||||
String endLabel = cg.emit.uniqueLabel();
|
||||
// Emit jumps, labels and body
|
||||
cg.emit.emit("jne", endLabel);
|
||||
visit(ast.body(), arg);
|
||||
cg.emit.emit("jmp", conditionLabel);
|
||||
cg.emit.emitLabel(endLabel);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register assign(Assign ast, Location arg) {
|
||||
Register value = cg.eg.visit(ast.right(), arg);
|
||||
|
||||
// If the type is a reference, visiting the lhs will yield its address
|
||||
// else, the lhs will yield the current value. We need a visitor that
|
||||
// returns a pointer to the position of the variable/field in memory
|
||||
// for primitive types.
|
||||
Register pointer = cg.eg.visit(ast.left(), arg.obtainReference());
|
||||
if (ast.left().type.isReferenceType()) {
|
||||
// Check null pointer
|
||||
String validPointerLabel = cg.emit.uniqueLabel();
|
||||
cg.emit.emit("cmp", 0, pointer);
|
||||
cg.emit.emit("jne", validPointerLabel);
|
||||
Interrupts.exit(cg, ExitCode.NULL_POINTER);
|
||||
cg.emit.emitLabel(validPointerLabel);
|
||||
}
|
||||
cg.emit.emitStore(value, 0, pointer);
|
||||
cg.rm.releaseRegister(pointer);
|
||||
cg.rm.releaseRegister(value);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register builtInWrite(BuiltInWrite ast, Location arg) {
|
||||
Register reg = cg.eg.visit(ast.arg(), arg);
|
||||
cg.emit.emit("sub", 16, STACK_REG);
|
||||
cg.emit.emitStore(reg, 4, STACK_REG);
|
||||
cg.emit.emitStore("$STR_D", 0, STACK_REG);
|
||||
cg.emit.emit("call", Config.PRINTF);
|
||||
cg.emit.emit("add", 16, STACK_REG);
|
||||
cg.rm.releaseRegister(reg);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register builtInWriteln(BuiltInWriteln ast, Location arg) {
|
||||
cg.emit.emit("sub", 16, STACK_REG);
|
||||
cg.emit.emitStore("$STR_NL", 0, STACK_REG);
|
||||
cg.emit.emit("call", Config.PRINTF);
|
||||
cg.emit.emit("add", 16, STACK_REG);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register returnStmt(ReturnStmt ast, Location arg) {
|
||||
if (ast.arg() != null) {
|
||||
Register retReg = cg.eg.visit(ast.arg(), arg);
|
||||
cg.emit.emitMove(retReg, Register.EAX);
|
||||
cg.rm.releaseRegister(retReg);
|
||||
}
|
||||
cg.emit.emit("jmp", Label.returnMethod(arg.classSym(), arg.methodSym()));
|
||||
return Register.EAX;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Register seq(Seq ast, Location arg) {
|
||||
for (Ast instruction : ast.children()) {
|
||||
Register res = visit(instruction, arg);
|
||||
if (res != null)
|
||||
return res; // don't generate instructions after a return
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,346 +0,0 @@
|
|||
package cd.frontend.semantic;
|
||||
|
||||
import cd.frontend.semantic.SemanticAnalyzer.SemanticLocation;
|
||||
import cd.frontend.semantic.SemanticFailure.Cause;
|
||||
import cd.ir.Ast;
|
||||
import cd.ir.Ast.*;
|
||||
import cd.ir.ExprVisitor;
|
||||
import cd.ir.Symbol;
|
||||
import cd.ir.Symbol.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Semantic analyzer for {@link Expr} nodes in the pattern of a visitor. <br>
|
||||
* All methods may throw {@link SemanticFailure} if an error is discovered,
|
||||
* with the appropriate {@link Cause}. <br>
|
||||
* The current implementation returns the resulting {@link Symbol} for each expression and
|
||||
* uses the {@link ClassDecl} argument to obtain the self-reference in the {@link ThisRef} node.
|
||||
* <br> Some methods may throw a {@link RuntimeException} if they are in a
|
||||
* illegal situation, given the grammar of Javali and current implementation.
|
||||
* This signals an error in the implementation, not a user-caused error.
|
||||
* @see cd.frontend.semantic.StmtAnalyzer
|
||||
* @author Carlos Galindo
|
||||
* @author Levin Moser
|
||||
*/
|
||||
public class ExprAnalyzer extends ExprVisitor<TypeSymbol,SemanticLocation> {
|
||||
/** Reference to the main semantic analyzer, used to access the type list via
|
||||
* {@link SemanticAnalyzer#findType(String)} */
|
||||
private SemanticAnalyzer sem;
|
||||
|
||||
ExprAnalyzer(SemanticAnalyzer sem) {
|
||||
this.sem = sem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the possible semantic errors that can appear in a {@link BinaryOp} node
|
||||
* @param ast The node describing the binary operation
|
||||
* @param arg The class and method in which the operation is performed
|
||||
* @return A {@link PrimitiveTypeSymbol} of resulting type, depending on the operation
|
||||
* performed (either {@code int} or {@code boolean})
|
||||
* @throws SemanticFailure If the types of both operands aren't equal
|
||||
* @throws SemanticFailure With {@code ==} or {@code !=} operators, if no argument's type is
|
||||
* a subtype of the other
|
||||
* @throws SemanticFailure If the type of the operands doesn't match the type
|
||||
* of the operation (either {@code int} or {@code boolean}
|
||||
* @throws RuntimeException If the implementation has missed any {@link BinaryOp.BOp} type
|
||||
*/
|
||||
@Override
|
||||
public TypeSymbol binaryOp(BinaryOp ast, SemanticLocation arg) {
|
||||
TypeSymbol leftType = visit(ast.left(), arg);
|
||||
TypeSymbol rightType = visit(ast.right(), arg);
|
||||
TypeSymbol desiredType;
|
||||
TypeSymbol returnType = PrimitiveTypeSymbol.booleanType;
|
||||
switch (ast.operator) {
|
||||
case B_AND:
|
||||
case B_OR:
|
||||
desiredType = PrimitiveTypeSymbol.booleanType;
|
||||
break;
|
||||
case B_PLUS:
|
||||
case B_MINUS:
|
||||
case B_TIMES:
|
||||
case B_DIV:
|
||||
case B_MOD:
|
||||
desiredType = PrimitiveTypeSymbol.intType;
|
||||
returnType = desiredType;
|
||||
break;
|
||||
case B_LESS_THAN:
|
||||
case B_GREATER_THAN:
|
||||
case B_LESS_OR_EQUAL:
|
||||
case B_GREATER_OR_EQUAL:
|
||||
desiredType = PrimitiveTypeSymbol.intType;
|
||||
break;
|
||||
case B_EQUAL:
|
||||
case B_NOT_EQUAL:
|
||||
// Can take any pair of types that are a subtype of each other
|
||||
desiredType = null;
|
||||
break;
|
||||
default:
|
||||
throw new RuntimeException("Missing BOp in implementation of ExprAnalyzer#binaryOp(...)");
|
||||
}
|
||||
|
||||
if (desiredType == null) {
|
||||
if (leftType.isSubtypeOf(rightType) || rightType.isSubtypeOf(leftType))
|
||||
return returnType;
|
||||
else
|
||||
throw new SemanticFailure(Cause.TYPE_ERROR,
|
||||
"Operation \"%s\" operation with incompatible types %s and %s",
|
||||
ast.operator.repr, leftType, rightType);
|
||||
} else if (leftType == rightType) {
|
||||
if (leftType == desiredType)
|
||||
return returnType;
|
||||
else
|
||||
throw new SemanticFailure(Cause.TYPE_ERROR,
|
||||
"Arguments of %s operation must be of %s type, found %s",
|
||||
ast.operator.repr, desiredType, leftType);
|
||||
} else {
|
||||
throw new SemanticFailure(Cause.TYPE_ERROR,
|
||||
"Arguments of %s operation must be of same type %s, found %s and %s",
|
||||
ast.operator.repr, desiredType, leftType, rightType);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the {@code boolean} type
|
||||
* @param ast Node describing the constant
|
||||
* @param arg Class and method in which the expression exists
|
||||
* @return {@link PrimitiveTypeSymbol} representing the {@code boolean} type
|
||||
*/
|
||||
@Override
|
||||
public TypeSymbol booleanConst(BooleanConst ast, SemanticLocation arg) {
|
||||
return PrimitiveTypeSymbol.booleanType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the {@code int} type (all reads in Javali result in an integer)
|
||||
* @param ast Node describing the expression
|
||||
* @param arg Class and method in which the expression exists
|
||||
* @return {@link PrimitiveTypeSymbol} representing the {@code int} type
|
||||
*/
|
||||
@Override
|
||||
public TypeSymbol builtInRead(BuiltInRead ast, SemanticLocation arg) {
|
||||
return PrimitiveTypeSymbol.intType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks possible semantic errors in a {@link Cast} node. <br>
|
||||
* Uses {@link TypeSymbol#isSubtypeOf(TypeSymbol)} to determine if the types are compatible.
|
||||
* @return The resulting type after the cast
|
||||
* @throws SemanticFailure If the destination type doesn't exist
|
||||
* @throws SemanticFailure If the types are incompatible (none is a subtype of the other)
|
||||
*/
|
||||
@Override
|
||||
public TypeSymbol cast(Cast ast, SemanticLocation arg) {
|
||||
TypeSymbol castType = sem.findType(ast.typeName);
|
||||
TypeSymbol exprType = visit(ast.arg(), arg);
|
||||
if (exprType.isSubtypeOf(castType) || castType.isSubtypeOf(exprType))
|
||||
return castType;
|
||||
else
|
||||
throw new SemanticFailure(Cause.TYPE_ERROR,
|
||||
"Type %s cannot be casted to %s, as they are unrelated", exprType, castType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks possible semantic errors in a {@link Field} node.
|
||||
* @param ast Node describing the field access
|
||||
* @param arg Class and method in which the expression exists
|
||||
* @return Field accessed, as defined in the corresponding {@link ClassSymbol}
|
||||
* @throws SemanticFailure If the field to access doesn't exist
|
||||
* @throws SemanticFailure If the expression in whose field is accessed
|
||||
* is not a {@link ClassSymbol}
|
||||
*/
|
||||
@Override
|
||||
public TypeSymbol field(Field ast, SemanticLocation arg) {
|
||||
TypeSymbol variableType = visit(ast.arg(), arg);
|
||||
if (!(variableType instanceof ClassSymbol))
|
||||
throw new SemanticFailure(Cause.TYPE_ERROR,
|
||||
"The field of a %s variable cannot be accessed, it is not a object type", variableType);
|
||||
ClassSymbol classType = (ClassSymbol) variableType;
|
||||
VariableSymbol field = classType.getField(ast.fieldName);
|
||||
if (field == null)
|
||||
throw new SemanticFailure(Cause.NO_SUCH_FIELD,
|
||||
"The field %s doesn't exist in class %s",
|
||||
ast.fieldName, variableType);
|
||||
else
|
||||
return field.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks possible semantic errors in a {@link Index} node.
|
||||
* @param ast Node describing the index operation
|
||||
* @param arg Class and method in which the expression exists
|
||||
* @return Type of the elements of the array indexed
|
||||
* @throws SemanticFailure If the index is not an integer
|
||||
* @throws SemanticFailure If the expression indexed is not an array
|
||||
*/
|
||||
@Override
|
||||
public TypeSymbol index(Index ast, SemanticLocation arg) {
|
||||
TypeSymbol arrayType = visit(ast.left(), arg);
|
||||
TypeSymbol indexType = visit(ast.right(), arg);
|
||||
|
||||
if (indexType != PrimitiveTypeSymbol.intType)
|
||||
throw new SemanticFailure(Cause.TYPE_ERROR, "The index must be a int, found %s", indexType);
|
||||
else if (!(arrayType instanceof ArrayTypeSymbol))
|
||||
throw new SemanticFailure(Cause.TYPE_ERROR, "Expecting array type, found %s", arrayType);
|
||||
else
|
||||
return ((ArrayTypeSymbol) arrayType).elementType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the {@code int} type
|
||||
* @param ast Node describing the constant
|
||||
* @param arg Class and method in which the expression exists
|
||||
* @return A PrimitiveTypeSymbol for the type int
|
||||
*/
|
||||
@Override
|
||||
public TypeSymbol intConst(IntConst ast, SemanticLocation arg) {
|
||||
return PrimitiveTypeSymbol.intType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the method referenced exists in the current scope and the typing of
|
||||
* the parameters.
|
||||
* @param ast Node describing the method call
|
||||
* @param arg Class and method in which the expression exists
|
||||
* @return The return type for the method called
|
||||
* @throws SemanticFailure If the number of parameters doesn't match the declaration
|
||||
* @throws SemanticFailure If the parameters' types don't match the declaration
|
||||
* @throws SemanticFailure If the method doesn't exist
|
||||
* @throws SemanticFailure If the {@link MethodCallExpr#receiver()} is not
|
||||
* a object type (methods cannot be called on PrimitiveTypes or Arrays)
|
||||
*/
|
||||
@Override
|
||||
public TypeSymbol methodCall(MethodCallExpr ast, SemanticLocation arg) {
|
||||
TypeSymbol receiverType = visit(ast.receiver(), arg);
|
||||
// Check receiver validity
|
||||
if (!(receiverType instanceof ClassSymbol))
|
||||
throw new SemanticFailure(Cause.TYPE_ERROR, "Expected object class, found %s", receiverType);
|
||||
// Find method symbol
|
||||
ast.sym = sem.findMethod(ast.methodName, (ClassSymbol) receiverType);
|
||||
if (ast.sym == null)
|
||||
throw new SemanticFailure(Cause.NO_SUCH_METHOD, "Method %s(...) doesn't exist", ast.methodName);
|
||||
// Compare expected with available parameters
|
||||
List<Expr> realParams = ast.argumentsWithoutReceiver();
|
||||
List<VariableSymbol> expectedParams = ast.sym.parameters;
|
||||
if (realParams.size() != expectedParams.size())
|
||||
throw new SemanticFailure(Cause.WRONG_NUMBER_OF_ARGUMENTS,
|
||||
"%s call has %d parameters, expected %d",
|
||||
ast.sym, realParams.size(), expectedParams.size());
|
||||
for (int i = 0; i < realParams.size(); i++) {
|
||||
TypeSymbol realType = visit(realParams.get(i), arg);
|
||||
TypeSymbol expectedType = expectedParams.get(i).type;
|
||||
if (!realType.isSubtypeOf(expectedType))
|
||||
throw new SemanticFailure(Cause.TYPE_ERROR, "Parameter %d of %s of type %s, expected %s",
|
||||
i, ast.sym, realType, expectedType);
|
||||
}
|
||||
return ast.sym.returnType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for semantic errors in the creation of a new object of the specified type
|
||||
* @param ast Node describing the new object expression
|
||||
* @param arg Class and method in which the expression exists
|
||||
* @return {@link ClassSymbol} corresponding to the type of the new object
|
||||
* @throws SemanticFailure If the type referenced doesn't exist
|
||||
*/
|
||||
@Override
|
||||
public TypeSymbol newObject(NewObject ast, SemanticLocation arg) {
|
||||
return sem.findType(ast.typeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for semantic errors in the creation of a new array of the specified type and size
|
||||
* @param ast Node describing the new array expression
|
||||
* @param arg Class and method in which the expression exists
|
||||
* @return ArrayTypeSymbol of the corresponding type
|
||||
* @throws SemanticFailure If the array type doesn't exist
|
||||
* @throws SemanticFailure If the expression indicating the size of the array is not an {@code int}
|
||||
*/
|
||||
@Override
|
||||
public TypeSymbol newArray(NewArray ast, SemanticLocation arg) {
|
||||
// Type exists (if it does, it is guaranteed by the grammar to be a ArrayTypeSymbol)
|
||||
ArrayTypeSymbol type = (ArrayTypeSymbol) sem.findType(ast.typeName);
|
||||
// Array length must be of type int
|
||||
TypeSymbol indexType = visit(ast.arg(), arg);
|
||||
if (indexType != PrimitiveTypeSymbol.intType)
|
||||
throw new SemanticFailure(Cause.TYPE_ERROR, "Array size must be of type int, found %s", indexType);
|
||||
else
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the {@code null} type
|
||||
* @param ast Node describing the new object instantiation expression
|
||||
* @param arg Class and method in which the expression exists
|
||||
* @return The ClassSymbol corresponding to {@code null}
|
||||
*/
|
||||
@Override
|
||||
public TypeSymbol nullConst(NullConst ast, SemanticLocation arg) {
|
||||
return ClassSymbol.nullType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the reference to {@code this}
|
||||
* @param ast The AST node
|
||||
* @param arg Class and method in which the class reference is accessed
|
||||
* @return The VariableSymbol {@code this} from the class
|
||||
*/
|
||||
@Override
|
||||
public TypeSymbol thisRef(ThisRef ast, SemanticLocation arg) {
|
||||
return arg.classSymbol.thisSymbol.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a {@link UnaryOp} is valid semantically
|
||||
* @param ast The node describing the operation
|
||||
* @param arg Class and method in which the expression exists
|
||||
* @return The resulting type of the operation
|
||||
* @throws SemanticFailure With "{@code !}" operator, if the operand is not a {@code boolean}
|
||||
* @throws SemanticFailure With "{@code +}" or "{@code -}" operators, if the operand is not an {@code int}
|
||||
*/
|
||||
@Override
|
||||
public TypeSymbol unaryOp(UnaryOp ast, SemanticLocation arg) {
|
||||
TypeSymbol argType = visit(ast.arg(), arg);
|
||||
|
||||
TypeSymbol desiredType = PrimitiveTypeSymbol.intType;
|
||||
if (ast.operator.equals(Ast.UnaryOp.UOp.U_BOOL_NOT))
|
||||
desiredType = PrimitiveTypeSymbol.booleanType;
|
||||
|
||||
if (argType == desiredType)
|
||||
return desiredType;
|
||||
else
|
||||
throw new SemanticFailure(Cause.TYPE_ERROR,
|
||||
"Operator %s expected operand of type %s, found %s",
|
||||
ast.operator.repr, desiredType, argType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the variable referenced by the {@link Var} node exists
|
||||
* in the current scope, either as local variable, method parameter or field
|
||||
* (including inherited fields)
|
||||
* <br> In the process, the {@link Var} node is linked to the
|
||||
* {@link VariableSymbol}
|
||||
* @param ast Node describing the variable access
|
||||
* @param arg Class and method that define the current scope in which the
|
||||
* variable is looked for
|
||||
* @return The {@link VariableSymbol} corresponding to the variable
|
||||
* @throws SemanticFailure If the variable doesn't exist in the current scope
|
||||
*/
|
||||
@Override
|
||||
public TypeSymbol var(Var ast, SemanticLocation arg) {
|
||||
// Local variable
|
||||
ast.sym = arg.getMethod().locals.get(ast.name);
|
||||
if (ast.sym != null)
|
||||
return ast.sym.type;
|
||||
// Method parameter
|
||||
for(VariableSymbol var : arg.getMethod().parameters)
|
||||
if (var.name.equals(ast.name))
|
||||
return (ast.sym = var).type;
|
||||
// Class field (or field of a superclass)
|
||||
ast.sym = arg.classSymbol.getField(ast.name);
|
||||
if (ast.sym != null)
|
||||
return ast.sym.type;
|
||||
// The variable cannot be found in any known scope
|
||||
throw new SemanticFailure(Cause.NO_SUCH_VARIABLE, "Variable %s not found", ast.name);
|
||||
}
|
||||
}
|
75
src/cd/frontend/semantic/InheritanceChecker.java
Normal file
75
src/cd/frontend/semantic/InheritanceChecker.java
Normal file
|
@ -0,0 +1,75 @@
|
|||
package cd.frontend.semantic;
|
||||
|
||||
import cd.frontend.semantic.SemanticFailure.Cause;
|
||||
import cd.ir.Ast.ClassDecl;
|
||||
import cd.ir.Ast.MethodDecl;
|
||||
import cd.ir.AstVisitor;
|
||||
import cd.ir.Symbol.ClassSymbol;
|
||||
import cd.ir.Symbol.MethodSymbol;
|
||||
import cd.ir.Symbol.VariableSymbol;
|
||||
import cd.util.Pair;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class InheritanceChecker extends AstVisitor<Void, Void> {
|
||||
|
||||
ClassSymbol classSym;
|
||||
|
||||
@Override
|
||||
public Void classDecl(ClassDecl ast, Void arg) {
|
||||
classSym = ast.sym;
|
||||
|
||||
// check for cycles in the inheritance hierarchy:
|
||||
Set<ClassSymbol> supers = new HashSet<ClassSymbol>();
|
||||
ClassSymbol sc = classSym.superClass;
|
||||
supers.add(classSym);
|
||||
while (sc != null) {
|
||||
if (supers.contains(sc))
|
||||
throw new SemanticFailure(
|
||||
Cause.CIRCULAR_INHERITANCE,
|
||||
"Class %s has %s as a superclass twice",
|
||||
ast.name, sc.name);
|
||||
supers.add(sc);
|
||||
sc = sc.superClass;
|
||||
}
|
||||
|
||||
this.visitChildren(ast, null);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void methodDecl(MethodDecl ast, Void arg) {
|
||||
|
||||
// check that methods overridden from a parent class agree
|
||||
// on number/type of parameters
|
||||
MethodSymbol sym = ast.sym;
|
||||
MethodSymbol superSym = classSym.superClass.getMethod(ast.name);
|
||||
if (superSym != null) {
|
||||
if (superSym.parameters.size() != sym.parameters.size())
|
||||
throw new SemanticFailure(
|
||||
Cause.INVALID_OVERRIDE,
|
||||
"Overridden method %s has %d parameters, " +
|
||||
"but original has %d",
|
||||
ast.name, sym.parameters.size(),
|
||||
superSym.parameters.size());
|
||||
for (Pair<VariableSymbol> pair : Pair.zip(sym.parameters, superSym.parameters))
|
||||
if (pair.a.type != pair.b.type)
|
||||
throw new SemanticFailure(
|
||||
Cause.INVALID_OVERRIDE,
|
||||
"Method parameter %s has type %s, but " +
|
||||
"corresponding base class parameter %s has type %s",
|
||||
pair.a.name, pair.a.type, pair.b.name, pair.b.type);
|
||||
if (superSym.returnType != sym.returnType)
|
||||
throw new SemanticFailure(
|
||||
Cause.INVALID_OVERRIDE,
|
||||
"Overridden method %s has return type %s," +
|
||||
"but its superclass has %s",
|
||||
ast.name, sym.returnType, superSym.returnType);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
52
src/cd/frontend/semantic/ReturnCheckerVisitor.java
Normal file
52
src/cd/frontend/semantic/ReturnCheckerVisitor.java
Normal file
|
@ -0,0 +1,52 @@
|
|||
package cd.frontend.semantic;
|
||||
|
||||
import cd.ir.Ast;
|
||||
import cd.ir.Ast.*;
|
||||
import cd.ir.AstVisitor;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Visitor that checks if all paths of a given sequence have a
|
||||
* return statement.
|
||||
*
|
||||
* This visitor only needs to be used if are not using the Control Flow Graph.
|
||||
*
|
||||
* @author Leo Buttiker
|
||||
*/
|
||||
public class ReturnCheckerVisitor extends AstVisitor<Boolean, Void> {
|
||||
|
||||
@Override
|
||||
protected Boolean dfltStmt(Stmt ast, Void arg) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean returnStmt(ReturnStmt ast, Void arg) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean ifElse(IfElse ast, Void arg) {
|
||||
boolean allPathHaveAReturnStmt = true;
|
||||
allPathHaveAReturnStmt &= visit(ast.then(), null);
|
||||
allPathHaveAReturnStmt &= visit(ast.otherwise(), null);
|
||||
return allPathHaveAReturnStmt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean seq(Seq ast, Void arg) {
|
||||
|
||||
boolean allPathHaveAReturnStmt = false;
|
||||
for (Ast child : ast.children()) {
|
||||
allPathHaveAReturnStmt |= this.visit(child, null);
|
||||
}
|
||||
return allPathHaveAReturnStmt;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean whileLoop(WhileLoop ast, Void arg) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
|
@ -2,234 +2,154 @@ package cd.frontend.semantic;
|
|||
|
||||
import cd.Main;
|
||||
import cd.frontend.semantic.SemanticFailure.Cause;
|
||||
import cd.ir.Ast;
|
||||
import cd.ir.Ast.ClassDecl;
|
||||
import cd.ir.Ast.MethodDecl;
|
||||
import cd.ir.Symbol;
|
||||
import cd.ir.Symbol.*;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SemanticAnalyzer {
|
||||
/** Name of the main class of the program, containing the start point for its execution */
|
||||
public static final String MAIN_CLASS_NAME = "Main";
|
||||
/** Name of the start point method inside the main class, with signature {@code void MAIN_METHOD_NAME()} */
|
||||
public static final String MAIN_METHOD_NAME = "main";
|
||||
|
||||
/** Reference to main method, for main class and list of types */
|
||||
|
||||
public final Main main;
|
||||
/** Statement analyzer, entrypoint for the method body analysis */
|
||||
public final StmtAnalyzer sa = new StmtAnalyzer(this);
|
||||
/** For analyzing expressions, used by the StmtAnalyzer when an expression is found */
|
||||
public final ExprAnalyzer ea = new ExprAnalyzer(this);
|
||||
|
||||
private List<ClassDecl> classDecls;
|
||||
|
||||
|
||||
public SemanticAnalyzer(Main main) {
|
||||
this.main = main;
|
||||
}
|
||||
|
||||
/**
|
||||
* Entrypoint for semantic analysis, will check that a list of classes is correct semantically
|
||||
* @param classDeclList List of classes that form the Javali program
|
||||
* @throws SemanticFailure If any semantic error is found
|
||||
*/
|
||||
public void check(List<ClassDecl> classDeclList) throws SemanticFailure {
|
||||
this.classDecls = classDeclList;
|
||||
checkCircularInheritance(classDecls);
|
||||
// Built-in type generation
|
||||
main.allTypeSymbols = new ArrayList<>();
|
||||
addBuiltInTypes(main.allTypeSymbols);
|
||||
// Main semantic analysis
|
||||
for (ClassDecl classDecl : classDecls)
|
||||
sa.visit(classDecl, null);
|
||||
// Find entrypoint Main and Main.main(...)
|
||||
findMainClass();
|
||||
findEntrypoint(main.mainType);
|
||||
|
||||
public void check(List<ClassDecl> classDecls)
|
||||
throws SemanticFailure {
|
||||
{
|
||||
SymTable<TypeSymbol> typeSymbols = createSymbols(classDecls);
|
||||
checkInheritance(classDecls);
|
||||
checkStartPoint(typeSymbols);
|
||||
checkMethodBodies(typeSymbols, classDecls);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the basic types that exist in any Javali program by default
|
||||
* @param list Type list to which the types must be added
|
||||
* Creates a symbol table with symbols for all built-in types,
|
||||
* as well as all classes and their fields and methods. Also
|
||||
* creates a corresponding array symbol for every type
|
||||
* (named {@code type[]}).
|
||||
* @see SymbolCreator
|
||||
*/
|
||||
private void addBuiltInTypes(List<TypeSymbol> list) {
|
||||
// Add primitive types
|
||||
list.add(PrimitiveTypeSymbol.booleanType);
|
||||
list.add(PrimitiveTypeSymbol.intType);
|
||||
list.add(PrimitiveTypeSymbol.voidType);
|
||||
// Add basic class types
|
||||
list.add(ClassSymbol.objectType);
|
||||
list.add(ClassSymbol.nullType);
|
||||
// Add arrays of primitive and class types
|
||||
list.add(new ArrayTypeSymbol(PrimitiveTypeSymbol.intType));
|
||||
list.add(new ArrayTypeSymbol(PrimitiveTypeSymbol.booleanType));
|
||||
list.add(new ArrayTypeSymbol(ClassSymbol.objectType));
|
||||
private SymTable<TypeSymbol> createSymbols(List<ClassDecl> classDecls) {
|
||||
|
||||
// Start by creating a symbol for all built-in types.
|
||||
SymTable<TypeSymbol> typeSymbols = new SymTable<TypeSymbol>(null);
|
||||
|
||||
typeSymbols.add(PrimitiveTypeSymbol.intType);
|
||||
typeSymbols.add(PrimitiveTypeSymbol.booleanType);
|
||||
typeSymbols.add(PrimitiveTypeSymbol.voidType);
|
||||
typeSymbols.add(ClassSymbol.objectType);
|
||||
|
||||
// Add symbols for all declared classes.
|
||||
for (ClassDecl ast : classDecls) {
|
||||
// Check for classes named Object
|
||||
if (ast.name.equals(ClassSymbol.objectType.name))
|
||||
throw new SemanticFailure(Cause.OBJECT_CLASS_DEFINED);
|
||||
ast.sym = new ClassSymbol(ast);
|
||||
typeSymbols.add(ast.sym);
|
||||
}
|
||||
|
||||
// Create symbols for arrays of each type.
|
||||
for (Symbol sym : new ArrayList<Symbol>(typeSymbols.localSymbols())) {
|
||||
Symbol.ArrayTypeSymbol array =
|
||||
new Symbol.ArrayTypeSymbol((TypeSymbol) sym);
|
||||
typeSymbols.add(array);
|
||||
}
|
||||
|
||||
// For each class, create symbols for each method and field
|
||||
SymbolCreator sc = new SymbolCreator(main, typeSymbols);
|
||||
for (ClassDecl ast : classDecls)
|
||||
sc.createSymbols(ast);
|
||||
|
||||
return typeSymbols;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for errors related to inheritance:
|
||||
* circular inheritance, invalid super
|
||||
* classes, methods with different types, etc.
|
||||
* Note that this must be run early because other code assumes
|
||||
* that the inheritance is correct, for type checking etc.
|
||||
* @see InheritanceChecker
|
||||
*/
|
||||
private void checkInheritance(List<ClassDecl> classDecls) {
|
||||
for (ClassDecl cd : classDecls)
|
||||
new InheritanceChecker().visit(cd, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the {@code Main} class in a Javali program
|
||||
* @throws SemanticFailure If there is no such class
|
||||
* Guarantee there is a class Main which defines a method main
|
||||
* with no arguments.
|
||||
*/
|
||||
private void findMainClass() {
|
||||
for (TypeSymbol type : main.allTypeSymbols)
|
||||
if (type.name.equals(MAIN_CLASS_NAME))
|
||||
main.mainType = (ClassSymbol) type;
|
||||
if (main.mainType == null)
|
||||
throw new SemanticFailure(Cause.INVALID_START_POINT,
|
||||
"Can't find class called \"%s\"", MAIN_CLASS_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the entrypoint for the Javali program
|
||||
* @param mainType {@code Main} class of the program, where the entrypoint should be.
|
||||
* @throws SemanticFailure If there is no such entrypoint, of it has incorrect return
|
||||
* value or parameters.
|
||||
*/
|
||||
private void findEntrypoint(ClassSymbol mainType) {
|
||||
MethodSymbol mainMethod = mainType.getMethod(MAIN_METHOD_NAME);
|
||||
if (mainMethod == null)
|
||||
throw new SemanticFailure(Cause.INVALID_START_POINT,
|
||||
"No method called \"%s\" in %s class", MAIN_METHOD_NAME, MAIN_CLASS_NAME);
|
||||
else if (mainMethod.returnType != PrimitiveTypeSymbol.voidType)
|
||||
throw new SemanticFailure(Cause.INVALID_START_POINT,
|
||||
"Method %s.%s(...) returns %s, expected void",
|
||||
MAIN_CLASS_NAME, MAIN_METHOD_NAME, mainMethod.returnType);
|
||||
else if (!mainMethod.parameters.isEmpty())
|
||||
throw new SemanticFailure(Cause.INVALID_START_POINT,
|
||||
"Method %s.%s(...) must have no arguments, found %d",
|
||||
MAIN_CLASS_NAME, MAIN_METHOD_NAME, mainMethod.parameters.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the requested type in the list of types. If not found, will try to read the remaining
|
||||
* {@link ClassDecl} nodes to find the desired type.
|
||||
* @param type Name of the type. Can be built-in, user-declared, or an array type.
|
||||
* @return The TypeSymbol object associated with the type (no new symbol is created, only looked up from
|
||||
* the general list).
|
||||
* @throws SemanticFailure If the type doesn't exist
|
||||
*/
|
||||
public TypeSymbol findType(String type) {
|
||||
// Option 1: type already exists
|
||||
for (TypeSymbol symbol : main.allTypeSymbols)
|
||||
if (type.equals(symbol.name))
|
||||
return symbol;
|
||||
// Option 2: type is declared by user but hasn't been visited yet
|
||||
String noArrayType = type;
|
||||
if (type.contains("[]")) // To find the type of an array, remove "[]" and search for the name
|
||||
noArrayType = type.substring(0, type.indexOf("[]"));
|
||||
for (ClassDecl classDecl : classDecls)
|
||||
if (classDecl.name.equals(noArrayType)) {
|
||||
sa.visit(classDecl, null);
|
||||
return findType(type); // Will use option 1
|
||||
}
|
||||
// Option 3: the type doesn't exist
|
||||
throw new SemanticFailure(Cause.NO_SUCH_TYPE, "Type %s doesn't exist", type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up the requested method in the corresponding class. If not found, will try to find it as
|
||||
* {@link MethodDecl} inside the {@link ClassDecl}. Will also look in superclasses of {@code receiverType}.
|
||||
* @param methodName Name of the method that is being looked up
|
||||
* @param receiverType Class in which the method must be found
|
||||
* @return The method symbol (not created, but looked up from the class' method list.
|
||||
*/
|
||||
public MethodSymbol findMethod(String methodName, ClassSymbol receiverType) {
|
||||
// Option 0: looking for methods in Object or null class (that has no methods or associated ast)
|
||||
if (receiverType == ClassSymbol.objectType || receiverType == ClassSymbol.nullType)
|
||||
throw new SemanticFailure(Cause.NO_SUCH_METHOD, "Type %s has no methods", receiverType);
|
||||
// Option 1: method has already been discovered
|
||||
MethodSymbol res = receiverType.methods.get(methodName);
|
||||
if (res != null) return res;
|
||||
// Option 2: method exists but hasn't been read yet (look in superclasses)
|
||||
assert receiverType.ast != null;
|
||||
for (MethodDecl methodDecl : receiverType.ast.methods()) {
|
||||
if (methodDecl.name.equals(methodName)) {
|
||||
sa.visit(methodDecl, new SemanticLocation(receiverType));
|
||||
return findMethod(methodName, receiverType); // Will use Option 1
|
||||
private void checkStartPoint(SymTable<TypeSymbol> typeSymbols) {
|
||||
Symbol mainClass = typeSymbols.get("Main");
|
||||
if (mainClass != null && mainClass instanceof ClassSymbol) {
|
||||
ClassSymbol cs = (ClassSymbol) mainClass;
|
||||
MethodSymbol mainMethod = cs.getMethod("main");
|
||||
if (mainMethod != null && mainMethod.parameters.size() == 0 &&
|
||||
mainMethod.returnType == PrimitiveTypeSymbol.voidType) {
|
||||
return; // found the main() method!
|
||||
}
|
||||
}
|
||||
// Option 3: method exists in superclass
|
||||
res = findMethod(methodName, receiverType.superClass);
|
||||
if (res != null) return res;
|
||||
// Option 4: method doesn't exist
|
||||
throw new SemanticFailure(Cause.NO_SUCH_METHOD, "Method %s of class %s doesn't exist",
|
||||
methodName, receiverType.name);
|
||||
throw new SemanticFailure(Cause.INVALID_START_POINT, "No Main class with method 'void main()' found");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Recursively checks for circular inheritance <br>
|
||||
* This check is done with the Strings extracted from the list of class declarations, as to perform it
|
||||
* before the type check (which can be difficult to deal with given undiscovered circular inheritance), in order
|
||||
* to avoid methods such as {@link ClassSymbol#getMethod(String)} or {@link ClassSymbol#getField(String)}, which
|
||||
* are recursive by nature, throw a {@link StackOverflowError}.
|
||||
* @param classDecls List of class declarations to check against
|
||||
* @throws SemanticFailure If circular inheritance is found
|
||||
* @throws SemanticFailure If any type inherited by a class doesn't exist (guaranteed to be a class type by
|
||||
* the language grammar).
|
||||
* Check the bodies of methods for errors, particularly type errors
|
||||
* but also undefined identifiers and the like.
|
||||
* @see TypeChecker
|
||||
*/
|
||||
public void checkCircularInheritance(List<ClassDecl> classDecls) {
|
||||
Map<String, String> inherits = new HashMap<>();
|
||||
for (ClassDecl classDecl : classDecls)
|
||||
inherits.put(classDecl.name, classDecl.superClass);
|
||||
for (ClassDecl classDecl : classDecls)
|
||||
checkCircularInheritance(classDecl.name, new HashSet<>(), inherits);
|
||||
}
|
||||
private void checkMethodBodies(
|
||||
SymTable<TypeSymbol> typeSymbols,
|
||||
List<ClassDecl> classDecls)
|
||||
{
|
||||
TypeChecker tc = new TypeChecker(typeSymbols);
|
||||
|
||||
for (ClassDecl classd : classDecls) {
|
||||
|
||||
SymTable<VariableSymbol> fldTable = new SymTable<VariableSymbol>(null);
|
||||
|
||||
private void checkCircularInheritance(String className, Set<String> set, Map<String, String> inherits) {
|
||||
if (className.equals("Object")) return;
|
||||
if (!set.add(className))
|
||||
throw new SemanticFailure(Cause.CIRCULAR_INHERITANCE,
|
||||
"Class %s has itself as superclass", className);
|
||||
if (inherits.get(className) != null)
|
||||
checkCircularInheritance(inherits.get(className), set, inherits);
|
||||
else
|
||||
throw new SemanticFailure(Cause.NO_SUCH_TYPE, "Class %s doesn't exist", className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class that stores the position of a statement or expression being visited, by providing a link
|
||||
* to the method and class symbols. This is useful when visiting variable nodes, method calls
|
||||
* and {@code this} references. <br>
|
||||
* When entering and existing the body of a method, {@link SemanticLocation#beginMethod(MethodSymbol)} and
|
||||
* {@link SemanticLocation#endMethod()} are used to set and unset the methodSymbol field.
|
||||
*/
|
||||
public static class SemanticLocation {
|
||||
private MethodSymbol methodSymbol;
|
||||
public final ClassSymbol classSymbol;
|
||||
|
||||
/**
|
||||
* Creates a semantic location that points to the {@code classSymbol}
|
||||
*/
|
||||
SemanticLocation(ClassSymbol classSymbol) {
|
||||
this(classSymbol, null);
|
||||
}
|
||||
|
||||
private SemanticLocation(ClassSymbol classSymbol, MethodSymbol methodSymbol) {
|
||||
assert classSymbol != null;
|
||||
this.methodSymbol = methodSymbol;
|
||||
this.classSymbol = classSymbol;
|
||||
}
|
||||
|
||||
public MethodSymbol getMethod() {
|
||||
return methodSymbol;
|
||||
}
|
||||
|
||||
public boolean isInMethod() {
|
||||
return methodSymbol != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for methodSymbol
|
||||
*/
|
||||
public void beginMethod(MethodSymbol methodSymbol) {
|
||||
this.methodSymbol = methodSymbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsetter for methodSymbol
|
||||
*/
|
||||
public void endMethod() {
|
||||
this.methodSymbol = null;
|
||||
// add all fields of this class, or any of its super classes
|
||||
for (ClassSymbol p = classd.sym; p != null; p = p.superClass)
|
||||
for (VariableSymbol s : p.fields.values())
|
||||
if (!fldTable.contains(s.name))
|
||||
fldTable.add(s);
|
||||
|
||||
// type check any method bodies and final locals
|
||||
for (MethodDecl md : classd.methods()) {
|
||||
|
||||
boolean hasReturn = new ReturnCheckerVisitor().visit(md.body(), null);
|
||||
|
||||
if (!md.returnType.equals("void") && !hasReturn) {
|
||||
|
||||
throw new SemanticFailure(Cause.MISSING_RETURN,
|
||||
"Method %s.%s is missing a return statement",
|
||||
classd.name,
|
||||
md.name);
|
||||
|
||||
}
|
||||
|
||||
SymTable<VariableSymbol> mthdTable = new SymTable<VariableSymbol>(fldTable);
|
||||
|
||||
mthdTable.add(classd.sym.thisSymbol);
|
||||
|
||||
for (VariableSymbol p : md.sym.parameters) {
|
||||
mthdTable.add(p);
|
||||
}
|
||||
|
||||
for (VariableSymbol l : md.sym.locals.values()) {
|
||||
mthdTable.add(l);
|
||||
}
|
||||
|
||||
tc.checkMethodDecl(md, mthdTable);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,12 +7,7 @@ public class SemanticFailure extends RuntimeException {
|
|||
private static final long serialVersionUID = 5375946759285719123L;
|
||||
|
||||
public enum Cause {
|
||||
/**
|
||||
* Caused by a nested method being found. Those are not
|
||||
* supported.
|
||||
*/
|
||||
NESTED_METHODS_UNSUPPORTED,
|
||||
|
||||
|
||||
/**
|
||||
* Caused by an assignment to either a final field, {@code this},
|
||||
* or some other kind of expression which cannot be assigned to.
|
||||
|
@ -20,9 +15,6 @@ public class SemanticFailure extends RuntimeException {
|
|||
* under {@link #TYPE_ERROR}. */
|
||||
NOT_ASSIGNABLE,
|
||||
|
||||
/** The value of a final field is not a compile-time constant */
|
||||
NOT_A_CONSTANT_EXPR,
|
||||
|
||||
/** Two variables, fields, methods, or classes with the same name
|
||||
* were declared in the same scope */
|
||||
DOUBLE_DECLARATION,
|
||||
|
|
|
@ -1,353 +0,0 @@
|
|||
package cd.frontend.semantic;
|
||||
|
||||
import cd.frontend.semantic.SemanticAnalyzer.SemanticLocation;
|
||||
import cd.frontend.semantic.SemanticFailure.Cause;
|
||||
import cd.ir.Ast;
|
||||
import cd.ir.Ast.*;
|
||||
import cd.ir.AstVisitor;
|
||||
import cd.ir.Symbol;
|
||||
import cd.ir.Symbol.*;
|
||||
import cd.ir.Symbol.VariableSymbol.Kind;
|
||||
|
||||
/**
|
||||
* Semantic analyzer for {@link Ast} nodes in the pattern of a visitor. <br>
|
||||
* All methods may throw {@link SemanticFailure} if an error is discovered,
|
||||
* with the appropriate {@link Cause} and a short explanation to give some context
|
||||
* to the user. <br>
|
||||
* The current implementation employs the {@link Symbol} returned to signal a
|
||||
* statement, or group of them that contain a unconditional return (or return in both
|
||||
* branches of a {@link IfElse} node. The type is checked by
|
||||
* {@link StmtAnalyzer#returnStmt(ReturnStmt, SemanticLocation)} and the existence, by
|
||||
* {@link StmtAnalyzer#methodDecl(MethodDecl, SemanticLocation)} <br>
|
||||
* Uses the {@link SemanticLocation} argument to give context to each statement and
|
||||
* expression visited, so that they may access variables, fields and methods.
|
||||
* @see ExprAnalyzer
|
||||
* @author Carlos Galindo
|
||||
* @author Levin Moser
|
||||
*/
|
||||
public class StmtAnalyzer extends AstVisitor<Symbol,SemanticLocation> {
|
||||
/**
|
||||
* Reference to the main semantic analyzer <br>
|
||||
* Needed to access {@link SemanticAnalyzer#findType(String)} and the
|
||||
* {@link ExprAnalyzer}
|
||||
*/
|
||||
private SemanticAnalyzer sa;
|
||||
|
||||
StmtAnalyzer(SemanticAnalyzer sa) {
|
||||
this.sa = sa;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks semantic errors in a {@link Assign} node
|
||||
* @param ast The node containing all the information
|
||||
* @param arg The class and method in which the statement takes place
|
||||
* @return {@code null} (No return can occur in an assigment statement)
|
||||
* @throws SemanticFailure If the right-hand side is not a subtype of the left-hand side
|
||||
* @throws SemanticFailure If the left-hand side is a method call or a {@code this} reference
|
||||
*/
|
||||
@Override
|
||||
public Symbol assign(Assign ast, SemanticLocation arg) {
|
||||
TypeSymbol right = sa.ea.visit(ast.right(), arg);
|
||||
TypeSymbol left = sa.ea.visit(ast.left(), arg);
|
||||
if (!right.isSubtypeOf(left))
|
||||
throw new SemanticFailure(Cause.TYPE_ERROR,
|
||||
"In assignment, %s is not a subtype of %s", right, left);
|
||||
// Only variables, fields and array dereferences are valid as left-hand side
|
||||
if (ast.left() instanceof Var)
|
||||
return null;
|
||||
if (ast.left() instanceof Field)
|
||||
return null;
|
||||
if (ast.left() instanceof Index)
|
||||
return null;
|
||||
String leftExprType;
|
||||
if (ast.left() instanceof MethodCallExpr)
|
||||
leftExprType = "method call";
|
||||
else if (ast.left() instanceof ThisRef)
|
||||
leftExprType = "this reference";
|
||||
else
|
||||
throw new RuntimeException("Missing valid or invalid left-hand side of assignment, check grammar");
|
||||
throw new SemanticFailure(Cause.NOT_ASSIGNABLE,
|
||||
"Left-hand side of assignment must be field, var or index, found %s", leftExprType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a possible error in a {@link BuiltInWrite} AST node.
|
||||
* @param ast The node containing all the information
|
||||
* @param arg The class and method in which the statement takes place
|
||||
* @return {@code null} (No return statement can happen inside a write)
|
||||
* @throws SemanticFailure If the type of the expression to write is not integer
|
||||
*/
|
||||
@Override
|
||||
public Symbol builtInWrite(BuiltInWrite ast, SemanticLocation arg) {
|
||||
TypeSymbol type = sa.ea.visit(ast.arg(), arg);
|
||||
if (type != PrimitiveTypeSymbol.intType)
|
||||
throw new SemanticFailure(Cause.TYPE_ERROR,
|
||||
"Argument of write function must be an int, found %s", type);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks a class declaration and all contained fields and methods for semantic errors. <br>
|
||||
* Will add itself and its corresponding array type to the types list.
|
||||
* @param ast The node describing the class declaration
|
||||
* @param arg {@code null}, as nested classes aren't permitted by the Javali grammar
|
||||
* @return {@code null} (no need to check return types)
|
||||
* @throws SemanticFailure If the type of the class already exists
|
||||
* @throws SemanticFailure If the name of the class is Object (base class)
|
||||
* @throws SemanticFailure If the superclass is not defined in the Javali program
|
||||
*/
|
||||
@Override
|
||||
public Symbol classDecl(ClassDecl ast, SemanticLocation arg) {
|
||||
if (ast.sym != null) return null; // Class already visited
|
||||
|
||||
// Generate first the symbol and link it to the ast node
|
||||
ast.sym = new ClassSymbol(ast);
|
||||
|
||||
// No class can be named Object
|
||||
if (ast.name.equals(ClassSymbol.objectType.name))
|
||||
throw new SemanticFailure(Cause.OBJECT_CLASS_DEFINED, "The %s class cannot be redefined", ast.name);
|
||||
|
||||
// All classes have unique names
|
||||
for (TypeSymbol type : sa.main.allTypeSymbols)
|
||||
if (type.name.equals(ast.sym.name))
|
||||
throw new SemanticFailure(Cause.DOUBLE_DECLARATION, "Another class exists with the name %s", type);
|
||||
|
||||
// Add basic and array type to typeList (this is the only method that generates
|
||||
// a ClassSymbol or ArrayTypeSymbol)
|
||||
sa.main.allTypeSymbols.add(ast.sym);
|
||||
sa.main.allTypeSymbols.add(new ArrayTypeSymbol(ast.sym));
|
||||
|
||||
// The superclass exists (will visit other classes to look it up)
|
||||
ast.sym.superClass = (ClassSymbol) sa.findType(ast.superClass);
|
||||
|
||||
// Visit children (fields and methods)
|
||||
SemanticLocation thisClass = new SemanticLocation(ast.sym);
|
||||
for (VarDecl varDecl : ast.fields())
|
||||
ast.sym.fields.put(varDecl.name, (VariableSymbol) visit(varDecl, thisClass));
|
||||
for (MethodDecl methodDecl : ast.methods())
|
||||
visit(methodDecl, thisClass);
|
||||
|
||||
return ast.sym;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for semantic correctness of a {@link MethodDecl} node, regarding both the method's
|
||||
* signature and its body. <br>
|
||||
* For the declaration of local variables of the method, the underlying
|
||||
* {@link Seq} node is not visited, as to keep the logic in
|
||||
* {@link StmtAnalyzer#seq(Seq, SemanticLocation)} simpler, and only related to
|
||||
* the accumulation of the return paths.
|
||||
* @param ast The node describing the method declaration
|
||||
* @param arg The class the method is being declared in
|
||||
* @return {@code null} (no need to check return types)
|
||||
* @throws SemanticFailure If multiple local variables share the same name
|
||||
* @throws SemanticFailure If there is a return type but no return statement in all paths
|
||||
* @throws SemanticFailure If the method is overloaded
|
||||
* @throws SemanticFailure If the method overrides another incorrectly
|
||||
* (mismatched parameter number, type or return type)
|
||||
* @throws SemanticFailure If the return type doesn't exist
|
||||
*/
|
||||
@Override
|
||||
public Symbol methodDecl(MethodDecl ast, SemanticLocation arg) {
|
||||
if (ast.sym != null) return null; // Already visited
|
||||
ast.sym = new MethodSymbol(ast);
|
||||
arg.beginMethod(ast.sym); // Modify the location to include the method symbol
|
||||
|
||||
// Check overloading (none is permitted)
|
||||
if (arg.classSymbol.methods.get(ast.name) != null)
|
||||
throw new SemanticFailure(Cause.DOUBLE_DECLARATION,
|
||||
"Another method exists with the name %s in class %s, overloading is not permitted",
|
||||
ast.name, arg);
|
||||
|
||||
// Check return type
|
||||
ast.sym.returnType = sa.findType(ast.returnType);
|
||||
|
||||
// Check params type and generate VariableSymbols
|
||||
assert ast.argumentNames.size() == ast.argumentTypes.size(); // This should be guaranteed by parser
|
||||
for (int i = 0; i < ast.argumentNames.size(); i++) {
|
||||
// check for repeated param name
|
||||
for (VariableSymbol var : ast.sym.parameters)
|
||||
if (var.name.equals(ast.argumentNames.get(i)))
|
||||
throw new SemanticFailure(Cause.DOUBLE_DECLARATION,
|
||||
"There are two parameters named %s in method %s", var, ast.sym);
|
||||
// add to sym param list
|
||||
ast.sym.parameters.add(new VariableSymbol(
|
||||
ast.argumentNames.get(i),
|
||||
sa.findType(ast.argumentTypes.get(i)),
|
||||
Kind.PARAM));
|
||||
}
|
||||
|
||||
// Check override (only possible if parameters and return type match)
|
||||
MethodSymbol overridden = arg.classSymbol.getMethod(ast.name);
|
||||
if (overridden != null) {
|
||||
// Return value must be the same
|
||||
if (overridden.returnType != ast.sym.returnType)
|
||||
throw new SemanticFailure(Cause.INVALID_OVERRIDE,
|
||||
"Overridden methods must share return type, expected %s, found %s",
|
||||
overridden.returnType, ast.sym.returnType);
|
||||
// Number of parameters must be the same
|
||||
if (overridden.parameters.size() != ast.sym.parameters.size())
|
||||
throw new SemanticFailure(Cause.INVALID_OVERRIDE,
|
||||
"Overridden methods must have the same number of parameters, expected %d, found %d",
|
||||
overridden.parameters.size(), ast.sym.parameters.size());
|
||||
// Types must match for all parameters
|
||||
for (int i = 0; i < ast.sym.parameters.size(); i++)
|
||||
if (overridden.parameters.get(i).type != ast.sym.parameters.get(i).type)
|
||||
throw new SemanticFailure(Cause.INVALID_OVERRIDE,
|
||||
"Overridden methods must match parameters type, expected %s, found %s for parameter %d",
|
||||
overridden.parameters.get(i).type, ast.sym.parameters.get(i).type, i);
|
||||
}
|
||||
|
||||
// Check all local variables
|
||||
for (Ast decl : ast.decls().children()) {
|
||||
VarDecl varDecl = (VarDecl) decl;
|
||||
VariableSymbol varSymbol = (VariableSymbol) visit(varDecl, arg);
|
||||
ast.sym.locals.put(varDecl.name, varSymbol);
|
||||
}
|
||||
// Check statements and return type
|
||||
Symbol returnSymbol = visit(ast.body(), arg);
|
||||
if (returnSymbol == null && ast.sym.returnType != PrimitiveTypeSymbol.voidType) {
|
||||
throw new SemanticFailure(Cause.MISSING_RETURN,
|
||||
"Method %s missing return statement", ast.sym);
|
||||
}
|
||||
|
||||
// Add method to class Symbol node
|
||||
arg.classSymbol.methods.put(ast.name, ast.sym);
|
||||
|
||||
arg.endMethod();
|
||||
return ast.sym;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for semantic errors in a {@link VarDecl} node. It can either receive a local variable
|
||||
* or a class field.
|
||||
* @param ast Node containing the type and name for the variable
|
||||
* @param arg Class [and method] in which the variable is being declared
|
||||
* @return {@link VariableSymbol} representing the variable
|
||||
* @throws SemanticFailure If the variable has already been declared in this class
|
||||
* (hiding a field from a superclass is allowed)
|
||||
* @throws SemanticFailure If the type of the variable doesn't exist
|
||||
* @throws SemanticFailure If the local variable has already been declared
|
||||
*/
|
||||
@Override
|
||||
public Symbol varDecl(VarDecl ast, SemanticLocation arg) {
|
||||
if (arg.isInMethod()) { // Checks for local variable
|
||||
if (arg.getMethod().locals.get(ast.name) != null)
|
||||
throw new SemanticFailure(Cause.DOUBLE_DECLARATION,
|
||||
"Variable %s is already declared as a variable", ast.name);
|
||||
for (VariableSymbol var : arg.getMethod().parameters)
|
||||
if (var.name.equals(ast.name))
|
||||
throw new SemanticFailure(Cause.DOUBLE_DECLARATION,
|
||||
"Variable %s is already declared as a parameter", ast.name);
|
||||
} else { // Checks for field variable
|
||||
if (arg.classSymbol.fields.get(ast.name) != null)
|
||||
throw new SemanticFailure(Cause.DOUBLE_DECLARATION,
|
||||
"Class %s cannot have more than one variable with the same name (%s)", arg, ast.name);
|
||||
}
|
||||
ast.sym = new VariableSymbol(
|
||||
ast.name,
|
||||
sa.findType(ast.type), // Type check included here
|
||||
arg.isInMethod() ? Kind.FIELD : Kind.LOCAL);
|
||||
return ast.sym;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for semantic errors in a {@link IfElse} node
|
||||
* @param ast Contains all the information regarding the condition and if/else bodies
|
||||
* @param arg The class and method in whose body the if statement exists
|
||||
* @return The return value if both branches have one, or {@code null} otherwise
|
||||
* @throws SemanticFailure If the condition is not of boolean type
|
||||
*/
|
||||
@Override
|
||||
public Symbol ifElse(IfElse ast, SemanticLocation arg) {
|
||||
TypeSymbol condType = sa.ea.visit(ast.condition(), arg);
|
||||
if (condType != PrimitiveTypeSymbol.booleanType)
|
||||
throw new SemanticFailure(Cause.TYPE_ERROR, "Condition of if must be int, found %s", condType);
|
||||
Symbol then = visit(ast.then(), arg);
|
||||
Symbol otherwise = visit(ast.otherwise(), arg);
|
||||
|
||||
if (then != null && otherwise != null)
|
||||
return then;
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for semantic errors in a {@link ReturnStmt} node
|
||||
* @param ast Contains all information about the return statement
|
||||
* @param arg The class and method in which this statement takes place
|
||||
* @return Type of the underlying expression, to be returned by the method
|
||||
* @throws SemanticFailure If the return type is not a subtype of the method signature's return type
|
||||
* @throws SemanticFailure If the method does return a non-void but the return statement has no
|
||||
* expression to return
|
||||
*/
|
||||
@Override
|
||||
public Symbol returnStmt(ReturnStmt ast, SemanticLocation arg) {
|
||||
if (ast.arg() != null) {
|
||||
TypeSymbol returnType = sa.ea.visit(ast.arg(), arg);
|
||||
if (returnType.isSubtypeOf(arg.getMethod().returnType))
|
||||
return returnType;
|
||||
throw new SemanticFailure(Cause.TYPE_ERROR,
|
||||
"Return type of %s must be a subtype of %s, found %s",
|
||||
arg.getMethod(), arg.getMethod().returnType, returnType);
|
||||
} else {
|
||||
if (arg.getMethod().returnType != PrimitiveTypeSymbol.voidType)
|
||||
throw new SemanticFailure(Cause.TYPE_ERROR,
|
||||
"Empty return, expecting type %s or subtype", arg.getMethod().returnType);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for semantic errors in a method call
|
||||
* @see ExprAnalyzer#methodCall(MethodCallExpr, SemanticLocation)
|
||||
* @return {@code null} (No return statement in this node)
|
||||
*/
|
||||
@Override
|
||||
public Symbol methodCall(MethodCall ast, SemanticLocation arg) {
|
||||
sa.ea.visit(ast.getMethodCallExpr(), arg);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that all members of the {@link Seq} node are valid. <br>
|
||||
* This method will only visit sequences of statements, as the
|
||||
* sequences of variables are checked in
|
||||
* {@link StmtAnalyzer#methodDecl(MethodDecl, SemanticLocation)}, therefore
|
||||
* the logic is simpler and separated for unrelated elements of the method
|
||||
* body (all local variable declaration must come before the statements)
|
||||
* @param ast The node with the sequence of statements
|
||||
* @param arg Class and method in which this sequence exists, to be passed
|
||||
* down to visited statements
|
||||
* @return The first type returned by one of the underlying statements
|
||||
*/
|
||||
@Override
|
||||
public Symbol seq(Seq ast, SemanticLocation arg) {
|
||||
Symbol firstReturn = null;
|
||||
for (Ast aChildren : ast.children()) {
|
||||
Symbol sym = visit(aChildren, arg);
|
||||
if (sym != null && firstReturn == null)
|
||||
firstReturn = sym;
|
||||
}
|
||||
return firstReturn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks semantic errors in a while loop
|
||||
* @param ast Node containing all the information about the {@code while} loop
|
||||
* @param arg The class and method in which the loop exists
|
||||
* @return {@code null} (condition of the loop is not check to determine if
|
||||
* it is always accessed, therefore it cannot be treated as an unconditional return)
|
||||
* @throws SemanticFailure If the condition for the loop is not a boolean type
|
||||
*/
|
||||
@Override
|
||||
public Symbol whileLoop(WhileLoop ast, SemanticLocation arg) {
|
||||
TypeSymbol condType = sa.ea.visit(ast.condition(), arg);
|
||||
if (condType != PrimitiveTypeSymbol.booleanType)
|
||||
throw new SemanticFailure(Cause.TYPE_ERROR,
|
||||
"Condition of while loop must be int, found %s", condType);
|
||||
visit(ast.body(), arg);
|
||||
return null;
|
||||
}
|
||||
}
|
76
src/cd/frontend/semantic/SymTable.java
Normal file
76
src/cd/frontend/semantic/SymTable.java
Normal file
|
@ -0,0 +1,76 @@
|
|||
package cd.frontend.semantic;
|
||||
|
||||
import cd.frontend.semantic.SemanticFailure.Cause;
|
||||
import cd.ir.Symbol;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A simple symbol table, with a pointer to the enclosing scope.
|
||||
* Used by {@link TypeChecker} to store the various scopes for
|
||||
* local, parameter, and field lookup. */
|
||||
public class SymTable<S extends Symbol> {
|
||||
|
||||
private final Map<String, S> map = new HashMap<String, S>();
|
||||
|
||||
private final SymTable<S> parent;
|
||||
|
||||
public SymTable(SymTable<S> parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public void add(S sym) {
|
||||
// check that the symbol is not already declared *at this level*
|
||||
if (containsLocally(sym.name))
|
||||
throw new SemanticFailure(Cause.DOUBLE_DECLARATION);
|
||||
map.put(sym.name, sym);
|
||||
}
|
||||
|
||||
public Collection<S> localSymbols() {
|
||||
return this.map.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* True if there is a declaration with the given name at any
|
||||
* level in the symbol table
|
||||
*/
|
||||
public boolean contains(String name) {
|
||||
return get(name) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if there is a declaration at THIS level in the symbol
|
||||
* table; may return {@code false} even if a declaration exists
|
||||
* in some enclosing scope
|
||||
*/
|
||||
public boolean containsLocally(String name) {
|
||||
return this.map.containsKey(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Base method: returns {@code null} if no symbol by that
|
||||
* name can be found, in this table or in its parents */
|
||||
public S get(String name) {
|
||||
S res = map.get(name);
|
||||
if (res != null)
|
||||
return res;
|
||||
if (parent == null)
|
||||
return null;
|
||||
return parent.get(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the symbol with the given name, or fails with a
|
||||
* NO_SUCH_TYPE error. Only really makes sense to use this
|
||||
* if S == TypeSymbol, but we don't strictly forbid it... */
|
||||
public S getType(String name) {
|
||||
S res = get(name);
|
||||
if (res == null)
|
||||
throw new SemanticFailure(
|
||||
Cause.NO_SUCH_TYPE,
|
||||
"No type '%s' was found", name);
|
||||
return res;
|
||||
}
|
||||
}
|
131
src/cd/frontend/semantic/SymbolCreator.java
Normal file
131
src/cd/frontend/semantic/SymbolCreator.java
Normal file
|
@ -0,0 +1,131 @@
|
|||
package cd.frontend.semantic;
|
||||
|
||||
import cd.Main;
|
||||
import cd.frontend.semantic.SemanticFailure.Cause;
|
||||
import cd.ir.Ast.ClassDecl;
|
||||
import cd.ir.Ast.MethodDecl;
|
||||
import cd.ir.Ast.VarDecl;
|
||||
import cd.ir.AstVisitor;
|
||||
import cd.ir.Symbol;
|
||||
import cd.ir.Symbol.*;
|
||||
import cd.ir.Symbol.VariableSymbol.Kind;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A helper class for {@link SemanticAnalyzer} which iterates through
|
||||
* a class definition and creates symbols for all of its fields and
|
||||
* methods. For each method, we also create symbols for parameters
|
||||
* and local variables.
|
||||
*/
|
||||
public class SymbolCreator extends Object {
|
||||
|
||||
final Main main;
|
||||
final SymTable<TypeSymbol> typesTable;
|
||||
|
||||
public SymbolCreator(Main main, SymTable<TypeSymbol> typesTable) {
|
||||
this.main = main;
|
||||
this.typesTable = typesTable;
|
||||
}
|
||||
|
||||
public void createSymbols(ClassDecl cd) {
|
||||
// lookup the super class. the grammar guarantees that this
|
||||
// will refer to a class, if the lookup succeeds.
|
||||
cd.sym.superClass = (ClassSymbol) typesTable.getType(cd.superClass);
|
||||
new ClassSymbolCreator(cd.sym).visitChildren(cd, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Useful method which adds a symbol to a map, checking to see
|
||||
* that there is not already an entry with the same name.
|
||||
* If a symbol with the same name exists, throws an exception.
|
||||
*/
|
||||
public <S extends Symbol> void add(Map<String,S> map, S sym) {
|
||||
if (map.containsKey(sym.name))
|
||||
throw new SemanticFailure(
|
||||
Cause.DOUBLE_DECLARATION,
|
||||
"Symbol '%s' was declared twice in the same scope",
|
||||
sym.name);
|
||||
map.put(sym.name, sym);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates symbols for all fields/constants/methods in a class.
|
||||
* Uses {@link MethodSymbolCreator} to create symbols for all
|
||||
* parameters and local variables to each method as well.
|
||||
* Checks for duplicate members.
|
||||
*/
|
||||
public class ClassSymbolCreator extends AstVisitor<Void, Void> {
|
||||
|
||||
final ClassSymbol classSym;
|
||||
|
||||
public ClassSymbolCreator(ClassSymbol classSym) {
|
||||
this.classSym = classSym;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void varDecl(VarDecl ast, Void arg) {
|
||||
ast.sym = new VariableSymbol(
|
||||
ast.name, typesTable.getType(ast.type), Kind.FIELD);
|
||||
add(classSym.fields, ast.sym);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void methodDecl(MethodDecl ast, Void arg) {
|
||||
|
||||
ast.sym = new MethodSymbol(ast);
|
||||
|
||||
add(classSym.methods, ast.sym);
|
||||
|
||||
// create return type symbol
|
||||
if (ast.returnType.equals("void")) {
|
||||
ast.sym.returnType = PrimitiveTypeSymbol.voidType;
|
||||
} else {
|
||||
ast.sym.returnType = typesTable.getType(ast.returnType);
|
||||
}
|
||||
|
||||
// create symbols for each parameter
|
||||
Set<String> pnames = new HashSet<String>();
|
||||
for (int i = 0; i < ast.argumentNames.size(); i++) {
|
||||
String argumentName = ast.argumentNames.get(i);
|
||||
String argumentType = ast.argumentTypes.get(i);
|
||||
if (pnames.contains(argumentName))
|
||||
throw new SemanticFailure(
|
||||
Cause.DOUBLE_DECLARATION,
|
||||
"Method '%s' has two parameters named '%s'",
|
||||
ast.sym, argumentName);
|
||||
pnames.add(argumentName);
|
||||
VariableSymbol vs = new VariableSymbol(
|
||||
argumentName, typesTable.getType(argumentType));
|
||||
ast.sym.parameters.add(vs);
|
||||
}
|
||||
|
||||
// create symbols for the local variables
|
||||
new MethodSymbolCreator(ast.sym).visitChildren(ast.decls(), null);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class MethodSymbolCreator extends AstVisitor<Void, Void> {
|
||||
|
||||
final MethodSymbol methodSym;
|
||||
|
||||
public MethodSymbolCreator(MethodSymbol methodSym) {
|
||||
this.methodSym = methodSym;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void varDecl(VarDecl ast, Void arg) {
|
||||
ast.sym = new VariableSymbol(
|
||||
ast.name, typesTable.getType(ast.type), Kind.LOCAL);
|
||||
add(methodSym.locals, ast.sym);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
399
src/cd/frontend/semantic/TypeChecker.java
Normal file
399
src/cd/frontend/semantic/TypeChecker.java
Normal file
|
@ -0,0 +1,399 @@
|
|||
package cd.frontend.semantic;
|
||||
|
||||
import cd.frontend.semantic.SemanticFailure.Cause;
|
||||
import cd.ir.Ast;
|
||||
import cd.ir.Ast.*;
|
||||
import cd.ir.AstVisitor;
|
||||
import cd.ir.ExprVisitor;
|
||||
import cd.ir.Symbol.*;
|
||||
import cd.util.debug.AstOneLine;
|
||||
|
||||
public class TypeChecker {
|
||||
|
||||
final private SymTable<TypeSymbol> typeSymbols;
|
||||
|
||||
public TypeChecker(SymTable<TypeSymbol> typeSymbols) {
|
||||
this.typeSymbols = typeSymbols;
|
||||
}
|
||||
|
||||
public void checkMethodDecl(MethodDecl method, SymTable<VariableSymbol> locals) {
|
||||
new MethodDeclVisitor(method, locals).visit(method.body(), null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether two expressions have the same type
|
||||
* and throws an exception otherwise.
|
||||
* @return The common type of the two expression.
|
||||
*/
|
||||
private TypeSymbol checkTypesEqual(Expr leftExpr, Expr rightExpr, SymTable<VariableSymbol> locals) {
|
||||
final TypeSymbol leftType = typeExpr(leftExpr, locals);
|
||||
final TypeSymbol rightType = typeExpr(rightExpr, locals);
|
||||
|
||||
if (leftType != rightType) {
|
||||
throw new SemanticFailure(
|
||||
Cause.TYPE_ERROR,
|
||||
"Expected operand types to be equal but found %s, %s",
|
||||
leftType,
|
||||
rightType);
|
||||
}
|
||||
|
||||
return leftType;
|
||||
}
|
||||
|
||||
private void checkTypeIsInt(TypeSymbol type) {
|
||||
if (type != PrimitiveTypeSymbol.intType) {
|
||||
throw new SemanticFailure(
|
||||
Cause.TYPE_ERROR,
|
||||
"Expected %s for operands but found type %s",
|
||||
PrimitiveTypeSymbol.intType,
|
||||
type);
|
||||
}
|
||||
}
|
||||
|
||||
private ClassSymbol asClassSymbol(TypeSymbol type) {
|
||||
if (type instanceof ClassSymbol)
|
||||
return (ClassSymbol) type;
|
||||
throw new SemanticFailure(
|
||||
Cause.TYPE_ERROR,
|
||||
"A class type was required, but %s was found", type);
|
||||
}
|
||||
|
||||
private ArrayTypeSymbol asArraySymbol(TypeSymbol type) {
|
||||
if (type instanceof ArrayTypeSymbol)
|
||||
return (ArrayTypeSymbol) type;
|
||||
throw new SemanticFailure(
|
||||
Cause.TYPE_ERROR,
|
||||
"An array type was required, but %s was found", type);
|
||||
}
|
||||
|
||||
private TypeSymbol typeExpr(Expr expr, SymTable<VariableSymbol> locals) {
|
||||
return new TypingVisitor().visit(expr, locals);
|
||||
}
|
||||
|
||||
private void checkType(Expr ast, TypeSymbol expected, SymTable<VariableSymbol> locals) {
|
||||
TypeSymbol actual = typeExpr(ast, locals);
|
||||
if (!expected.isSuperTypeOf(actual))
|
||||
throw new SemanticFailure(
|
||||
Cause.TYPE_ERROR,
|
||||
"Expected %s but type was %s",
|
||||
expected,
|
||||
actual);
|
||||
}
|
||||
|
||||
private class MethodDeclVisitor extends AstVisitor<Void, Void> {
|
||||
|
||||
private MethodDecl method;
|
||||
private SymTable<VariableSymbol> locals;
|
||||
|
||||
public MethodDeclVisitor(MethodDecl method, SymTable<VariableSymbol> locals) {
|
||||
this.method = method;
|
||||
this.locals = locals;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void dfltExpr(Expr ast, Void arg) {
|
||||
throw new RuntimeException("Should not get here");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void assign(Assign ast, Void arg) {
|
||||
TypeSymbol lhs = typeLhs(ast.left(), locals);
|
||||
TypeSymbol rhs = typeExpr(ast.right(), locals);
|
||||
if (!lhs.isSuperTypeOf(rhs))
|
||||
throw new SemanticFailure(
|
||||
Cause.TYPE_ERROR,
|
||||
"Type %s cannot be assigned to %s",
|
||||
rhs, lhs);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void builtInWrite(BuiltInWrite ast, Void arg) {
|
||||
checkType(ast.arg(), PrimitiveTypeSymbol.intType, locals);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void builtInWriteln(BuiltInWriteln ast, Void arg) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void ifElse(IfElse ast, Void arg) {
|
||||
checkType((Expr)ast.condition(), PrimitiveTypeSymbol.booleanType, locals);
|
||||
visit(ast.then(), arg);
|
||||
if (ast.otherwise() != null)
|
||||
visit(ast.otherwise(), arg);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void methodCall(MethodCall ast, Void arg) {
|
||||
typeExpr(ast.getMethodCallExpr(), locals);
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void whileLoop(WhileLoop ast, Void arg) {
|
||||
checkType((Expr)ast.condition(), PrimitiveTypeSymbol.booleanType, locals);
|
||||
return visit(ast.body(), arg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Void returnStmt(ReturnStmt ast, Void arg) {
|
||||
boolean hasArg = ast.arg() != null;
|
||||
if (hasArg && method.sym.returnType == PrimitiveTypeSymbol.voidType) {
|
||||
// void m() { return m(); }
|
||||
throw new SemanticFailure(
|
||||
Cause.TYPE_ERROR,
|
||||
"Return statement of method with void return type should "
|
||||
+ "not have arguments.");
|
||||
} else if (!hasArg) {
|
||||
// X m() { return; }
|
||||
if (method.sym.returnType != PrimitiveTypeSymbol.voidType) {
|
||||
throw new SemanticFailure(
|
||||
Cause.TYPE_ERROR,
|
||||
"Return statement has no arguments. Expected %s but type was %s",
|
||||
method.sym.returnType,
|
||||
PrimitiveTypeSymbol.voidType);
|
||||
}
|
||||
} else {
|
||||
// X m() { return y; }
|
||||
checkType(ast.arg(), method.sym.returnType, locals);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class TypingVisitor extends ExprVisitor<TypeSymbol, SymTable<VariableSymbol>> {
|
||||
|
||||
@Override
|
||||
public TypeSymbol visit(Expr ast, SymTable<VariableSymbol> arg) {
|
||||
ast.type = super.visit(ast, arg);
|
||||
return ast.type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeSymbol binaryOp(BinaryOp ast, SymTable<VariableSymbol> locals) {
|
||||
switch (ast.operator) {
|
||||
case B_TIMES:
|
||||
case B_DIV:
|
||||
case B_MOD:
|
||||
case B_PLUS:
|
||||
case B_MINUS:
|
||||
TypeSymbol type = checkTypesEqual(ast.left(), ast.right(), locals);
|
||||
checkTypeIsInt(type);
|
||||
return type;
|
||||
|
||||
case B_AND:
|
||||
case B_OR:
|
||||
checkType(ast.left(), PrimitiveTypeSymbol.booleanType, locals);
|
||||
checkType(ast.right(), PrimitiveTypeSymbol.booleanType, locals);
|
||||
return PrimitiveTypeSymbol.booleanType;
|
||||
|
||||
case B_EQUAL:
|
||||
case B_NOT_EQUAL:
|
||||
TypeSymbol left = typeExpr(ast.left(), locals);
|
||||
TypeSymbol right = typeExpr(ast.right(), locals);
|
||||
if (left.isSuperTypeOf(right) || right.isSuperTypeOf(left))
|
||||
return PrimitiveTypeSymbol.booleanType;
|
||||
throw new SemanticFailure(
|
||||
Cause.TYPE_ERROR,
|
||||
"Types %s and %s could never be equal",
|
||||
left, right);
|
||||
|
||||
case B_LESS_THAN:
|
||||
case B_LESS_OR_EQUAL:
|
||||
case B_GREATER_THAN:
|
||||
case B_GREATER_OR_EQUAL:
|
||||
checkTypeIsInt(checkTypesEqual(ast.left(), ast.right(), locals));
|
||||
return PrimitiveTypeSymbol.booleanType;
|
||||
|
||||
}
|
||||
throw new RuntimeException("Unhandled operator "+ast.operator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeSymbol booleanConst(BooleanConst ast, SymTable<VariableSymbol> arg) {
|
||||
return PrimitiveTypeSymbol.booleanType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeSymbol builtInRead(BuiltInRead ast, SymTable<VariableSymbol> arg) {
|
||||
return PrimitiveTypeSymbol.intType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeSymbol cast(Cast ast, SymTable<VariableSymbol> locals) {
|
||||
TypeSymbol argType = typeExpr(ast.arg(), locals);
|
||||
ast.type = typeSymbols.getType(ast.typeName);
|
||||
|
||||
if (argType.isSuperTypeOf(ast.type) || ast.type.isSuperTypeOf(argType))
|
||||
return ast.type;
|
||||
|
||||
throw new SemanticFailure(
|
||||
Cause.TYPE_ERROR,
|
||||
"Types %s and %s in cast are completely unrelated.",
|
||||
argType, ast.type);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TypeSymbol dfltExpr(Expr ast, SymTable<VariableSymbol> arg) {
|
||||
throw new RuntimeException("Unhandled type");
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeSymbol field(Field ast, SymTable<VariableSymbol> locals) {
|
||||
ClassSymbol argType = asClassSymbol(typeExpr(ast.arg(), locals)); // class of the receiver of the field access
|
||||
ast.sym = argType.getField(ast.fieldName);
|
||||
if (ast.sym == null)
|
||||
throw new SemanticFailure(
|
||||
Cause.NO_SUCH_FIELD,
|
||||
"Type %s has no field %s",
|
||||
argType, ast.fieldName);
|
||||
return ast.sym.type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeSymbol index(Index ast, SymTable<VariableSymbol> locals) {
|
||||
ArrayTypeSymbol argType = asArraySymbol(typeExpr(ast.left(), locals));
|
||||
checkType(ast.right(), PrimitiveTypeSymbol.intType, locals);
|
||||
return argType.elementType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeSymbol intConst(IntConst ast, SymTable<VariableSymbol> arg) {
|
||||
return PrimitiveTypeSymbol.intType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeSymbol newArray(NewArray ast, SymTable<VariableSymbol> locals) {
|
||||
checkType(ast.arg(), PrimitiveTypeSymbol.intType, locals);
|
||||
return typeSymbols.getType(ast.typeName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeSymbol newObject(NewObject ast, SymTable<VariableSymbol> arg) {
|
||||
return typeSymbols.getType(ast.typeName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeSymbol nullConst(NullConst ast, SymTable<VariableSymbol> arg) {
|
||||
return ClassSymbol.nullType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeSymbol thisRef(ThisRef ast, SymTable<VariableSymbol> locals) {
|
||||
VariableSymbol vsym = locals.get("this");
|
||||
return vsym.type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeSymbol unaryOp(UnaryOp ast, SymTable<VariableSymbol> locals) {
|
||||
|
||||
switch (ast.operator) {
|
||||
case U_PLUS:
|
||||
case U_MINUS:
|
||||
{
|
||||
TypeSymbol type = typeExpr(ast.arg(), locals);
|
||||
checkTypeIsInt(type);
|
||||
return type;
|
||||
}
|
||||
|
||||
case U_BOOL_NOT:
|
||||
checkType(ast.arg(), PrimitiveTypeSymbol.booleanType, locals);
|
||||
return PrimitiveTypeSymbol.booleanType;
|
||||
}
|
||||
throw new RuntimeException("Unknown unary op "+ast.operator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeSymbol var(Var ast, SymTable<VariableSymbol> locals) {
|
||||
if (!locals.contains(ast.name))
|
||||
throw new SemanticFailure(
|
||||
Cause.NO_SUCH_VARIABLE,
|
||||
"No variable %s was found",
|
||||
ast.name);
|
||||
ast.setSymbol(locals.get(ast.name));
|
||||
return ast.sym.type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeSymbol methodCall(MethodCallExpr ast, SymTable<VariableSymbol> locals) {
|
||||
|
||||
ClassSymbol rcvrType = asClassSymbol(typeExpr(ast.receiver(), locals));
|
||||
MethodSymbol mthd = rcvrType.getMethod(ast.methodName);
|
||||
if (mthd == null)
|
||||
throw new SemanticFailure(
|
||||
Cause.NO_SUCH_METHOD,
|
||||
"Class %s has no method %s()",
|
||||
rcvrType.name, ast.methodName);
|
||||
|
||||
ast.sym = mthd;
|
||||
|
||||
// Check that the number of arguments is correct.
|
||||
if (ast.argumentsWithoutReceiver().size() != mthd.parameters.size())
|
||||
throw new SemanticFailure(
|
||||
Cause.WRONG_NUMBER_OF_ARGUMENTS,
|
||||
"Method %s() takes %d arguments, but was invoked with %d",
|
||||
ast.methodName,
|
||||
mthd.parameters.size(),
|
||||
ast.argumentsWithoutReceiver().size());
|
||||
|
||||
// Check that the arguments are of correct type.
|
||||
int i = 0;
|
||||
for (Ast argAst : ast.argumentsWithoutReceiver())
|
||||
checkType((Expr)argAst, mthd.parameters.get(i++).type, locals);
|
||||
|
||||
return ast.sym.returnType;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks an expr as the left-hand-side of an assignment,
|
||||
* returning the type of value that may be assigned there.
|
||||
* May fail if the expression is not a valid LHS (for example,
|
||||
* a "final" field). */
|
||||
private TypeSymbol typeLhs(Expr expr, SymTable<VariableSymbol> locals) {
|
||||
return new LValueVisitor().visit(expr, locals);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see TypeChecker#typeLhs(Expr, SymTable)
|
||||
*/
|
||||
private class LValueVisitor extends ExprVisitor<TypeSymbol, SymTable<VariableSymbol>> {
|
||||
/** Fields, array-indexing, and vars can be on the LHS: */
|
||||
|
||||
@Override
|
||||
public TypeSymbol field(Field ast, SymTable<VariableSymbol> locals) {
|
||||
return typeExpr(ast, locals);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeSymbol index(Index ast, SymTable<VariableSymbol> locals) {
|
||||
return typeExpr(ast, locals);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeSymbol var(Var ast, SymTable<VariableSymbol> locals) {
|
||||
return typeExpr(ast, locals);
|
||||
}
|
||||
|
||||
/** Any other kind of expression is not a value lvalue */
|
||||
@Override
|
||||
protected TypeSymbol dfltExpr(Expr ast, SymTable<VariableSymbol> locals) {
|
||||
throw new SemanticFailure(
|
||||
Cause.NOT_ASSIGNABLE,
|
||||
"'%s' is not a valid lvalue",
|
||||
AstOneLine.toString(ast));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,5 @@
|
|||
package cd.ir;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import cd.ir.Symbol.ClassSymbol;
|
||||
import cd.ir.Symbol.MethodSymbol;
|
||||
import cd.ir.Symbol.TypeSymbol;
|
||||
|
@ -12,6 +7,11 @@ import cd.ir.Symbol.VariableSymbol;
|
|||
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 {
|
||||
|
||||
/**
|
||||
|
@ -60,7 +60,7 @@ public abstract class Ast {
|
|||
|
||||
/** Convenient debugging printout */
|
||||
@Override
|
||||
public String toString() {
|
||||
public String toString() {
|
||||
return String.format(
|
||||
"(%s)@%x",
|
||||
AstOneLine.toString(this),
|
||||
|
@ -81,7 +81,7 @@ public abstract class Ast {
|
|||
public TypeSymbol type;
|
||||
|
||||
@Override
|
||||
public <R,A> R accept(AstVisitor<R, A> visitor, A arg) {
|
||||
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);
|
||||
|
@ -150,23 +150,23 @@ public abstract class Ast {
|
|||
* 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;
|
||||
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; }
|
||||
|
||||
/**
|
||||
|
@ -177,26 +177,26 @@ public abstract class Ast {
|
|||
* 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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
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) {
|
||||
|
@ -338,21 +338,21 @@ public abstract class Ast {
|
|||
|
||||
public static class UnaryOp extends ArgExpr {
|
||||
|
||||
public static enum UOp {
|
||||
U_PLUS("+"),
|
||||
U_MINUS("-"),
|
||||
U_BOOL_NOT("!");
|
||||
public String repr;
|
||||
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) {
|
||||
};
|
||||
|
||||
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);
|
||||
|
|
31
src/cd/ir/AstRewriteVisitor.java
Normal file
31
src/cd/ir/AstRewriteVisitor.java
Normal file
|
@ -0,0 +1,31 @@
|
|||
package cd.ir;
|
||||
|
||||
import java.util.ListIterator;
|
||||
|
||||
public class AstRewriteVisitor<A> extends AstVisitor<Ast, A> {
|
||||
|
||||
@Override
|
||||
public Ast visitChildren(Ast ast, A arg) {
|
||||
ListIterator<Ast> children = ast.rwChildren.listIterator();
|
||||
while (children.hasNext()) {
|
||||
Ast child = children.next();
|
||||
if (child != null) {
|
||||
Ast replace = visit(child, arg);
|
||||
if (replace != child) {
|
||||
children.set(replace);
|
||||
nodeReplaced(child, replace);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ast;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is called when a node is replaced. Subclasses can override it to do some
|
||||
* bookkeeping.
|
||||
* <p>
|
||||
* The default implementation does nothing.
|
||||
*/
|
||||
protected void nodeReplaced(Ast oldNode, Ast newNode) {}
|
||||
|
||||
}
|
|
@ -16,6 +16,17 @@ public class AstVisitor<R,A> extends ExprVisitor<R,A> {
|
|||
return ast.accept(this, arg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides {@link ExprVisitor#visitChildren(Expr, Object)} and
|
||||
* delegates to the more general {@link #visitChildren(Ast, Object)}
|
||||
* with {@link Ast} parameter. This method is final to prevent
|
||||
* overriding only one of the two versions.
|
||||
*/
|
||||
@Override
|
||||
public final R visitChildren(Expr ast, A arg) {
|
||||
return visitChildren((Ast) ast, arg);
|
||||
}
|
||||
|
||||
/**
|
||||
* A handy function which visits the children of {@code ast},
|
||||
* providing "arg" to each of them. It returns the result of
|
||||
|
@ -45,6 +56,7 @@ public class AstVisitor<R,A> extends ExprVisitor<R,A> {
|
|||
|
||||
/**
|
||||
* The default action for expressions is to call this */
|
||||
@Override
|
||||
protected R dfltExpr(Expr ast, A arg) {
|
||||
return dflt(ast, arg);
|
||||
}
|
||||
|
|
|
@ -17,41 +17,36 @@ public abstract class Symbol {
|
|||
|
||||
public abstract boolean isReferenceType();
|
||||
|
||||
public String toString() {
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the TypeSymbol is a subtype of the parameter
|
||||
* <br> The conditions are the following
|
||||
* <ul>
|
||||
* <li>Any type is a subtype of itself</li>
|
||||
* <li>Primitive types have no subtypes</li>
|
||||
* <li>Reference types are a subtype of {@code Object}</li>
|
||||
* <li>Reference types are a supertype of {@code null}</li>
|
||||
* <li>Classes can only be subtypes of other classes</li>
|
||||
* <li>Classes that inherit another are its subtypes (recursively)</li>
|
||||
* </ul>
|
||||
*/
|
||||
public boolean isSubtypeOf(TypeSymbol sym) {
|
||||
// Any type is a subtype of itself
|
||||
if (sym == this) return true;
|
||||
// A PrimitiveType doesn't have subtypes
|
||||
if (this instanceof PrimitiveTypeSymbol || sym instanceof PrimitiveTypeSymbol)
|
||||
return false;
|
||||
// Any reference type is a subtype of Object
|
||||
if (this.isReferenceType() && sym == ClassSymbol.objectType)
|
||||
|
||||
public abstract TypeSymbol getSuperType();
|
||||
|
||||
public boolean isSuperTypeOf(TypeSymbol sub) {
|
||||
// "void" is not a subtype of any type not even itself
|
||||
if(this == PrimitiveTypeSymbol.voidType || sub == PrimitiveTypeSymbol.voidType)
|
||||
return false;
|
||||
|
||||
if (sub == this)
|
||||
return true;
|
||||
// null is a subtype of any reference type
|
||||
if (sym.isReferenceType() && this == ClassSymbol.nullType)
|
||||
|
||||
if (this instanceof PrimitiveTypeSymbol || sub instanceof PrimitiveTypeSymbol)
|
||||
return false; // no hierarchy with primitive types
|
||||
|
||||
if (sub == ClassSymbol.nullType && this.isReferenceType())
|
||||
return true;
|
||||
// Class types can only be subtypes of other Class types
|
||||
// A class type is subtype of another if has inherited it
|
||||
if (sym instanceof ClassSymbol && this instanceof ClassSymbol)
|
||||
for (ClassSymbol c = (ClassSymbol) this; c != ClassSymbol.objectType; c = c.superClass)
|
||||
if (c == sym) return true;
|
||||
|
||||
TypeSymbol curr = sub;
|
||||
while (curr != null) {
|
||||
if (curr == this)
|
||||
return true;
|
||||
curr = curr.getSuperType();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class PrimitiveTypeSymbol extends TypeSymbol {
|
||||
|
@ -65,9 +60,15 @@ public abstract class Symbol {
|
|||
super(name);
|
||||
}
|
||||
|
||||
public boolean isReferenceType() {
|
||||
@Override
|
||||
public boolean isReferenceType() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeSymbol getSuperType() {
|
||||
throw new RuntimeException("should not call this on PrimitiveTypeSymbol");
|
||||
}
|
||||
}
|
||||
|
||||
public static class ArrayTypeSymbol extends TypeSymbol {
|
||||
|
@ -78,10 +79,16 @@ public abstract class Symbol {
|
|||
this.elementType = elementType;
|
||||
}
|
||||
|
||||
public boolean isReferenceType() {
|
||||
@Override
|
||||
public boolean isReferenceType() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeSymbol getSuperType() {
|
||||
return ClassSymbol.objectType;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class ClassSymbol extends TypeSymbol {
|
||||
|
@ -89,12 +96,14 @@ public abstract class Symbol {
|
|||
public ClassSymbol superClass;
|
||||
public final VariableSymbol thisSymbol =
|
||||
new VariableSymbol("this", this);
|
||||
public final Map<String, VariableSymbol> fields = new HashMap<>();
|
||||
public final Map<String, MethodSymbol> methods = new HashMap<>();
|
||||
public final Map<String, VariableSymbol> fields =
|
||||
new HashMap<String, VariableSymbol>();
|
||||
public final Map<String, MethodSymbol> methods =
|
||||
new HashMap<String, MethodSymbol>();
|
||||
|
||||
/** Symbols for the built-in Object and null types */
|
||||
public static final ClassSymbol nullType = new ClassSymbol("<null>");
|
||||
public static final ClassSymbol objectType = new ClassSymbol("Object");
|
||||
public static final ClassSymbol objectType = new ClassSymbol("Object");
|
||||
|
||||
public ClassSymbol(Ast.ClassDecl ast) {
|
||||
super(ast.name);
|
||||
|
@ -108,10 +117,16 @@ public abstract class Symbol {
|
|||
this.ast = null;
|
||||
}
|
||||
|
||||
public boolean isReferenceType() {
|
||||
@Override
|
||||
public boolean isReferenceType() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeSymbol getSuperType() {
|
||||
return superClass;
|
||||
}
|
||||
|
||||
public VariableSymbol getField(String name) {
|
||||
VariableSymbol fsym = fields.get(name);
|
||||
if (fsym == null && superClass != null)
|
||||
|
@ -130,8 +145,10 @@ public abstract class Symbol {
|
|||
public static class MethodSymbol extends Symbol {
|
||||
|
||||
public final Ast.MethodDecl ast;
|
||||
public final Map<String, VariableSymbol> locals = new HashMap<>();
|
||||
public final List<VariableSymbol> parameters = new ArrayList<>();
|
||||
public final Map<String, VariableSymbol> locals =
|
||||
new HashMap<String, VariableSymbol>();
|
||||
public final List<VariableSymbol> parameters =
|
||||
new ArrayList<VariableSymbol>();
|
||||
|
||||
public TypeSymbol returnType;
|
||||
|
||||
|
@ -140,14 +157,15 @@ public abstract class Symbol {
|
|||
this.ast = ast;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
@Override
|
||||
public String toString() {
|
||||
return name + "(...)";
|
||||
}
|
||||
}
|
||||
|
||||
public static class VariableSymbol extends Symbol {
|
||||
|
||||
public enum Kind { PARAM, LOCAL, FIELD }
|
||||
public static enum Kind { PARAM, LOCAL, FIELD };
|
||||
public final TypeSymbol type;
|
||||
public final Kind kind;
|
||||
|
||||
|
@ -161,7 +179,8 @@ public abstract class Symbol {
|
|||
this.kind = kind;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
@ -169,4 +188,5 @@ public abstract class Symbol {
|
|||
protected Symbol(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,13 +1,6 @@
|
|||
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.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
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;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
public class AstDump {
|
||||
|
||||
public static String toString(Ast ast) {
|
||||
|
|
|
@ -1,32 +1,7 @@
|
|||
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.MethodCall;
|
||||
import cd.ir.Ast.MethodCallExpr;
|
||||
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.ReturnStmt;
|
||||
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.Ast.*;
|
||||
import cd.ir.AstVisitor;
|
||||
|
||||
public class AstOneLine {
|
||||
|
|
34
src/cd/util/debug/DumpUtils.java
Normal file
34
src/cd/util/debug/DumpUtils.java
Normal file
|
@ -0,0 +1,34 @@
|
|||
package cd.util.debug;
|
||||
|
||||
import cd.ir.Ast.ClassDecl;
|
||||
import cd.ir.Ast.MethodDecl;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.Collections.sort;
|
||||
|
||||
class DumpUtils {
|
||||
|
||||
static final Comparator<ClassDecl> classComparator = new Comparator<ClassDecl>() {
|
||||
public int compare(ClassDecl left, ClassDecl right) {
|
||||
return left.name.compareTo(right.name);
|
||||
}
|
||||
};
|
||||
|
||||
static final Comparator<MethodDecl> methodComparator = new Comparator<MethodDecl>() {
|
||||
public int compare(MethodDecl left, MethodDecl right) {
|
||||
return left.name.compareTo(right.name);
|
||||
}
|
||||
};
|
||||
|
||||
static List<String> sortedStrings(Set<?> set) {
|
||||
List<String> strings = new ArrayList<String>();
|
||||
for(Object element : set)
|
||||
strings.add(element.toString());
|
||||
sort(strings);
|
||||
return strings;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue