From 12f678a924c454c779c877ced515fcd0af79788f Mon Sep 17 00:00:00 2001 From: Carlos Galindo Date: Wed, 15 Jan 2020 22:18:07 +0100 Subject: [PATCH] Homework 1 --- .classpath | 11 + .gitignore | 245 ++++++ .project | 23 + .settings/org.eclipse.core.resources.prefs | 2 + .settings/org.eclipse.jdt.core.prefs | 12 + Grade.txt | 5 + build.xml | 87 ++ javali_tests/HW1/EightVariables.javali | 24 + javali_tests/HW1/HelloWorld.javali | 7 + javali_tests/HW1/ImmediateOperands.javali | 19 + javali_tests/HW1/Negation.javali | 18 + javali_tests/HW1_nop90/Division.javali | 21 + .../HW1_nop90/EightVariablesWrite.javali | 20 + javali_tests/HW1_nop90/Multiplication.javali | 31 + javali_tests/HW1_nop90/Overflow.javali | 25 + javali_tests/HW1_nop90/Overflow.javali.in | 1 + javali_tests/HW1_nop90/ReadWrite.javali | 26 + javali_tests/HW1_nop90/ReadWrite.javali.in | 6 + javali_tests/HW1_nop90/UnaryOperators.javali | 15 + javali_tests/HW1_nop90/noParentheses.javali | 24 + src/cd/Config.java | 148 ++++ src/cd/Main.java | 126 +++ src/cd/ToDoException.java | 14 + src/cd/backend/codegen/AssemblyEmitter.java | 164 ++++ .../codegen/AssemblyFailedException.java | 14 + src/cd/backend/codegen/AstCodeGenerator.java | 58 ++ src/cd/backend/codegen/ExprGenerator.java | 307 +++++++ src/cd/backend/codegen/Interrupts.java | 97 +++ src/cd/backend/codegen/RegisterManager.java | 123 +++ src/cd/backend/codegen/StmtGenerator.java | 206 +++++ src/cd/frontend/parser/.gitignore | 6 + src/cd/frontend/parser/ParseFailure.java | 9 + src/cd/ir/Ast.java | 752 ++++++++++++++++++ src/cd/ir/AstVisitor.java | 106 +++ src/cd/ir/ExprVisitor.java | 92 +++ src/cd/util/FileUtil.java | 121 +++ src/cd/util/Pair.java | 53 ++ src/cd/util/Tuple.java | 16 + src/cd/util/debug/AstDump.java | 125 +++ src/cd/util/debug/AstOneLine.java | 160 ++++ .../AbstractTestAgainstFrozenReference.java | 223 ++++++ test/cd/test/Diff.java | 87 ++ test/cd/test/TestSamplePrograms.java | 74 ++ 43 files changed, 3703 insertions(+) create mode 100644 .classpath create mode 100644 .gitignore create mode 100644 .project create mode 100644 .settings/org.eclipse.core.resources.prefs create mode 100644 .settings/org.eclipse.jdt.core.prefs create mode 100644 Grade.txt create mode 100644 build.xml create mode 100644 javali_tests/HW1/EightVariables.javali create mode 100644 javali_tests/HW1/HelloWorld.javali create mode 100644 javali_tests/HW1/ImmediateOperands.javali create mode 100644 javali_tests/HW1/Negation.javali create mode 100755 javali_tests/HW1_nop90/Division.javali create mode 100644 javali_tests/HW1_nop90/EightVariablesWrite.javali create mode 100755 javali_tests/HW1_nop90/Multiplication.javali create mode 100755 javali_tests/HW1_nop90/Overflow.javali create mode 100755 javali_tests/HW1_nop90/Overflow.javali.in create mode 100755 javali_tests/HW1_nop90/ReadWrite.javali create mode 100755 javali_tests/HW1_nop90/ReadWrite.javali.in create mode 100755 javali_tests/HW1_nop90/UnaryOperators.javali create mode 100755 javali_tests/HW1_nop90/noParentheses.javali create mode 100644 src/cd/Config.java create mode 100644 src/cd/Main.java create mode 100644 src/cd/ToDoException.java create mode 100644 src/cd/backend/codegen/AssemblyEmitter.java create mode 100644 src/cd/backend/codegen/AssemblyFailedException.java create mode 100644 src/cd/backend/codegen/AstCodeGenerator.java create mode 100644 src/cd/backend/codegen/ExprGenerator.java create mode 100644 src/cd/backend/codegen/Interrupts.java create mode 100644 src/cd/backend/codegen/RegisterManager.java create mode 100644 src/cd/backend/codegen/StmtGenerator.java create mode 100644 src/cd/frontend/parser/.gitignore create mode 100644 src/cd/frontend/parser/ParseFailure.java create mode 100644 src/cd/ir/Ast.java create mode 100644 src/cd/ir/AstVisitor.java create mode 100644 src/cd/ir/ExprVisitor.java create mode 100644 src/cd/util/FileUtil.java create mode 100644 src/cd/util/Pair.java create mode 100644 src/cd/util/Tuple.java create mode 100644 src/cd/util/debug/AstDump.java create mode 100644 src/cd/util/debug/AstOneLine.java create mode 100644 test/cd/test/AbstractTestAgainstFrozenReference.java create mode 100644 test/cd/test/Diff.java create mode 100644 test/cd/test/TestSamplePrograms.java diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..646ce8c --- /dev/null +++ b/.classpath @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7679d5c --- /dev/null +++ b/.gitignore @@ -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 diff --git a/.project b/.project new file mode 100644 index 0000000..1f75518 --- /dev/null +++ b/.project @@ -0,0 +1,23 @@ + + + Javali-HW1 + + + + + + org.eclipse.xtext.ui.shared.xtextBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.xtext.ui.shared.xtextNature + + diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..99f26c0 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..a698e59 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -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 diff --git a/Grade.txt b/Grade.txt new file mode 100644 index 0000000..ede9e61 --- /dev/null +++ b/Grade.txt @@ -0,0 +1,5 @@ +Grade: 24/25 + +31/31 tests passed [20/20] + +Submitted test cases [4/5] \ No newline at end of file diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..c0b6cee --- /dev/null +++ b/build.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/javali_tests/HW1/EightVariables.javali b/javali_tests/HW1/EightVariables.javali new file mode 100644 index 0000000..b5cb125 --- /dev/null +++ b/javali_tests/HW1/EightVariables.javali @@ -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(); + } +} diff --git a/javali_tests/HW1/HelloWorld.javali b/javali_tests/HW1/HelloWorld.javali new file mode 100644 index 0000000..80d9511 --- /dev/null +++ b/javali_tests/HW1/HelloWorld.javali @@ -0,0 +1,7 @@ +/* Kind of like hello world */ +class Main { + void main() { + write(53110); + writeln(); + } +} diff --git a/javali_tests/HW1/ImmediateOperands.javali b/javali_tests/HW1/ImmediateOperands.javali new file mode 100644 index 0000000..3deaaca --- /dev/null +++ b/javali_tests/HW1/ImmediateOperands.javali @@ -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(); + } +} diff --git a/javali_tests/HW1/Negation.javali b/javali_tests/HW1/Negation.javali new file mode 100644 index 0000000..5083d7a --- /dev/null +++ b/javali_tests/HW1/Negation.javali @@ -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(); + } +} diff --git a/javali_tests/HW1_nop90/Division.javali b/javali_tests/HW1_nop90/Division.javali new file mode 100755 index 0000000..13358d3 --- /dev/null +++ b/javali_tests/HW1_nop90/Division.javali @@ -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(); + } +} \ No newline at end of file diff --git a/javali_tests/HW1_nop90/EightVariablesWrite.javali b/javali_tests/HW1_nop90/EightVariablesWrite.javali new file mode 100644 index 0000000..e9391ca --- /dev/null +++ b/javali_tests/HW1_nop90/EightVariablesWrite.javali @@ -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(); + } +} diff --git a/javali_tests/HW1_nop90/Multiplication.javali b/javali_tests/HW1_nop90/Multiplication.javali new file mode 100755 index 0000000..0c092a6 --- /dev/null +++ b/javali_tests/HW1_nop90/Multiplication.javali @@ -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(); + } +} diff --git a/javali_tests/HW1_nop90/Overflow.javali b/javali_tests/HW1_nop90/Overflow.javali new file mode 100755 index 0000000..5216592 --- /dev/null +++ b/javali_tests/HW1_nop90/Overflow.javali @@ -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(); + + } +} diff --git a/javali_tests/HW1_nop90/Overflow.javali.in b/javali_tests/HW1_nop90/Overflow.javali.in new file mode 100755 index 0000000..a51fa7d --- /dev/null +++ b/javali_tests/HW1_nop90/Overflow.javali.in @@ -0,0 +1 @@ +2147483647 diff --git a/javali_tests/HW1_nop90/ReadWrite.javali b/javali_tests/HW1_nop90/ReadWrite.javali new file mode 100755 index 0000000..e9f3944 --- /dev/null +++ b/javali_tests/HW1_nop90/ReadWrite.javali @@ -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 + + } +} diff --git a/javali_tests/HW1_nop90/ReadWrite.javali.in b/javali_tests/HW1_nop90/ReadWrite.javali.in new file mode 100755 index 0000000..fff4c89 --- /dev/null +++ b/javali_tests/HW1_nop90/ReadWrite.javali.in @@ -0,0 +1,6 @@ + 22 + + + 1 + +-15 diff --git a/javali_tests/HW1_nop90/UnaryOperators.javali b/javali_tests/HW1_nop90/UnaryOperators.javali new file mode 100755 index 0000000..4a5485b --- /dev/null +++ b/javali_tests/HW1_nop90/UnaryOperators.javali @@ -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(); + } +} \ No newline at end of file diff --git a/javali_tests/HW1_nop90/noParentheses.javali b/javali_tests/HW1_nop90/noParentheses.javali new file mode 100755 index 0000000..0413586 --- /dev/null +++ b/javali_tests/HW1_nop90/noParentheses.javali @@ -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(); + + } +} diff --git a/src/cd/Config.java b/src/cd/Config.java new file mode 100644 index 0000000..6c7403a --- /dev/null +++ b/src/cd/Config.java @@ -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 = "#"; + } + } + +} diff --git a/src/cd/Main.java b/src/cd/Main.java new file mode 100644 index 0000000..b0413b7 --- /dev/null +++ b/src/cd/Main.java @@ -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 best 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 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 parse(Reader reader) throws IOException { + List result = new ArrayList(); + + 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 astRoots) { + { + // Not registersNeeded until later. Ignore. + } + } + + public void generateCode(List astRoots, Writer out) { + { + AstCodeGenerator cg = AstCodeGenerator.createCodeGenerator(this, out); + cg.go(astRoots); + } + } + + /** Dumps the AST to the debug stream */ + private void dumpAst(List astRoots) throws IOException { + if (this.debug == null) return; + this.debug.write(AstDump.toString(astRoots)); + } +} diff --git a/src/cd/ToDoException.java b/src/cd/ToDoException.java new file mode 100644 index 0000000..615a148 --- /dev/null +++ b/src/cd/ToDoException.java @@ -0,0 +1,14 @@ +package cd; + +/** TAs insert this to mark code that students need to write */ +public class ToDoException extends RuntimeException { + private static final long serialVersionUID = 4054810321239901944L; + + public ToDoException() { + } + + public ToDoException(String message) { + super(message); + } + +} diff --git a/src/cd/backend/codegen/AssemblyEmitter.java b/src/cd/backend/codegen/AssemblyEmitter.java new file mode 100644 index 0000000..f9c8c01 --- /dev/null +++ b/src/cd/backend/codegen/AssemblyEmitter.java @@ -0,0 +1,164 @@ +package cd.backend.codegen; + +import java.io.IOException; +import java.io.Writer; + +import cd.Config; +import cd.backend.codegen.RegisterManager.Register; + +public class AssemblyEmitter { + public Writer out; + public StringBuilder indent = new StringBuilder(); + public int counter = 0; + + public AssemblyEmitter(Writer out) { + this.out = out; + } + + /** Creates an constant operand. */ + static String constant(int i) { + return "$" + i; + } + + /** Creates an constant operand with the address of a label. */ + static String labelAddress(String lbl) { + return "$" + lbl; + } + + /** Creates an operand relative to another operand. */ + static String registerOffset(int offset, Register reg) { + return String.format("%d(%s)", offset, reg); + } + + /** Creates an operand addressing an item in an array */ + static String arrayAddress(Register arrReg, Register idxReg) { + final int offset = Config.SIZEOF_PTR * 2; // one word each in front for + // vptr and length + final int mul = Config.SIZEOF_PTR; // assume all arrays of 4-byte elem + return String.format("%d(%s,%s,%d)", offset, arrReg, idxReg, mul); + } + + void increaseIndent(String comment) { + indent.append(" "); + if (comment != null) + emitComment(comment); + } + + void decreaseIndent() { + indent.setLength(indent.length() - 2); + } + + void emitCommentSection(String name) { + int indentLen = indent.length(); + int breakLen = 68 - indentLen - name.length(); + StringBuffer sb = new StringBuffer(); + sb.append(Config.COMMENT_SEP).append(" "); + for (int i = 0; i < indentLen; i++) + sb.append("_"); + sb.append(name); + for (int i = 0; i < breakLen; i++) + sb.append("_"); + + try { + out.write(sb.toString()); + out.write("\n"); + } catch (IOException e) { + } + } + + void emitComment(String comment) { + emitRaw(Config.COMMENT_SEP + " " + comment); + } + + void emit(String op, Register src, String dest) { + emit(op, src.repr, dest); + } + + void emit(String op, String src, Register dest) { + emit(op, src, dest.repr); + } + + void emit(String op, Register src, Register dest) { + emit(op, src.repr, dest.repr); + } + + void emit(String op, String src, String dest) { + emitRaw(String.format("%s %s, %s", op, src, dest)); + } + + void emit(String op, int src, Register dest) { + emit(op, constant(src), dest); + } + + void emit(String op, String dest) { + emitRaw(op + " " + dest); + } + + void emit(String op) { + emitRaw(op); + } + + void emit(String op, Register reg) { + emit(op, reg.repr); + } + + void emit(String op, int dest) { + emit(op, constant(dest)); + } + + void emitMove(Register src, String dest) { + emitMove(src.repr, dest); + } + + void emitMove(Register src, Register dest) { + emitMove(src.repr, dest.repr); + } + + void emitMove(String src, Register dest) { + emitMove(src, dest.repr); + } + + void emitMove(String src, String dest) { + if (!src.equals(dest)) + emit("movl", src, dest); + } + + void emitLoad(int srcOffset, Register src, Register dest) { + emitMove(registerOffset(srcOffset, src), dest.repr); + } + + void emitStore(Register src, int destOffset, Register dest) { + emitStore(src.repr, destOffset, dest); + } + + void emitStore(String src, int destOffset, Register dest) { + emitMove(src, registerOffset(destOffset, dest)); + } + + void emitConstantData(String data) { + emitRaw(String.format("%s %s", Config.DOT_INT, data)); + } + + String uniqueLabel() { + String labelName = "label" + counter++; + return labelName; + } + + void emitLabel(String label) { + try { + out.write(label + ":" + "\n"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + void emitRaw(String op) { + try { + out.write(indent.toString()); + out.write(op); + out.write("\n"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/src/cd/backend/codegen/AssemblyFailedException.java b/src/cd/backend/codegen/AssemblyFailedException.java new file mode 100644 index 0000000..112fee9 --- /dev/null +++ b/src/cd/backend/codegen/AssemblyFailedException.java @@ -0,0 +1,14 @@ +package cd.backend.codegen; + +public class AssemblyFailedException extends RuntimeException { + private static final long serialVersionUID = -5658502514441032016L; + + public final String assemblerOutput; + public AssemblyFailedException( + String assemblerOutput) { + super("Executing assembler failed.\n" + + "Output:\n" + + assemblerOutput); + this.assemblerOutput = assemblerOutput; + } +} diff --git a/src/cd/backend/codegen/AstCodeGenerator.java b/src/cd/backend/codegen/AstCodeGenerator.java new file mode 100644 index 0000000..cdbb510 --- /dev/null +++ b/src/cd/backend/codegen/AstCodeGenerator.java @@ -0,0 +1,58 @@ +package cd.backend.codegen; + +import cd.Main; +import cd.ir.Ast.ClassDecl; + +import java.io.Writer; +import java.util.List; + +public class AstCodeGenerator { + + protected ExprGenerator eg; + protected StmtGenerator sg; + + protected final Main main; + + protected final AssemblyEmitter emit; + protected final RegisterManager rm = new RegisterManager(); + + AstCodeGenerator(Main main, Writer out) { + this.emit = new AssemblyEmitter(out); + this.main = main; + this.eg = new ExprGenerator(this); + this.sg = new StmtGenerator(this); + } + + protected void debug(String format, Object... args) { + this.main.debug(format, args); + } + + public static AstCodeGenerator createCodeGenerator(Main main, Writer out) { + return new AstCodeGenerator(main, out); + } + + + /** + * Main method. Causes us to emit x86 assembly corresponding to {@code ast} + * into {@code file}. Throws a {@link RuntimeException} should any I/O error + * occur. + * + *

+ * The generated file will be divided into two sections: + *

    + *
  1. Prologue: Generated by {@link #emitPrefix()}. This contains any + * introductory declarations and the like. + *
  2. Body: Generated by {@link ExprGenerator}. This contains the main + * method definitions. + *
+ */ + public void go(List astRoots) { + for (ClassDecl ast : astRoots) { + sg.gen(ast); + } + } + + + protected void initMethodData() { + } +} \ No newline at end of file diff --git a/src/cd/backend/codegen/ExprGenerator.java b/src/cd/backend/codegen/ExprGenerator.java new file mode 100644 index 0000000..7632727 --- /dev/null +++ b/src/cd/backend/codegen/ExprGenerator.java @@ -0,0 +1,307 @@ +package cd.backend.codegen; + +import cd.backend.codegen.RegisterManager.Register; +import cd.ir.Ast.*; +import cd.ir.Ast.BinaryOp.BOp; +import cd.ir.ExprVisitor; +import cd.util.debug.AstOneLine; + +/** + * Generates code to evaluate expressions. After emitting the code, returns a + * String which indicates the register where the result can be found. + */ +class ExprGenerator extends ExprVisitor { + protected final AstCodeGenerator cg; + + ExprGenerator(AstCodeGenerator astCodeGenerator) { + cg = astCodeGenerator; + } + + public Register gen(Expr ast) { + return visit(ast, null); + } + + @Override + public Register visit(Expr ast, Void arg) { + try { + cg.emit.increaseIndent("Emitting " + AstOneLine.toString(ast)); + return super.visit(ast, null); + } finally { + cg.emit.decreaseIndent(); + } + + } + + @Override + public Register binaryOp(BinaryOp ast, Void arg) { + String op; + if (ast.operator == BOp.B_TIMES) + op = "imul"; + else if (ast.operator == BOp.B_PLUS) + op = "add"; + else if (ast.operator == BOp.B_MINUS) + op = "sub"; + else if (ast.operator == BOp.B_DIV) + op = "div"; + else + throw new RuntimeException("Not required (optional)"); + op += "l"; // long = 4B = 32b + + Register left, right; + int leftNeeded = ast.left().registersNeeded(); + int rightNeeded = ast.right().registersNeeded(); + if (leftNeeded > rightNeeded) { + left = cg.eg.visit(ast.left(), arg); + right = cg.eg.visit(ast.right(), arg); + } else { + right = cg.eg.visit(ast.right(), arg); + left = cg.eg.visit(ast.left(), arg); + } + + if (ast.operator == BOp.B_DIV) { + // The dividend (left) will be replaced with the quotient + // So free the divisor register (right) and return the quotient (left) + division(left, right); + cg.rm.releaseRegister(right); + return left; + } else { + // All operators take the form OP A, B --> B = B OP A --> B is left, A is right + // Therefore the result is stored in B (left) and A (right) can be freed + cg.emit.emit(op, right, left); + cg.rm.releaseRegister(right); + return left; + } + } + + /** + * Division with divide by zero check incorporated + */ + private void division(Register dividend, Register divisor) { + division(dividend, divisor, true); + } + + /** + * Special implementation for divisions, as it requires specific registers to be used + * @param dividend Maximum 32bit (even though the division is implemented as 64bit) + * @param divisor Maximum 32bit + * @param checkZero Whether to generate code that checks for division by zero or not + * @return The quotient will be in place of the dividend and the remainder in the divisor + */ + protected void division(Register dividend, Register divisor, boolean checkZero) { + if (checkZero) { + String beginDivision = cg.emit.uniqueLabel(); + cg.emit.emit("cmp", 0, divisor); + cg.emit.emit("jne", beginDivision); + // Exit with error code in case of div/0 + Interrupts.exit(cg, Interrupts.EXIT_DIV_0); + cg.emit.emitLabel(beginDivision); + } + + // D = d * q + r (dividend, divisor, quotient, remainder) + // EDX:EAX = d * EAX + EDX (before and after the div operation) + + // Take care of register use: move EAX to memory if necessary + // The stack is not needed because the division operator is not recursive + // After the division is performed, move the data back to dividend and divisor + Register d, D = Register.EAX; + if (divisor.equals(D)) { + d = cg.rm.getRegister(); + cg.emit.emitMove(divisor, d); + cg.emit.emitMove(dividend, Register.EAX); + } else { + if (!dividend.equals(D) && cg.rm.isInUse(D)) + cg.emit.emitMove(D, "(" + Interrupts.Mem.EAX.pos + ")"); + cg.emit.emitMove(dividend, D); + if (divisor.equals(Register.EDX)) { + d = cg.rm.getRegister(); + cg.emit.emitMove(divisor, d); + } + } + + // Division + cg.emit.emit("cdq"); + cg.emit.emit("idiv", divisor); + cg.emit.emitMove(Register.EAX, dividend); + cg.emit.emitMove(Register.EDX, divisor); + + Register q = Register.EAX, r = Register.EDX; + if (divisor.equals(D)) { + cg.emit.emitMove(q, dividend); + cg.emit.emitMove(r, divisor); + } else { + if (!dividend.equals(D) && cg.rm.isInUse(D)) + cg.emit.emitMove(Interrupts.Mem.EAX.pos, D); + cg.emit.emitMove(dividend, D); + if (!divisor.equals(r)) { + cg.emit.emitMove(r, divisor); + } + } + } + + @Override + public Register booleanConst(BooleanConst ast, Void arg) { + { + throw new RuntimeException("Not required"); + } + } + + @Override + public Register builtInRead(BuiltInRead ast, Void arg) { + // Holds the first digit and then is used to accumulate the total value + Register value = cg.rm.getRegister(); + // Used to mark for positive (0) or negative (1) value + Register negative = cg.rm.getRegister(); + // Holds the next digit read, once the first has been read + Register nextDigit = cg.rm.getRegister(); + + // Labels for the read structure + String removeWhitespace = cg.emit.uniqueLabel(); + String negativeFirstDigit = cg.emit.uniqueLabel(); + String inputError = cg.emit.uniqueLabel(); + String moreDigits = cg.emit.uniqueLabel(); + String endRead = cg.emit.uniqueLabel(); + String checkSign = cg.emit.uniqueLabel(); + + // By default, variable is positive (negative == 0) + cg.emit.emitMove(AssemblyEmitter.constant(0), negative); + + // Skip whitespace ( \t\n\r) at the beginning + cg.emit.emitLabel(removeWhitespace); + Interrupts.readChar(cg, value); + cg.emit.emit("cmp", '\n', value); + cg.emit.emit("je", removeWhitespace); + cg.emit.emit("cmp", ' ', value); + cg.emit.emit("je", removeWhitespace); + cg.emit.emit("cmp", '\t', value); + cg.emit.emit("je", removeWhitespace); + cg.emit.emit("cmp", '\r', value); + cg.emit.emit("je", removeWhitespace); + + // Recognize first digit, if negative read another digit to place at value + cg.emit.emit("cmp", '-', value); + cg.emit.emit("je", negativeFirstDigit); + charToIntOrJump(value, inputError); + cg.emit.emit("jmp", moreDigits); + + // Negative first digit + cg.emit.emitLabel(negativeFirstDigit); + Interrupts.readChar(cg, value); + cg.emit.emitMove(AssemblyEmitter.constant(1), negative); + charToIntOrJump(value, inputError); + + // All other digits: + // while ( (nextDigit = read()).isDigit()) + // value = value * 10 + nextDigit + cg.emit.emitLabel(moreDigits); + Interrupts.readChar(cg, nextDigit); + charToIntOrJump(nextDigit, checkSign); + cg.emit.emit("imul", 10, value); + cg.emit.emit("add", nextDigit, value); + cg.emit.emit("jmp", moreDigits); + + // No number can be read (non-digit first or following the '-') + cg.emit.emitLabel(inputError); + Interrupts.exit(cg, Interrupts.EXIT_INPUT_MISMATCH); + + // Sign check and end + cg.emit.emitLabel(checkSign); + cg.emit.emit("cmp", 0, negative); + cg.emit.emit("je", endRead); + cg.emit.emit("negl", value); + cg.emit.emitLabel(endRead); + + // Register bookkeeping and return read value + cg.rm.releaseRegister(negative); + cg.rm.releaseRegister(nextDigit); + return value; + } + + private void charToIntOrJump(Register reg, String jumpLabel) { + cg.emit.emit("cmp", '0', reg); + cg.emit.emit("jl", jumpLabel); + cg.emit.emit("cmp", '9', reg); + cg.emit.emit("jg", jumpLabel); + cg.emit.emit("sub", '0', reg); + } + + @Override + public Register cast(Cast ast, Void arg) { + { + throw new RuntimeException("Not required"); + } + } + + @Override + public Register index(Index ast, Void arg) { + { + throw new RuntimeException("Not required"); + } + } + + @Override + public Register intConst(IntConst ast, Void arg) { + Register r = cg.rm.getRegister(); + cg.emit.emitMove(AssemblyEmitter.constant(ast.value), r); + return r; + } + + @Override + public Register field(Field ast, Void arg) { + { + throw new RuntimeException("Not required"); + } + } + + @Override + public Register newArray(NewArray ast, Void arg) { + { + throw new RuntimeException("Not required"); + } + } + + @Override + public Register newObject(NewObject ast, Void arg) { + { + throw new RuntimeException("Not required"); + } + } + + @Override + public Register nullConst(NullConst ast, Void arg) { + { + throw new RuntimeException("Not required"); + } + } + + @Override + public Register thisRef(ThisRef ast, Void arg) { + { + throw new RuntimeException("Not required"); + } + } + + @Override + public Register unaryOp(UnaryOp ast, Void arg) { + Register aux = cg.eg.visit(ast.arg(), arg); + switch (ast.operator) { + case U_PLUS: + return aux; + case U_MINUS: + cg.emit.emit("negl", aux); + return aux; + case U_BOOL_NOT: + cg.emit.emit("notl", aux); + return aux; + default: + throw new RuntimeException("Not implemented"); + } + } + + @Override + public Register var(Var ast, Void arg) { + Register r = cg.rm.getRegister(); + cg.emit.emitMove(ast.getLabel(), r); + return r; + } + +} diff --git a/src/cd/backend/codegen/Interrupts.java b/src/cd/backend/codegen/Interrupts.java new file mode 100644 index 0000000..c1c2992 --- /dev/null +++ b/src/cd/backend/codegen/Interrupts.java @@ -0,0 +1,97 @@ +package cd.backend.codegen; + +import cd.backend.codegen.RegisterManager.Register; + +import static cd.backend.codegen.Interrupts.Mem.BUFFER; + +public class Interrupts { + + protected static final int EXIT_OK = 0, EXIT_DIV_0 = 7, EXIT_INPUT_MISMATCH = 8; + protected static final int STDIN = 0, STDOUT = 1, STDERR = 2; + protected static final int I_EXIT = 1, I_READ = 3, I_WRITE = 4; + + protected enum Mem { + BUFFER("io_buffer"), EAX("eax"); + + String pos; + Mem(String pos) { + this.pos = pos; + } + + @Override + public String toString() { + return pos; + } + } + + /** + * Write auxiliary data variables needed for I/O and division + * @param cg AstCodeGenerator to print instructions + */ + protected static void reserveMemory(AstCodeGenerator cg) { + for (Mem m : Mem.values()) { + cg.emit.emitLabel(m.pos); + cg.emit.emitConstantData("0"); + } + } + + /** + * Writes a single character passed as parameter + * @param cg AstCodeGenerator to print instructions + * @param i Character code in ASCII (or single casted character) + */ + protected static void printChar(AstCodeGenerator cg, int i) { + cg.emit.emitMove(AssemblyEmitter.constant(i), BUFFER.pos); + printChar(cg); + } + + /** + * Writes a single character from the BUFFER memory pos to stdout + * @param cg AstCodeGenerator to print instructions + */ + protected static void printChar(AstCodeGenerator cg) { + Register[] needed = {Register.EAX, Register.EBX, Register.ECX, Register.EDX}; + for (int i = 0; i < needed.length; i++) + if (cg.rm.isInUse(needed[i])) + cg.emit.emit("push", needed[i]); + cg.emit.emitMove(AssemblyEmitter.constant(I_WRITE), Register.EAX); + cg.emit.emitMove(AssemblyEmitter.constant(STDOUT), Register.EBX); + cg.emit.emitMove("$" + BUFFER.pos, Register.ECX); + cg.emit.emitMove(AssemblyEmitter.constant(1), Register.EDX); + cg.emit.emit("int", 0x80); + for (int i = needed.length - 1; i >= 0; i--) + if (cg.rm.isInUse(needed[i])) + cg.emit.emit("pop", needed[i]); + } + + /** + * Reads a single character from stdin and places it in the + * memory pos BUFFER + */ + protected static void readChar(AstCodeGenerator cg, Register destination) { + Register[] needed = {Register.EAX, Register.EBX, Register.ECX, Register.EDX}; + for (int i = 0; i < needed.length; i++) + if (cg.rm.isInUse(needed[i])) + cg.emit.emit("push", needed[i]); + cg.emit.emitMove(AssemblyEmitter.constant(I_READ), Register.EAX); + cg.emit.emitMove(AssemblyEmitter.constant(STDIN), Register.EBX); + cg.emit.emitMove("$" + BUFFER.pos, Register.ECX); + cg.emit.emitMove(AssemblyEmitter.constant(1), Register.EDX); + cg.emit.emit("int", 0x80); + for (int i = needed.length - 1; i >= 0; i--) + if (cg.rm.isInUse(needed[i])) + cg.emit.emit("pop", needed[i]); + cg.emit.emitMove(BUFFER.pos, destination); + } + + /** + * Generates a exit interrupt with the code provided + * @param cg AstCodeGenerator to print instructions + * @param exitCode Number to use as exit code (can use constants in this class) + */ + protected static void exit(AstCodeGenerator cg, int exitCode) { + cg.emit.emitMove(AssemblyEmitter.constant(I_EXIT), Register.EAX); + cg.emit.emitMove(AssemblyEmitter.constant(exitCode), Register.EBX); + cg.emit.emit("int", 0x80); + } +} diff --git a/src/cd/backend/codegen/RegisterManager.java b/src/cd/backend/codegen/RegisterManager.java new file mode 100644 index 0000000..02c96b9 --- /dev/null +++ b/src/cd/backend/codegen/RegisterManager.java @@ -0,0 +1,123 @@ +package cd.backend.codegen; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Simple class that manages the set of currently used + * and unused registers + */ +public class RegisterManager { + private List registers = new ArrayList(); + + // lists of register to save by the callee and the caller + public static final Register CALLEE_SAVE[] = new Register[]{Register.ESI, + Register.EDI, Register.EBX}; + public static final Register CALLER_SAVE[] = new Register[]{Register.EAX, + Register.ECX, Register.EDX}; + + // list of general purpose registers + public static final Register GPR[] = new Register[]{Register.EAX, Register.EBX, + Register.ECX, Register.EDX, Register.ESI, Register.EDI}; + + // special purpose registers + public static final Register BASE_REG = Register.EBP; + public static final Register STACK_REG = Register.ESP; + + public static final int SIZEOF_REG = 4; + + + public enum Register { + EAX("%eax", ByteRegister.EAX), EBX("%ebx", ByteRegister.EBX), ECX( + "%ecx", ByteRegister.ECX), EDX("%edx", ByteRegister.EDX), ESI( + "%esi", null), EDI("%edi", null), EBP("%ebp", null), ESP( + "%esp", null); + + public final String repr; + private final ByteRegister lowByteVersion; + + private Register(String repr, ByteRegister bv) { + this.repr = repr; + this.lowByteVersion = bv; + } + + @Override + public String toString() { + return repr; + } + + /** + * determines if this register has an 8bit version + */ + public boolean hasLowByteVersion() { + return lowByteVersion != null; + } + + /** + * Given a register like {@code %eax} returns {@code %al}, but doesn't + * work for {@code %esi} and {@code %edi}! + */ + public ByteRegister lowByteVersion() { + assert hasLowByteVersion(); + return lowByteVersion; + } + } + + public enum ByteRegister { + EAX("%al"), EBX("%bl"), ECX("%cl"), EDX("%dl"); + + public final String repr; + + private ByteRegister(String repr) { + this.repr = repr; + } + + @Override + public String toString() { + return repr; + } + } + + /** + * Reset all general purpose registers to free + */ + public void initRegisters() { + registers.clear(); + registers.addAll(Arrays.asList(GPR)); + } + + /** + * returns a free register and marks it as used + */ + public Register getRegister() { + int last = registers.size() - 1; + if (last < 0) + throw new AssemblyFailedException( + "Program requires too many registers"); + + return registers.remove(last); + } + + /** + * marks a currently used register as free + */ + public void releaseRegister(Register reg) { + assert !registers.contains(reg); + registers.add(reg); + } + + /** + * Returns whether the register is currently non-free + */ + public boolean isInUse(Register reg) { + return !registers.contains(reg); + } + + /** + * returns the number of free registers + */ + public int availableRegisters() { + return registers.size(); + } +} \ No newline at end of file diff --git a/src/cd/backend/codegen/StmtGenerator.java b/src/cd/backend/codegen/StmtGenerator.java new file mode 100644 index 0000000..acfa300 --- /dev/null +++ b/src/cd/backend/codegen/StmtGenerator.java @@ -0,0 +1,206 @@ +package cd.backend.codegen; + +import cd.backend.codegen.RegisterManager.Register; +import cd.ir.Ast; +import cd.ir.Ast.*; +import cd.ir.AstVisitor; +import cd.util.debug.AstOneLine; + +import java.util.*; + +/** + * Generates code to process statements and declarations. + */ +class StmtGenerator extends AstVisitor { + protected final AstCodeGenerator cg; + + StmtGenerator(AstCodeGenerator astCodeGenerator) { + cg = astCodeGenerator; + } + + public void gen(Ast ast) { + visit(ast, null); + } + + @Override + public Register visit(Ast ast, Void arg) { + try { + cg.emit.increaseIndent("Emitting " + AstOneLine.toString(ast)); + return super.visit(ast, arg); + } finally { + cg.emit.decreaseIndent(); + } + } + + /** + * Declares a variable in the data segment. This method was implemented because it was necessary + * to create the data segment + * @param ast Variable declaration node + * @param arg Extra arguments + * @return null + */ + @Override + public Register varDecl(Ast.VarDecl ast, Void arg) { + cg.emit.emitLabel(ast.getLabel()); + cg.emit.emitConstantData("0"); + return null; + } + + @Override + public Register methodCall(MethodCall ast, Void dummy) { + { + throw new RuntimeException("Not required"); + } + } + + @Override + public Register methodDecl(MethodDecl ast, Void arg) { + // Prologue: in future version a more complex prologue that + // takes care of parameters and return values is necessary + // Method code + cg.rm.initRegisters(); + cg.emit.emitRaw(".section .data"); + Interrupts.reserveMemory(cg); + // Variables + cg.sg.visit(ast.decls(), arg); + cg.emit.emitRaw(".section .text"); + cg.emit.emitRaw(".globl " + ast.name); + cg.emit.emitLabel(ast.name); + Register result = cg.sg.visit(ast.body(), arg); + // Epilogue: not needed in the simple HW1 + return null; + } + + @Override + public Register ifElse(IfElse ast, Void arg) { + { + throw new RuntimeException("Not required"); + } + } + + @Override + public Register whileLoop(WhileLoop ast, Void arg) { + { + throw new RuntimeException("Not required"); + } + } + + @Override + public Register assign(Assign ast, Void arg) { + // Because we only handle very simple programs in HW1, + // you can just emit the prologue here! + Register value = cg.eg.visit(ast.right(), arg); + Ast.Var variable = (Var) ast.left(); + cg.emit.emitMove(value, variable.getLabel()); + cg.rm.releaseRegister(value); + return null; + } + + @Override + public Register builtInWrite(BuiltInWrite ast, Void arg) { + String printNonZero = cg.emit.uniqueLabel(); + String end = cg.emit.uniqueLabel(); + String printPositive = cg.emit.uniqueLabel(); + String stackLoop = cg.emit.uniqueLabel(); + String printLoop = cg.emit.uniqueLabel(); + + Register value = cg.eg.visit(ast.arg(), arg); + // Begin: decide if print 0 or print number + cg.emit.emit("cmp", 0, value); + cg.emit.emit("jne", printNonZero); // value != 0 + + // Print 0 + Interrupts.printChar(cg, '0'); // print 0 + cg.emit.emit("jmp", end); + + // Not 0: positive or negative? + cg.emit.emitLabel(printNonZero); + cg.emit.emit("jg", printPositive); // value > 0 + + // Number is negative, print '-' then number (with sign changed) + Interrupts.printChar(cg, '-'); + cg.emit.emit("negl", value); + + // Print number: extract and print all digits of number + cg.emit.emitLabel(printPositive); + + // In order to avoid using EAX and EDX, we are going to guarantee + // that there will be at least 2 other registers available + List auxRegs = Arrays.asList(Register.EDI, Register.ESI); + List divRegs = Arrays.asList(Register.EAX, Register.EDX); + Map isPushed = new HashMap<>(); + boolean usingAuxRegs = cg.rm.availableRegisters() < 4; + if (usingAuxRegs) + for (int i = 0; i < auxRegs.size(); i++) + isPushed.put(auxRegs.get(i), cg.rm.isInUse(auxRegs.get(i))); + + // Free EAX and EDX for div operand use + isPushed.put(Register.EAX, cg.rm.isInUse(Register.EAX) && !value.equals(Register.EAX)); + isPushed.put(Register.EDX, cg.rm.isInUse(Register.EDX)); + + // Push all elements + for (Register r : isPushed.keySet()) + if (isPushed.get(r)) + cg.emit.emit("push", r); + + // Force counter and divisor to not be EDX/EAX, as they need to coexist + Register counter = cg.rm.getRegister(); + Register divisor = cg.rm.getRegister(); + while (divRegs.contains(counter)) { + Register aux = cg.rm.getRegister(); + cg.rm.releaseRegister(counter); + counter = aux; + } + while (divRegs.contains(divisor)) { + Register aux = cg.rm.getRegister(); + cg.rm.releaseRegister(divisor); + divisor = aux; + } + + // Divide by 10 to extract the remainders and push them to the stack + // Registers used: EAX is the remaining digits (initially the value) + // in div it is used for the lower half of the dividend and for quotient + // divisor + // EDX is used as the high half of the dividend and as remainder + cg.emit.emitMove(value, Register.EAX); + cg.rm.releaseRegister(value); + cg.emit.emitMove(AssemblyEmitter.constant(0), counter); + + cg.emit.emitLabel(stackLoop); + cg.emit.emitMove(AssemblyEmitter.constant(0), Register.EDX); + cg.emit.emitMove(AssemblyEmitter.constant(10), divisor); + cg.emit.emit("div", divisor); + cg.emit.emit("add", '0', Register.EDX); + cg.emit.emit("push", Register.EDX); + cg.emit.emit("inc", counter); + cg.emit.emit("cmp", 0, Register.EAX); + cg.emit.emit("je", printLoop); + cg.emit.emit("jmp", stackLoop); + // Release divisor register + cg.rm.releaseRegister(divisor); + + // Print digits from the stack + cg.emit.emitLabel(printLoop); + cg.emit.emit("cmp", 0, counter); + cg.emit.emit("jz", end); + cg.emit.emit("dec", counter); + cg.emit.emit("pop", Interrupts.Mem.BUFFER.pos); + Interrupts.printChar(cg); + cg.emit.emit("jmp", printLoop); + cg.emit.emitLabel(end); + + cg.rm.releaseRegister(counter); + + // Restore original registers + for (Register r : isPushed.keySet()) + if (isPushed.get(r)) + cg.emit.emit("pop", r); + return null; + } + + @Override + public Register builtInWriteln(BuiltInWriteln ast, Void arg) { + Interrupts.printChar(cg, '\n'); + return null; + } +} diff --git a/src/cd/frontend/parser/.gitignore b/src/cd/frontend/parser/.gitignore new file mode 100644 index 0000000..3713151 --- /dev/null +++ b/src/cd/frontend/parser/.gitignore @@ -0,0 +1,6 @@ +/Javali.tokens +/JavaliBaseVisitor.java +/JavaliLexer.java +/JavaliLexer.tokens +/JavaliParser.java +/JavaliVisitor.java diff --git a/src/cd/frontend/parser/ParseFailure.java b/src/cd/frontend/parser/ParseFailure.java new file mode 100644 index 0000000..71ae266 --- /dev/null +++ b/src/cd/frontend/parser/ParseFailure.java @@ -0,0 +1,9 @@ +package cd.frontend.parser; + +public class ParseFailure extends RuntimeException { + private static final long serialVersionUID = -949992757679367939L; + public ParseFailure(int line, String string) { + super(String.format("Parse failure on line %d: %s", + line, string)); + } +} diff --git a/src/cd/ir/Ast.java b/src/cd/ir/Ast.java new file mode 100644 index 0000000..300fb97 --- /dev/null +++ b/src/cd/ir/Ast.java @@ -0,0 +1,752 @@ +package cd.ir; + +import cd.util.Pair; +import cd.util.debug.AstOneLine; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public abstract class Ast { + + /** + * The list of children AST nodes. Typically, this list is of a fixed size: its contents + * can also be accessed using the various accessors defined on the Ast subtypes. + * + *

Note: this list may contain null pointers! + */ + public final List rwChildren; + final String VAR_LABEL_FORMAT = "var_%s"; + + protected Ast(int fixedCount) { + if (fixedCount == -1) + this.rwChildren = new ArrayList(); + else + this.rwChildren = Arrays.asList(new Ast[fixedCount]); + } + + /** + * Returns a copy of the list of children for this node. The result will + * never contain null pointers. + */ + public List children() { + ArrayList result = new ArrayList(); + for (Ast n : rwChildren) { + if (n != null) result.add(n); + } + return result; + } + + /** + * Returns a new list containing all children AST nodes + * that are of the given type. + */ + public List childrenOfType(Class C) { + List res = new ArrayList(); + for (Ast c : children()) { + if (C.isInstance(c)) + res.add(C.cast(c)); + } + return res; + } + + + /** Accept method for the pattern Visitor. */ + public abstract R accept(AstVisitor visitor, A arg); + + /** Convenient debugging printout */ + @Override + public String toString() { + return String.format( + "(%s)@%x", + AstOneLine.toString(this), + System.identityHashCode(this)); + } + + // _________________________________________________________________ + // Expressions + + /** Base class for all expressions */ + public static abstract class Expr extends Ast { + + int needed = -1; + + protected Expr(int fixedCount) { + super(fixedCount); + } + + @Override + public R accept(AstVisitor visitor, A arg) { + return this.accept((ExprVisitor) visitor, arg); + } + + public abstract R accept(ExprVisitor visitor, A arg); + + /** + * Registers needed to perform the operation represented by this Expr node + * If not already run, the cost is visiting all subnodes, else the cost is constant + * @return Minimum number of registers needed + */ + public abstract int registersNeeded(); + + /** Copies any non-AST fields. */ + protected E postCopy(E item) { + return item; + } + } + + /** Base class used for exprs with left/right operands. + * We use this for all expressions that take strictly two operands, + * such as binary operators or array indexing. */ + public static abstract class LeftRightExpr extends Expr { + + public LeftRightExpr(Expr left, Expr right) { + super(2); + assert left != null; + assert right != null; + setLeft(left); + setRight(right); + } + + @Override + public int registersNeeded() { + if (needed != -1) + return needed; + int leftNeed = left().registersNeeded(); + int rightNeed = right().registersNeeded(); + if (leftNeed > rightNeed) + return needed = leftNeed; + else if (leftNeed < rightNeed) + return needed = rightNeed; + else + return needed = ++rightNeed; + } + + public Expr left() { return (Expr) this.rwChildren.get(0); } + public void setLeft(Expr node) { this.rwChildren.set(0, node); } + + public Expr right() { return (Expr) this.rwChildren.get(1); } + public void setRight(Expr node) { this.rwChildren.set(1, node); } + } + + /** Base class used for expressions with a single argument */ + public static abstract class ArgExpr extends Expr { + + public ArgExpr(Expr arg) { + super(1); + assert arg != null; + setArg(arg); + } + + @Override + public int registersNeeded() { + return arg().registersNeeded(); + } + + public Expr arg() { return (Expr) this.rwChildren.get(0); } + public void setArg(Expr node) { this.rwChildren.set(0, node); } + + } + + /** Base class used for things with no arguments */ + protected static abstract class LeafExpr extends Expr { + public LeafExpr() { + super(0); + } + + @Override + public int registersNeeded() { + return 1; + } + } + + /** Represents {@code this}, the current object */ + public static class ThisRef extends LeafExpr { + + @Override + public R accept(ExprVisitor visitor, A arg) { + return visitor.thisRef(this, arg); + } + + } + + /** A binary operation combining a left and right operand, + * such as "1+2" or "3*4" */ + public static class BinaryOp extends LeftRightExpr { + + public static enum BOp { + + B_TIMES("*"), + B_DIV("/"), + B_MOD("%"), + B_PLUS("+"), + B_MINUS("-"), + B_AND("&&"), + B_OR("||"), + B_EQUAL("=="), + B_NOT_EQUAL("!="), + B_LESS_THAN("<"), + B_LESS_OR_EQUAL("<="), + B_GREATER_THAN(">"), + B_GREATER_OR_EQUAL(">="); + + public String repr; + private BOp(String repr) { this.repr = repr; } + + /** + * Note that this method ignores short-circuit evaluation of boolean + * AND/OR. + * + * @return true iff A op B == B op A for this + * operator. + */ + public boolean isCommutative() { + switch(this) { + case B_PLUS: + case B_TIMES: + case B_AND: + case B_OR: + case B_EQUAL: + case B_NOT_EQUAL: + return true; + default: + return false; + } + } + }; + + public BOp operator; + + public BinaryOp(Expr left, BOp operator, Expr right) { + super(left, right); + this.operator = operator; + } + + @Override + public R accept(ExprVisitor visitor, A arg) { + return visitor.binaryOp(this, arg); + } + + } + + /** A Cast from one type to another: {@code (typeName)arg} */ + public static class Cast extends ArgExpr { + + public String typeName; + + public Cast(Expr arg, String typeName) { + super(arg); + this.typeName = typeName; + } + + @Override + public R accept(ExprVisitor visitor, A arg) { + return visitor.cast(this, arg); + } + + } + + public static class IntConst extends LeafExpr { + + public final int value; + public IntConst(int value) { + this.value = value; + } + + @Override + public R accept(ExprVisitor visitor, A arg) { + return visitor.intConst(this, arg); + } + + } + + public static class BooleanConst extends LeafExpr { + + public final boolean value; + public BooleanConst(boolean value) { + this.value = value; + } + + @Override + public R accept(ExprVisitor visitor, A arg) { + return visitor.booleanConst(this, arg); + } + + } + + public static class NullConst extends LeafExpr { + + @Override + public R accept(ExprVisitor visitor, A arg) { + return visitor.nullConst(this, arg); + } + + } + + public static class Field extends ArgExpr { + + public final String fieldName; + + public Field(Expr arg, String fieldName) { + super(arg); + assert arg != null && fieldName != null; + this.fieldName = fieldName; + } + + @Override + public R accept(ExprVisitor visitor, A arg) { + return visitor.field(this, arg); + } + + } + + public static class Index extends LeftRightExpr { + + public Index(Expr array, Expr index) { + super(array, index); + } + + @Override + public R accept(ExprVisitor visitor, A arg) { + return visitor.index(this, arg); + } + + } + + public static class NewObject extends LeafExpr { + + /** Name of the type to be created */ + public String typeName; + + public NewObject(String typeName) { + this.typeName = typeName; + } + + @Override + public R accept(ExprVisitor visitor, A arg) { + return visitor.newObject(this, arg); + } + + } + + public static class NewArray extends ArgExpr { + + /** Name of the type to be created: must be an array type */ + public String typeName; + + public NewArray(String typeName, Expr capacity) { + super(capacity); + this.typeName = typeName; + } + + @Override + public R accept(ExprVisitor visitor, A arg) { + return visitor.newArray(this, arg); + } + + } + + public static class UnaryOp extends ArgExpr { + + public static enum UOp { + U_PLUS("+"), + U_MINUS("-"), + U_BOOL_NOT("!"); + public String repr; + private UOp(String repr) { this.repr = repr; } + }; + + public final UOp operator; + + public UnaryOp(UOp operator, Expr arg) { + super(arg); + this.operator = operator; + } + + @Override + public R accept(ExprVisitor visitor, A arg) { + return visitor.unaryOp(this, arg); + } + + } + + public static class Var extends LeafExpr { + + public String name; + + /** + * Use this constructor to build an instance of this AST + * in the parser. + */ + public Var(String name) { + this.name = name; + } + + public String getLabel() { + return String.format(VAR_LABEL_FORMAT, name); + } + + @Override + public R accept(ExprVisitor visitor, A arg) { + return visitor.var(this, arg); + } + + } + + public static class BuiltInRead extends LeafExpr { + + @Override + public R accept(ExprVisitor visitor, A arg) { + return visitor.builtInRead(this, arg); + } + + } + + public static class MethodCallExpr extends Expr { + + public String methodName; + + public MethodCallExpr(Expr rcvr, String methodName, List arguments) { + super(-1); + assert rcvr != null && methodName != null && arguments != null; + this.methodName = methodName; + this.rwChildren.add(rcvr); + this.rwChildren.addAll(arguments); + } + + /** Returns the receiver of the method call. + * i.e., for a method call {@code a.b(c,d)} returns {@code a}. */ + public Expr receiver() { return (Expr) this.rwChildren.get(0); } + + /** Changes the receiver of the method call. + * i.e., for a method call {@code a.b(c,d)} changes {@code a}. */ + public void setReceiver(Expr rcvr) { this.rwChildren.set(0, rcvr); } + + /** Returns all arguments to the method, including the receiver. + * i.e, for a method call {@code a.b(c,d)} returns {@code [a, c, d]} */ + public List allArguments() + { + ArrayList result = new ArrayList(); + for (Ast chi : this.rwChildren) + result.add((Expr) chi); + return Collections.unmodifiableList(result); + } + + /** Returns all arguments to the method, without the receiver. + * i.e, for a method call {@code a.b(c,d)} returns {@code [c, d]} */ + public List argumentsWithoutReceiver() + { + ArrayList result = new ArrayList(); + for (int i = 1; i < this.rwChildren.size(); i++) + result.add((Expr) this.rwChildren.get(i)); + return Collections.unmodifiableList(result); + } + + + @Override + public R accept(ExprVisitor visitor, A arg) { + return visitor.methodCall(this, arg); + } + + @Override + public int registersNeeded() { + throw new RuntimeException("Not implemented"); + } + + } + + // _________________________________________________________________ + // Statements + + /** Interface for all statements */ + public static abstract class Stmt extends Ast { + protected Stmt(int fixedCount) { + super(fixedCount); + } + } + + /** Represents an empty statement: has no effect. */ + public static class Nop extends Stmt { + + public Nop() { + super(0); + } + + @Override + public R accept(AstVisitor visitor, A arg) { + return visitor.nop(this, arg); + } + + } + + /** An assignment from {@code right()} to the location + * represented by {@code left()}. + */ + public static class Assign extends Stmt { + + public Assign(Expr left, Expr right) { + super(2); + assert left != null && right != null; + setLeft(left); + setRight(right); + } + + public Expr left() { return (Expr) this.rwChildren.get(0); } + public void setLeft(Expr node) { this.rwChildren.set(0, node); } + + public Expr right() { return (Expr) this.rwChildren.get(1); } + public void setRight(Expr node) { this.rwChildren.set(1, node); } + + @Override + public R accept(AstVisitor visitor, A arg) { + return visitor.assign(this, arg); + } + + } + + public static class IfElse extends Stmt { + + public IfElse(Expr cond, Ast then, Ast otherwise) { + super(3); + assert cond != null && then != null && otherwise != null; + setCondition(cond); + setThen(then); + setOtherwise(otherwise); + } + + public Expr condition() { return (Expr) this.rwChildren.get(0); } + public void setCondition(Expr node) { this.rwChildren.set(0, node); } + + public Ast then() { return this.rwChildren.get(1); } + public void setThen(Ast node) { this.rwChildren.set(1, node); } + + public Ast otherwise() { return this.rwChildren.get(2); } + public void setOtherwise(Ast node) { this.rwChildren.set(2, node); } + + @Override + public R accept(AstVisitor visitor, A arg) { + return visitor.ifElse(this, arg); + } + + } + + + public static class ReturnStmt extends Stmt { + + public ReturnStmt(Expr arg) { + super(1); + setArg(arg); + } + + public Expr arg() { return (Expr) this.rwChildren.get(0); } + public void setArg(Expr node) { this.rwChildren.set(0, node); } + + @Override + public R accept(AstVisitor visitor, A arg) { + return visitor.returnStmt(this, arg); + } + + } + + public static class BuiltInWrite extends Stmt { + + public BuiltInWrite(Expr arg) { + super(1); + assert arg != null; + setArg(arg); + } + + public Expr arg() { return (Expr) this.rwChildren.get(0); } + public void setArg(Expr node) { this.rwChildren.set(0, node); } + + @Override + public R accept(AstVisitor visitor, A arg) { + return visitor.builtInWrite(this, arg); + } + + } + + public static class BuiltInWriteln extends Stmt { + + public BuiltInWriteln() { + super(0); + } + + @Override + public R accept(AstVisitor visitor, A arg) { + return visitor.builtInWriteln(this, arg); + } + + } + + public static class MethodCall extends Stmt { + + public MethodCall(MethodCallExpr mce) { + super(1); + this.rwChildren.set(0, mce); + } + + public MethodCallExpr getMethodCallExpr() { + return (MethodCallExpr)this.rwChildren.get(0); + } + + @Override + public R accept(AstVisitor visitor, A arg) { + return visitor.methodCall(this, arg); + } + + } + + public static class WhileLoop extends Stmt { + + public WhileLoop(Expr condition, Ast body) { + super(2); + assert condition != null && body != null; + setCondition(condition); + setBody(body); + } + + public Expr condition() { return (Expr) this.rwChildren.get(0); } + public void setCondition(Expr cond) { this.rwChildren.set(0, cond); } + + public Ast body() { return this.rwChildren.get(1); } + public void setBody(Ast body) { this.rwChildren.set(1, body); } + + @Override + public R accept(AstVisitor visitor, A arg) { + return visitor.whileLoop(this, arg); + } + } + + // _________________________________________________________________ + // Declarations + + /** Interface for all declarations */ + public static abstract class Decl extends Ast { + protected Decl(int fixedCount) { + super(fixedCount); + } + } + + public static class VarDecl extends Decl { + + public String type; + public String name; + public VarDecl(String type, String name) { + this(0, type, name); + } + + protected VarDecl(int num, String type, String name) { + super(num); + this.type = type; + this.name = name; + } + + public String getLabel() { + return String.format(VAR_LABEL_FORMAT, name); + } + + @Override + public R accept(AstVisitor visitor, A arg) { + return visitor.varDecl(this, arg); + } + + } + + /** Used in {@link MethodDecl} to group together declarations + * and method bodies. */ + public static class Seq extends Decl { + + public Seq(List nodes) { + super(-1); + if (nodes != null) this.rwChildren.addAll(nodes); + } + + /** Grant access to the raw list of children for seq nodes */ + public List rwChildren() { + return this.rwChildren; + } + + @Override + public R accept(AstVisitor visitor, A arg) { + return visitor.seq(this, arg); + } + } + + public static class MethodDecl extends Decl { + + public String returnType; + public String name; + public List argumentTypes; + public List argumentNames; + public MethodDecl( + String returnType, + String name, + List> formalParams, + Seq decls, + Seq body) { + this(returnType, name, Pair.unzipA(formalParams), Pair.unzipB(formalParams), decls, body); + } + public MethodDecl( + String returnType, + String name, + List argumentTypes, + List argumentNames, + Seq decls, + Seq body) { + super(2); + this.returnType = returnType; + this.name = name; + this.argumentTypes = argumentTypes; + this.argumentNames = argumentNames; + setDecls(decls); + setBody(body); + } + + public Seq decls() { return (Seq) this.rwChildren.get(0); } + public void setDecls(Seq decls) { this.rwChildren.set(0, decls); } + + public Seq body() { return (Seq) this.rwChildren.get(1); } + public void setBody(Seq body) { this.rwChildren.set(1, body); } + + @Override + public R accept(AstVisitor visitor, A arg) { + return visitor.methodDecl(this, arg); + } + + } + + public static class ClassDecl extends Decl { + + public String name; + public String superClass; + public ClassDecl( + String name, + String superClass, + List members) { + super(-1); + this.name = name; + this.superClass = superClass; + this.rwChildren.addAll(members); + } + + public List members() { + return Collections.unmodifiableList(this.rwChildren); + } + + public List fields() { + return childrenOfType(VarDecl.class); + } // includes constants! + + public List methods() { + return childrenOfType(MethodDecl.class); + } + + @Override + public R accept(AstVisitor visitor, A arg) { + return visitor.classDecl(this, arg); + } + + } +} diff --git a/src/cd/ir/AstVisitor.java b/src/cd/ir/AstVisitor.java new file mode 100644 index 0000000..0576c24 --- /dev/null +++ b/src/cd/ir/AstVisitor.java @@ -0,0 +1,106 @@ +package cd.ir; + +import cd.ir.Ast.Decl; +import cd.ir.Ast.Expr; +import cd.ir.Ast.Stmt; + +/** A visitor that visits any kind of node */ +public class AstVisitor extends ExprVisitor { + + /** + * Recurse and process {@code ast}. It is preferred to + * call this rather than calling accept directly, since + * it can be overloaded to introduce memoization, + * for example. */ + public R visit(Ast ast, A arg) { + return ast.accept(this, arg); + } + + /** + * A handy function which visits the children of {@code ast}, + * providing "arg" to each of them. It returns the result of + * the last child in the list. It is invoked by the method + * {@link #dflt(Ast, Object)} by default. + */ + public R visitChildren(Ast ast, A arg) { + R lastValue = null; + for (Ast child : ast.children()) + lastValue = visit(child, arg); + return lastValue; + } + + /** + * The default action for default actions is to call this, + * which simply recurses to any children. Also called + * by seq() by default. */ + protected R dflt(Ast ast, A arg) { + return visitChildren(ast, arg); + } + + /** + * The default action for statements is to call this */ + protected R dfltStmt(Stmt ast, A arg) { + return dflt(ast, arg); + } + + /** + * The default action for expressions is to call this */ + protected R dfltExpr(Expr ast, A arg) { + return dflt(ast, arg); + } + + /** + * The default action for AST nodes representing declarations + * is to call this function */ + protected R dfltDecl(Decl ast, A arg) { + return dflt(ast, arg); + } + + public R assign(Ast.Assign ast, A arg) { + return dfltStmt(ast, arg); + } + + public R builtInWrite(Ast.BuiltInWrite ast, A arg) { + return dfltStmt(ast, arg); + } + + public R builtInWriteln(Ast.BuiltInWriteln ast, A arg) { + return dfltStmt(ast, arg); + } + + public R classDecl(Ast.ClassDecl ast, A arg) { + return dfltDecl(ast, arg); + } + + public R methodDecl(Ast.MethodDecl ast, A arg) { + return dfltDecl(ast, arg); + } + + public R varDecl(Ast.VarDecl ast, A arg) { + return dfltDecl(ast, arg); + } + + public R ifElse(Ast.IfElse ast, A arg) { + return dfltStmt(ast, arg); + } + + public R returnStmt(Ast.ReturnStmt ast, A arg) { + return dfltStmt(ast, arg); + } + + public R methodCall(Ast.MethodCall ast, A arg) { + return dfltStmt(ast, arg); + } + + public R nop(Ast.Nop ast, A arg) { + return dfltStmt(ast, arg); + } + + public R seq(Ast.Seq ast, A arg) { + return dflt(ast, arg); + } + + public R whileLoop(Ast.WhileLoop ast, A arg) { + return dfltStmt(ast, arg); + } +} diff --git a/src/cd/ir/ExprVisitor.java b/src/cd/ir/ExprVisitor.java new file mode 100644 index 0000000..121fd57 --- /dev/null +++ b/src/cd/ir/ExprVisitor.java @@ -0,0 +1,92 @@ +package cd.ir; + +import cd.ir.Ast.Expr; + +/** + * A visitor that only visits {@link Expr} nodes. + */ +public class ExprVisitor { + /** + * Recurse and process {@code ast}. It is preferred to + * call this rather than calling accept directly, since + * it can be overloaded to introduce memoization, + * for example. */ + public R visit(Expr ast, A arg) { + return ast.accept(this, arg); + } + + /** + * Visits all children of the expression. Relies on the fact + * that {@link Expr} nodes only contain other {@link Expr} nodes. + */ + public R visitChildren(Expr ast, A arg) { + R lastValue = null; + for (Ast child : ast.children()) + lastValue = visit((Expr)child, arg); + return lastValue; + } + + /** + * The default action for default actions is to call this, + * which simply recurses to any children. Also called + * by seq() by default. */ + protected R dfltExpr(Expr ast, A arg) { + return visitChildren(ast, arg); + } + + public R binaryOp(Ast.BinaryOp ast, A arg) { + return dfltExpr(ast, arg); + } + + public R booleanConst(Ast.BooleanConst ast, A arg) { + return dfltExpr(ast, arg); + } + + public R builtInRead(Ast.BuiltInRead ast, A arg) { + return dfltExpr(ast, arg); + } + + public R cast(Ast.Cast ast, A arg) { + return dfltExpr(ast, arg); + } + + public R field(Ast.Field ast, A arg) { + return dfltExpr(ast, arg); + } + + public R index(Ast.Index ast, A arg) { + return dfltExpr(ast, arg); + } + + public R intConst(Ast.IntConst ast, A arg) { + return dfltExpr(ast, arg); + } + + public R methodCall(Ast.MethodCallExpr ast, A arg) { + return dfltExpr(ast, arg); + } + + public R newObject(Ast.NewObject ast, A arg) { + return dfltExpr(ast, arg); + } + + public R newArray(Ast.NewArray ast, A arg) { + return dfltExpr(ast, arg); + } + + public R nullConst(Ast.NullConst ast, A arg) { + return dfltExpr(ast, arg); + } + + public R thisRef(Ast.ThisRef ast, A arg) { + return dfltExpr(ast, arg); + } + + public R unaryOp(Ast.UnaryOp ast, A arg) { + return dfltExpr(ast, arg); + } + + public R var(Ast.Var ast, A arg) { + return dfltExpr(ast, arg); + } +} diff --git a/src/cd/util/FileUtil.java b/src/cd/util/FileUtil.java new file mode 100644 index 0000000..bf5cc60 --- /dev/null +++ b/src/cd/util/FileUtil.java @@ -0,0 +1,121 @@ +package cd.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.util.ArrayList; +import java.util.List; + +public class FileUtil { + + public static String readAll(Reader ubReader) throws IOException { + try (BufferedReader bReader = new BufferedReader(ubReader)) { + StringBuilder sb = new StringBuilder(); + + while (true) { + int ch = bReader.read(); + if (ch == -1) + break; + + sb.append((char) ch); + } + return sb.toString(); + } + } + + public static String read(File file) throws IOException { + return readAll(new FileReader(file)); + } + + public static void write(File file, String text) throws IOException { + try (FileWriter writer = new FileWriter(file)) { + writer.write(text); + } + } + + public static String runCommand(File dir, String[] command, + String[] substs, String input, boolean detectError) + throws IOException { + // Substitute the substitution strings $0, $1, etc + String newCommand[] = new String[command.length]; + for (int i = 0; i < command.length; i++) { + String newItem = command[i]; + for (int j = 0; j < substs.length; j++) + newItem = newItem.replace("$" + j, substs[j]); + newCommand[i] = newItem; + } + + // Run the command in the specified directory + ProcessBuilder pb = new ProcessBuilder(newCommand); + pb.directory(dir); + pb.redirectErrorStream(true); + final Process p = pb.start(); + if (input != null && !input.equals("")) { + try (OutputStreamWriter osw = new OutputStreamWriter(p.getOutputStream())) { + osw.write(input); + } + } + + try { + final StringBuffer result = new StringBuffer(); + // thread to read stdout from child so that p.waitFor() is interruptible + // by JUnit's timeout mechanism. Otherwise it would block in readAll() + Thread t = new Thread () { + public void run() { + try { + result.append(readAll(new InputStreamReader(p.getInputStream()))); + } catch (IOException e) { + } + } + }; + t.start(); + + if (detectError) { + int err = p.waitFor(); + + // hack: same as ReferenceServer returns when + // a dynamic error occurs running the interpreter + if (err != 0) + return "Error: " + err + "\n"; + } + + t.join(); + return result.toString(); + } catch (InterruptedException e) { + return "Error: execution of " + command[0] + " got interrupted (probably timed out)\n"; + } finally { + // in case child is still running, destroy + p.destroy(); + } + } + + /** + * Finds all .javali under directory {@code testDir}, adding File objects + * into {@code result} for each one. + */ + public static void findJavaliFiles(File testDir, List 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 findJavaliFiles(File testDir) { + List result = new ArrayList(); + for (File testFile : testDir.listFiles()) { + if (testFile.getName().endsWith(".javali")) + result.add(testFile); + else if (testFile.isDirectory()) + result.addAll(findJavaliFiles(testFile)); + } + return result; + } +} diff --git a/src/cd/util/Pair.java b/src/cd/util/Pair.java new file mode 100644 index 0000000..c11ee90 --- /dev/null +++ b/src/cd/util/Pair.java @@ -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 { + public T a; + public T b; + + public Pair(T a, T b) { + this.a = a; + this.b = b; + } + + public static List> zip(List listA, List listB) { + List> res = new ArrayList>(); + for (int i = 0; i < Math.min(listA.size(), listB.size()); i++) { + res.add(new Pair(listA.get(i), listB.get(i))); + } + return res; + } + + public static List unzipA(List> list) { + List res = new ArrayList(); + for (Pair p : list) + res.add(p.a); + return res; + } + + public static List unzipB(List> list) { + List res = new ArrayList(); + for (Pair p : list) + res.add(p.b); + return res; + } + + public static String join( + List> 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(); + } +} diff --git a/src/cd/util/Tuple.java b/src/cd/util/Tuple.java new file mode 100644 index 0000000..fd377b4 --- /dev/null +++ b/src/cd/util/Tuple.java @@ -0,0 +1,16 @@ +package cd.util; + +/** Simple class for joining two objects of different type */ +public class Tuple { + 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() + ")"; + } +} diff --git a/src/cd/util/debug/AstDump.java b/src/cd/util/debug/AstDump.java new file mode 100644 index 0000000..d4a4e73 --- /dev/null +++ b/src/cd/util/debug/AstDump.java @@ -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 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> 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> 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>, Void> { + + @Override + protected List> dflt(Ast ast, Void arg) { + ArrayList> res = new ArrayList>(); + + // Get the list of fields and sort them by name: + java.lang.Class rclass = ast.getClass(); + List rflds = + Arrays.asList(rclass.getFields()); + Collections.sort(rflds, new Comparator () { + 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(rfld.getName(), value)); + } catch (IllegalArgumentException e) { + throw new RuntimeException(e); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + + return res; + } + + } + +} diff --git a/src/cd/util/debug/AstOneLine.java b/src/cd/util/debug/AstOneLine.java new file mode 100644 index 0000000..135990f --- /dev/null +++ b/src/cd/util/debug/AstOneLine.java @@ -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 { + + 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())); + } + + } +} diff --git a/test/cd/test/AbstractTestAgainstFrozenReference.java b/test/cd/test/AbstractTestAgainstFrozenReference.java new file mode 100644 index 0000000..9a966eb --- /dev/null +++ b/test/cd/test/AbstractTestAgainstFrozenReference.java @@ -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 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 testParser() throws Exception { + String parserRef = findParserRef(); + List 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 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); + } + +} diff --git a/test/cd/test/Diff.java b/test/cd/test/Diff.java new file mode 100644 index 0000000..a86b5ee --- /dev/null +++ b/test/cd/test/Diff.java @@ -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(); + } + +} diff --git a/test/cd/test/TestSamplePrograms.java b/test/cd/test/TestSamplePrograms.java new file mode 100644 index 0000000..654c6d4 --- /dev/null +++ b/test/cd/test/TestSamplePrograms.java @@ -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 testFiles() { + List result = new ArrayList(); + 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(); + + } + +}