Homework 1

This commit is contained in:
Carlos Galindo 2020-01-15 22:18:07 +01:00
parent 49bca7f856
commit 12f678a924
Signed by: kauron
GPG key ID: 83E68706DEE119A3
43 changed files with 3703 additions and 0 deletions

11
.classpath Normal file
View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="test"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="lib" path="lib/antlr-4.4-complete.jar"/>
<classpathentry kind="lib" path="lib/junit-4.12.jar"/>
<classpathentry kind="lib" path="lib/hamcrest-core-1.3.jar"/>
<classpathentry kind="lib" path="lib/javaliParserObf.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>

245
.gitignore vendored Normal file
View file

@ -0,0 +1,245 @@
# Created by https://www.gitignore.io/api/java,linux,macos,windows,intellij,intellij+all
# Edit at https://www.gitignore.io/?templates=java,linux,macos,windows,intellij,intellij+all
### Intellij ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### Intellij Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
# *.iml
# modules.xml
# .idea/misc.xml
# *.ipr
# Sonarlint plugin
.idea/**/sonarlint/
# SonarQube Plugin
.idea/**/sonarIssues.xml
# Markdown Navigator plugin
.idea/**/markdown-navigator.xml
.idea/**/markdown-navigator/
### Intellij+all ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
# Generated files
# Sensitive or high-churn files
# Gradle
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
# Mongo Explorer plugin
# File-based project format
# IntelliJ
# mpeltonen/sbt-idea plugin
# JIRA plugin
# Cursive Clojure plugin
# Crashlytics plugin (for Android Studio and IntelliJ)
# Editor-based Rest Client
# Android studio 3.1+ serialized cache file
### Intellij+all Patch ###
# Ignores the whole .idea folder and all .iml files
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
#
#.idea/
#
## Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
#
#*.iml
#modules.xml
#.idea/misc.xml
#*.ipr
#
## Sonarlint plugin
#.idea/sonarlint
#
### Java ###
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.jar
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db
# Dump file
*.stackdump
# Folder config file
[Dd]esktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp
# Windows shortcuts
*.lnk
# End of https://www.gitignore.io/api/java,linux,macos,windows,intellij,intellij+all

23
.project Normal file
View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>Javali-HW1</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.xtext.ui.shared.xtextBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.xtext.ui.shared.xtextNature</nature>
</natures>
</projectDescription>

View file

@ -0,0 +1,2 @@
eclipse.preferences.version=1
encoding/<project>=UTF-8

View file

@ -0,0 +1,12 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.8

5
Grade.txt Normal file
View file

@ -0,0 +1,5 @@
Grade: 24/25
31/31 tests passed [20/20]
Submitted test cases [4/5]

87
build.xml Normal file
View file

@ -0,0 +1,87 @@
<project name="compiler-design" default="compile" basedir="." xmlns:jacoco="antlib:org.jacoco.ant">
<!-- A very simple ant file that primarily exists to run cup and lex;
it also includes targets to build the compiler and run tests for those
who would rather not use Eclipse -->
<!-- Set project properties. -->
<property name="src.dir" value="${basedir}/src"/>
<property name="test.dir" value="${basedir}/test"/>
<property name="javali_tests.dir" value="${basedir}/javali_tests"/>
<property name="parser.dir" value="${src.dir}/cd/frontend/parser"/>
<property name="parser.jar" value="${basedir}/lib/javaliParserObf.jar"/>
<property name="build.dir" value="${basedir}/bin"/>
<property name="junit.jar" value="${basedir}/lib/junit-4.12.jar"/>
<property name="hamcrest.jar" value="${basedir}/lib/hamcrest-core-1.3.jar"/>
<property name="antlr.jar" value="${basedir}/lib/antlr-4.4-complete.jar"/>
<property name="antlr.profile" value="false"/>
<property name="antlr.report" value="false"/>
<property name="jacocoant.jar" value="${basedir}/lib/jacocoant.jar"/>
<property name="coverage.file" location="${build.dir}/jacoco.exec"/>
<property name="min.coverage" value="0.5"/>
<property name="coverage.check" value="cd.frontend.*:cd.backend.*"/>
<!-- Cleans generated code, but NOT the parser source! -->
<target name="clean">
<delete dir="${build.dir}"/>
</target>
<!-- Builds the compiler framework for HW1. -->
<target name="compile">
<mkdir dir="${build.dir}"/>
<javac debug="true" destdir="${build.dir}" includeantruntime="false">
<src path="${src.dir}"/>
<src path="${test.dir}"/>
<classpath>
<pathelement location="${antlr.jar}"/>
<pathelement location="${junit.jar}"/>
<pathelement location="${hamcrest.jar}"/>
<pathelement location="${parser.jar}"/>
</classpath>
</javac>
</target>
<!-- Deletes all byproducts of running the tests -->
<target name="clean-test">
<delete>
<fileset dir="${javali_tests.dir}" includes="**/*.err"/>
<fileset dir="${javali_tests.dir}" includes="**/*.s"/>
<fileset dir="${javali_tests.dir}" includes="**/*.bin"/>
<fileset dir="${javali_tests.dir}" includes="**/*.dot"/>
<fileset dir="${javali_tests.dir}" includes="**/*.exe"/>
<fileset dir="${javali_tests.dir}" includes="**/*.ref"/>
</delete>
</target>
<taskdef uri="antlib:org.jacoco.ant" resource="org/jacoco/ant/antlib.xml">
<classpath path="${jacocoant.jar}"/>
</taskdef>
<!-- Runs the tests. Use the compile target first! -->
<target name="test" depends="compile">
<jacoco:coverage destfile="${coverage.file}">
<junit fork="true" forkmode="once" failureproperty="tests-failed" outputtoformatters="false">
<formatter type="brief" usefile="false"/>
<batchtest skipNonTests="true">
<fileset dir="bin" includes="**/*.class" />
</batchtest>
<assertions enablesystemassertions="true" />
<sysproperty key="cd.meta_hidden.Version" value="REFERENCE" />
<classpath>
<pathelement location="${build.dir}"/>
<pathelement location="${junit.jar}"/>
<pathelement location="${hamcrest.jar}"/>
<pathelement location="${antlr.jar}"/>
<pathelement location="${parser.jar}"/>
</classpath>
</junit>
</jacoco:coverage>
<fail if="tests-failed" />
</target>
</project>

View file

@ -0,0 +1,24 @@
/* Test expression evaluation with 8 variables */
class Main {
void main() {
int r1, r2, r3;
int i0, i1, i2, i3, i4, i5, i6, i7;
i0 = 0;
i1 = 1;
i2 = 2;
i3 = 3;
i4 = 4;
i5 = 5;
i6 = 6;
i7 = 7;
r1 = i0 + (i1 + ( i2 + ( i3 + ( i4 + (i5 + (i6 + i7))))));
r2 = ((((((i0 + i1) + i2) + i3) + i4) + i5) + i6) + i7;
r3 = ((i0 + i1) + (i2 + i3)) + ((i4 + i5) + (i6 + i7));
write(r1); writeln();
write(r2); writeln();
write(r3); writeln();
}
}

View file

@ -0,0 +1,7 @@
/* Kind of like hello world */
class Main {
void main() {
write(53110);
writeln();
}
}

View file

@ -0,0 +1,19 @@
/* Check if code selection for immediate operands works */
class Main {
void main() {
int i0;
i0 = 0;
i0 = 5 + i0;
write(i0);
writeln();
i0 = i0 + 5;
write(i0);
writeln();
i0 = i0 + 5 + 3;
write(i0);
writeln();
}
}

View file

@ -0,0 +1,18 @@
class Main {
void main() {
int A,B,a,b,c,d;
A = 1;
B = 1;
a = A * (-B);
b = -A * B;
c = -(A + B);
d = -(A * B);
write(a); writeln();
write(b); writeln();
write(c); writeln();
write(d); writeln();
}
}

View file

@ -0,0 +1,21 @@
class Main {
void main() {
int a, b, c, d, e;
a = 10;
b = -1;
c = 0;
d = 100;
e = 2;
write(a / b); writeln();
write(d / e); writeln();
write(c / d); writeln();
write(b / a + c); writeln();
write(d / e * a / b * c); writeln();
write(d / e * a / b); writeln();
write(d / e + a / b); writeln();
write(d / e * a * b * c); writeln();
write(d / e * a - b + c); writeln();
}
}

View file

@ -0,0 +1,20 @@
/* Test expression evaluation with 8 variables */
class Main {
void main() {
int r1, r2, r3;
int i0, i1, i2, i3, i4, i5, i6, i7;
i0 = 0;
i1 = 1;
i2 = 2;
i3 = 3;
i4 = 4;
i5 = 5;
i6 = 6;
i7 = 7;
write(i0 + (i1 + ( i2 + ( i3 + ( i4 + (i5 + (i6 + i7))))))); writeln();
write(((((((i0 + i1) + i2) + i3) + i4) + i5) + i6) + i7); writeln();
write(((i0 + i1) + (i2 + i3)) + ((i4 + i5) + (i6 + i7))); writeln();
}
}

View file

@ -0,0 +1,31 @@
class Main {
void main() {
int r1;
int i0, i1;
int x,y,z;
i0 = 5;
i1 = 2;
r1 = i1 * 3;
write(r1); writeln();
r1 = i0 * i1;
write(r1); writeln();
r1 = r1 * i0 * i1 * 3;
write(r1); writeln();
y = 5;
z = 10;
x = (-y * z);
write(x); writeln();
y = 0;
z = - 10;
x = (y * z);
write(x); writeln();
write(y * z); writeln();
write(0 * -10); writeln();
}
}

View file

@ -0,0 +1,25 @@
/* Test what happens for Integers that are to big for 32bits
2147483647 (=0x7FFFFFFF) is the biggest integer in 32bits (IntMAX)
*/
class Main {
void main() {
int x,y,z;
x = 2147483647;
write( x ); writeln();
/* add 1 to IntMax and output it */
write( x + 1); writeln();
/* read an int bigger than IntMAX */
x = read();
write(x); writeln();
/* performe some operation that should generate an int overflow */
x = 2147483640;
y = 60000;
write( x + y); writeln();
z = 20000000;
write( y * z); writeln();
write( (y * z) ); writeln();
}
}

View file

@ -0,0 +1 @@
2147483647

View file

@ -0,0 +1,26 @@
/* Test read/write native functions */
class Main {
void main() {
int r1, r2, readvar, a1;
r1 = 6;
/* r2 = 22 */
r2 = read();
write(r1); writeln(); // 6
/* test expressions inside write() */
write(r1 - 3); writeln(); // 3
write(r1 - 6); writeln(); // 0
write(r1 - 7); writeln(); // -1
/* should output 111 */
readvar = read(); // 1
write( (r1 + (r2 * 5)) + readvar); // 117
write(- r1); // -6
writeln();
/* should output 15 */
a1 = read(); // -15
write(- a1); // 15
}
}

View file

@ -0,0 +1,6 @@
22
1
-15

View file

@ -0,0 +1,15 @@
class Main {
void main() {
int a, b;
a = 1;
b = 2;
write(+a); writeln();
write(-a); writeln();
write(+a --b); writeln();
write(-a--b); writeln();
write(-----a-----5--b); writeln();
write(-----a*---------b);writeln();
}
}

View file

@ -0,0 +1,24 @@
/* test what happens if there are no parentheses */
class Main {
void main() {
int x;
int y;
int z;
x = 5;
y = 10;
z = 100;
write( x + y + z); writeln();
/* */
write( - x + y - z); writeln();
/* should output 205 */
write( x + 2 * z); writeln();
write( x + 2 * z / x + 1); writeln();
write(+x); writeln();
}
}

148
src/cd/Config.java Normal file
View file

@ -0,0 +1,148 @@
package cd;
import java.io.File;
public class Config {
public static enum SystemKind {
LINUX,
WINDOWS,
MACOSX
}
/**
* What kind of system we are on
*/
public static final SystemKind systemKind;
/**
* Defines the extension used for assembler files on this platform.
* Currently always {@code .s}.
*/
public static final String ASMEXT = ".s";
/** Defines the extension used for binary files on this platform. */
public static final String BINARYEXT;
/** Defines the name of the main function to be used in .s file */
public static final String MAIN;
/** Defines the name of the printf function to be used in .s file */
public static final String PRINTF;
/** Defines the name of the scanf function to be used in .s file */
public static final String SCANF;
/** Defines the name of the calloc function to be used in .s file */
public static final String CALLOC;
/** Defines the name of the exit function to be used in .s file */
public static final String EXIT;
/** The assembler directive used to define a constant string */
public static final String DOT_STRING;
/** The assembler directive used to define a constant int */
public static final String DOT_INT;
/** The assembler directive used to start the text section */
public static final String TEXT_SECTION;
/** The assembler directive used to start the section for integer data */
public static final String DATA_INT_SECTION;
/**
* The assembler directive used to start the section for string data (if
* different from {@link #DATA_INT_SECTION}
*/
public static final String DATA_STR_SECTION;
/** Comment separator used in assembly files */
public static final String COMMENT_SEP;
/**
* Defines the assembler command to use. Should be a string array where each
* entry is one argument. Use the special string "$0" to refer to the output
* file, and $1 to refer to the ".s" file.
*/
public static final String[] ASM;
/**
* The directory from which to run the assembler. In a CYGWIN installation,
* this can make a big difference!
*/
public static final File ASM_DIR;
/**
* sizeof a pointer in bytes in the target platform.
*/
public static final int SIZEOF_PTR = 4;
/**
* Name of java executable in JRE path
*/
public static final String JAVA_EXE;
static {
final String os = System.getProperty("os.name").toLowerCase();
if(os.contains("windows") || os.contains("nt")) {
systemKind = SystemKind.WINDOWS;
BINARYEXT = ".exe";
MAIN = "_main";
PRINTF = "_printf";
SCANF = "_scanf";
CALLOC = "_calloc";
EXIT = "_exit";
// These are set up for a Cygwin installation on C:,
// you can change as registersNeeded.
ASM = new String[]{"gcc", "-o", "$0", "$1"};
ASM_DIR = new File("C:\\CYGWIN\\BIN");
JAVA_EXE = "javaw.exe";
DOT_STRING = ".string";
DOT_INT = ".int";
TEXT_SECTION = ".section .text";
DATA_INT_SECTION = ".section .data";
DATA_STR_SECTION = ".section .data";
COMMENT_SEP = "#";
}
else if(os.contains("mac os x") || os.contains("darwin")) {
systemKind = SystemKind.MACOSX;
BINARYEXT = ".bin";
MAIN = "_main";
PRINTF = "_printf";
SCANF = "_scanf";
CALLOC = "_calloc";
EXIT = "_exit";
ASM = new String[]{"gcc", "-m32", "-o", "$0", "$1"};
ASM_DIR = new File(".");
JAVA_EXE = "java";
DOT_STRING = ".asciz";
DOT_INT = ".long";
TEXT_SECTION = ".text";
DATA_INT_SECTION = ".data";
DATA_STR_SECTION = ".cstring";
COMMENT_SEP = "#";
}
else {
systemKind = SystemKind.LINUX;
BINARYEXT = ".bin";
MAIN = "main";
PRINTF = "printf";
SCANF = "scanf";
CALLOC = "calloc";
EXIT = "exit";
ASM = new String[]{"gcc", "-m32", "-o", "$0", "$1"};
ASM_DIR = new File(".");
JAVA_EXE = "java";
DOT_STRING = ".string";
DOT_INT = ".int";
TEXT_SECTION = ".section .text";
DATA_INT_SECTION = ".section .data";
DATA_STR_SECTION = ".section .data";
COMMENT_SEP = "#";
}
}
}

126
src/cd/Main.java Normal file
View file

@ -0,0 +1,126 @@
package cd;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.BailErrorStrategy;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.misc.ParseCancellationException;
import cd.backend.codegen.AstCodeGenerator;
import cd.frontend.parser.JavaliAstVisitor;
import cd.frontend.parser.JavaliLexer;
import cd.frontend.parser.JavaliParser;
import cd.frontend.parser.JavaliParser.UnitContext;
import cd.frontend.parser.ParseFailure;
import cd.ir.Ast.ClassDecl;
import cd.util.debug.AstDump;
/**
* The main entrypoint for the compiler. Consists of a series
* of routines which must be invoked in order. The main()
* routine here invokes these routines, as does the unit testing
* code. This is not the <b>best</b> programming practice, as the
* series of calls to be invoked is duplicated in two places in the
* code, but it will do for now. */
public class Main {
// Set to non-null to write debug info out
public Writer debug = null;
// Set to non-null to write dump of control flow graph (Advanced Compiler Design)
public File cfgdumpbase;
public void debug(String format, Object... args) {
if (debug != null) {
String result = String.format(format, args);
try {
debug.write(result);
debug.write('\n');
debug.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
/** Parse command line, invoke compile() routine */
public static void main(String args[]) throws IOException {
Main m = new Main();
for (String arg : args) {
if (arg.equals("-d"))
m.debug = new OutputStreamWriter(System.err);
else {
FileReader fin = new FileReader(arg);
// Parse:
List<ClassDecl> astRoots = m.parse(fin);
// Run the semantic check:
m.semanticCheck(astRoots);
// Generate code:
String sFile = arg + Config.ASMEXT;
try (FileWriter fout = new FileWriter(sFile)) {
m.generateCode(astRoots, fout);
}
}
}
}
/** Parses an input stream into an AST
* @throws IOException */
public List<ClassDecl> parse(Reader reader) throws IOException {
List<ClassDecl> result = new ArrayList<ClassDecl>();
try {
JavaliLexer lexer = new JavaliLexer(new ANTLRInputStream(reader));
JavaliParser parser = new JavaliParser(new CommonTokenStream(lexer));
parser.setErrorHandler(new BailErrorStrategy());
UnitContext unit = parser.unit();
JavaliAstVisitor visitor = new JavaliAstVisitor();
visitor.visit(unit);
result = visitor.classDecls;
} catch (ParseCancellationException e) {
ParseFailure pf = new ParseFailure(0, "?");
pf.initCause(e);
throw pf;
}
debug("AST Resulting From Parsing Stage:");
dumpAst(result);
return result;
}
public void semanticCheck(List<ClassDecl> astRoots) {
{
// Not registersNeeded until later. Ignore.
}
}
public void generateCode(List<ClassDecl> astRoots, Writer out) {
{
AstCodeGenerator cg = AstCodeGenerator.createCodeGenerator(this, out);
cg.go(astRoots);
}
}
/** Dumps the AST to the debug stream */
private void dumpAst(List<ClassDecl> astRoots) throws IOException {
if (this.debug == null) return;
this.debug.write(AstDump.toString(astRoots));
}
}

14
src/cd/ToDoException.java Normal file
View file

@ -0,0 +1,14 @@
package cd;
/** TAs insert this to mark code that students need to write */
public class ToDoException extends RuntimeException {
private static final long serialVersionUID = 4054810321239901944L;
public ToDoException() {
}
public ToDoException(String message) {
super(message);
}
}

View file

@ -0,0 +1,164 @@
package cd.backend.codegen;
import java.io.IOException;
import java.io.Writer;
import cd.Config;
import cd.backend.codegen.RegisterManager.Register;
public class AssemblyEmitter {
public Writer out;
public StringBuilder indent = new StringBuilder();
public int counter = 0;
public AssemblyEmitter(Writer out) {
this.out = out;
}
/** Creates an constant operand. */
static String constant(int i) {
return "$" + i;
}
/** Creates an constant operand with the address of a label. */
static String labelAddress(String lbl) {
return "$" + lbl;
}
/** Creates an operand relative to another operand. */
static String registerOffset(int offset, Register reg) {
return String.format("%d(%s)", offset, reg);
}
/** Creates an operand addressing an item in an array */
static String arrayAddress(Register arrReg, Register idxReg) {
final int offset = Config.SIZEOF_PTR * 2; // one word each in front for
// vptr and length
final int mul = Config.SIZEOF_PTR; // assume all arrays of 4-byte elem
return String.format("%d(%s,%s,%d)", offset, arrReg, idxReg, mul);
}
void increaseIndent(String comment) {
indent.append(" ");
if (comment != null)
emitComment(comment);
}
void decreaseIndent() {
indent.setLength(indent.length() - 2);
}
void emitCommentSection(String name) {
int indentLen = indent.length();
int breakLen = 68 - indentLen - name.length();
StringBuffer sb = new StringBuffer();
sb.append(Config.COMMENT_SEP).append(" ");
for (int i = 0; i < indentLen; i++)
sb.append("_");
sb.append(name);
for (int i = 0; i < breakLen; i++)
sb.append("_");
try {
out.write(sb.toString());
out.write("\n");
} catch (IOException e) {
}
}
void emitComment(String comment) {
emitRaw(Config.COMMENT_SEP + " " + comment);
}
void emit(String op, Register src, String dest) {
emit(op, src.repr, dest);
}
void emit(String op, String src, Register dest) {
emit(op, src, dest.repr);
}
void emit(String op, Register src, Register dest) {
emit(op, src.repr, dest.repr);
}
void emit(String op, String src, String dest) {
emitRaw(String.format("%s %s, %s", op, src, dest));
}
void emit(String op, int src, Register dest) {
emit(op, constant(src), dest);
}
void emit(String op, String dest) {
emitRaw(op + " " + dest);
}
void emit(String op) {
emitRaw(op);
}
void emit(String op, Register reg) {
emit(op, reg.repr);
}
void emit(String op, int dest) {
emit(op, constant(dest));
}
void emitMove(Register src, String dest) {
emitMove(src.repr, dest);
}
void emitMove(Register src, Register dest) {
emitMove(src.repr, dest.repr);
}
void emitMove(String src, Register dest) {
emitMove(src, dest.repr);
}
void emitMove(String src, String dest) {
if (!src.equals(dest))
emit("movl", src, dest);
}
void emitLoad(int srcOffset, Register src, Register dest) {
emitMove(registerOffset(srcOffset, src), dest.repr);
}
void emitStore(Register src, int destOffset, Register dest) {
emitStore(src.repr, destOffset, dest);
}
void emitStore(String src, int destOffset, Register dest) {
emitMove(src, registerOffset(destOffset, dest));
}
void emitConstantData(String data) {
emitRaw(String.format("%s %s", Config.DOT_INT, data));
}
String uniqueLabel() {
String labelName = "label" + counter++;
return labelName;
}
void emitLabel(String label) {
try {
out.write(label + ":" + "\n");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
void emitRaw(String op) {
try {
out.write(indent.toString());
out.write(op);
out.write("\n");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View 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;
}
}

View file

@ -0,0 +1,58 @@
package cd.backend.codegen;
import cd.Main;
import cd.ir.Ast.ClassDecl;
import java.io.Writer;
import java.util.List;
public class AstCodeGenerator {
protected ExprGenerator eg;
protected StmtGenerator sg;
protected final Main main;
protected final AssemblyEmitter emit;
protected final RegisterManager rm = new RegisterManager();
AstCodeGenerator(Main main, Writer out) {
this.emit = new AssemblyEmitter(out);
this.main = main;
this.eg = new ExprGenerator(this);
this.sg = new StmtGenerator(this);
}
protected void debug(String format, Object... args) {
this.main.debug(format, args);
}
public static AstCodeGenerator createCodeGenerator(Main main, Writer out) {
return new AstCodeGenerator(main, out);
}
/**
* Main method. Causes us to emit x86 assembly corresponding to {@code ast}
* into {@code file}. Throws a {@link RuntimeException} should any I/O error
* occur.
*
* <p>
* The generated file will be divided into two sections:
* <ol>
* <li>Prologue: Generated by {@link #emitPrefix()}. This contains any
* introductory declarations and the like.
* <li>Body: Generated by {@link ExprGenerator}. This contains the main
* method definitions.
* </ol>
*/
public void go(List<? extends ClassDecl> astRoots) {
for (ClassDecl ast : astRoots) {
sg.gen(ast);
}
}
protected void initMethodData() {
}
}

View file

@ -0,0 +1,307 @@
package cd.backend.codegen;
import cd.backend.codegen.RegisterManager.Register;
import cd.ir.Ast.*;
import cd.ir.Ast.BinaryOp.BOp;
import cd.ir.ExprVisitor;
import cd.util.debug.AstOneLine;
/**
* Generates code to evaluate expressions. After emitting the code, returns a
* String which indicates the register where the result can be found.
*/
class ExprGenerator extends ExprVisitor<Register, Void> {
protected final AstCodeGenerator cg;
ExprGenerator(AstCodeGenerator astCodeGenerator) {
cg = astCodeGenerator;
}
public Register gen(Expr ast) {
return visit(ast, null);
}
@Override
public Register visit(Expr ast, Void arg) {
try {
cg.emit.increaseIndent("Emitting " + AstOneLine.toString(ast));
return super.visit(ast, null);
} finally {
cg.emit.decreaseIndent();
}
}
@Override
public Register binaryOp(BinaryOp ast, Void arg) {
String op;
if (ast.operator == BOp.B_TIMES)
op = "imul";
else if (ast.operator == BOp.B_PLUS)
op = "add";
else if (ast.operator == BOp.B_MINUS)
op = "sub";
else if (ast.operator == BOp.B_DIV)
op = "div";
else
throw new RuntimeException("Not required (optional)");
op += "l"; // long = 4B = 32b
Register left, right;
int leftNeeded = ast.left().registersNeeded();
int rightNeeded = ast.right().registersNeeded();
if (leftNeeded > rightNeeded) {
left = cg.eg.visit(ast.left(), arg);
right = cg.eg.visit(ast.right(), arg);
} else {
right = cg.eg.visit(ast.right(), arg);
left = cg.eg.visit(ast.left(), arg);
}
if (ast.operator == BOp.B_DIV) {
// The dividend (left) will be replaced with the quotient
// So free the divisor register (right) and return the quotient (left)
division(left, right);
cg.rm.releaseRegister(right);
return left;
} else {
// All operators take the form OP A, B --> B = B OP A --> B is left, A is right
// Therefore the result is stored in B (left) and A (right) can be freed
cg.emit.emit(op, right, left);
cg.rm.releaseRegister(right);
return left;
}
}
/**
* Division with divide by zero check incorporated
*/
private void division(Register dividend, Register divisor) {
division(dividend, divisor, true);
}
/**
* Special implementation for divisions, as it requires specific registers to be used
* @param dividend Maximum 32bit (even though the division is implemented as 64bit)
* @param divisor Maximum 32bit
* @param checkZero Whether to generate code that checks for division by zero or not
* @return The quotient will be in place of the dividend and the remainder in the divisor
*/
protected void division(Register dividend, Register divisor, boolean checkZero) {
if (checkZero) {
String beginDivision = cg.emit.uniqueLabel();
cg.emit.emit("cmp", 0, divisor);
cg.emit.emit("jne", beginDivision);
// Exit with error code in case of div/0
Interrupts.exit(cg, Interrupts.EXIT_DIV_0);
cg.emit.emitLabel(beginDivision);
}
// D = d * q + r (dividend, divisor, quotient, remainder)
// EDX:EAX = d * EAX + EDX (before and after the div operation)
// Take care of register use: move EAX to memory if necessary
// The stack is not needed because the division operator is not recursive
// After the division is performed, move the data back to dividend and divisor
Register d, D = Register.EAX;
if (divisor.equals(D)) {
d = cg.rm.getRegister();
cg.emit.emitMove(divisor, d);
cg.emit.emitMove(dividend, Register.EAX);
} else {
if (!dividend.equals(D) && cg.rm.isInUse(D))
cg.emit.emitMove(D, "(" + Interrupts.Mem.EAX.pos + ")");
cg.emit.emitMove(dividend, D);
if (divisor.equals(Register.EDX)) {
d = cg.rm.getRegister();
cg.emit.emitMove(divisor, d);
}
}
// Division
cg.emit.emit("cdq");
cg.emit.emit("idiv", divisor);
cg.emit.emitMove(Register.EAX, dividend);
cg.emit.emitMove(Register.EDX, divisor);
Register q = Register.EAX, r = Register.EDX;
if (divisor.equals(D)) {
cg.emit.emitMove(q, dividend);
cg.emit.emitMove(r, divisor);
} else {
if (!dividend.equals(D) && cg.rm.isInUse(D))
cg.emit.emitMove(Interrupts.Mem.EAX.pos, D);
cg.emit.emitMove(dividend, D);
if (!divisor.equals(r)) {
cg.emit.emitMove(r, divisor);
}
}
}
@Override
public Register booleanConst(BooleanConst ast, Void arg) {
{
throw new RuntimeException("Not required");
}
}
@Override
public Register builtInRead(BuiltInRead ast, Void arg) {
// Holds the first digit and then is used to accumulate the total value
Register value = cg.rm.getRegister();
// Used to mark for positive (0) or negative (1) value
Register negative = cg.rm.getRegister();
// Holds the next digit read, once the first has been read
Register nextDigit = cg.rm.getRegister();
// Labels for the read structure
String removeWhitespace = cg.emit.uniqueLabel();
String negativeFirstDigit = cg.emit.uniqueLabel();
String inputError = cg.emit.uniqueLabel();
String moreDigits = cg.emit.uniqueLabel();
String endRead = cg.emit.uniqueLabel();
String checkSign = cg.emit.uniqueLabel();
// By default, variable is positive (negative == 0)
cg.emit.emitMove(AssemblyEmitter.constant(0), negative);
// Skip whitespace ( \t\n\r) at the beginning
cg.emit.emitLabel(removeWhitespace);
Interrupts.readChar(cg, value);
cg.emit.emit("cmp", '\n', value);
cg.emit.emit("je", removeWhitespace);
cg.emit.emit("cmp", ' ', value);
cg.emit.emit("je", removeWhitespace);
cg.emit.emit("cmp", '\t', value);
cg.emit.emit("je", removeWhitespace);
cg.emit.emit("cmp", '\r', value);
cg.emit.emit("je", removeWhitespace);
// Recognize first digit, if negative read another digit to place at value
cg.emit.emit("cmp", '-', value);
cg.emit.emit("je", negativeFirstDigit);
charToIntOrJump(value, inputError);
cg.emit.emit("jmp", moreDigits);
// Negative first digit
cg.emit.emitLabel(negativeFirstDigit);
Interrupts.readChar(cg, value);
cg.emit.emitMove(AssemblyEmitter.constant(1), negative);
charToIntOrJump(value, inputError);
// All other digits:
// while ( (nextDigit = read()).isDigit())
// value = value * 10 + nextDigit
cg.emit.emitLabel(moreDigits);
Interrupts.readChar(cg, nextDigit);
charToIntOrJump(nextDigit, checkSign);
cg.emit.emit("imul", 10, value);
cg.emit.emit("add", nextDigit, value);
cg.emit.emit("jmp", moreDigits);
// No number can be read (non-digit first or following the '-')
cg.emit.emitLabel(inputError);
Interrupts.exit(cg, Interrupts.EXIT_INPUT_MISMATCH);
// Sign check and end
cg.emit.emitLabel(checkSign);
cg.emit.emit("cmp", 0, negative);
cg.emit.emit("je", endRead);
cg.emit.emit("negl", value);
cg.emit.emitLabel(endRead);
// Register bookkeeping and return read value
cg.rm.releaseRegister(negative);
cg.rm.releaseRegister(nextDigit);
return value;
}
private void charToIntOrJump(Register reg, String jumpLabel) {
cg.emit.emit("cmp", '0', reg);
cg.emit.emit("jl", jumpLabel);
cg.emit.emit("cmp", '9', reg);
cg.emit.emit("jg", jumpLabel);
cg.emit.emit("sub", '0', reg);
}
@Override
public Register cast(Cast ast, Void arg) {
{
throw new RuntimeException("Not required");
}
}
@Override
public Register index(Index ast, Void arg) {
{
throw new RuntimeException("Not required");
}
}
@Override
public Register intConst(IntConst ast, Void arg) {
Register r = cg.rm.getRegister();
cg.emit.emitMove(AssemblyEmitter.constant(ast.value), r);
return r;
}
@Override
public Register field(Field ast, Void arg) {
{
throw new RuntimeException("Not required");
}
}
@Override
public Register newArray(NewArray ast, Void arg) {
{
throw new RuntimeException("Not required");
}
}
@Override
public Register newObject(NewObject ast, Void arg) {
{
throw new RuntimeException("Not required");
}
}
@Override
public Register nullConst(NullConst ast, Void arg) {
{
throw new RuntimeException("Not required");
}
}
@Override
public Register thisRef(ThisRef ast, Void arg) {
{
throw new RuntimeException("Not required");
}
}
@Override
public Register unaryOp(UnaryOp ast, Void arg) {
Register aux = cg.eg.visit(ast.arg(), arg);
switch (ast.operator) {
case U_PLUS:
return aux;
case U_MINUS:
cg.emit.emit("negl", aux);
return aux;
case U_BOOL_NOT:
cg.emit.emit("notl", aux);
return aux;
default:
throw new RuntimeException("Not implemented");
}
}
@Override
public Register var(Var ast, Void arg) {
Register r = cg.rm.getRegister();
cg.emit.emitMove(ast.getLabel(), r);
return r;
}
}

View file

@ -0,0 +1,97 @@
package cd.backend.codegen;
import cd.backend.codegen.RegisterManager.Register;
import static cd.backend.codegen.Interrupts.Mem.BUFFER;
public class Interrupts {
protected static final int EXIT_OK = 0, EXIT_DIV_0 = 7, EXIT_INPUT_MISMATCH = 8;
protected static final int STDIN = 0, STDOUT = 1, STDERR = 2;
protected static final int I_EXIT = 1, I_READ = 3, I_WRITE = 4;
protected enum Mem {
BUFFER("io_buffer"), EAX("eax");
String pos;
Mem(String pos) {
this.pos = pos;
}
@Override
public String toString() {
return pos;
}
}
/**
* Write auxiliary data variables needed for I/O and division
* @param cg AstCodeGenerator to print instructions
*/
protected static void reserveMemory(AstCodeGenerator cg) {
for (Mem m : Mem.values()) {
cg.emit.emitLabel(m.pos);
cg.emit.emitConstantData("0");
}
}
/**
* Writes a single character passed as parameter
* @param cg AstCodeGenerator to print instructions
* @param i Character code in ASCII (or single casted character)
*/
protected static void printChar(AstCodeGenerator cg, int i) {
cg.emit.emitMove(AssemblyEmitter.constant(i), BUFFER.pos);
printChar(cg);
}
/**
* Writes a single character from the BUFFER memory pos to stdout
* @param cg AstCodeGenerator to print instructions
*/
protected static void printChar(AstCodeGenerator cg) {
Register[] needed = {Register.EAX, Register.EBX, Register.ECX, Register.EDX};
for (int i = 0; i < needed.length; i++)
if (cg.rm.isInUse(needed[i]))
cg.emit.emit("push", needed[i]);
cg.emit.emitMove(AssemblyEmitter.constant(I_WRITE), Register.EAX);
cg.emit.emitMove(AssemblyEmitter.constant(STDOUT), Register.EBX);
cg.emit.emitMove("$" + BUFFER.pos, Register.ECX);
cg.emit.emitMove(AssemblyEmitter.constant(1), Register.EDX);
cg.emit.emit("int", 0x80);
for (int i = needed.length - 1; i >= 0; i--)
if (cg.rm.isInUse(needed[i]))
cg.emit.emit("pop", needed[i]);
}
/**
* Reads a single character from stdin and places it in the
* memory pos BUFFER
*/
protected static void readChar(AstCodeGenerator cg, Register destination) {
Register[] needed = {Register.EAX, Register.EBX, Register.ECX, Register.EDX};
for (int i = 0; i < needed.length; i++)
if (cg.rm.isInUse(needed[i]))
cg.emit.emit("push", needed[i]);
cg.emit.emitMove(AssemblyEmitter.constant(I_READ), Register.EAX);
cg.emit.emitMove(AssemblyEmitter.constant(STDIN), Register.EBX);
cg.emit.emitMove("$" + BUFFER.pos, Register.ECX);
cg.emit.emitMove(AssemblyEmitter.constant(1), Register.EDX);
cg.emit.emit("int", 0x80);
for (int i = needed.length - 1; i >= 0; i--)
if (cg.rm.isInUse(needed[i]))
cg.emit.emit("pop", needed[i]);
cg.emit.emitMove(BUFFER.pos, destination);
}
/**
* Generates a exit interrupt with the code provided
* @param cg AstCodeGenerator to print instructions
* @param exitCode Number to use as exit code (can use constants in this class)
*/
protected static void exit(AstCodeGenerator cg, int exitCode) {
cg.emit.emitMove(AssemblyEmitter.constant(I_EXIT), Register.EAX);
cg.emit.emitMove(AssemblyEmitter.constant(exitCode), Register.EBX);
cg.emit.emit("int", 0x80);
}
}

View file

@ -0,0 +1,123 @@
package cd.backend.codegen;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Simple class that manages the set of currently used
* and unused registers
*/
public class RegisterManager {
private List<Register> registers = new ArrayList<Register>();
// lists of register to save by the callee and the caller
public static final Register CALLEE_SAVE[] = new Register[]{Register.ESI,
Register.EDI, Register.EBX};
public static final Register CALLER_SAVE[] = new Register[]{Register.EAX,
Register.ECX, Register.EDX};
// list of general purpose registers
public static final Register GPR[] = new Register[]{Register.EAX, Register.EBX,
Register.ECX, Register.EDX, Register.ESI, Register.EDI};
// special purpose registers
public static final Register BASE_REG = Register.EBP;
public static final Register STACK_REG = Register.ESP;
public static final int SIZEOF_REG = 4;
public enum Register {
EAX("%eax", ByteRegister.EAX), EBX("%ebx", ByteRegister.EBX), ECX(
"%ecx", ByteRegister.ECX), EDX("%edx", ByteRegister.EDX), ESI(
"%esi", null), EDI("%edi", null), EBP("%ebp", null), ESP(
"%esp", null);
public final String repr;
private final ByteRegister lowByteVersion;
private Register(String repr, ByteRegister bv) {
this.repr = repr;
this.lowByteVersion = bv;
}
@Override
public String toString() {
return repr;
}
/**
* determines if this register has an 8bit version
*/
public boolean hasLowByteVersion() {
return lowByteVersion != null;
}
/**
* Given a register like {@code %eax} returns {@code %al}, but doesn't
* work for {@code %esi} and {@code %edi}!
*/
public ByteRegister lowByteVersion() {
assert hasLowByteVersion();
return lowByteVersion;
}
}
public enum ByteRegister {
EAX("%al"), EBX("%bl"), ECX("%cl"), EDX("%dl");
public final String repr;
private ByteRegister(String repr) {
this.repr = repr;
}
@Override
public String toString() {
return repr;
}
}
/**
* Reset all general purpose registers to free
*/
public void initRegisters() {
registers.clear();
registers.addAll(Arrays.asList(GPR));
}
/**
* returns a free register and marks it as used
*/
public Register getRegister() {
int last = registers.size() - 1;
if (last < 0)
throw new AssemblyFailedException(
"Program requires too many registers");
return registers.remove(last);
}
/**
* marks a currently used register as free
*/
public void releaseRegister(Register reg) {
assert !registers.contains(reg);
registers.add(reg);
}
/**
* Returns whether the register is currently non-free
*/
public boolean isInUse(Register reg) {
return !registers.contains(reg);
}
/**
* returns the number of free registers
*/
public int availableRegisters() {
return registers.size();
}
}

View file

@ -0,0 +1,206 @@
package cd.backend.codegen;
import cd.backend.codegen.RegisterManager.Register;
import cd.ir.Ast;
import cd.ir.Ast.*;
import cd.ir.AstVisitor;
import cd.util.debug.AstOneLine;
import java.util.*;
/**
* Generates code to process statements and declarations.
*/
class StmtGenerator extends AstVisitor<Register, Void> {
protected final AstCodeGenerator cg;
StmtGenerator(AstCodeGenerator astCodeGenerator) {
cg = astCodeGenerator;
}
public void gen(Ast ast) {
visit(ast, null);
}
@Override
public Register visit(Ast ast, Void arg) {
try {
cg.emit.increaseIndent("Emitting " + AstOneLine.toString(ast));
return super.visit(ast, arg);
} finally {
cg.emit.decreaseIndent();
}
}
/**
* Declares a variable in the data segment. This method was implemented because it was necessary
* to create the data segment
* @param ast Variable declaration node
* @param arg Extra arguments
* @return null
*/
@Override
public Register varDecl(Ast.VarDecl ast, Void arg) {
cg.emit.emitLabel(ast.getLabel());
cg.emit.emitConstantData("0");
return null;
}
@Override
public Register methodCall(MethodCall ast, Void dummy) {
{
throw new RuntimeException("Not required");
}
}
@Override
public Register methodDecl(MethodDecl ast, Void arg) {
// Prologue: in future version a more complex prologue that
// takes care of parameters and return values is necessary
// Method code
cg.rm.initRegisters();
cg.emit.emitRaw(".section .data");
Interrupts.reserveMemory(cg);
// Variables
cg.sg.visit(ast.decls(), arg);
cg.emit.emitRaw(".section .text");
cg.emit.emitRaw(".globl " + ast.name);
cg.emit.emitLabel(ast.name);
Register result = cg.sg.visit(ast.body(), arg);
// Epilogue: not needed in the simple HW1
return null;
}
@Override
public Register ifElse(IfElse ast, Void arg) {
{
throw new RuntimeException("Not required");
}
}
@Override
public Register whileLoop(WhileLoop ast, Void arg) {
{
throw new RuntimeException("Not required");
}
}
@Override
public Register assign(Assign ast, Void arg) {
// Because we only handle very simple programs in HW1,
// you can just emit the prologue here!
Register value = cg.eg.visit(ast.right(), arg);
Ast.Var variable = (Var) ast.left();
cg.emit.emitMove(value, variable.getLabel());
cg.rm.releaseRegister(value);
return null;
}
@Override
public Register builtInWrite(BuiltInWrite ast, Void arg) {
String printNonZero = cg.emit.uniqueLabel();
String end = cg.emit.uniqueLabel();
String printPositive = cg.emit.uniqueLabel();
String stackLoop = cg.emit.uniqueLabel();
String printLoop = cg.emit.uniqueLabel();
Register value = cg.eg.visit(ast.arg(), arg);
// Begin: decide if print 0 or print number
cg.emit.emit("cmp", 0, value);
cg.emit.emit("jne", printNonZero); // value != 0
// Print 0
Interrupts.printChar(cg, '0'); // print 0
cg.emit.emit("jmp", end);
// Not 0: positive or negative?
cg.emit.emitLabel(printNonZero);
cg.emit.emit("jg", printPositive); // value > 0
// Number is negative, print '-' then number (with sign changed)
Interrupts.printChar(cg, '-');
cg.emit.emit("negl", value);
// Print number: extract and print all digits of number
cg.emit.emitLabel(printPositive);
// In order to avoid using EAX and EDX, we are going to guarantee
// that there will be at least 2 other registers available
List<Register> auxRegs = Arrays.asList(Register.EDI, Register.ESI);
List<Register> divRegs = Arrays.asList(Register.EAX, Register.EDX);
Map<Register, Boolean> isPushed = new HashMap<>();
boolean usingAuxRegs = cg.rm.availableRegisters() < 4;
if (usingAuxRegs)
for (int i = 0; i < auxRegs.size(); i++)
isPushed.put(auxRegs.get(i), cg.rm.isInUse(auxRegs.get(i)));
// Free EAX and EDX for div operand use
isPushed.put(Register.EAX, cg.rm.isInUse(Register.EAX) && !value.equals(Register.EAX));
isPushed.put(Register.EDX, cg.rm.isInUse(Register.EDX));
// Push all elements
for (Register r : isPushed.keySet())
if (isPushed.get(r))
cg.emit.emit("push", r);
// Force counter and divisor to not be EDX/EAX, as they need to coexist
Register counter = cg.rm.getRegister();
Register divisor = cg.rm.getRegister();
while (divRegs.contains(counter)) {
Register aux = cg.rm.getRegister();
cg.rm.releaseRegister(counter);
counter = aux;
}
while (divRegs.contains(divisor)) {
Register aux = cg.rm.getRegister();
cg.rm.releaseRegister(divisor);
divisor = aux;
}
// Divide by 10 to extract the remainders and push them to the stack
// Registers used: EAX is the remaining digits (initially the value)
// in div it is used for the lower half of the dividend and for quotient
// divisor
// EDX is used as the high half of the dividend and as remainder
cg.emit.emitMove(value, Register.EAX);
cg.rm.releaseRegister(value);
cg.emit.emitMove(AssemblyEmitter.constant(0), counter);
cg.emit.emitLabel(stackLoop);
cg.emit.emitMove(AssemblyEmitter.constant(0), Register.EDX);
cg.emit.emitMove(AssemblyEmitter.constant(10), divisor);
cg.emit.emit("div", divisor);
cg.emit.emit("add", '0', Register.EDX);
cg.emit.emit("push", Register.EDX);
cg.emit.emit("inc", counter);
cg.emit.emit("cmp", 0, Register.EAX);
cg.emit.emit("je", printLoop);
cg.emit.emit("jmp", stackLoop);
// Release divisor register
cg.rm.releaseRegister(divisor);
// Print digits from the stack
cg.emit.emitLabel(printLoop);
cg.emit.emit("cmp", 0, counter);
cg.emit.emit("jz", end);
cg.emit.emit("dec", counter);
cg.emit.emit("pop", Interrupts.Mem.BUFFER.pos);
Interrupts.printChar(cg);
cg.emit.emit("jmp", printLoop);
cg.emit.emitLabel(end);
cg.rm.releaseRegister(counter);
// Restore original registers
for (Register r : isPushed.keySet())
if (isPushed.get(r))
cg.emit.emit("pop", r);
return null;
}
@Override
public Register builtInWriteln(BuiltInWriteln ast, Void arg) {
Interrupts.printChar(cg, '\n');
return null;
}
}

6
src/cd/frontend/parser/.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
/Javali.tokens
/JavaliBaseVisitor.java
/JavaliLexer.java
/JavaliLexer.tokens
/JavaliParser.java
/JavaliVisitor.java

View file

@ -0,0 +1,9 @@
package cd.frontend.parser;
public class ParseFailure extends RuntimeException {
private static final long serialVersionUID = -949992757679367939L;
public ParseFailure(int line, String string) {
super(String.format("Parse failure on line %d: %s",
line, string));
}
}

752
src/cd/ir/Ast.java Normal file
View file

@ -0,0 +1,752 @@
package cd.ir;
import cd.util.Pair;
import cd.util.debug.AstOneLine;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public abstract class Ast {
/**
* The list of children AST nodes. Typically, this list is of a fixed size: its contents
* can also be accessed using the various accessors defined on the Ast subtypes.
*
* <p><b>Note:</b> this list may contain null pointers!
*/
public final List<Ast> rwChildren;
final String VAR_LABEL_FORMAT = "var_%s";
protected Ast(int fixedCount) {
if (fixedCount == -1)
this.rwChildren = new ArrayList<Ast>();
else
this.rwChildren = Arrays.asList(new Ast[fixedCount]);
}
/**
* Returns a copy of the list of children for this node. The result will
* never contain null pointers.
*/
public List<Ast> children() {
ArrayList<Ast> result = new ArrayList<Ast>();
for (Ast n : rwChildren) {
if (n != null) result.add(n);
}
return result;
}
/**
* Returns a new list containing all children AST nodes
* that are of the given type.
*/
public <A> List<A> childrenOfType(Class<A> C) {
List<A> res = new ArrayList<A>();
for (Ast c : children()) {
if (C.isInstance(c))
res.add(C.cast(c));
}
return res;
}
/** Accept method for the pattern Visitor. */
public abstract <R,A> R accept(AstVisitor<R, A> visitor, A arg);
/** Convenient debugging printout */
@Override
public String toString() {
return String.format(
"(%s)@%x",
AstOneLine.toString(this),
System.identityHashCode(this));
}
// _________________________________________________________________
// Expressions
/** Base class for all expressions */
public static abstract class Expr extends Ast {
int needed = -1;
protected Expr(int fixedCount) {
super(fixedCount);
}
@Override
public <R, A> R accept(AstVisitor<R, A> visitor, A arg) {
return this.accept((ExprVisitor<R, A>) visitor, arg);
}
public abstract <R, A> R accept(ExprVisitor<R, A> visitor, A arg);
/**
* Registers needed to perform the operation represented by this Expr node
* If not already run, the cost is visiting all subnodes, else the cost is constant
* @return Minimum number of registers needed
*/
public abstract int registersNeeded();
/** Copies any non-AST fields. */
protected <E extends Expr> E postCopy(E item) {
return item;
}
}
/** Base class used for exprs with left/right operands.
* We use this for all expressions that take strictly two operands,
* such as binary operators or array indexing. */
public static abstract class LeftRightExpr extends Expr {
public LeftRightExpr(Expr left, Expr right) {
super(2);
assert left != null;
assert right != null;
setLeft(left);
setRight(right);
}
@Override
public int registersNeeded() {
if (needed != -1)
return needed;
int leftNeed = left().registersNeeded();
int rightNeed = right().registersNeeded();
if (leftNeed > rightNeed)
return needed = leftNeed;
else if (leftNeed < rightNeed)
return needed = rightNeed;
else
return needed = ++rightNeed;
}
public Expr left() { return (Expr) this.rwChildren.get(0); }
public void setLeft(Expr node) { this.rwChildren.set(0, node); }
public Expr right() { return (Expr) this.rwChildren.get(1); }
public void setRight(Expr node) { this.rwChildren.set(1, node); }
}
/** Base class used for expressions with a single argument */
public static abstract class ArgExpr extends Expr {
public ArgExpr(Expr arg) {
super(1);
assert arg != null;
setArg(arg);
}
@Override
public int registersNeeded() {
return arg().registersNeeded();
}
public Expr arg() { return (Expr) this.rwChildren.get(0); }
public void setArg(Expr node) { this.rwChildren.set(0, node); }
}
/** Base class used for things with no arguments */
protected static abstract class LeafExpr extends Expr {
public LeafExpr() {
super(0);
}
@Override
public int registersNeeded() {
return 1;
}
}
/** Represents {@code this}, the current object */
public static class ThisRef extends LeafExpr {
@Override
public <R, A> R accept(ExprVisitor<R, A> visitor, A arg) {
return visitor.thisRef(this, arg);
}
}
/** A binary operation combining a left and right operand,
* such as "1+2" or "3*4" */
public static class BinaryOp extends LeftRightExpr {
public static enum BOp {
B_TIMES("*"),
B_DIV("/"),
B_MOD("%"),
B_PLUS("+"),
B_MINUS("-"),
B_AND("&&"),
B_OR("||"),
B_EQUAL("=="),
B_NOT_EQUAL("!="),
B_LESS_THAN("<"),
B_LESS_OR_EQUAL("<="),
B_GREATER_THAN(">"),
B_GREATER_OR_EQUAL(">=");
public String repr;
private BOp(String repr) { this.repr = repr; }
/**
* Note that this method ignores short-circuit evaluation of boolean
* AND/OR.
*
* @return <code>true</code> iff <code>A op B == B op A</code> for this
* operator.
*/
public boolean isCommutative() {
switch(this) {
case B_PLUS:
case B_TIMES:
case B_AND:
case B_OR:
case B_EQUAL:
case B_NOT_EQUAL:
return true;
default:
return false;
}
}
};
public BOp operator;
public BinaryOp(Expr left, BOp operator, Expr right) {
super(left, right);
this.operator = operator;
}
@Override
public <R, A> R accept(ExprVisitor<R, A> visitor, A arg) {
return visitor.binaryOp(this, arg);
}
}
/** A Cast from one type to another: {@code (typeName)arg} */
public static class Cast extends ArgExpr {
public String typeName;
public Cast(Expr arg, String typeName) {
super(arg);
this.typeName = typeName;
}
@Override
public <R, A> R accept(ExprVisitor<R, A> visitor, A arg) {
return visitor.cast(this, arg);
}
}
public static class IntConst extends LeafExpr {
public final int value;
public IntConst(int value) {
this.value = value;
}
@Override
public <R, A> R accept(ExprVisitor<R, A> visitor, A arg) {
return visitor.intConst(this, arg);
}
}
public static class BooleanConst extends LeafExpr {
public final boolean value;
public BooleanConst(boolean value) {
this.value = value;
}
@Override
public <R, A> R accept(ExprVisitor<R, A> visitor, A arg) {
return visitor.booleanConst(this, arg);
}
}
public static class NullConst extends LeafExpr {
@Override
public <R, A> R accept(ExprVisitor<R, A> visitor, A arg) {
return visitor.nullConst(this, arg);
}
}
public static class Field extends ArgExpr {
public final String fieldName;
public Field(Expr arg, String fieldName) {
super(arg);
assert arg != null && fieldName != null;
this.fieldName = fieldName;
}
@Override
public <R, A> R accept(ExprVisitor<R, A> visitor, A arg) {
return visitor.field(this, arg);
}
}
public static class Index extends LeftRightExpr {
public Index(Expr array, Expr index) {
super(array, index);
}
@Override
public <R, A> R accept(ExprVisitor<R, A> visitor, A arg) {
return visitor.index(this, arg);
}
}
public static class NewObject extends LeafExpr {
/** Name of the type to be created */
public String typeName;
public NewObject(String typeName) {
this.typeName = typeName;
}
@Override
public <R, A> R accept(ExprVisitor<R, A> visitor, A arg) {
return visitor.newObject(this, arg);
}
}
public static class NewArray extends ArgExpr {
/** Name of the type to be created: must be an array type */
public String typeName;
public NewArray(String typeName, Expr capacity) {
super(capacity);
this.typeName = typeName;
}
@Override
public <R, A> R accept(ExprVisitor<R, A> visitor, A arg) {
return visitor.newArray(this, arg);
}
}
public static class UnaryOp extends ArgExpr {
public static enum UOp {
U_PLUS("+"),
U_MINUS("-"),
U_BOOL_NOT("!");
public String repr;
private UOp(String repr) { this.repr = repr; }
};
public final UOp operator;
public UnaryOp(UOp operator, Expr arg) {
super(arg);
this.operator = operator;
}
@Override
public <R, A> R accept(ExprVisitor<R, A> visitor, A arg) {
return visitor.unaryOp(this, arg);
}
}
public static class Var extends LeafExpr {
public String name;
/**
* Use this constructor to build an instance of this AST
* in the parser.
*/
public Var(String name) {
this.name = name;
}
public String getLabel() {
return String.format(VAR_LABEL_FORMAT, name);
}
@Override
public <R, A> R accept(ExprVisitor<R, A> visitor, A arg) {
return visitor.var(this, arg);
}
}
public static class BuiltInRead extends LeafExpr {
@Override
public <R, A> R accept(ExprVisitor<R, A> visitor, A arg) {
return visitor.builtInRead(this, arg);
}
}
public static class MethodCallExpr extends Expr {
public String methodName;
public MethodCallExpr(Expr rcvr, String methodName, List<Expr> arguments) {
super(-1);
assert rcvr != null && methodName != null && arguments != null;
this.methodName = methodName;
this.rwChildren.add(rcvr);
this.rwChildren.addAll(arguments);
}
/** Returns the receiver of the method call.
* i.e., for a method call {@code a.b(c,d)} returns {@code a}. */
public Expr receiver() { return (Expr) this.rwChildren.get(0); }
/** Changes the receiver of the method call.
* i.e., for a method call {@code a.b(c,d)} changes {@code a}. */
public void setReceiver(Expr rcvr) { this.rwChildren.set(0, rcvr); }
/** Returns all arguments to the method, <b>including the receiver.</b>
* i.e, for a method call {@code a.b(c,d)} returns {@code [a, c, d]} */
public List<Expr> allArguments()
{
ArrayList<Expr> result = new ArrayList<Expr>();
for (Ast chi : this.rwChildren)
result.add((Expr) chi);
return Collections.unmodifiableList(result);
}
/** Returns all arguments to the method, without the receiver.
* i.e, for a method call {@code a.b(c,d)} returns {@code [c, d]} */
public List<Expr> argumentsWithoutReceiver()
{
ArrayList<Expr> result = new ArrayList<Expr>();
for (int i = 1; i < this.rwChildren.size(); i++)
result.add((Expr) this.rwChildren.get(i));
return Collections.unmodifiableList(result);
}
@Override
public <R, A> R accept(ExprVisitor<R, A> visitor, A arg) {
return visitor.methodCall(this, arg);
}
@Override
public int registersNeeded() {
throw new RuntimeException("Not implemented");
}
}
// _________________________________________________________________
// Statements
/** Interface for all statements */
public static abstract class Stmt extends Ast {
protected Stmt(int fixedCount) {
super(fixedCount);
}
}
/** Represents an empty statement: has no effect. */
public static class Nop extends Stmt {
public Nop() {
super(0);
}
@Override
public <R, A> R accept(AstVisitor<R, A> visitor, A arg) {
return visitor.nop(this, arg);
}
}
/** An assignment from {@code right()} to the location
* represented by {@code left()}.
*/
public static class Assign extends Stmt {
public Assign(Expr left, Expr right) {
super(2);
assert left != null && right != null;
setLeft(left);
setRight(right);
}
public Expr left() { return (Expr) this.rwChildren.get(0); }
public void setLeft(Expr node) { this.rwChildren.set(0, node); }
public Expr right() { return (Expr) this.rwChildren.get(1); }
public void setRight(Expr node) { this.rwChildren.set(1, node); }
@Override
public <R, A> R accept(AstVisitor<R, A> visitor, A arg) {
return visitor.assign(this, arg);
}
}
public static class IfElse extends Stmt {
public IfElse(Expr cond, Ast then, Ast otherwise) {
super(3);
assert cond != null && then != null && otherwise != null;
setCondition(cond);
setThen(then);
setOtherwise(otherwise);
}
public Expr condition() { return (Expr) this.rwChildren.get(0); }
public void setCondition(Expr node) { this.rwChildren.set(0, node); }
public Ast then() { return this.rwChildren.get(1); }
public void setThen(Ast node) { this.rwChildren.set(1, node); }
public Ast otherwise() { return this.rwChildren.get(2); }
public void setOtherwise(Ast node) { this.rwChildren.set(2, node); }
@Override
public <R, A> R accept(AstVisitor<R, A> visitor, A arg) {
return visitor.ifElse(this, arg);
}
}
public static class ReturnStmt extends Stmt {
public ReturnStmt(Expr arg) {
super(1);
setArg(arg);
}
public Expr arg() { return (Expr) this.rwChildren.get(0); }
public void setArg(Expr node) { this.rwChildren.set(0, node); }
@Override
public <R, A> R accept(AstVisitor<R, A> visitor, A arg) {
return visitor.returnStmt(this, arg);
}
}
public static class BuiltInWrite extends Stmt {
public BuiltInWrite(Expr arg) {
super(1);
assert arg != null;
setArg(arg);
}
public Expr arg() { return (Expr) this.rwChildren.get(0); }
public void setArg(Expr node) { this.rwChildren.set(0, node); }
@Override
public <R, A> R accept(AstVisitor<R, A> visitor, A arg) {
return visitor.builtInWrite(this, arg);
}
}
public static class BuiltInWriteln extends Stmt {
public BuiltInWriteln() {
super(0);
}
@Override
public <R, A> R accept(AstVisitor<R, A> visitor, A arg) {
return visitor.builtInWriteln(this, arg);
}
}
public static class MethodCall extends Stmt {
public MethodCall(MethodCallExpr mce) {
super(1);
this.rwChildren.set(0, mce);
}
public MethodCallExpr getMethodCallExpr() {
return (MethodCallExpr)this.rwChildren.get(0);
}
@Override
public <R, A> R accept(AstVisitor<R, A> visitor, A arg) {
return visitor.methodCall(this, arg);
}
}
public static class WhileLoop extends Stmt {
public WhileLoop(Expr condition, Ast body) {
super(2);
assert condition != null && body != null;
setCondition(condition);
setBody(body);
}
public Expr condition() { return (Expr) this.rwChildren.get(0); }
public void setCondition(Expr cond) { this.rwChildren.set(0, cond); }
public Ast body() { return this.rwChildren.get(1); }
public void setBody(Ast body) { this.rwChildren.set(1, body); }
@Override
public <R, A> R accept(AstVisitor<R, A> visitor, A arg) {
return visitor.whileLoop(this, arg);
}
}
// _________________________________________________________________
// Declarations
/** Interface for all declarations */
public static abstract class Decl extends Ast {
protected Decl(int fixedCount) {
super(fixedCount);
}
}
public static class VarDecl extends Decl {
public String type;
public String name;
public VarDecl(String type, String name) {
this(0, type, name);
}
protected VarDecl(int num, String type, String name) {
super(num);
this.type = type;
this.name = name;
}
public String getLabel() {
return String.format(VAR_LABEL_FORMAT, name);
}
@Override
public <R, A> R accept(AstVisitor<R, A> visitor, A arg) {
return visitor.varDecl(this, arg);
}
}
/** Used in {@link MethodDecl} to group together declarations
* and method bodies. */
public static class Seq extends Decl {
public Seq(List<Ast> nodes) {
super(-1);
if (nodes != null) this.rwChildren.addAll(nodes);
}
/** Grant access to the raw list of children for seq nodes */
public List<Ast> rwChildren() {
return this.rwChildren;
}
@Override
public <R, A> R accept(AstVisitor<R, A> visitor, A arg) {
return visitor.seq(this, arg);
}
}
public static class MethodDecl extends Decl {
public String returnType;
public String name;
public List<String> argumentTypes;
public List<String> argumentNames;
public MethodDecl(
String returnType,
String name,
List<Pair<String>> formalParams,
Seq decls,
Seq body) {
this(returnType, name, Pair.unzipA(formalParams), Pair.unzipB(formalParams), decls, body);
}
public MethodDecl(
String returnType,
String name,
List<String> argumentTypes,
List<String> argumentNames,
Seq decls,
Seq body) {
super(2);
this.returnType = returnType;
this.name = name;
this.argumentTypes = argumentTypes;
this.argumentNames = argumentNames;
setDecls(decls);
setBody(body);
}
public Seq decls() { return (Seq) this.rwChildren.get(0); }
public void setDecls(Seq decls) { this.rwChildren.set(0, decls); }
public Seq body() { return (Seq) this.rwChildren.get(1); }
public void setBody(Seq body) { this.rwChildren.set(1, body); }
@Override
public <R, A> R accept(AstVisitor<R, A> visitor, A arg) {
return visitor.methodDecl(this, arg);
}
}
public static class ClassDecl extends Decl {
public String name;
public String superClass;
public ClassDecl(
String name,
String superClass,
List<? extends Ast> members) {
super(-1);
this.name = name;
this.superClass = superClass;
this.rwChildren.addAll(members);
}
public List<Ast> members() {
return Collections.unmodifiableList(this.rwChildren);
}
public List<VarDecl> fields() {
return childrenOfType(VarDecl.class);
} // includes constants!
public List<MethodDecl> methods() {
return childrenOfType(MethodDecl.class);
}
@Override
public <R, A> R accept(AstVisitor<R, A> visitor, A arg) {
return visitor.classDecl(this, arg);
}
}
}

106
src/cd/ir/AstVisitor.java Normal file
View file

@ -0,0 +1,106 @@
package cd.ir;
import cd.ir.Ast.Decl;
import cd.ir.Ast.Expr;
import cd.ir.Ast.Stmt;
/** A visitor that visits any kind of node */
public class AstVisitor<R,A> extends ExprVisitor<R,A> {
/**
* Recurse and process {@code ast}. It is preferred to
* call this rather than calling accept directly, since
* it can be overloaded to introduce memoization,
* for example. */
public R visit(Ast ast, A arg) {
return ast.accept(this, arg);
}
/**
* A handy function which visits the children of {@code ast},
* providing "arg" to each of them. It returns the result of
* the last child in the list. It is invoked by the method
* {@link #dflt(Ast, Object)} by default.
*/
public R visitChildren(Ast ast, A arg) {
R lastValue = null;
for (Ast child : ast.children())
lastValue = visit(child, arg);
return lastValue;
}
/**
* The default action for default actions is to call this,
* which simply recurses to any children. Also called
* by seq() by default. */
protected R dflt(Ast ast, A arg) {
return visitChildren(ast, arg);
}
/**
* The default action for statements is to call this */
protected R dfltStmt(Stmt ast, A arg) {
return dflt(ast, arg);
}
/**
* The default action for expressions is to call this */
protected R dfltExpr(Expr ast, A arg) {
return dflt(ast, arg);
}
/**
* The default action for AST nodes representing declarations
* is to call this function */
protected R dfltDecl(Decl ast, A arg) {
return dflt(ast, arg);
}
public R assign(Ast.Assign ast, A arg) {
return dfltStmt(ast, arg);
}
public R builtInWrite(Ast.BuiltInWrite ast, A arg) {
return dfltStmt(ast, arg);
}
public R builtInWriteln(Ast.BuiltInWriteln ast, A arg) {
return dfltStmt(ast, arg);
}
public R classDecl(Ast.ClassDecl ast, A arg) {
return dfltDecl(ast, arg);
}
public R methodDecl(Ast.MethodDecl ast, A arg) {
return dfltDecl(ast, arg);
}
public R varDecl(Ast.VarDecl ast, A arg) {
return dfltDecl(ast, arg);
}
public R ifElse(Ast.IfElse ast, A arg) {
return dfltStmt(ast, arg);
}
public R returnStmt(Ast.ReturnStmt ast, A arg) {
return dfltStmt(ast, arg);
}
public R methodCall(Ast.MethodCall ast, A arg) {
return dfltStmt(ast, arg);
}
public R nop(Ast.Nop ast, A arg) {
return dfltStmt(ast, arg);
}
public R seq(Ast.Seq ast, A arg) {
return dflt(ast, arg);
}
public R whileLoop(Ast.WhileLoop ast, A arg) {
return dfltStmt(ast, arg);
}
}

View file

@ -0,0 +1,92 @@
package cd.ir;
import cd.ir.Ast.Expr;
/**
* A visitor that only visits {@link Expr} nodes.
*/
public class ExprVisitor<R,A> {
/**
* Recurse and process {@code ast}. It is preferred to
* call this rather than calling accept directly, since
* it can be overloaded to introduce memoization,
* for example. */
public R visit(Expr ast, A arg) {
return ast.accept(this, arg);
}
/**
* Visits all children of the expression. Relies on the fact
* that {@link Expr} nodes only contain other {@link Expr} nodes.
*/
public R visitChildren(Expr ast, A arg) {
R lastValue = null;
for (Ast child : ast.children())
lastValue = visit((Expr)child, arg);
return lastValue;
}
/**
* The default action for default actions is to call this,
* which simply recurses to any children. Also called
* by seq() by default. */
protected R dfltExpr(Expr ast, A arg) {
return visitChildren(ast, arg);
}
public R binaryOp(Ast.BinaryOp ast, A arg) {
return dfltExpr(ast, arg);
}
public R booleanConst(Ast.BooleanConst ast, A arg) {
return dfltExpr(ast, arg);
}
public R builtInRead(Ast.BuiltInRead ast, A arg) {
return dfltExpr(ast, arg);
}
public R cast(Ast.Cast ast, A arg) {
return dfltExpr(ast, arg);
}
public R field(Ast.Field ast, A arg) {
return dfltExpr(ast, arg);
}
public R index(Ast.Index ast, A arg) {
return dfltExpr(ast, arg);
}
public R intConst(Ast.IntConst ast, A arg) {
return dfltExpr(ast, arg);
}
public R methodCall(Ast.MethodCallExpr ast, A arg) {
return dfltExpr(ast, arg);
}
public R newObject(Ast.NewObject ast, A arg) {
return dfltExpr(ast, arg);
}
public R newArray(Ast.NewArray ast, A arg) {
return dfltExpr(ast, arg);
}
public R nullConst(Ast.NullConst ast, A arg) {
return dfltExpr(ast, arg);
}
public R thisRef(Ast.ThisRef ast, A arg) {
return dfltExpr(ast, arg);
}
public R unaryOp(Ast.UnaryOp ast, A arg) {
return dfltExpr(ast, arg);
}
public R var(Ast.Var ast, A arg) {
return dfltExpr(ast, arg);
}
}

121
src/cd/util/FileUtil.java Normal file
View file

@ -0,0 +1,121 @@
package cd.util;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
public class FileUtil {
public static String readAll(Reader ubReader) throws IOException {
try (BufferedReader bReader = new BufferedReader(ubReader)) {
StringBuilder sb = new StringBuilder();
while (true) {
int ch = bReader.read();
if (ch == -1)
break;
sb.append((char) ch);
}
return sb.toString();
}
}
public static String read(File file) throws IOException {
return readAll(new FileReader(file));
}
public static void write(File file, String text) throws IOException {
try (FileWriter writer = new FileWriter(file)) {
writer.write(text);
}
}
public static String runCommand(File dir, String[] command,
String[] substs, String input, boolean detectError)
throws IOException {
// Substitute the substitution strings $0, $1, etc
String newCommand[] = new String[command.length];
for (int i = 0; i < command.length; i++) {
String newItem = command[i];
for (int j = 0; j < substs.length; j++)
newItem = newItem.replace("$" + j, substs[j]);
newCommand[i] = newItem;
}
// Run the command in the specified directory
ProcessBuilder pb = new ProcessBuilder(newCommand);
pb.directory(dir);
pb.redirectErrorStream(true);
final Process p = pb.start();
if (input != null && !input.equals("")) {
try (OutputStreamWriter osw = new OutputStreamWriter(p.getOutputStream())) {
osw.write(input);
}
}
try {
final StringBuffer result = new StringBuffer();
// thread to read stdout from child so that p.waitFor() is interruptible
// by JUnit's timeout mechanism. Otherwise it would block in readAll()
Thread t = new Thread () {
public void run() {
try {
result.append(readAll(new InputStreamReader(p.getInputStream())));
} catch (IOException e) {
}
}
};
t.start();
if (detectError) {
int err = p.waitFor();
// hack: same as ReferenceServer returns when
// a dynamic error occurs running the interpreter
if (err != 0)
return "Error: " + err + "\n";
}
t.join();
return result.toString();
} catch (InterruptedException e) {
return "Error: execution of " + command[0] + " got interrupted (probably timed out)\n";
} finally {
// in case child is still running, destroy
p.destroy();
}
}
/**
* Finds all .javali under directory {@code testDir}, adding File objects
* into {@code result} for each one.
*/
public static void findJavaliFiles(File testDir, List<Object[]> result) {
for (File testFile : testDir.listFiles()) {
if (testFile.getName().endsWith(".javali"))
result.add(new Object[] { testFile });
else if (testFile.isDirectory())
findJavaliFiles(testFile, result);
}
}
/** Finds all .javali under directory {@code testDir} and returns them. */
public static List<File> findJavaliFiles(File testDir) {
List<File> result = new ArrayList<File>();
for (File testFile : testDir.listFiles()) {
if (testFile.getName().endsWith(".javali"))
result.add(testFile);
else if (testFile.isDirectory())
result.addAll(findJavaliFiles(testFile));
}
return result;
}
}

53
src/cd/util/Pair.java Normal file
View file

@ -0,0 +1,53 @@
package cd.util;
import java.util.ArrayList;
import java.util.List;
/** Simple class for joining two objects of the same type */
public class Pair<T> {
public T a;
public T b;
public Pair(T a, T b) {
this.a = a;
this.b = b;
}
public static <T> List<Pair<T>> zip(List<T> listA, List<T> listB) {
List<Pair<T>> res = new ArrayList<Pair<T>>();
for (int i = 0; i < Math.min(listA.size(), listB.size()); i++) {
res.add(new Pair<T>(listA.get(i), listB.get(i)));
}
return res;
}
public static <T> List<T> unzipA(List<Pair<T>> list) {
List<T> res = new ArrayList<T>();
for (Pair<T> p : list)
res.add(p.a);
return res;
}
public static <T> List<T> unzipB(List<Pair<T>> list) {
List<T> res = new ArrayList<T>();
for (Pair<T> p : list)
res.add(p.b);
return res;
}
public static String join(
List<Pair<?>> pairs,
String itemSep,
String pairSep) {
StringBuilder sb = new StringBuilder();
boolean first = true;
for (Pair<?> pair : pairs) {
if (!first) sb.append(pairSep);
sb.append(pair.a);
sb.append(itemSep);
sb.append(pair.b);
first = false;
}
return sb.toString();
}
}

16
src/cd/util/Tuple.java Normal file
View file

@ -0,0 +1,16 @@
package cd.util;
/** Simple class for joining two objects of different type */
public class Tuple<A,B> {
public A a;
public B b;
public Tuple(A a, B b) {
this.a = a;
this.b = b;
}
public String toString() {
return "(" + a.toString() + ", " + b.toString() + ")";
}
}

View file

@ -0,0 +1,125 @@
package cd.util.debug;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import cd.ir.Ast;
import cd.ir.AstVisitor;
import cd.util.Pair;
public class AstDump {
public static String toString(Ast ast) {
return toString(ast, "");
}
public static String toString(List<? extends Ast> astRoots) {
StringBuilder sb = new StringBuilder();
for (Ast a : astRoots) {
sb.append(toString(a));
}
return sb.toString();
}
public static String toString(Ast ast, String indent) {
AstDump ad = new AstDump();
ad.dump(ast, indent);
return ad.sb.toString();
}
public static String toStringFlat(Ast ast) {
AstDump ad = new AstDump();
ad.dumpFlat(ast);
return ad.sb.toString();
}
private StringBuilder sb = new StringBuilder();
private Visitor vis = new Visitor();
protected void dump(Ast ast, String indent) {
// print out the overall class structure
sb.append(indent);
String nodeName = ast.getClass().getSimpleName();
List<Pair<?>> flds = vis.visit(ast, null);
sb.append(String.format("%s (%s)\n",
nodeName,
Pair.join(flds, ": ", ", ")));
// print out any children
String newIndent = indent + "| ";
for (Ast child : ast.children()) {
dump(child, newIndent);
}
}
protected void dumpFlat(Ast ast) {
String nodeName = ast.getClass().getSimpleName();
List<Pair<?>> flds = vis.visit(ast, null);
sb.append(String.format("%s(%s)[",
nodeName,
Pair.join(flds, ":", ",")));
// print out any children
for (int child = 0; child < ast.children().size(); child++) {
dumpFlat(ast.children().get(child));
if (child < ast.children().size() - 1)
sb.append(",");
}
sb.append("]");
}
protected class Visitor extends AstVisitor<List<Pair<?>>, Void> {
@Override
protected List<Pair<?>> dflt(Ast ast, Void arg) {
ArrayList<Pair<?>> res = new ArrayList<Pair<?>>();
// Get the list of fields and sort them by name:
java.lang.Class<? extends Ast> rclass = ast.getClass();
List<java.lang.reflect.Field> rflds =
Arrays.asList(rclass.getFields());
Collections.sort(rflds, new Comparator<java.lang.reflect.Field> () {
public int compare(Field o1, Field o2) {
return o1.getName().compareTo(o2.getName());
}
});
// Create pairs for each one that is not of type Ast:
for (java.lang.reflect.Field rfld : rflds) {
rfld.setAccessible(true);
// ignore various weird fields that show up from
// time to time:
if (rfld.getName().startsWith("$"))
continue;
// ignore fields of AST type, and rwChildren (they should be
// uncovered using the normal tree walk)
if (rfld.getType().isAssignableFrom(Ast.class))
continue;
if (rfld.getName().equals("rwChildren"))
continue;
// ignore NULL fields, but add others to our list of pairs
try {
Object value = rfld.get(ast);
if (value != null)
res.add(new Pair<Object>(rfld.getName(), value));
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
return res;
}
}
}

View file

@ -0,0 +1,160 @@
package cd.util.debug;
import cd.ir.Ast;
import cd.ir.Ast.Assign;
import cd.ir.Ast.BinaryOp;
import cd.ir.Ast.BooleanConst;
import cd.ir.Ast.BuiltInRead;
import cd.ir.Ast.BuiltInWrite;
import cd.ir.Ast.BuiltInWriteln;
import cd.ir.Ast.Cast;
import cd.ir.Ast.ClassDecl;
import cd.ir.Ast.Field;
import cd.ir.Ast.IfElse;
import cd.ir.Ast.Index;
import cd.ir.Ast.IntConst;
import cd.ir.Ast.MethodDecl;
import cd.ir.Ast.NewArray;
import cd.ir.Ast.NewObject;
import cd.ir.Ast.Nop;
import cd.ir.Ast.NullConst;
import cd.ir.Ast.Seq;
import cd.ir.Ast.ThisRef;
import cd.ir.Ast.UnaryOp;
import cd.ir.Ast.Var;
import cd.ir.Ast.VarDecl;
import cd.ir.Ast.WhileLoop;
import cd.ir.AstVisitor;
public class AstOneLine {
public static String toString(Ast ast) {
return new Visitor().visit(ast, null);
}
protected static class Visitor extends AstVisitor<String, Void> {
public String str(Ast ast) {
return ast.accept(this, null);
}
@Override
public String assign(Assign ast, Void arg) {
return String.format("%s = %s", str(ast.left()), str(ast.right()));
}
@Override
public String binaryOp(BinaryOp ast, Void arg) {
return String.format("(%s %s %s)",
str(ast.left()), ast.operator.repr, str(ast.right()));
}
@Override
public String booleanConst(BooleanConst ast, Void arg) {
return Boolean.toString(ast.value);
}
@Override
public String builtInRead(BuiltInRead ast, Void arg) {
return String.format("read()");
}
@Override
public String builtInWrite(BuiltInWrite ast, Void arg) {
return String.format("write(%s)", str(ast.arg()));
}
@Override
public String builtInWriteln(BuiltInWriteln ast, Void arg) {
return String.format("writeln()");
}
@Override
public String cast(Cast ast, Void arg) {
return String.format("(%s)(%s)", ast.typeName, str(ast.arg()));
}
@Override
public String classDecl(ClassDecl ast, Void arg) {
return String.format("class %s {...}", ast.name);
}
@Override
public String field(Field ast, Void arg) {
return String.format("%s.%s", str(ast.arg()), ast.fieldName);
}
@Override
public String ifElse(IfElse ast, Void arg) {
return String.format("if (%s) {...} else {...}", str(ast.condition()));
}
@Override
public String index(Index ast, Void arg) {
return String.format("%s[%s]", str(ast.left()), str(ast.right()));
}
@Override
public String intConst(IntConst ast, Void arg) {
return Integer.toString(ast.value);
}
@Override
public String methodDecl(MethodDecl ast, Void arg) {
return String.format("%s %s(...) {...}", ast.returnType, ast.name);
}
@Override
public String newArray(NewArray ast, Void arg) {
return String.format("new %s[%s]", ast.typeName, str(ast.arg()));
}
@Override
public String newObject(NewObject ast, Void arg) {
return String.format("new %s()", ast.typeName);
}
@Override
public String nop(Nop ast, Void arg) {
return "nop";
}
@Override
public String nullConst(NullConst ast, Void arg) {
return "null";
}
@Override
public String seq(Seq ast, Void arg) {
return "(...)";
}
@Override
public String thisRef(ThisRef ast, Void arg) {
return "this";
}
@Override
public String unaryOp(UnaryOp ast, Void arg) {
return String.format("%s(%s)", ast.operator.repr, str(ast.arg()));
}
@Override
public String var(Var ast, Void arg) {
{
return ast.name;
}
}
@Override
public String varDecl(VarDecl ast, Void arg) {
return String.format("%s %s", ast.type, ast.name);
}
@Override
public String whileLoop(WhileLoop ast, Void arg) {
return String.format("while (%s) {...}", str(ast.condition()));
}
}
}

View file

@ -0,0 +1,223 @@
package cd.test;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
import cd.Config;
import cd.Main;
import cd.backend.codegen.AssemblyFailedException;
import cd.frontend.parser.ParseFailure;
import cd.ir.Ast.ClassDecl;
import cd.util.FileUtil;
import cd.util.debug.AstDump;
abstract public class AbstractTestAgainstFrozenReference {
public static final String SEMANTIC_OK = "OK";
public static final String PARSE_FAILURE = "ParseFailure";
public File file, sfile, binfile, infile;
public File parserreffile, semanticreffile, execreffile, cfgreffile, optreffile;
public File errfile;
public Main main;
public static int counter = 0;
@Test(timeout=10000)
public void test() throws Throwable {
System.err.println("[" + counter++ + " = " + file + "]");
try {
// Delete intermediate files from previous runs:
if (sfile.exists())
sfile.delete();
if (binfile.exists())
binfile.delete();
runReference();
List<ClassDecl> astRoots = testParser();
if (astRoots != null) {
{
testCodeGenerator(astRoots);
}
}
} catch (org.junit.ComparisonFailure cf) {
throw cf;
} catch (Throwable e) {
PrintStream err = new PrintStream(errfile);
err.println("Debug information for file: " + this.file);
err.println(this.main.debug.toString());
err.println("Test failed because an exception was thrown:");
err.println(" " + e.getLocalizedMessage());
err.println("Stack trace:");
e.printStackTrace(err);
System.err.println(FileUtil.read(errfile));
throw e;
}
// if we get here, then the test passed, so delete the errfile:
// (which has been accumulating debug output etc)
if (errfile.exists())
errfile.delete();
}
private void runReference() throws IOException, InterruptedException {
String slash = File.separator;
String colon = File.pathSeparator;
String javaExe = System.getProperty("java.home") + slash + "bin" + slash + Config.JAVA_EXE;
ProcessBuilder pb = new ProcessBuilder(
javaExe, "-Dcd.meta_hidden.Version=" + referenceVersion(),
"-cp", "lib/frozenReferenceObf.jar" + colon + " lib/junit-4.12.jar" + colon + "lib/antlr-4.4-complete.jar",
"cd.FrozenReferenceMain", file.getAbsolutePath());
Process proc = pb.start();
proc.waitFor();
try (InputStream err = proc.getErrorStream()) {
if (err.available() > 0) {
byte b[] = new byte[err.available()];
err.read(b, 0, b.length);
System.err.println(new String(b));
}
}
}
private static String referenceVersion() {
{
return "CD_HW_CODEGEN_SIMPLE_SOL";
}
}
/** Run the parser and compare the output against the reference results */
private List<ClassDecl> testParser() throws Exception {
String parserRef = findParserRef();
List<ClassDecl> astRoots = null;
String parserOut;
try {
astRoots = main.parse(new FileReader(this.file));
parserOut = AstDump.toString(astRoots);
} catch (ParseFailure pf) {
{
// For this assignment, we expect all tests to parse!
throw pf;
}
}
{
assertEquals("parser", parserRef, parserOut);
}
return astRoots;
}
private String findParserRef() throws IOException {
// Check for a .ref file
if (parserreffile.exists() && parserreffile.lastModified() >= file.lastModified()) {
return FileUtil.read(parserreffile);
}
throw new RuntimeException("ERROR: could not find parser .ref");
}
/**
* Run the code generator, assemble the resulting .s file, and (if the output
* is well-defined) compare against the expected output.
*/
private void testCodeGenerator(List<ClassDecl> astRoots)
throws IOException {
// Determine the input and expected output.
String inFile = (infile.exists() ? FileUtil.read(infile) : "");
String execRef = findExecRef();
// Run the code generator:
try (FileWriter fw = new FileWriter(this.sfile)) {
main.generateCode(astRoots, fw);
}
// At this point, we have generated a .s file and we have to compile
// it to a binary file. We need to call out to GCC or something
// to do this.
String asmOutput = FileUtil.runCommand(
Config.ASM_DIR,
Config.ASM,
new String[] { binfile.getAbsolutePath(),
sfile.getAbsolutePath() }, null, false);
// To check if gcc succeeded, check if the binary file exists.
// We could use the return code instead, but this seems more
// portable to other compilers / make systems.
if (!binfile.exists())
throw new AssemblyFailedException(asmOutput);
// Execute the binary file, providing input if relevant, and
// capturing the output. Check the error code so see if the
// code signaled dynamic errors.
String execOut = FileUtil.runCommand(new File("."),
new String[] { binfile.getAbsolutePath() }, new String[] {},
inFile, true);
// Compute the output to what we expected to see.
if (!execRef.equals(execOut))
assertEqualOutput("exec", execRef, execOut);
}
private String findExecRef() throws IOException {
// Check for a .ref file
if (execreffile.exists() && execreffile.lastModified() > file.lastModified()) {
return FileUtil.read(execreffile);
}
throw new RuntimeException("ERROR: could not find execution .ref");
}
private void assertEquals(String phase, String exp, String act_) {
String act = act_.replace("\r\n", "\n"); // for windows machines
if (!exp.equals(act)) {
warnAboutDiff(phase, exp, act);
}
}
/**
* Compare the output of two executions
*/
private void assertEqualOutput(String phase, String exp, String act_) {
String act = act_.replace("\r\n", "\n"); // for windows machines
if (!exp.equals(act)) {
warnAboutDiff(phase, exp, act);
}
}
private void warnAboutDiff(String phase, String exp, String act) {
try {
try (PrintStream err = new PrintStream(errfile)) {
err.println("Debug information for file: " + this.file);
err.println(this.main.debug.toString());
err.println(String.format(
"Phase %s failed because we expected to see:", phase));
err.println(exp);
err.println("But we actually saw:");
err.println(act);
err.println("The difference is:");
err.println(Diff.computeDiff(exp, act));
}
} catch (FileNotFoundException exc) {
System.err.println("Unable to write debug output to " + errfile
+ ":");
exc.printStackTrace();
}
Assert.assertEquals(
String.format("Phase %s for %s failed!", phase,
file.getPath()), exp, act);
}
}

87
test/cd/test/Diff.java Normal file
View file

@ -0,0 +1,87 @@
package cd.test;
// NOTE: This code was adapted from the code found at
// http://www.cs.princeton.edu/introcs/96optimization/Diff.java.html
/*************************************************************************
* Compilation: javac Diff
* Execution: java Diff filename1 filename2
* Dependencies: In.java
*
* Reads in two files and compute their diff.
* A bare bones version.
*
* % java Diff input1.txt input2.txt
*
*
* Limitations
* -----------
* - Could hash the lines to avoid potentially more expensive
* string comparisons.
*
*************************************************************************/
public class Diff {
public static String computeDiff(String in0, String in1) {
StringBuffer out = new StringBuffer();
// break into lines of each file
String[] x = in0.split("\\n");
String[] y = in1.split("\\n");
// number of lines of each file
int M = x.length;
int N = y.length;
// opt[i][j] = length of LCS of x[i..M] and y[j..N]
int[][] opt = new int[M+1][N+1];
// compute length of LCS and all subproblems via dynamic programming
for (int i = M-1; i >= 0; i--) {
for (int j = N-1; j >= 0; j--) {
if (x[i].equals(y[j]))
opt[i][j] = opt[i+1][j+1] + 1;
else
opt[i][j] = Math.max(opt[i+1][j], opt[i][j+1]);
}
}
// recover LCS itself and print out non-matching lines to standard output
boolean lineNumber = false;
int i = 0, j = 0;
while(i < M && j < N) {
if (x[i].equals(y[j])) {
i++;
j++;
lineNumber = false;
}
else {
if (!lineNumber) {
out.append(String.format(
"At line %3d / %3d:\n", i+1, j+1));
lineNumber = true;
}
assert lineNumber;
if (opt[i+1][j] >= opt[i][j+1])
out.append("< " + x[i++] + "\n");
else
out.append("> " + y[j++] + "\n");
}
}
// dump out one remainder of one string if the other is exhausted
if (!lineNumber) {
out.append(String.format("Line %3d / %3d:\n", i+1, j+1));
}
while(i < M || j < N) {
if (i == M)
out.append("> " + y[j++] + "\n");
else if (j == N)
out.append("< " + x[i++] + "\n");
}
return out.toString();
}
}

View file

@ -0,0 +1,74 @@
package cd.test;
import java.io.File;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import cd.Config;
import cd.Main;
import cd.util.FileUtil;
@RunWith(Parameterized.class)
public class TestSamplePrograms extends AbstractTestAgainstFrozenReference {
/**
* If you want to run the test on just one file, then initialize this
* variable like:
* {@code justFile = new File("javali_tests/HW2/Inheritance.javali")}.
*/
// public static final File justFile = new File("javali_tests/HW1/HelloWorld.javali");
public static final File justFile = null;
/**
* Directory in which to search for test files. If null, then the default is
* the current directory (to include all files). To run only tests in a
* particular directory, use sth. like:
* {@code testDir = new File("javali_tests/HW2/")}.
*/
// public static final File testDir = new File("javali_tests/HW1");
public static final File testDir = new File("javali_tests");
@Parameters(name="{index}:{0}")
public static Collection<Object[]> testFiles() {
List<Object[]> result = new ArrayList<Object[]>();
if (justFile != null)
result.add(new Object[] { justFile });
else if (testDir != null) {
for (File file : FileUtil.findJavaliFiles(testDir)) {
result.add(new Object[] { file });
}
} else {
for (File file : FileUtil.findJavaliFiles(new File("."))) {
result.add(new Object[] { file });
}
}
return result;
}
/**
* @param file
* The javali file to test.
*/
public TestSamplePrograms(File file) {
this.file = file;
this.sfile = new File(file.getPath() + Config.ASMEXT);
this.binfile = new File(file.getPath() + Config.BINARYEXT);
this.infile = new File(file.getPath() + ".in");
this.parserreffile = new File(file.getPath() + ".parser.ref");
this.semanticreffile = new File(file.getPath() + ".semantic.ref");
this.execreffile = new File(file.getPath() + ".exec.ref");
this.cfgreffile = new File(file.getPath() + ".cfg.dot.ref");
this.optreffile = new File(file.getPath() + ".opt.ref");
this.errfile = new File(String.format("%s.err", file.getPath()));
this.main = new Main();
this.main.debug = new StringWriter();
}
}