From 72cc3206c42efb1f5a52dfcbe44d7c92eb12243d Mon Sep 17 00:00:00 2001 From: Carlos Galindo Date: Wed, 15 Jan 2020 22:34:57 +0100 Subject: [PATCH] Homework 4 --- .classpath | 4 +- .project | 2 +- .settings/org.eclipse.core.resources.prefs | 2 - Grade.txt | 39 +- README.md | 144 ++-- SemanticChecks.md | 43 -- TODO.md | 75 --- build.xml | 107 +-- javali_tests/HW4/ErrDowncast.javali | 15 + javali_tests/HW4/FibonacciRecursive.javali | 23 + javali_tests/HW4/OkArrayElementsMath.javali | 14 + javali_tests/HW4/OkCallWithParamField.javali | 20 + javali_tests/HW4/OkDifferentReads.javali | 28 + javali_tests/HW4/OkDifferentReads.javali.in | 4 + javali_tests/HW4/OkDowncast.javali | 14 + javali_tests/HW4/OkFieldInArray.javali | 29 + javali_tests/HW4/OkVirtualMethod.javali | 50 ++ javali_tests/HW4/Quicksort.javali | 81 +++ .../HW4_nop90/Array/ErrArrayIndex.javali | 12 + .../HW4_nop90/Array/ErrArrayIndex2.javali | 9 + .../HW4_nop90/Array/ErrArrayIndex3.javali | 9 + .../Array/ErrArrayNullPointerAccess.javali | 10 + .../Array/ErrArrayNullPointerAccess2.javali | 9 + .../HW4_nop90/Array/ErrArraySize.javali | 10 + .../HW4_nop90/Array/OkArrayAccess.javali | 16 + .../HW4_nop90/Array/OkArrayAccess2.javali | 15 + .../HW4_nop90/Array/OkArraySizeIsZero.javali | 8 + .../HW4_nop90/Array/OkFieldArray.javali | 12 + .../HW4_nop90/Array/OkInheritedArray.javali | 19 + .../HW4_nop90/Array/OkObjectArray.javali | 11 + .../Assignments/OkArraySubtypes.javali | 14 + .../HW4_nop90/Assignments/OkSubtypes.javali | 21 + .../HW4_nop90/Booleans/OkBooleanAndOr.javali | 8 + .../HW4_nop90/Booleans/OkEquals.javali | 31 + .../HW4_nop90/Booleans/OkEquals2.javali | 15 + .../HW4_nop90/Booleans/OkEquals3.javali | 14 + javali_tests/HW4_nop90/Booleans/OkNot.javali | 36 + .../Casts/ErrCastUnrelatedType.javali | 21 + .../Casts/ErrObjectToArrayCast.javali | 14 + .../HW4_nop90/Casts/ErrPrimitiveCast.javali | 12 + .../HW4_nop90/Casts/ErrPrimitiveCast2.javali | 11 + .../Casts/OkArrayToObjectCast.javali | 12 + .../Casts/OkArrayToObjectCast2.javali | 14 + javali_tests/HW4_nop90/Casts/OkSubtype.javali | 15 + .../HW4_nop90/Casts/OkTypeCast.javali | 26 + .../HW4_nop90/Casts/OkTypeToObjectCast.javali | 23 + .../EightVariablesWrite.javali | 2 +- .../HW4_nop90/ErrDivisionByZero.javali | 10 + .../Fields/OkInheritedFieldAccess.javali | 27 + .../HW4_nop90/Fields/OkObjectFields.javali | 19 + javali_tests/HW4_nop90/ManyRegisters.javali | 5 + javali_tests/HW4_nop90/OkAccessField.javali | 30 + javali_tests/HW4_nop90/OkAssignments.javali | 34 + .../HW4_nop90/OkAssignments.javali.in | 1 + .../OkBoolBinaryOp.javali} | 4 +- .../HW4_nop90/OkCallInheritedMethod.javali | 17 + .../Casts.javali => HW4_nop90/OkCasts.javali} | 0 javali_tests/HW4_nop90/OkEmptyBlocks.javali | 10 + javali_tests/HW4_nop90/OkEmptyWhile.javali | 5 + javali_tests/HW4_nop90/OkHideField.javali | 16 + .../OkNullAssignment.javali} | 6 +- .../HW4_nop90/OkOverwriteMainMethod.javali | 14 + javali_tests/HW4_nop90/OkRegisterUse.javali | 32 + .../OkUnaryOperators.javali} | 0 javali_tests/HW4_nop90/OkVariables.javali | 29 + .../HW4_nop90/OkZeroInitialized.javali | 9 + .../OkComplexIfElseAndReturnSubtyping.javali | 79 +++ .../ReturnTests/OkIgnoredReturn.javali | 9 + .../ReturnTests/OkMiddleReturn.javali | 21 + .../ReturnTests/OkReturnObject.javali | 22 + .../ReturnTests/OkSimpleIfElse.javali | 24 + .../method invocation/OkCallByValue.javali | 22 + .../method invocation/OkCallByValue2.javali | 18 + .../method invocation/OkCallByValue3.javali | 18 + .../OkCallInheritedMethod.javali | 17 + .../method invocation/OkMethod.javali | 19 + .../method invocation/OkMethod2.javali | 35 + javali_tests/from_prev_HW/Arrays.javali | 24 - javali_tests/from_prev_HW/Assignments.javali | 12 - javali_tests/from_prev_HW/Division.javali | 21 - .../from_prev_HW/DoubleInheritance.javali | 9 - javali_tests/from_prev_HW/Expressions.javali | 22 - .../from_prev_HW/Multiplication.javali | 31 - .../from_prev_HW/OrderOfDeclarations.javali | 8 - javali_tests/from_prev_HW/Overflow.javali | 25 - javali_tests/from_prev_HW/Overflow.javali.in | 1 - javali_tests/from_prev_HW/ParamLists.javali | 15 - javali_tests/from_prev_HW/ReadWrite.javali | 26 - javali_tests/from_prev_HW/ReadWrite.javali.in | 6 - javali_tests/from_prev_HW/Statements.javali | 34 - javali_tests/from_prev_HW/invalidCasts.javali | 12 - javali_tests/from_prev_HW/newTest.javali | 14 - .../from_prev_HW/noParentheses.javali | 24 - src/cd/Config.java | 9 - src/cd/Main.java | 38 +- src/cd/backend/ExitCode.java | 19 + src/cd/backend/codegen/AssemblyEmitter.java | 176 +++++ .../codegen/AssemblyFailedException.java | 14 + src/cd/backend/codegen/AstCodeGenerator.java | 172 +++++ src/cd/backend/codegen/ExprGenerator.java | 613 ++++++++++++++++++ src/cd/backend/codegen/Interrupts.java | 19 + src/cd/backend/codegen/Location.java | 42 ++ src/cd/backend/codegen/RegisterManager.java | 130 ++++ src/cd/backend/codegen/RegsNeededVisitor.java | 138 ++++ src/cd/backend/codegen/StmtGenerator.java | 288 ++++++++ src/cd/frontend/semantic/ExprAnalyzer.java | 346 ---------- .../frontend/semantic/InheritanceChecker.java | 75 +++ .../semantic/ReturnCheckerVisitor.java | 52 ++ .../frontend/semantic/SemanticAnalyzer.java | 326 ++++------ src/cd/frontend/semantic/SemanticFailure.java | 10 +- src/cd/frontend/semantic/StmtAnalyzer.java | 353 ---------- src/cd/frontend/semantic/SymTable.java | 76 +++ src/cd/frontend/semantic/SymbolCreator.java | 131 ++++ src/cd/frontend/semantic/TypeChecker.java | 399 ++++++++++++ src/cd/ir/Ast.java | 108 +-- src/cd/ir/AstRewriteVisitor.java | 31 + src/cd/ir/AstVisitor.java | 12 + src/cd/ir/Symbol.java | 100 +-- src/cd/util/FileUtil.java | 9 +- src/cd/util/debug/AstDump.java | 10 +- src/cd/util/debug/AstOneLine.java | 27 +- src/cd/util/debug/DumpUtils.java | 34 + .../AbstractTestAgainstFrozenReference.java | 108 ++- test/cd/{test => }/Diff.java | 2 +- test/cd/{test => }/TestSamplePrograms.java | 10 +- 125 files changed, 4200 insertions(+), 1636 deletions(-) delete mode 100644 .settings/org.eclipse.core.resources.prefs delete mode 100644 SemanticChecks.md delete mode 100644 TODO.md create mode 100644 javali_tests/HW4/ErrDowncast.javali create mode 100644 javali_tests/HW4/FibonacciRecursive.javali create mode 100644 javali_tests/HW4/OkArrayElementsMath.javali create mode 100644 javali_tests/HW4/OkCallWithParamField.javali create mode 100644 javali_tests/HW4/OkDifferentReads.javali create mode 100644 javali_tests/HW4/OkDifferentReads.javali.in create mode 100644 javali_tests/HW4/OkDowncast.javali create mode 100644 javali_tests/HW4/OkFieldInArray.javali create mode 100644 javali_tests/HW4/OkVirtualMethod.javali create mode 100644 javali_tests/HW4/Quicksort.javali create mode 100644 javali_tests/HW4_nop90/Array/ErrArrayIndex.javali create mode 100644 javali_tests/HW4_nop90/Array/ErrArrayIndex2.javali create mode 100644 javali_tests/HW4_nop90/Array/ErrArrayIndex3.javali create mode 100644 javali_tests/HW4_nop90/Array/ErrArrayNullPointerAccess.javali create mode 100644 javali_tests/HW4_nop90/Array/ErrArrayNullPointerAccess2.javali create mode 100644 javali_tests/HW4_nop90/Array/ErrArraySize.javali create mode 100644 javali_tests/HW4_nop90/Array/OkArrayAccess.javali create mode 100644 javali_tests/HW4_nop90/Array/OkArrayAccess2.javali create mode 100644 javali_tests/HW4_nop90/Array/OkArraySizeIsZero.javali create mode 100644 javali_tests/HW4_nop90/Array/OkFieldArray.javali create mode 100644 javali_tests/HW4_nop90/Array/OkInheritedArray.javali create mode 100644 javali_tests/HW4_nop90/Array/OkObjectArray.javali create mode 100644 javali_tests/HW4_nop90/Assignments/OkArraySubtypes.javali create mode 100644 javali_tests/HW4_nop90/Assignments/OkSubtypes.javali create mode 100644 javali_tests/HW4_nop90/Booleans/OkBooleanAndOr.javali create mode 100644 javali_tests/HW4_nop90/Booleans/OkEquals.javali create mode 100755 javali_tests/HW4_nop90/Booleans/OkEquals2.javali create mode 100755 javali_tests/HW4_nop90/Booleans/OkEquals3.javali create mode 100644 javali_tests/HW4_nop90/Booleans/OkNot.javali create mode 100755 javali_tests/HW4_nop90/Casts/ErrCastUnrelatedType.javali create mode 100755 javali_tests/HW4_nop90/Casts/ErrObjectToArrayCast.javali create mode 100755 javali_tests/HW4_nop90/Casts/ErrPrimitiveCast.javali create mode 100755 javali_tests/HW4_nop90/Casts/ErrPrimitiveCast2.javali create mode 100755 javali_tests/HW4_nop90/Casts/OkArrayToObjectCast.javali create mode 100755 javali_tests/HW4_nop90/Casts/OkArrayToObjectCast2.javali create mode 100755 javali_tests/HW4_nop90/Casts/OkSubtype.javali create mode 100755 javali_tests/HW4_nop90/Casts/OkTypeCast.javali create mode 100755 javali_tests/HW4_nop90/Casts/OkTypeToObjectCast.javali rename javali_tests/{from_prev_HW => HW4_nop90}/EightVariablesWrite.javali (82%) create mode 100755 javali_tests/HW4_nop90/ErrDivisionByZero.javali create mode 100644 javali_tests/HW4_nop90/Fields/OkInheritedFieldAccess.javali create mode 100755 javali_tests/HW4_nop90/Fields/OkObjectFields.javali create mode 100644 javali_tests/HW4_nop90/ManyRegisters.javali create mode 100644 javali_tests/HW4_nop90/OkAccessField.javali create mode 100644 javali_tests/HW4_nop90/OkAssignments.javali create mode 100644 javali_tests/HW4_nop90/OkAssignments.javali.in rename javali_tests/{from_prev_HW/conditionExpressions.javali => HW4_nop90/OkBoolBinaryOp.javali} (85%) create mode 100644 javali_tests/HW4_nop90/OkCallInheritedMethod.javali rename javali_tests/{from_prev_HW/Casts.javali => HW4_nop90/OkCasts.javali} (100%) create mode 100644 javali_tests/HW4_nop90/OkEmptyBlocks.javali create mode 100644 javali_tests/HW4_nop90/OkEmptyWhile.javali create mode 100644 javali_tests/HW4_nop90/OkHideField.javali rename javali_tests/{from_prev_HW/NULLTest.javali => HW4_nop90/OkNullAssignment.javali} (82%) create mode 100644 javali_tests/HW4_nop90/OkOverwriteMainMethod.javali create mode 100644 javali_tests/HW4_nop90/OkRegisterUse.javali rename javali_tests/{from_prev_HW/UnaryOperators.javali => HW4_nop90/OkUnaryOperators.javali} (100%) create mode 100755 javali_tests/HW4_nop90/OkVariables.javali create mode 100755 javali_tests/HW4_nop90/OkZeroInitialized.javali create mode 100644 javali_tests/HW4_nop90/ReturnTests/OkComplexIfElseAndReturnSubtyping.javali create mode 100644 javali_tests/HW4_nop90/ReturnTests/OkIgnoredReturn.javali create mode 100644 javali_tests/HW4_nop90/ReturnTests/OkMiddleReturn.javali create mode 100755 javali_tests/HW4_nop90/ReturnTests/OkReturnObject.javali create mode 100644 javali_tests/HW4_nop90/ReturnTests/OkSimpleIfElse.javali create mode 100755 javali_tests/HW4_nop90/method invocation/OkCallByValue.javali create mode 100755 javali_tests/HW4_nop90/method invocation/OkCallByValue2.javali create mode 100755 javali_tests/HW4_nop90/method invocation/OkCallByValue3.javali create mode 100755 javali_tests/HW4_nop90/method invocation/OkCallInheritedMethod.javali create mode 100755 javali_tests/HW4_nop90/method invocation/OkMethod.javali create mode 100755 javali_tests/HW4_nop90/method invocation/OkMethod2.javali delete mode 100644 javali_tests/from_prev_HW/Arrays.javali delete mode 100644 javali_tests/from_prev_HW/Assignments.javali delete mode 100755 javali_tests/from_prev_HW/Division.javali delete mode 100644 javali_tests/from_prev_HW/DoubleInheritance.javali delete mode 100644 javali_tests/from_prev_HW/Expressions.javali delete mode 100755 javali_tests/from_prev_HW/Multiplication.javali delete mode 100644 javali_tests/from_prev_HW/OrderOfDeclarations.javali delete mode 100755 javali_tests/from_prev_HW/Overflow.javali delete mode 100755 javali_tests/from_prev_HW/Overflow.javali.in delete mode 100644 javali_tests/from_prev_HW/ParamLists.javali delete mode 100755 javali_tests/from_prev_HW/ReadWrite.javali delete mode 100755 javali_tests/from_prev_HW/ReadWrite.javali.in delete mode 100644 javali_tests/from_prev_HW/Statements.javali delete mode 100755 javali_tests/from_prev_HW/invalidCasts.javali delete mode 100644 javali_tests/from_prev_HW/newTest.javali delete mode 100755 javali_tests/from_prev_HW/noParentheses.javali create mode 100644 src/cd/backend/ExitCode.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/Location.java create mode 100644 src/cd/backend/codegen/RegisterManager.java create mode 100644 src/cd/backend/codegen/RegsNeededVisitor.java create mode 100644 src/cd/backend/codegen/StmtGenerator.java delete mode 100644 src/cd/frontend/semantic/ExprAnalyzer.java create mode 100644 src/cd/frontend/semantic/InheritanceChecker.java create mode 100644 src/cd/frontend/semantic/ReturnCheckerVisitor.java delete mode 100644 src/cd/frontend/semantic/StmtAnalyzer.java create mode 100644 src/cd/frontend/semantic/SymTable.java create mode 100644 src/cd/frontend/semantic/SymbolCreator.java create mode 100644 src/cd/frontend/semantic/TypeChecker.java create mode 100644 src/cd/ir/AstRewriteVisitor.java create mode 100644 src/cd/util/debug/DumpUtils.java rename test/cd/{test => }/AbstractTestAgainstFrozenReference.java (66%) rename test/cd/{test => }/Diff.java (99%) rename test/cd/{test => }/TestSamplePrograms.java (89%) diff --git a/.classpath b/.classpath index 646ce8c..acfe841 100644 --- a/.classpath +++ b/.classpath @@ -2,10 +2,10 @@ - - + + diff --git a/.project b/.project index 7f068ef..7e77cd0 100644 --- a/.project +++ b/.project @@ -1,6 +1,6 @@ - Javali-HW3 + Javali-HW4 diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs deleted file mode 100644 index 99f26c0..0000000 --- a/.settings/org.eclipse.core.resources.prefs +++ /dev/null @@ -1,2 +0,0 @@ -eclipse.preferences.version=1 -encoding/=UTF-8 diff --git a/Grade.txt b/Grade.txt index 4d76e06..2432205 100644 --- a/Grade.txt +++ b/Grade.txt @@ -1,6 +1,39 @@ -Grade: 25/25 +nop90: 29/30 points -105/105 tests passed [20/20] +Comments: +I found that the following tasks were not implemented correctly or contained errors. -Submitted test cases: [5/5] +* Null pointers check doesn't work for methods: + " + class Main { + void main() { + A a; + a.m(); + } + } + class A { + void m() { } + } + " +* Side effects + If methods have side effects, then the execution order matters: + " + class Main { + void main() { + write(m1() + m2()); + writeln(); + } + + int m1() { + write(1); + return 1; + } + + int m2() { + write(2); + return 2; + } + } + " +* Can't compile programs which use many registers diff --git a/README.md b/README.md index b1ad73f..284ef07 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,111 @@ -# Compiler design -## Parser and syntactic analysis -See repo containing [Homework #2](https://svn.inf.ethz.ch/svn/trg/cd_students/ss18/teams/nop90/HW2). +# HW4: code generation +Better viewed as markdown -## Semantic analysis -This project checks a list class declaration as trees of Ast nodes for -possible semantic errors. +# VTables +They are generated when each class is visited, except for those of Object, +Object[], int[] and boolean[], which are generated in the bootstrapping. -### Design -The semantic checks described [here](./SemanticChecks.md) are implemented in three -phases. +When the object vtable is generated, so is the array table, which just +contains a reference to the supertype (in our case `Object`) and an unused +pointer to the declared type of the element's vtable. -1. Circular inheritance check (just with the names, and checking for non-existent -types) to avoid stack overflow problems in the main analysis. May throw a -`NO_SUCH_TYPE` error if the parent of a given class doesn't exist. -2. Main semantic analysis, performed by `StmtAnalyzer` and `ExprAnalyzer`, -two visitors that traverse each `ClassDecl` and all of its contents looking -for semantic errors. Some types and methods may not have been discovered -when they are accessed, and to that end the functions `findType(String)` -and `findMethod(String,ClassSymbol)` lookup both, searching unvisited class -and method declarations in order to find the corresponding symbol. -3. Lookup of start point (method `void main()` in `Main` class) + supertype pointer (Object) <-- vtable pointer + element vtable pointer (unused) -### Testing -A set of test cases (Javali programs, valid and invalid) are provided. The ones -designed specifically for this homework assignment are located in -`javali_tests/HW3_nop90`, and they cover all the code in the `cd.frontend.semantic` -and `cd.ir` packages. They cover all the kinds of errors that can arise -from a semantic analysis of Javali programs. As the order of the errors -is not determined, each test case only checks one failing condition, and -therefore many more test cases are needed for this homework than for -previous ones. +Each type's vtable contains the supertype as the first element (trait shared +with array type's vtables for easier cast type comparisons). Then they +contain all the methods available to an object of that type, including +all those inherited. As the dynamic type is not known, the number and +position of the methods is not known when executing a method, therefore +each method's pointer is prepended by the method's name hash (similar to +fields in an object). -The test files are organized in folders, with names ending in Tests or Errors, -the latter only contain test cases that result in semantic errors. + supertype pointer <-- vtable pointer + hash("method0") + method0 pointer + hash("method1") + method1 pointer + ... + hash("methodN") + methodN pointer -To run the tests that exist in the `javali_tests` folder you can simply run +# Method calls - $ ant clean clean-test compile test +The basic scheme used for method calls and the structure of the stack +frame is that described in the `__cdecl` used in GCC. In short, this is +the execution of a method call: -### Development environment -This project is designed for Eclipse and GNU/Linux, but it can be run -in other environments, as long as they have a 32bit `IA 86x assembly` compiler -and a `JVM`. Check the `Config` class for more information. +1. The caller pushes the arguments in the stack, from right-to-left, +leaving the 0th argument (the reference to `this`) at the top of the stack. +2. The caller executes `call` (the instruction pointer gets stored in the stack) +3. The callee now takes control, and its first action is to save the base +pointer to the stack and set the base pointer to the current stack pointer. +Therefore, the arguments and return address are available to the method. +4. The local variables are reserved and zeroed in the stack. +5. The CALLEE_SAVED registers are saved to the stack. +6. The method executes its code +7. The CALLEE_SAVED registers are restored to their original status. +8. The callee restores the stack and base registers to its initial statuses. +This is accomplished with a `leave` instruction. Then it returns using `ret`. +This removes the instruction pointer from the stack leaving only the arguments. +9. The caller finally removes all the arguments from the stack and obtains the +return value from EAX. -### Documentation -Available as [javadoc](javadoc/index.html). It is generated running the following command: +Therefore, the stack frame follows the following structure (N arguments +and M local variables): - $ ant clean-doc generate-doc + top of stack <-- ESP + ... + saved registers + localM <-- EBP - 4 - M * 4 + ... + local1 <-- EBP - 8 + local0 <-- EBP - 4 + Old EBP <-- EBP + Return EIP + this <-- EBP + 8 + arg0 <-- EBP + 12 + ... + argN <-- EBP + 12 + N * 4 -### Important files -* `TODO.md`: contains tasks to complete -* `SemanticChecks.md`: contains the semantic checks numbered for quick reference -* [Javali Grammar](https://www.ethz.ch/content/dam/ethz/special-interest/infk/inst-cs/lst-dam/documents/Education/Classes/Spring2018/210_Compiler_Design/Homework/javali.pdf) -* [HW3 statement](https://www.ethz.ch/content/dam/ethz/special-interest/infk/inst-cs/lst-dam/documents/Education/Classes/Spring2018/210_Compiler_Design/Homework/hw3.pdf) \ No newline at end of file +# Variable and object representation +## Primitive variables +They are represented by value, with both integers and booleans occupying +4 bytes, and booleans being represented as TRUE = 0, FALSE = 1. + +## Reference variables +Stored in the heap, using calloc (to zero-initialize them). + +### Arrays +On top of storing the array elements, some overhead is stored with it, +namely a pointer to the array vtable for casts, and the size of the +array to check the bounds of the array on accesses or assignments. +The pointer to the array points to the vtable pointer (the first +element in the object structure): + + array vtable pointer <-- var pointer + array size + element0 + element1 + ... + elementN + +### Objects + +Stores a pointer to the object's type vtable and all the fields. As the +dynamic type of the object cannot be determined and therefore the fields +not statically ordered, each field occupies 8 bytes, with the first 4 containing +the hash for the VariableSymbol that represents it and the others the +value or reference to the field. + +The hash allows hiding variables, but introduces an overhead when accessing +a field, to look for the position in which it is located. + + vtable pointer <-- var pointer + field0's hash + field0 + field1's hash + field1 + ... + fieldN's hash + fieldN diff --git a/SemanticChecks.md b/SemanticChecks.md deleted file mode 100644 index ba0401f..0000000 --- a/SemanticChecks.md +++ /dev/null @@ -1,43 +0,0 @@ -# Class and method declaration errors -1. All programs must have a method main() with void return type in the class Main (`INVALID_START_POINT`) -2. The superclass of each class exists (`NO_SUCH_TYPE`) -3. There is no circular inheritance (`CIRCULAR_INHERITANCE`) -4. No class is declared with the name `Object` (`OBJECT_CLASS_DEFINED`) -5. Unique names (`DOUBLE_DECLARATION`) for each group: - 1. Class names - 2. Field names in a single class (field shadowing is allowed) - 3. Method names in a single class (Override is allowed, overloading not) - 4. Method parameters and local variable names -6. Overridden methods must: (`INVALID_OVERRIDE`) - 1. Have the same number of parameters - 2. The return types must match - 3. The type of the parameters must match (name can differ) - -# Method body errors -1. `write()` must take one argument of type `int` (`TYPE_ERROR`) -2. `read()` and `writeln()` must take no arguments (`WRONG_NUMBER_OF_ARGUMENTS`) -3. The condition of a `while` or `if` statement must be typed `booelan` (`TYPE_ERROR`) -4. Assignments: the type of the right-hand side must be a suptype of the type of the left-hand side. (`TYPE_ERROR`) -5. Operators (`TYPE_ERROR`): - 1. `*`, `/`, `%`, `+`, `-`, `<`, `>`, `<=`, `>=` take `int` arguments only. - 2. `!`, `&&`, `||` take `boolean` arguments only. - 3. `==` and `!=` takes any couple of arguments as long as one of the types is a subtype of the other. - 4. A cast is only possible if one of the types is a subtype of the other -6. Method invocation, must match with declaration: - 1. The number of parameters (`WRONG_NUMBER_OF_ARGUMENTS`) - 2. The type of each of the parameters (`TYPE_ERROR`) -7. Arrays: - 1. When indexing, the index expression must be an `int` (`TYPE_ERROR`) - 2. When creating one, the index expression must be an `int` (`TYPE_ERROR`) -8. The constant `null` can be assigned to any reference type. -9. The type name in a `new` statement must exist (`NO_SUCH_TYPE`) -10. The left-hand side of an assignment can only be: (`NOT_ASSIGNABLE`) - 1. A variable - 2. A field - 3. Array dereferences (indexing) -11. There must be no attempt to access the field or method of a -non-object type, such as arrays or primitive types (`TYPE_ERROR`) -12. A method with a return type must have a return statement at the end -of all its paths (`MISSING_RETURN`). A while loop is not a valid return -regardless of the condition. A ifElse must have a return on both the -else and otherwise block. There may be some statements after a return. \ No newline at end of file diff --git a/TODO.md b/TODO.md deleted file mode 100644 index f95cadc..0000000 --- a/TODO.md +++ /dev/null @@ -1,75 +0,0 @@ -## Possible code changes -### General -* DONE Use more specific messages when throwing a `SemanticFailure` exception. This has the caveat that the automated testing relies on those tests? - -### StmtAnalyzer -* DONE Propagate the change of class parameter from `ExprAnalyzer`. -* DONE Complete checks for `MISSING_RETURN`, some ideas: - * DONE Assume no instructions will appear after a return statement and analyze only the last `Stmt` using other visitor - with different class parameters (for example passing the desired type and returning a `Optional`). - * DONE Decide the logic behind a return statement in a `while` loop, maybe treat it as an `if` with an empty `else` block. - * A `while` as last instruction is not a valid return. - * A `if` as last instruction must have a valid return a the end of its then and otherwise blocks. - -### ExprAnalyzer -* DONE Change the second class parameter to give method AND class information (needed by var and this nodes) - * DISCARDED Add a ClassSymbol to MethodSymbol and use it as class parameter, the problem arises when visiting a method, - should an empty method with the proper class be provided? Could also be solved by using `Symbol` as class parameter. - * DONE Create a new class that includes a `ClassSymbol` and a `MethodSymbol` and use that. Keep `MethodSymbol == null` - when appropriate. - -## Tests -This tests have been selected to increase to 100% the code coverage obtained by running the tests designed for the previous HW, -which are in folder svn/HW2/javali_tests/HW2_nop90 - -As they are mostly related to errors that haven't been triggered, each test should be separate, in order to generate all -of the errors. - -### Remaining -### Done -* access var from class or superclass -* test ! operator -* type error in unaryOp -* type error in array size -* call method from superclass -* call a method that doesn't exist -* call a method with the wrong type of params -* call a method with the wrong number of params -* assign to all possible syntactically correct options - * field - * var - * array index - * method (error) - * this (error) -* indexing both types error -* try to access a field of something that is not a class type -* typing error in cast -* AND and OR operations -* equals operators (a case that is OK and another that's not) -* binary operation with wrong type (any but == and !=) -* search for a type that doesn't exist in a method body -* don't have a Main class -* don't have a main() method in the Main class (all possibilities) - * wrong name - * wrong return value - * wrong parameters -* inherited main method (it should work) -* write something that is not an integer -* declare a variable mutiple times (local variables) -* declare a local variable with the same name as a parameter -* check MISSING_RETURN - * nested ifElse as end of method - * while as end of method - * normal returns at the end of the method - * return with further statements after it -* while with non-int condition -* use a array type that hasn't been discovered yet - * ```class A { B[] name; } class B {}``` -* name a class Object -* name a class like another class -* name a parameter like another -* override methods and try to break that - * same return type - * same number of params - * same type of params -* name a variable like another (field level) diff --git a/build.xml b/build.xml index 7beb2b1..c863af9 100644 --- a/build.xml +++ b/build.xml @@ -15,31 +15,26 @@ - - - - - - - + + - - - - - - - - - - - + + + + + + + + + + + @@ -49,70 +44,28 @@ - + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/javali_tests/HW4/ErrDowncast.javali b/javali_tests/HW4/ErrDowncast.javali new file mode 100644 index 0000000..c7cf96b --- /dev/null +++ b/javali_tests/HW4/ErrDowncast.javali @@ -0,0 +1,15 @@ +/* Test illegal downcasts */ + +class A { } +class B extends A { } + +class Main { + void main() { + A a; + B b; + a = new A(); + b = (B) a; /* Should fail at runtime */ + write(0); + writeln(); + } +} diff --git a/javali_tests/HW4/FibonacciRecursive.javali b/javali_tests/HW4/FibonacciRecursive.javali new file mode 100644 index 0000000..1f70013 --- /dev/null +++ b/javali_tests/HW4/FibonacciRecursive.javali @@ -0,0 +1,23 @@ +class Main { + void main() { + int a; + a = fib(20); + write(a); + writeln(); + } + + int fib(int n) { + + int fib; + int fib2; + + if (n <= 1) { + fib = n; + } else { + fib = fib(n-1); + fib2 = fib(n-2); + fib = fib + fib2; + } + return fib; + } +} diff --git a/javali_tests/HW4/OkArrayElementsMath.javali b/javali_tests/HW4/OkArrayElementsMath.javali new file mode 100644 index 0000000..078d722 --- /dev/null +++ b/javali_tests/HW4/OkArrayElementsMath.javali @@ -0,0 +1,14 @@ +/* Test expressions using array elements as operands */ +class Main { + int[] x; + void main() { + int i; + x = new int[3]; + x[0] = 3; + x[1] = 4; + x[2] = 5; + i = x[0] + x[1] + x[2]; + write(i); + writeln(); + } +} diff --git a/javali_tests/HW4/OkCallWithParamField.javali b/javali_tests/HW4/OkCallWithParamField.javali new file mode 100644 index 0000000..891b5c3 --- /dev/null +++ b/javali_tests/HW4/OkCallWithParamField.javali @@ -0,0 +1,20 @@ +/* Test access to parameters and fields */ +class A { + int i; + void foo(int p) { + write(p); + write(i); + writeln(); + } +} + +class Main { + void main() { + A a; + a = new A(); + a.i = 10; + a.foo(1); + a.foo(2); + a.foo(3); + } +} diff --git a/javali_tests/HW4/OkDifferentReads.javali b/javali_tests/HW4/OkDifferentReads.javali new file mode 100644 index 0000000..f1f5140 --- /dev/null +++ b/javali_tests/HW4/OkDifferentReads.javali @@ -0,0 +1,28 @@ +/* Test read() with different kinds of LHS values */ +class Main { + int x; + + void main() { + int y; + int[] arr; + + write(1); + writeln(); + + y = read(); + write(y + 1); + writeln(); + + x = read(); + write(x + 1); + writeln(); + + arr = new int[64]; + arr[x] = read(); + write(arr[x] + 1); + writeln(); + } + +} + + diff --git a/javali_tests/HW4/OkDifferentReads.javali.in b/javali_tests/HW4/OkDifferentReads.javali.in new file mode 100644 index 0000000..75f2b77 --- /dev/null +++ b/javali_tests/HW4/OkDifferentReads.javali.in @@ -0,0 +1,4 @@ +9 +51 +12 + diff --git a/javali_tests/HW4/OkDowncast.javali b/javali_tests/HW4/OkDowncast.javali new file mode 100644 index 0000000..3269e59 --- /dev/null +++ b/javali_tests/HW4/OkDowncast.javali @@ -0,0 +1,14 @@ +/* Test legal downcasts */ + +class A { } +class B extends A { } + +class Main { + void main() { + A a; + B b; + a = new B(); + b = (B) a; /* OK at runtime */ + write(0); + } +} diff --git a/javali_tests/HW4/OkFieldInArray.javali b/javali_tests/HW4/OkFieldInArray.javali new file mode 100644 index 0000000..93d01fd --- /dev/null +++ b/javali_tests/HW4/OkFieldInArray.javali @@ -0,0 +1,29 @@ +/* Test access to fields from elements of arrays */ +class A { + int field; + void foo() { + write(1); + write(field); + writeln(); + } +} + +class Main { + A[] x; + void main() { + int i; + x = new A[2]; + i = 1; + write(i); + writeln(); + + x[i] = new A(); + x[i].field = i + 1; + + i = x[1].field; + write(i); + writeln(); + + x[1].foo(); + } +} diff --git a/javali_tests/HW4/OkVirtualMethod.javali b/javali_tests/HW4/OkVirtualMethod.javali new file mode 100644 index 0000000..40ad5c7 --- /dev/null +++ b/javali_tests/HW4/OkVirtualMethod.javali @@ -0,0 +1,50 @@ +/* Test virtual method calls */ +class A { + void override() { + write(0); + writeln(); + } + void base() { + write(1); + writeln(); + } +} + +class B extends A { + void override() { + write(2); + writeln(); + } + void sub() { + write(3); + writeln(); + } +} + +class Main { + void main() { + A a; + B b; + + a = null; + b = null; + + a = new A(); + a.base(); + a.override(); + + b = new B(); + b.base(); + b.override(); + b.sub(); + + a = b; + a.base(); + a.override(); + + b.base(); + b.override(); + b.sub(); + + } +} diff --git a/javali_tests/HW4/Quicksort.javali b/javali_tests/HW4/Quicksort.javali new file mode 100644 index 0000000..57b98f8 --- /dev/null +++ b/javali_tests/HW4/Quicksort.javali @@ -0,0 +1,81 @@ +/* Overall test of arrays, loops, etc. that does a simple quicksort */ + +class Record { + int a; + void print() { + write(a); + writeln(); + } +} + +class Main { + Record [] a; + int i; + + void swap(Record r1, Record r2) { + int temp; + + temp = r1.a; + r1.a = r2.a; + r2.a = temp; + } + + void sort(int left, int right) { + int i,j; + int m; + + m = (a[left].a + a[right].a) / 2; + i = left; + j = right; + while (i <= j) { + while (a[i].a < m) { i = i+1; } + while (a[j].a > m) { j = j-1; } + if (i <= j) { + swap(a[i], a[j]); + i = i + 1; + j = j - 1; + } + } + if (left < j) { sort(left, j); } + if (i < right) { sort(i, right); } + } + + void main() { + + int SIZE; + int j; + + SIZE = 5; + + a = new Record[SIZE]; + j = 0; + while (j < SIZE) { + a[j] = new Record(); + j = j + 1; + } + a[0].a = 5; + a[1].a = 3; + a[2].a = 1; + a[3].a = 4; + a[4].a = 2; + + /* Numbers before sorting */ + j = 0; + while (j < SIZE) { + a[j].print(); + j = j + 1; + } + writeln(); + + sort(0, 4); + + /* Numbers after sorting */ + j = 0; + while (j < SIZE) { + a[j].print(); + j = j + 1; + } + writeln(); + + } +} diff --git a/javali_tests/HW4_nop90/Array/ErrArrayIndex.javali b/javali_tests/HW4_nop90/Array/ErrArrayIndex.javali new file mode 100644 index 0000000..941b8ad --- /dev/null +++ b/javali_tests/HW4_nop90/Array/ErrArrayIndex.javali @@ -0,0 +1,12 @@ +/* Test accessing arrays with invalid index*/ +class Main { + void main() { + int[] x; + x = new int[5]; + x[0] = 3; + x[1] = 4; + x[2] = 5; + + x[5] = 5; //this should fail + } +} diff --git a/javali_tests/HW4_nop90/Array/ErrArrayIndex2.javali b/javali_tests/HW4_nop90/Array/ErrArrayIndex2.javali new file mode 100644 index 0000000..de668df --- /dev/null +++ b/javali_tests/HW4_nop90/Array/ErrArrayIndex2.javali @@ -0,0 +1,9 @@ +/* Test accessing arrays with invalid index*/ +class Main { + void main() { + int[] x; + x = new int[5]; + + x[8] = 5; //this should fail + } +} diff --git a/javali_tests/HW4_nop90/Array/ErrArrayIndex3.javali b/javali_tests/HW4_nop90/Array/ErrArrayIndex3.javali new file mode 100644 index 0000000..1143c74 --- /dev/null +++ b/javali_tests/HW4_nop90/Array/ErrArrayIndex3.javali @@ -0,0 +1,9 @@ +/* Test accessing arrays with invalid index*/ +class Main { + void main() { + int[] x; + x = new int[5]; + + x[-1] = 5; //this should fail + } +} diff --git a/javali_tests/HW4_nop90/Array/ErrArrayNullPointerAccess.javali b/javali_tests/HW4_nop90/Array/ErrArrayNullPointerAccess.javali new file mode 100644 index 0000000..0a96e04 --- /dev/null +++ b/javali_tests/HW4_nop90/Array/ErrArrayNullPointerAccess.javali @@ -0,0 +1,10 @@ +/* Test access an array on a null pointer */ + +class Main { + void main() { + int[] x; + x = null; + + x[1] = 5; //this should throw null pointer exception + } +} diff --git a/javali_tests/HW4_nop90/Array/ErrArrayNullPointerAccess2.javali b/javali_tests/HW4_nop90/Array/ErrArrayNullPointerAccess2.javali new file mode 100644 index 0000000..35198f4 --- /dev/null +++ b/javali_tests/HW4_nop90/Array/ErrArrayNullPointerAccess2.javali @@ -0,0 +1,9 @@ +/* Test access an array on a null pointer */ + +class Main { + void main() { + int[] x; + + x[1] = 5; //this should throw null pointer exception + } +} diff --git a/javali_tests/HW4_nop90/Array/ErrArraySize.javali b/javali_tests/HW4_nop90/Array/ErrArraySize.javali new file mode 100644 index 0000000..f9c2891 --- /dev/null +++ b/javali_tests/HW4_nop90/Array/ErrArraySize.javali @@ -0,0 +1,10 @@ +/* Test creating an array with negative length */ +class Main { + void main() { + int[] x; + x = new int[-3]; + x[0] = 3; + x[1] = 4; + x[2] = 5; + } +} \ No newline at end of file diff --git a/javali_tests/HW4_nop90/Array/OkArrayAccess.javali b/javali_tests/HW4_nop90/Array/OkArrayAccess.javali new file mode 100644 index 0000000..f93581d --- /dev/null +++ b/javali_tests/HW4_nop90/Array/OkArrayAccess.javali @@ -0,0 +1,16 @@ +/* Test accessing arrays */ +class Main { + void main() { + int[] x; + int i; + x = new int[3]; + x[0] = 3; + x[1] = 4; + x[2] = 5; + i = x[0] + x[1] + x[2]; + x[2]=55; + write(i); + writeln(); + } +} + diff --git a/javali_tests/HW4_nop90/Array/OkArrayAccess2.javali b/javali_tests/HW4_nop90/Array/OkArrayAccess2.javali new file mode 100644 index 0000000..0ffbe55 --- /dev/null +++ b/javali_tests/HW4_nop90/Array/OkArrayAccess2.javali @@ -0,0 +1,15 @@ +/* Test creating arrays of objects and accessing a null element*/ + +class A{} + +class Main { + void main() { + A[] x; + A a; + x = new A[3]; + x[1] = new A(); + x[2] = a; + + x[2] = new A(); + } +} \ No newline at end of file diff --git a/javali_tests/HW4_nop90/Array/OkArraySizeIsZero.javali b/javali_tests/HW4_nop90/Array/OkArraySizeIsZero.javali new file mode 100644 index 0000000..5f736e7 --- /dev/null +++ b/javali_tests/HW4_nop90/Array/OkArraySizeIsZero.javali @@ -0,0 +1,8 @@ +/* Test array size=0 */ + +class Main { + void main() { + int[] x; + x = new int[0]; + } +} diff --git a/javali_tests/HW4_nop90/Array/OkFieldArray.javali b/javali_tests/HW4_nop90/Array/OkFieldArray.javali new file mode 100644 index 0000000..493e1af --- /dev/null +++ b/javali_tests/HW4_nop90/Array/OkFieldArray.javali @@ -0,0 +1,12 @@ +/* Test Arrays as Fields */ + +class Main { + int[] x; + void main() { + int i; + x = new int[3]; + x[0] = 3; + x[1] = 4; + x[2] = 5; + } +} diff --git a/javali_tests/HW4_nop90/Array/OkInheritedArray.javali b/javali_tests/HW4_nop90/Array/OkInheritedArray.javali new file mode 100644 index 0000000..7d5a116 --- /dev/null +++ b/javali_tests/HW4_nop90/Array/OkInheritedArray.javali @@ -0,0 +1,19 @@ +/* Test inherited Array */ + +class Main { + void main() { + C1 c1; + C2 c2; + c1 = new C1(); + c2 = new C2(); + c1.x = new int[3]; + c2.x = new int[4]; + } +} + + +class C1{ + int[] x; +} + +class C2 extends C1 {} \ No newline at end of file diff --git a/javali_tests/HW4_nop90/Array/OkObjectArray.javali b/javali_tests/HW4_nop90/Array/OkObjectArray.javali new file mode 100644 index 0000000..52ac4db --- /dev/null +++ b/javali_tests/HW4_nop90/Array/OkObjectArray.javali @@ -0,0 +1,11 @@ +/* Test creating arrays of objects */ + +class A{} + +class Main { + void main() { + A[] x; + x = new A[2]; + x[0] = new A(); + } +} \ No newline at end of file diff --git a/javali_tests/HW4_nop90/Assignments/OkArraySubtypes.javali b/javali_tests/HW4_nop90/Assignments/OkArraySubtypes.javali new file mode 100644 index 0000000..bbaf6b9 --- /dev/null +++ b/javali_tests/HW4_nop90/Assignments/OkArraySubtypes.javali @@ -0,0 +1,14 @@ +// any array is a subtype of Object +// assignment and 'is equal' with an object of type 'Object' is fine + +class Main { + void main() { + boolean y; + Object x; + int[] testArray; + testArray = new int[10]; + x = testArray; + testArray = null; + x = null; + } +} diff --git a/javali_tests/HW4_nop90/Assignments/OkSubtypes.javali b/javali_tests/HW4_nop90/Assignments/OkSubtypes.javali new file mode 100644 index 0000000..15781b0 --- /dev/null +++ b/javali_tests/HW4_nop90/Assignments/OkSubtypes.javali @@ -0,0 +1,21 @@ +// assignment of subtypes + +class Main { + void main() { + A a1, a2; + B b1, b2; + C c; + a1 = new A(); + b2 = new B(); + c = new C(); + a2 = a1; + b1 = c; + + } +} + +class A {} + +class B extends A {} + +class C extends B {} diff --git a/javali_tests/HW4_nop90/Booleans/OkBooleanAndOr.javali b/javali_tests/HW4_nop90/Booleans/OkBooleanAndOr.javali new file mode 100644 index 0000000..f0f70c5 --- /dev/null +++ b/javali_tests/HW4_nop90/Booleans/OkBooleanAndOr.javali @@ -0,0 +1,8 @@ +// Test boolean && and || operators + +class Main { + void main() { + boolean a, b, c; + a = b && c || a; + } +} \ No newline at end of file diff --git a/javali_tests/HW4_nop90/Booleans/OkEquals.javali b/javali_tests/HW4_nop90/Booleans/OkEquals.javali new file mode 100644 index 0000000..7dcd3a8 --- /dev/null +++ b/javali_tests/HW4_nop90/Booleans/OkEquals.javali @@ -0,0 +1,31 @@ +// Test the equal/not equal operations, one of the types must be a subtype of the other + +class Main { + void main() { + C1 c1; + C2 c2; + C3 c3; + Object o1, o2; + boolean s; + + o1 = new Object(); + o2 = new Object(); + c1 = new C1(); + c2 = new C2(); + c3 = new C3(); + + s = o1 == o2; + s = c1 != c2; + s = c3 == c1; + s = o2 != o1; + s = null == c2; + s = o1 == c2; + //s = null == o; + } +} + +class C1 {} + +class C2 extends C1{} + +class C3 extends C2{} diff --git a/javali_tests/HW4_nop90/Booleans/OkEquals2.javali b/javali_tests/HW4_nop90/Booleans/OkEquals2.javali new file mode 100755 index 0000000..22967a5 --- /dev/null +++ b/javali_tests/HW4_nop90/Booleans/OkEquals2.javali @@ -0,0 +1,15 @@ +// Test the equal/not equal operations, one of the types must be a subtype of the other + +class Main { + void main() { + int a,b; + boolean c; + a = 8; + b = 6; + c = a==b; + + if (!c){ + write(5);} + } +} + diff --git a/javali_tests/HW4_nop90/Booleans/OkEquals3.javali b/javali_tests/HW4_nop90/Booleans/OkEquals3.javali new file mode 100755 index 0000000..8a49707 --- /dev/null +++ b/javali_tests/HW4_nop90/Booleans/OkEquals3.javali @@ -0,0 +1,14 @@ +// Test the equal/not equal operations, one of the types must be a subtype of the other + +class Main { + void main() { + boolean a,b,c; + a = true; + b = 6<10; + c = a==b; + + if (c){ + write(5);} + } +} + diff --git a/javali_tests/HW4_nop90/Booleans/OkNot.javali b/javali_tests/HW4_nop90/Booleans/OkNot.javali new file mode 100644 index 0000000..eb293e7 --- /dev/null +++ b/javali_tests/HW4_nop90/Booleans/OkNot.javali @@ -0,0 +1,36 @@ +// Test '!' operator + +class Main { + void main() { + boolean a,b,c,d; + int x,y; + + a = true; + c = !false; + b = !a; + x = 100; + y = 5; + + if (a) { + write(1);} + writeln(); + + if (!b){ + write(2); + } + writeln(); + + while(c){ + write(3); + c = false; + } + writeln(); + + // !x < 2 * y --> !x type error + // !(x < 2 * y) --> correct syntax + while(!(x<2*y)){ + write(y); + y = y*2; + } + } +} diff --git a/javali_tests/HW4_nop90/Casts/ErrCastUnrelatedType.javali b/javali_tests/HW4_nop90/Casts/ErrCastUnrelatedType.javali new file mode 100755 index 0000000..05b0d3b --- /dev/null +++ b/javali_tests/HW4_nop90/Casts/ErrCastUnrelatedType.javali @@ -0,0 +1,21 @@ +// Test types in a Cast (cannot cast to unrelated type) + +class Main { + void main() { + C1 a, b; + C2 c, d; + Object o; + c = new C2(); + a = new C1(); + + b = (C1) c; + d = (C2) a; + o = (Object) a; + o = (Object) c; + o = (Main) c; + } +} + +class C1 {} + +class C2 extends C1{} \ No newline at end of file diff --git a/javali_tests/HW4_nop90/Casts/ErrObjectToArrayCast.javali b/javali_tests/HW4_nop90/Casts/ErrObjectToArrayCast.javali new file mode 100755 index 0000000..e0c87b8 --- /dev/null +++ b/javali_tests/HW4_nop90/Casts/ErrObjectToArrayCast.javali @@ -0,0 +1,14 @@ +// Test downcast from Object to array + +class Main { + void main() { + A[] a; + Object o; + + o = new Object(); + + a = (A[]) o; + } +} + +class A{} \ No newline at end of file diff --git a/javali_tests/HW4_nop90/Casts/ErrPrimitiveCast.javali b/javali_tests/HW4_nop90/Casts/ErrPrimitiveCast.javali new file mode 100755 index 0000000..0fcc73f --- /dev/null +++ b/javali_tests/HW4_nop90/Casts/ErrPrimitiveCast.javali @@ -0,0 +1,12 @@ +// Test types in a Cast +// cannot cast an int to an boolean + +class Main { + void main() { + int a; + boolean b; + a = 1; + + b = (boolean) a; + } +} diff --git a/javali_tests/HW4_nop90/Casts/ErrPrimitiveCast2.javali b/javali_tests/HW4_nop90/Casts/ErrPrimitiveCast2.javali new file mode 100755 index 0000000..cb01944 --- /dev/null +++ b/javali_tests/HW4_nop90/Casts/ErrPrimitiveCast2.javali @@ -0,0 +1,11 @@ +// Test types in a Cast (cannot cast to unrelated type) + +class Main { + void main() { + int a; + boolean b; + //b = false; + + a = (int) b; + } +} diff --git a/javali_tests/HW4_nop90/Casts/OkArrayToObjectCast.javali b/javali_tests/HW4_nop90/Casts/OkArrayToObjectCast.javali new file mode 100755 index 0000000..1292ab4 --- /dev/null +++ b/javali_tests/HW4_nop90/Casts/OkArrayToObjectCast.javali @@ -0,0 +1,12 @@ +// Test downcast from array to Object + +class Main { + void main() { + int[] x; + Object o; + + x = new int[5]; + + o = (Object) x; + } +} diff --git a/javali_tests/HW4_nop90/Casts/OkArrayToObjectCast2.javali b/javali_tests/HW4_nop90/Casts/OkArrayToObjectCast2.javali new file mode 100755 index 0000000..238d348 --- /dev/null +++ b/javali_tests/HW4_nop90/Casts/OkArrayToObjectCast2.javali @@ -0,0 +1,14 @@ +// Test downcast from array to Object + +class Main { + void main() { + A[] a; + Object o; + + a = new A[5]; + + o = (Object) a; + } +} + +class A{} \ No newline at end of file diff --git a/javali_tests/HW4_nop90/Casts/OkSubtype.javali b/javali_tests/HW4_nop90/Casts/OkSubtype.javali new file mode 100755 index 0000000..6e27be4 --- /dev/null +++ b/javali_tests/HW4_nop90/Casts/OkSubtype.javali @@ -0,0 +1,15 @@ +// Test cast from a type to the same type + +class Main { + void main() { + C c,d; + Object o,g; + c = new C(); + g = new Object(); + + d = (C) c; + o = (Object) g; + } +} + +class C {} \ No newline at end of file diff --git a/javali_tests/HW4_nop90/Casts/OkTypeCast.javali b/javali_tests/HW4_nop90/Casts/OkTypeCast.javali new file mode 100755 index 0000000..0a39012 --- /dev/null +++ b/javali_tests/HW4_nop90/Casts/OkTypeCast.javali @@ -0,0 +1,26 @@ +// Test valid casts + +class Main { + void main() { + C1 a, x; + C2 c,d; + C4 b,f; + Object o; + c = new C2(); + a = new C1(); + b = new C4(); + f = new C4(); + + x = (C1) b; + o = (Object) f; + } +} + +class C1 {} + +class C2 extends C1{} + +class C3 extends C2{} + +class C4 extends C3{} + diff --git a/javali_tests/HW4_nop90/Casts/OkTypeToObjectCast.javali b/javali_tests/HW4_nop90/Casts/OkTypeToObjectCast.javali new file mode 100755 index 0000000..d31cd6b --- /dev/null +++ b/javali_tests/HW4_nop90/Casts/OkTypeToObjectCast.javali @@ -0,0 +1,23 @@ +// Test casts from type to object + +class Main { + void main() { + C1 a; + Object o,f,g; + int x; + boolean y; + a = new C1(); + x = 2; + y = true; + + + o = (Object) a; + //f = (Object) x; + //g = (Object) y; + } +} + +class C1 {} + + + diff --git a/javali_tests/from_prev_HW/EightVariablesWrite.javali b/javali_tests/HW4_nop90/EightVariablesWrite.javali similarity index 82% rename from javali_tests/from_prev_HW/EightVariablesWrite.javali rename to javali_tests/HW4_nop90/EightVariablesWrite.javali index e9391ca..8862fb8 100755 --- a/javali_tests/from_prev_HW/EightVariablesWrite.javali +++ b/javali_tests/HW4_nop90/EightVariablesWrite.javali @@ -13,7 +13,7 @@ class Main { i6 = 6; i7 = 7; - write(i0 + (i1 + ( i2 + ( i3 + ( i4 + (i5 + (i6 + i7))))))); writeln(); + write(i0 + (i1 + ( i2 + ( i3 + ( i4 + (i5 + (i6 + (i7 + i0)))))))); 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/HW4_nop90/ErrDivisionByZero.javali b/javali_tests/HW4_nop90/ErrDivisionByZero.javali new file mode 100755 index 0000000..e075dd6 --- /dev/null +++ b/javali_tests/HW4_nop90/ErrDivisionByZero.javali @@ -0,0 +1,10 @@ +// test division by zero + +class Main { + void main() { + int a; + int b; + a = 10; + b = a / 0; + } +} \ No newline at end of file diff --git a/javali_tests/HW4_nop90/Fields/OkInheritedFieldAccess.javali b/javali_tests/HW4_nop90/Fields/OkInheritedFieldAccess.javali new file mode 100644 index 0000000..ec165e5 --- /dev/null +++ b/javali_tests/HW4_nop90/Fields/OkInheritedFieldAccess.javali @@ -0,0 +1,27 @@ +// access a field from a class and a superclass (also includes hidden fields) + +class Main { + void main() { + C1 c1; + C2 c2; + C4 c4; + c1 = new C1(); + c2 = new C2(); + c4 = new C4(); + c1.a = 5; + c2.a = 6; + c4.a = false; + } +} + +class C1{ + int a; +} + +class C2 extends C1 {} + +class C3 extends C2 {} + +class C4 extends C3 { + boolean a; +} diff --git a/javali_tests/HW4_nop90/Fields/OkObjectFields.javali b/javali_tests/HW4_nop90/Fields/OkObjectFields.javali new file mode 100755 index 0000000..a5362a1 --- /dev/null +++ b/javali_tests/HW4_nop90/Fields/OkObjectFields.javali @@ -0,0 +1,19 @@ +// test an Array of Objects and access their field + +class Main { + void main() { + D[] x; + x = new D[50]; + x[22].c.a = 2; + } +} + +class C{ + int a; +} + + +class D { + C c; +} + diff --git a/javali_tests/HW4_nop90/ManyRegisters.javali b/javali_tests/HW4_nop90/ManyRegisters.javali new file mode 100644 index 0000000..751f037 --- /dev/null +++ b/javali_tests/HW4_nop90/ManyRegisters.javali @@ -0,0 +1,5 @@ +class Main { + void main() { + write(0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15); + } +} \ No newline at end of file diff --git a/javali_tests/HW4_nop90/OkAccessField.javali b/javali_tests/HW4_nop90/OkAccessField.javali new file mode 100644 index 0000000..6895e56 --- /dev/null +++ b/javali_tests/HW4_nop90/OkAccessField.javali @@ -0,0 +1,30 @@ +// Access inherited and hidden fields (general check) + +class Other { + int a; + int b; + Object c; + Other o; +} + +class Main extends Other { + int a, b; + + void main() { + Other o; + o = (Other) this; + a = 1; + b = 2; + o.a = -1; + o.b = -2; + write(a); + write(b); + write(o.a); + write(o.b); + if (c != null) { + if (o.o != null) { + write(0); + } + } + } +} diff --git a/javali_tests/HW4_nop90/OkAssignments.javali b/javali_tests/HW4_nop90/OkAssignments.javali new file mode 100644 index 0000000..f32381f --- /dev/null +++ b/javali_tests/HW4_nop90/OkAssignments.javali @@ -0,0 +1,34 @@ +/* testing assign statements*/ +class Main { + int methodCall() { return 0; } + int methodCall2(int a, int b) { + if (a >= b) { + return 0; + } else { + if (b <= a) { + return 1; + } + } + return a * b; + } + + void main() { + int a, b, c, e; + Ast d; + int[] g; + Object f; + a = read(); + b = methodCall(); + c = methodCall2(a, b); + d = new Ast(); + e = d.field; + g = new int[a]; + f = new Object[a]; + f = g; + g = (int[]) f; + } +} + +class Ast { + int field; +} diff --git a/javali_tests/HW4_nop90/OkAssignments.javali.in b/javali_tests/HW4_nop90/OkAssignments.javali.in new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/javali_tests/HW4_nop90/OkAssignments.javali.in @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/javali_tests/from_prev_HW/conditionExpressions.javali b/javali_tests/HW4_nop90/OkBoolBinaryOp.javali similarity index 85% rename from javali_tests/from_prev_HW/conditionExpressions.javali rename to javali_tests/HW4_nop90/OkBoolBinaryOp.javali index 5257a61..36ff9c3 100755 --- a/javali_tests/from_prev_HW/conditionExpressions.javali +++ b/javali_tests/HW4_nop90/OkBoolBinaryOp.javali @@ -8,11 +8,11 @@ class Main { d = 40; e = 1; - while ( b > c ) { + while ( b >= c ) { write(b); b = b-1; } - a = c < (b+4-5/2); + a = c <= (b+4-5/2); if (a){ write(13);} if (100/2>d*e){ diff --git a/javali_tests/HW4_nop90/OkCallInheritedMethod.javali b/javali_tests/HW4_nop90/OkCallInheritedMethod.javali new file mode 100644 index 0000000..41c491c --- /dev/null +++ b/javali_tests/HW4_nop90/OkCallInheritedMethod.javali @@ -0,0 +1,17 @@ +// call an method from a superclass + +class Main { + void main() { + C2 c; + c = new C2(); + c.aux(); + } +} + +class C1{ + void aux(){ + write(2); + } +} + +class C2 extends C1 {} diff --git a/javali_tests/from_prev_HW/Casts.javali b/javali_tests/HW4_nop90/OkCasts.javali similarity index 100% rename from javali_tests/from_prev_HW/Casts.javali rename to javali_tests/HW4_nop90/OkCasts.javali diff --git a/javali_tests/HW4_nop90/OkEmptyBlocks.javali b/javali_tests/HW4_nop90/OkEmptyBlocks.javali new file mode 100644 index 0000000..d1422a1 --- /dev/null +++ b/javali_tests/HW4_nop90/OkEmptyBlocks.javali @@ -0,0 +1,10 @@ +class Main { + void main() { + while(false) {} + if(false) { + return; + } + if (false) {} else {} + if (false) {} else {return;} + } +} \ No newline at end of file diff --git a/javali_tests/HW4_nop90/OkEmptyWhile.javali b/javali_tests/HW4_nop90/OkEmptyWhile.javali new file mode 100644 index 0000000..f7bae2f --- /dev/null +++ b/javali_tests/HW4_nop90/OkEmptyWhile.javali @@ -0,0 +1,5 @@ +class Main { + void main() { + while(false) {} + } +} \ No newline at end of file diff --git a/javali_tests/HW4_nop90/OkHideField.javali b/javali_tests/HW4_nop90/OkHideField.javali new file mode 100644 index 0000000..cf9cc60 --- /dev/null +++ b/javali_tests/HW4_nop90/OkHideField.javali @@ -0,0 +1,16 @@ +// Hide a field from a superclass (type doesn't need to match) + +class Parent { + int a; +} +class Main extends Parent { + Object a; + Main b; + void main() { + Parent c; + b = new Main(); + b.a = new Object(); + c = new Parent(); + c.a = 10; + } +} \ No newline at end of file diff --git a/javali_tests/from_prev_HW/NULLTest.javali b/javali_tests/HW4_nop90/OkNullAssignment.javali similarity index 82% rename from javali_tests/from_prev_HW/NULLTest.javali rename to javali_tests/HW4_nop90/OkNullAssignment.javali index 206f3cb..53e6c47 100755 --- a/javali_tests/from_prev_HW/NULLTest.javali +++ b/javali_tests/HW4_nop90/OkNullAssignment.javali @@ -7,14 +7,10 @@ class Main { int a; boolean b; Object c; - - a = null; - b = null; + c = null; write(a); writeln(); - //write(null) - } } \ No newline at end of file diff --git a/javali_tests/HW4_nop90/OkOverwriteMainMethod.javali b/javali_tests/HW4_nop90/OkOverwriteMainMethod.javali new file mode 100644 index 0000000..6e498aa --- /dev/null +++ b/javali_tests/HW4_nop90/OkOverwriteMainMethod.javali @@ -0,0 +1,14 @@ +/* test overwritten main method */ + + +class OtherMain { + void main () { + int a; + } +} + +class Main extends OtherMain { + void main () { + int b; + } +} \ No newline at end of file diff --git a/javali_tests/HW4_nop90/OkRegisterUse.javali b/javali_tests/HW4_nop90/OkRegisterUse.javali new file mode 100644 index 0000000..3c7e635 --- /dev/null +++ b/javali_tests/HW4_nop90/OkRegisterUse.javali @@ -0,0 +1,32 @@ +/* this test forces the compiler to push registers to the stack */ + +class Main { + void main() { + int u,v,x,y,z; + boolean e,f,g,h,i,j; + B b1,b2; + e = true; + f = true; + g = true; + z = 0; + while (z < 30) { + if (f){ + if (g){ + u = 5; + v = 8; + x = u/v; + b1 = new B(); + e = false; + } + } + else { + j = true; + } + z = z + 1; + + } + + } +} + +class B {} diff --git a/javali_tests/from_prev_HW/UnaryOperators.javali b/javali_tests/HW4_nop90/OkUnaryOperators.javali similarity index 100% rename from javali_tests/from_prev_HW/UnaryOperators.javali rename to javali_tests/HW4_nop90/OkUnaryOperators.javali diff --git a/javali_tests/HW4_nop90/OkVariables.javali b/javali_tests/HW4_nop90/OkVariables.javali new file mode 100755 index 0000000..73e0d95 --- /dev/null +++ b/javali_tests/HW4_nop90/OkVariables.javali @@ -0,0 +1,29 @@ +/* Test expression evaluation with more than 8 variables + Test Register use +*/ + +class Main { + void main() { + int r1, r2, r3; + int i0, i1, i2, i3, i4, i5, i6, i7; + int a,b,c,d,e; + + i0 = 0; + i1 = 1; + i2 = 2; + i3 = 3; + i4 = 4; + i5 = 5; + i6 = 6; + i7 = 7; + a = 5; + b = 5; + c = 5; + d = 5; + e = 5; + + write(i0 + (i1 + ( i2 + ( i3 + (a+b+c+d+e) + ( i4 + (i5 + (i6 + (i7 + i0)))))))); writeln(); + write(((((((i0 + i1) + i2) + i3) + i4 + (a*b*c*d*e)) + i5) + i6) + i7); writeln(); + write(((i0 + i1) + (i2 + i3)) + ((i4 + i5) + (i6 + i7))); writeln(); + } +} diff --git a/javali_tests/HW4_nop90/OkZeroInitialized.javali b/javali_tests/HW4_nop90/OkZeroInitialized.javali new file mode 100755 index 0000000..83460c4 --- /dev/null +++ b/javali_tests/HW4_nop90/OkZeroInitialized.javali @@ -0,0 +1,9 @@ +/* Test that variables are zero initialized */ + +class Main { + void main() { + int a; + write(a); + } +} + diff --git a/javali_tests/HW4_nop90/ReturnTests/OkComplexIfElseAndReturnSubtyping.javali b/javali_tests/HW4_nop90/ReturnTests/OkComplexIfElseAndReturnSubtyping.javali new file mode 100644 index 0000000..cef633a --- /dev/null +++ b/javali_tests/HW4_nop90/ReturnTests/OkComplexIfElseAndReturnSubtyping.javali @@ -0,0 +1,79 @@ +// A if/else block is valid return statment if all possible branches return some value +// Also serves to check subtyping +// The return logic leads to nonsensical programs with statements after the return, but that's ok + +class Main { + // All returns must be empty, but we don't care for their existence + void main() { + int a, b, c; + if (a == b) { + return; + } else { + while (a > b) { + return; + } + if (c > b) { + return; + } + } + } + + // All returns must be null, Main (all subtypes of Main), and there must be an unconditional return + Main action1() { + Main a; + Object b; + boolean x, y; + if (x) { + if (y) { + if (x || y) { + if (x == y && x != y) { + return null; + } else { + return a; + } + } else { + if (b == null) { + return a; + } else { + return a; + } + } + } else { + if (b == a || a == null) { + return null; + } else { + return a; + } + } + } else { + return null; + } + } + + // All returns must be null, Main[] (all subtypes of Main[]), and there must be an unconditional return + Main[] action2() { + Main[] a, b; + if (a == b) { + return a; + } else { + if (b != null) { + return b; + } else { + return null; + } + } + } + + // All returns must be int (no subtypes), and there must be an unconditional return + int action3() { + int a, b, c, d; + if (a > b) { + return c - d; + while (a != a) { + return b; + } + } else { + return c * d / a + b % c - a; + } + } +} diff --git a/javali_tests/HW4_nop90/ReturnTests/OkIgnoredReturn.javali b/javali_tests/HW4_nop90/ReturnTests/OkIgnoredReturn.javali new file mode 100644 index 0000000..5116ed2 --- /dev/null +++ b/javali_tests/HW4_nop90/ReturnTests/OkIgnoredReturn.javali @@ -0,0 +1,9 @@ +class Main { + int n() { + return 0; + } + + void main() { + n(); + } +} \ No newline at end of file diff --git a/javali_tests/HW4_nop90/ReturnTests/OkMiddleReturn.javali b/javali_tests/HW4_nop90/ReturnTests/OkMiddleReturn.javali new file mode 100644 index 0000000..fb2622f --- /dev/null +++ b/javali_tests/HW4_nop90/ReturnTests/OkMiddleReturn.javali @@ -0,0 +1,21 @@ +// The return statement can be placed in the middle of the method, and no warning will be thrown about unexecuted +// statements + +class Main { + void main() { + write(things()); + writeln(); + } + + int things() { + int a; + a = 10; + return a; + + // These statements won't be reached + // but there is a complete return statement + a = 20; + writeln(); + writeln(); + } +} \ No newline at end of file diff --git a/javali_tests/HW4_nop90/ReturnTests/OkReturnObject.javali b/javali_tests/HW4_nop90/ReturnTests/OkReturnObject.javali new file mode 100755 index 0000000..7ea4654 --- /dev/null +++ b/javali_tests/HW4_nop90/ReturnTests/OkReturnObject.javali @@ -0,0 +1,22 @@ +/* test method that returns an object */ + +class Main { + void main() { + int x; + B b; + b = aux(); + x = b.a; + write(x); + } + + B aux(){ + B b; + b = new B(); + b.a = 10; + return b; + } +} + +class B { + int a; +} diff --git a/javali_tests/HW4_nop90/ReturnTests/OkSimpleIfElse.javali b/javali_tests/HW4_nop90/ReturnTests/OkSimpleIfElse.javali new file mode 100644 index 0000000..bb6f83a --- /dev/null +++ b/javali_tests/HW4_nop90/ReturnTests/OkSimpleIfElse.javali @@ -0,0 +1,24 @@ +// A if/else with a return in both branches is valid + +class Main { + void main() { + write(things()); + writeln(); + } + + int things() { + int a; + int b; + Object c, d; + a = 10; + b = 100; + c = new Object(); + d = new Object(); + + if (c == d) { + return 10; + } else { + return 5; + } + } +} \ No newline at end of file diff --git a/javali_tests/HW4_nop90/method invocation/OkCallByValue.javali b/javali_tests/HW4_nop90/method invocation/OkCallByValue.javali new file mode 100755 index 0000000..64b80e8 --- /dev/null +++ b/javali_tests/HW4_nop90/method invocation/OkCallByValue.javali @@ -0,0 +1,22 @@ +// test that call-by-value is used + + +class Main { + void main() { + int x; + A a; + a = new A(); + a.a = 50; + aux(a.a); + x = a.a; + write(x); + } + + void aux (int arg){ + arg = arg + 1; + } +} + +class A { + int a; +} \ No newline at end of file diff --git a/javali_tests/HW4_nop90/method invocation/OkCallByValue2.javali b/javali_tests/HW4_nop90/method invocation/OkCallByValue2.javali new file mode 100755 index 0000000..f3ae0dd --- /dev/null +++ b/javali_tests/HW4_nop90/method invocation/OkCallByValue2.javali @@ -0,0 +1,18 @@ +// test that call-by-value is used + + +class Main { + void main() { + int[] y; + int x; + y = new int[5]; + y[2] = 50; + aux(y[2]); + x = y[2]; + write(x); + } + + void aux (int arg){ + arg = arg + 1; + } +} diff --git a/javali_tests/HW4_nop90/method invocation/OkCallByValue3.javali b/javali_tests/HW4_nop90/method invocation/OkCallByValue3.javali new file mode 100755 index 0000000..25b9f8a --- /dev/null +++ b/javali_tests/HW4_nop90/method invocation/OkCallByValue3.javali @@ -0,0 +1,18 @@ +// test that call-by-value is used + + +class Main { + void main() { + int[] y; + int x; + y = new int[5]; + y[2] = 50; + aux(y); + x = y[2]; + write(x); + } + + void aux (int[] arg){ + arg[2] = 100; + } +} diff --git a/javali_tests/HW4_nop90/method invocation/OkCallInheritedMethod.javali b/javali_tests/HW4_nop90/method invocation/OkCallInheritedMethod.javali new file mode 100755 index 0000000..ded39ba --- /dev/null +++ b/javali_tests/HW4_nop90/method invocation/OkCallInheritedMethod.javali @@ -0,0 +1,17 @@ +// call an method from a superclass + +class Main { + void main() { + C2 c; + c = new C2(); + c.aux(); + } +} + +class C1{ + void aux(){ + write(2); + } +} + +class C2 extends C1 {} \ No newline at end of file diff --git a/javali_tests/HW4_nop90/method invocation/OkMethod.javali b/javali_tests/HW4_nop90/method invocation/OkMethod.javali new file mode 100755 index 0000000..65b9732 --- /dev/null +++ b/javali_tests/HW4_nop90/method invocation/OkMethod.javali @@ -0,0 +1,19 @@ +// call a simple method and use its return value + +class Main { + void main() { + int a,b; + C c; + c = new C(); + a = 5; + b = c.aux(a); + } +} + +class C{ + int aux(int arg){ + return arg*2; + } +} + + diff --git a/javali_tests/HW4_nop90/method invocation/OkMethod2.javali b/javali_tests/HW4_nop90/method invocation/OkMethod2.javali new file mode 100755 index 0000000..ed97b26 --- /dev/null +++ b/javali_tests/HW4_nop90/method invocation/OkMethod2.javali @@ -0,0 +1,35 @@ +// call a method with many parameters and use its return value +// also Test Register use, by allocating memory + +class Main { + void main() { + int a,b,c,d,e,f,g,h,x; + a = 5; + b = 5; + c = 5; + d = 5; + e = 5; + f = 5; + g = 5; + h = 5; + + x = aux(a,b,c,d,e,f,g,h); + + } + + int aux(int arg1, int arg2, int arg3, int arg4, int arg5, int arg6, int arg7, int arg8){ + int[] x; + int i; + D d; + x = new int[20]; + i = arg1 + arg2 + arg3 + arg4 - arg5 - arg6 + arg7 - arg8; + d = new D(); + return i; + } +} + +class D{ + int[] x; +} + + diff --git a/javali_tests/from_prev_HW/Arrays.javali b/javali_tests/from_prev_HW/Arrays.javali deleted file mode 100644 index 2638373..0000000 --- a/javali_tests/from_prev_HW/Arrays.javali +++ /dev/null @@ -1,24 +0,0 @@ -/* testing arrays with primitive types as well as new objects -the if/else statements shows that the array is boolean array is initialized with false - */ -class Main { - void main() { - int [] testArray; - boolean [] boolarray; - - boolarray = new boolean [3]; - testArray = new int [10]; - - testArray[5] = 3; - boolarray[1] = true; - - if (boolarray[0]){ - write(1);} - else{ - write(5);} - writeln(); - if (boolarray[1]){ - write(1);} - - } -} \ No newline at end of file diff --git a/javali_tests/from_prev_HW/Assignments.javali b/javali_tests/from_prev_HW/Assignments.javali deleted file mode 100644 index d239f5f..0000000 --- a/javali_tests/from_prev_HW/Assignments.javali +++ /dev/null @@ -1,12 +0,0 @@ -/* testing assign statements*/ -class Main { - void main() { - a = read(); - b = methodCall(); - c = methodCall(param1, param2); - d = object.access; - e = new Ast(); - d = new int[size]; - f = new Object[size]; - } -} \ No newline at end of file diff --git a/javali_tests/from_prev_HW/Division.javali b/javali_tests/from_prev_HW/Division.javali deleted file mode 100755 index 13358d3..0000000 --- a/javali_tests/from_prev_HW/Division.javali +++ /dev/null @@ -1,21 +0,0 @@ -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/from_prev_HW/DoubleInheritance.javali b/javali_tests/from_prev_HW/DoubleInheritance.javali deleted file mode 100644 index f950cea..0000000 --- a/javali_tests/from_prev_HW/DoubleInheritance.javali +++ /dev/null @@ -1,9 +0,0 @@ -/* testing basic inheritance*/ -class C1 {} -class C2 extends C1 {} -class C3 extends C2 {} -class C4 extends C2 {} -class C5 extends C3 {} -class Main { - void main() {} -} \ No newline at end of file diff --git a/javali_tests/from_prev_HW/Expressions.javali b/javali_tests/from_prev_HW/Expressions.javali deleted file mode 100644 index 54830ce..0000000 --- a/javali_tests/from_prev_HW/Expressions.javali +++ /dev/null @@ -1,22 +0,0 @@ -/* testing different expressions -compiler should recognize Type error: Return statement of method with void return type should not have arguments - */ -class Main { - void main() { - return; - return true; - return false; - return 0x10; - return 10; - return variable; - return array[index]; - return methodAccess(); - return object.field; - return object.call(); - return op + op2; - return op / asd * asd && a == true; - return this.run(); - return this; - return this.field; - } -} \ No newline at end of file diff --git a/javali_tests/from_prev_HW/Multiplication.javali b/javali_tests/from_prev_HW/Multiplication.javali deleted file mode 100755 index 0c092a6..0000000 --- a/javali_tests/from_prev_HW/Multiplication.javali +++ /dev/null @@ -1,31 +0,0 @@ -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/from_prev_HW/OrderOfDeclarations.javali b/javali_tests/from_prev_HW/OrderOfDeclarations.javali deleted file mode 100644 index ea555ab..0000000 --- a/javali_tests/from_prev_HW/OrderOfDeclarations.javali +++ /dev/null @@ -1,8 +0,0 @@ -/*Check the order of the declarations in the generated parser -* Do the variables come always first or in their place? */ -class ClassName { - void a() {} - int a; - void a() {} - void tests(boolean d, nulle a) {} -} \ No newline at end of file diff --git a/javali_tests/from_prev_HW/Overflow.javali b/javali_tests/from_prev_HW/Overflow.javali deleted file mode 100755 index 97725ac..0000000 --- a/javali_tests/from_prev_HW/Overflow.javali +++ /dev/null @@ -1,25 +0,0 @@ -/* 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 = 21474836400; - y = 60000; - write( x + y); writeln(); - z = 20000000; - write( y * z); writeln(); - write( (y * z) ); writeln(); - - } -} diff --git a/javali_tests/from_prev_HW/Overflow.javali.in b/javali_tests/from_prev_HW/Overflow.javali.in deleted file mode 100755 index a51fa7d..0000000 --- a/javali_tests/from_prev_HW/Overflow.javali.in +++ /dev/null @@ -1 +0,0 @@ -2147483647 diff --git a/javali_tests/from_prev_HW/ParamLists.javali b/javali_tests/from_prev_HW/ParamLists.javali deleted file mode 100644 index ac19360..0000000 --- a/javali_tests/from_prev_HW/ParamLists.javali +++ /dev/null @@ -1,15 +0,0 @@ -/*Testing all related to methods (declaration and execution)*/ -class Main { - void main() { - callWithParams(a, b, c, 0, false); - object.call(a, b, d); - } - - int method(int a, String b, int[] c) { - - } - - int[] method2() {} - Object method3() {} - Model[] method4() {} -} \ No newline at end of file diff --git a/javali_tests/from_prev_HW/ReadWrite.javali b/javali_tests/from_prev_HW/ReadWrite.javali deleted file mode 100755 index e9f3944..0000000 --- a/javali_tests/from_prev_HW/ReadWrite.javali +++ /dev/null @@ -1,26 +0,0 @@ -/* 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/from_prev_HW/ReadWrite.javali.in b/javali_tests/from_prev_HW/ReadWrite.javali.in deleted file mode 100755 index fff4c89..0000000 --- a/javali_tests/from_prev_HW/ReadWrite.javali.in +++ /dev/null @@ -1,6 +0,0 @@ - 22 - - - 1 - --15 diff --git a/javali_tests/from_prev_HW/Statements.javali b/javali_tests/from_prev_HW/Statements.javali deleted file mode 100644 index f5f2279..0000000 --- a/javali_tests/from_prev_HW/Statements.javali +++ /dev/null @@ -1,34 +0,0 @@ -/* testing different statements -'condition' is not initialized - */ -class Main { - void main() { - if (condition) { - instructions(); - asd.b = c; - if (cond2) { - - } else { - nonEmptyBlock = a; - } - } else { - - } - // Whiles - while (condition) { - while (anotherLoop == false) { - nestedLoops(); - } - } - while (false) {} // emptyloop - // Returns - return; // empty - return expr; // with expressions (expressions already tested) - return array[index]; - // Writes - write(a); - write(9 + 10); - writeln(); - write(call()); - } -} \ No newline at end of file diff --git a/javali_tests/from_prev_HW/invalidCasts.javali b/javali_tests/from_prev_HW/invalidCasts.javali deleted file mode 100755 index b42d747..0000000 --- a/javali_tests/from_prev_HW/invalidCasts.javali +++ /dev/null @@ -1,12 +0,0 @@ -/* testing invalid casts */ -class Main -{ - void main() - { - int a; - Object d; - d = new Object(); - a = (Object) d; - a = (boolean[]) a; - } -} \ No newline at end of file diff --git a/javali_tests/from_prev_HW/newTest.javali b/javali_tests/from_prev_HW/newTest.javali deleted file mode 100644 index 2b6e530..0000000 --- a/javali_tests/from_prev_HW/newTest.javali +++ /dev/null @@ -1,14 +0,0 @@ -class Main { - void main() { - boolean b; - int a,c,n; - a = 10; - //b = true; - - while ( a>0 ) { - a = a-1; - } - - - -} \ No newline at end of file diff --git a/javali_tests/from_prev_HW/noParentheses.javali b/javali_tests/from_prev_HW/noParentheses.javali deleted file mode 100755 index 0413586..0000000 --- a/javali_tests/from_prev_HW/noParentheses.javali +++ /dev/null @@ -1,24 +0,0 @@ -/* 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 index 2655fc1..db99934 100644 --- a/src/cd/Config.java +++ b/src/cd/Config.java @@ -10,11 +10,6 @@ public class Config { MACOSX } - /** - * What kind of system we are on - */ - public static final SystemKind systemKind; - /** * Defines the extension used for assembler files on this platform. * Currently always {@code .s}. @@ -84,11 +79,9 @@ public class Config { public static final String JAVA_EXE; static { - final String os = System.getProperty("os.name").toLowerCase(); if(os.contains("windows") || os.contains("nt")) { - systemKind = SystemKind.WINDOWS; BINARYEXT = ".exe"; MAIN = "_main"; PRINTF = "_printf"; @@ -108,7 +101,6 @@ public class Config { COMMENT_SEP = "#"; } else if(os.contains("mac os x") || os.contains("darwin")) { - systemKind = SystemKind.MACOSX; BINARYEXT = ".bin"; MAIN = "_main"; PRINTF = "_printf"; @@ -126,7 +118,6 @@ public class Config { COMMENT_SEP = "#"; } else { - systemKind = SystemKind.LINUX; BINARYEXT = ".bin"; MAIN = "main"; PRINTF = "printf"; diff --git a/src/cd/Main.java b/src/cd/Main.java index 16ba79f..335c48a 100644 --- a/src/cd/Main.java +++ b/src/cd/Main.java @@ -1,19 +1,6 @@ package cd; -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.Reader; -import java.io.Writer; -import java.util.ArrayList; -import java.util.List; - -import org.antlr.v4.runtime.ANTLRInputStream; -import org.antlr.v4.runtime.BailErrorStrategy; -import org.antlr.v4.runtime.CommonTokenStream; -import org.antlr.v4.runtime.misc.ParseCancellationException; - +import cd.backend.codegen.AstCodeGenerator; import cd.frontend.parser.JavaliAstVisitor; import cd.frontend.parser.JavaliLexer; import cd.frontend.parser.JavaliParser; @@ -24,6 +11,14 @@ import cd.ir.Ast.ClassDecl; import cd.ir.Symbol; import cd.ir.Symbol.TypeSymbol; import cd.util.debug.AstDump; +import org.antlr.v4.runtime.ANTLRInputStream; +import org.antlr.v4.runtime.BailErrorStrategy; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.misc.ParseCancellationException; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; /** * The main entrypoint for the compiler. Consists of a series @@ -37,7 +32,7 @@ public class Main { // Set to non-null to write debug info out public Writer debug = null; - // Set to non-null to write dump of control flow graph (Advanced Compiler Design) + // Set to non-null to write dump of control flow graph public File cfgdumpbase; /** Symbol for the Main type */ @@ -74,6 +69,12 @@ public class Main { // Run the semantic check: m.semanticCheck(astRoots); + + // Generate code: + String sFile = arg + Config.ASMEXT; + try (FileWriter fout = new FileWriter(sFile)) { + m.generateCode(astRoots, fout); + } } } } @@ -111,6 +112,13 @@ public class Main { new SemanticAnalyzer(this).check(astRoots); } } + + public void generateCode(List 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 { diff --git a/src/cd/backend/ExitCode.java b/src/cd/backend/ExitCode.java new file mode 100644 index 0000000..9ec741e --- /dev/null +++ b/src/cd/backend/ExitCode.java @@ -0,0 +1,19 @@ +package cd.backend; + +public enum ExitCode { + OK(0), + INVALID_DOWNCAST(1), + INVALID_ARRAY_STORE(2), + INVALID_ARRAY_BOUNDS(3), + NULL_POINTER(4), + INVALID_ARRAY_SIZE(5), + INFINITE_LOOP(6), + DIVISION_BY_ZERO(7), + INTERNAL_ERROR(22); + + public final int value; + + private ExitCode(int value) { + this.value = value; + } +} \ No newline at end of file diff --git a/src/cd/backend/codegen/AssemblyEmitter.java b/src/cd/backend/codegen/AssemblyEmitter.java new file mode 100644 index 0000000..11cbb75 --- /dev/null +++ b/src/cd/backend/codegen/AssemblyEmitter.java @@ -0,0 +1,176 @@ +package cd.backend.codegen; + +import cd.Config; +import cd.backend.codegen.RegisterManager.Register; + +import java.io.IOException; +import java.io.Writer; + +public class AssemblyEmitter { + public Writer out; + public StringBuilder indent = new StringBuilder(); + public int counter = 0; + + public AssemblyEmitter(Writer out) { + this.out = out; + } + + /** Creates an constant operand. */ + static String constant(int i) { + return "$" + i; + } + + /** Creates an constant operand with the address of a label. */ + static String labelAddress(String lbl) { + return "$" + lbl; + } + + /** Creates an operand relative to another operand. */ + static String registerOffset(int offset, Register reg) { + return String.format("%d(%s)", offset, reg); + } + + /** Creates an operand addressing an item in an array */ + static String arrayAddress(Register arrReg, Register idxReg) { + final int offset = Config.SIZEOF_PTR * 2; // one word each in front for + // vptr and length + final int mul = Config.SIZEOF_PTR; // assume all arrays of 4-byte elem + return String.format("%d(%s,%s,%d)", offset, arrReg, idxReg, mul); + } + + void increaseIndent(String comment) { + indent.append(" "); + if (comment != null) + emitComment(comment); + } + + void decreaseIndent() { + indent.setLength(indent.length() - 2); + } + + void emitCommentSection(String name) { + int indentLen = indent.length(); + int breakLen = 68 - indentLen - name.length(); + StringBuffer sb = new StringBuffer(); + sb.append(Config.COMMENT_SEP).append(" "); + for (int i = 0; i < indentLen; i++) + sb.append("_"); + sb.append(name); + for (int i = 0; i < breakLen; i++) + sb.append("_"); + + try { + out.write(sb.toString()); + out.write("\n"); + } catch (IOException e) { + } + } + + void emitComment(String comment) { + emitRaw(Config.COMMENT_SEP + " " + comment); + } + + void emit(String op, Register src, String dest) { + emit(op, src.repr, dest); + } + + void emit(String op, String src, Register dest) { + emit(op, src, dest.repr); + } + + void emit(String op, Register src, Register dest) { + emit(op, src.repr, dest.repr); + } + + void emit(String op, String src, String dest) { + emitRaw(String.format("%s %s, %s", op, src, dest)); + } + + void emit(String op, int src, Register dest) { + emit(op, constant(src), dest); + } + + void emit(String op, int src, String dest) { + emit(op, constant(src), dest); + } + + void emit(String op, String dest) { + emitRaw(op + " " + dest); + } + + void emit(String op, Register reg) { + emit(op, reg.repr); + } + + void emit(String op, int dest) { + emit(op, constant(dest)); + } + + void emit(String op, int src, int dest) { + emit(op, constant(src), constant(dest)); + } + + void emitMove(Register src, String dest) { + emitMove(src.repr, dest); + } + + void emitMove(Register src, Register dest) { + emitMove(src.repr, dest.repr); + } + + void emitMove(String src, Register dest) { + emitMove(src, dest.repr); + } + + void emitMove(String src, String dest) { + if (!src.equals(dest)) + emit("movl", src, dest); + } + + void emitMove(int src, Register dest) { + emitMove(constant(src), dest); + } + + void emitLoad(int srcOffset, Register src, Register dest) { + emitMove(registerOffset(srcOffset, src), dest.repr); + } + + void emitStore(Register src, int destOffset, Register dest) { + emitStore(src.repr, destOffset, dest); + } + + void emitStore(String src, int destOffset, Register dest) { + emitMove(src, registerOffset(destOffset, dest)); + } + + void emitStore(int src, int destOffset, Register dest) { + emitStore(constant(src), destOffset, dest); + } + + void emitConstantData(String data) { + emitRaw(String.format("%s %s", Config.DOT_INT, data)); + } + + String uniqueLabel() { + String labelName = "label" + counter++; + return labelName; + } + + void emitLabel(String label) { + try { + out.write(label + ":" + "\n"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + void emitRaw(String op) { + try { + out.write(indent.toString()); + out.write(op); + out.write("\n"); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} \ 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..dbcfd7b --- /dev/null +++ b/src/cd/backend/codegen/AstCodeGenerator.java @@ -0,0 +1,172 @@ +package cd.backend.codegen; + +import cd.Config; +import cd.Main; +import cd.backend.codegen.RegisterManager.Register; +import cd.ir.Ast; +import cd.ir.Ast.ClassDecl; +import cd.ir.Symbol.*; + +import java.io.Writer; +import java.util.*; + +import static cd.Config.MAIN; +import static cd.backend.codegen.RegisterManager.BASE_REG; +import static cd.backend.codegen.RegisterManager.STACK_REG; + +public class AstCodeGenerator { + /** Constant representing the boolean TRUE as integer */ + static final int TRUE = 1; + /** Constant representing the boolean FALSE as integer */ + static final int FALSE = 0; + /** Size of any variable in assembly + * Primitive variables take up 4 bytes (booleans are integers) + * Reference variables are a 4 byte pointer + */ + static final int VAR_SIZE = 4; + + RegsNeededVisitor rnv; + + ExprGenerator eg; + StmtGenerator sg; + + protected final Main main; + + final AssemblyEmitter emit; + final RegisterManager rm = new RegisterManager(); + + AstCodeGenerator(Main main, Writer out) { + initMethodData(); + main.allTypeSymbols = new ArrayList<>(); + + this.emit = new AssemblyEmitter(out); + this.main = main; + this.rnv = new RegsNeededVisitor(); + + this.eg = new ExprGenerator(this); + this.sg = new StmtGenerator(this); + } + + protected void debug(String format, Object... args) { + this.main.debug(format, args); + } + + public static AstCodeGenerator createCodeGenerator(Main main, Writer out) { + return new AstCodeGenerator(main, out); + } + + /** + * Main method. Causes us to emit x86 assembly corresponding to {@code ast} + * into {@code file}. Throws a {@link RuntimeException} should any I/O error + * occur. + * + *

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

+ * The default implementation does nothing. + */ + protected void nodeReplaced(Ast oldNode, Ast newNode) {} + +} diff --git a/src/cd/ir/AstVisitor.java b/src/cd/ir/AstVisitor.java index 0576c24..8d334d6 100644 --- a/src/cd/ir/AstVisitor.java +++ b/src/cd/ir/AstVisitor.java @@ -16,6 +16,17 @@ public class AstVisitor extends ExprVisitor { return ast.accept(this, arg); } + /** + * Overrides {@link ExprVisitor#visitChildren(Expr, Object)} and + * delegates to the more general {@link #visitChildren(Ast, Object)} + * with {@link Ast} parameter. This method is final to prevent + * overriding only one of the two versions. + */ + @Override + public final R visitChildren(Expr ast, A arg) { + return visitChildren((Ast) ast, arg); + } + /** * A handy function which visits the children of {@code ast}, * providing "arg" to each of them. It returns the result of @@ -45,6 +56,7 @@ public class AstVisitor extends ExprVisitor { /** * The default action for expressions is to call this */ + @Override protected R dfltExpr(Expr ast, A arg) { return dflt(ast, arg); } diff --git a/src/cd/ir/Symbol.java b/src/cd/ir/Symbol.java index 0d84cf5..b2f5502 100644 --- a/src/cd/ir/Symbol.java +++ b/src/cd/ir/Symbol.java @@ -17,41 +17,36 @@ public abstract class Symbol { public abstract boolean isReferenceType(); - public String toString() { + @Override + public String toString() { return name; } - - /** - * Determine if the TypeSymbol is a subtype of the parameter - *
The conditions are the following - *

    - *
  • Any type is a subtype of itself
  • - *
  • Primitive types have no subtypes
  • - *
  • Reference types are a subtype of {@code Object}
  • - *
  • Reference types are a supertype of {@code null}
  • - *
  • Classes can only be subtypes of other classes
  • - *
  • Classes that inherit another are its subtypes (recursively)
  • - *
- */ - public boolean isSubtypeOf(TypeSymbol sym) { - // Any type is a subtype of itself - if (sym == this) return true; - // A PrimitiveType doesn't have subtypes - if (this instanceof PrimitiveTypeSymbol || sym instanceof PrimitiveTypeSymbol) - return false; - // Any reference type is a subtype of Object - if (this.isReferenceType() && sym == ClassSymbol.objectType) + + public abstract TypeSymbol getSuperType(); + + public boolean isSuperTypeOf(TypeSymbol sub) { + // "void" is not a subtype of any type not even itself + if(this == PrimitiveTypeSymbol.voidType || sub == PrimitiveTypeSymbol.voidType) + return false; + + if (sub == this) return true; - // null is a subtype of any reference type - if (sym.isReferenceType() && this == ClassSymbol.nullType) + + if (this instanceof PrimitiveTypeSymbol || sub instanceof PrimitiveTypeSymbol) + return false; // no hierarchy with primitive types + + if (sub == ClassSymbol.nullType && this.isReferenceType()) return true; - // Class types can only be subtypes of other Class types - // A class type is subtype of another if has inherited it - if (sym instanceof ClassSymbol && this instanceof ClassSymbol) - for (ClassSymbol c = (ClassSymbol) this; c != ClassSymbol.objectType; c = c.superClass) - if (c == sym) return true; + + TypeSymbol curr = sub; + while (curr != null) { + if (curr == this) + return true; + curr = curr.getSuperType(); + } return false; } + } public static class PrimitiveTypeSymbol extends TypeSymbol { @@ -65,9 +60,15 @@ public abstract class Symbol { super(name); } - public boolean isReferenceType() { + @Override + public boolean isReferenceType() { return false; } + + @Override + public TypeSymbol getSuperType() { + throw new RuntimeException("should not call this on PrimitiveTypeSymbol"); + } } public static class ArrayTypeSymbol extends TypeSymbol { @@ -78,10 +79,16 @@ public abstract class Symbol { this.elementType = elementType; } - public boolean isReferenceType() { + @Override + public boolean isReferenceType() { return true; } + @Override + public TypeSymbol getSuperType() { + return ClassSymbol.objectType; + } + } public static class ClassSymbol extends TypeSymbol { @@ -89,12 +96,14 @@ public abstract class Symbol { public ClassSymbol superClass; public final VariableSymbol thisSymbol = new VariableSymbol("this", this); - public final Map fields = new HashMap<>(); - public final Map methods = new HashMap<>(); + public final Map fields = + new HashMap(); + public final Map methods = + new HashMap(); /** Symbols for the built-in Object and null types */ public static final ClassSymbol nullType = new ClassSymbol(""); - public static final ClassSymbol objectType = new ClassSymbol("Object"); + public static final ClassSymbol objectType = new ClassSymbol("Object"); public ClassSymbol(Ast.ClassDecl ast) { super(ast.name); @@ -108,10 +117,16 @@ public abstract class Symbol { this.ast = null; } - public boolean isReferenceType() { + @Override + public boolean isReferenceType() { return true; } + @Override + public TypeSymbol getSuperType() { + return superClass; + } + public VariableSymbol getField(String name) { VariableSymbol fsym = fields.get(name); if (fsym == null && superClass != null) @@ -130,8 +145,10 @@ public abstract class Symbol { public static class MethodSymbol extends Symbol { public final Ast.MethodDecl ast; - public final Map locals = new HashMap<>(); - public final List parameters = new ArrayList<>(); + public final Map locals = + new HashMap(); + public final List parameters = + new ArrayList(); public TypeSymbol returnType; @@ -140,14 +157,15 @@ public abstract class Symbol { this.ast = ast; } - public String toString() { + @Override + public String toString() { return name + "(...)"; } } public static class VariableSymbol extends Symbol { - public enum Kind { PARAM, LOCAL, FIELD } + public static enum Kind { PARAM, LOCAL, FIELD }; public final TypeSymbol type; public final Kind kind; @@ -161,7 +179,8 @@ public abstract class Symbol { this.kind = kind; } - public String toString() { + @Override + public String toString() { return name; } } @@ -169,4 +188,5 @@ public abstract class Symbol { protected Symbol(String name) { this.name = name; } + } diff --git a/src/cd/util/FileUtil.java b/src/cd/util/FileUtil.java index bf5cc60..991e33e 100644 --- a/src/cd/util/FileUtil.java +++ b/src/cd/util/FileUtil.java @@ -1,13 +1,6 @@ package cd.util; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.io.Reader; +import java.io.*; import java.util.ArrayList; import java.util.List; diff --git a/src/cd/util/debug/AstDump.java b/src/cd/util/debug/AstDump.java index d4a4e73..cd7a975 100644 --- a/src/cd/util/debug/AstDump.java +++ b/src/cd/util/debug/AstDump.java @@ -1,16 +1,12 @@ package cd.util.debug; -import java.lang.reflect.Field; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - import cd.ir.Ast; import cd.ir.AstVisitor; import cd.util.Pair; +import java.lang.reflect.Field; +import java.util.*; + public class AstDump { public static String toString(Ast ast) { diff --git a/src/cd/util/debug/AstOneLine.java b/src/cd/util/debug/AstOneLine.java index 7c0a768..9d64dbd 100644 --- a/src/cd/util/debug/AstOneLine.java +++ b/src/cd/util/debug/AstOneLine.java @@ -1,32 +1,7 @@ package cd.util.debug; import cd.ir.Ast; -import cd.ir.Ast.Assign; -import cd.ir.Ast.BinaryOp; -import cd.ir.Ast.BooleanConst; -import cd.ir.Ast.BuiltInRead; -import cd.ir.Ast.BuiltInWrite; -import cd.ir.Ast.BuiltInWriteln; -import cd.ir.Ast.Cast; -import cd.ir.Ast.ClassDecl; -import cd.ir.Ast.Field; -import cd.ir.Ast.IfElse; -import cd.ir.Ast.Index; -import cd.ir.Ast.IntConst; -import cd.ir.Ast.MethodCall; -import cd.ir.Ast.MethodCallExpr; -import cd.ir.Ast.MethodDecl; -import cd.ir.Ast.NewArray; -import cd.ir.Ast.NewObject; -import cd.ir.Ast.Nop; -import cd.ir.Ast.NullConst; -import cd.ir.Ast.ReturnStmt; -import cd.ir.Ast.Seq; -import cd.ir.Ast.ThisRef; -import cd.ir.Ast.UnaryOp; -import cd.ir.Ast.Var; -import cd.ir.Ast.VarDecl; -import cd.ir.Ast.WhileLoop; +import cd.ir.Ast.*; import cd.ir.AstVisitor; public class AstOneLine { diff --git a/src/cd/util/debug/DumpUtils.java b/src/cd/util/debug/DumpUtils.java new file mode 100644 index 0000000..a78e8bd --- /dev/null +++ b/src/cd/util/debug/DumpUtils.java @@ -0,0 +1,34 @@ +package cd.util.debug; + +import cd.ir.Ast.ClassDecl; +import cd.ir.Ast.MethodDecl; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Set; + +import static java.util.Collections.sort; + +class DumpUtils { + + static final Comparator classComparator = new Comparator() { + public int compare(ClassDecl left, ClassDecl right) { + return left.name.compareTo(right.name); + } + }; + + static final Comparator methodComparator = new Comparator() { + public int compare(MethodDecl left, MethodDecl right) { + return left.name.compareTo(right.name); + } + }; + + static List sortedStrings(Set set) { + List strings = new ArrayList(); + for(Object element : set) + strings.add(element.toString()); + sort(strings); + return strings; + } +} diff --git a/test/cd/test/AbstractTestAgainstFrozenReference.java b/test/cd/AbstractTestAgainstFrozenReference.java similarity index 66% rename from test/cd/test/AbstractTestAgainstFrozenReference.java rename to test/cd/AbstractTestAgainstFrozenReference.java index 2ce2faa..dfbe35a 100644 --- a/test/cd/test/AbstractTestAgainstFrozenReference.java +++ b/test/cd/AbstractTestAgainstFrozenReference.java @@ -1,18 +1,23 @@ -package cd.test; +package cd; 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 java.util.concurrent.TimeUnit; import org.junit.Assert; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; -import cd.Config; -import cd.Main; +import cd.backend.codegen.AssemblyFailedException; import cd.frontend.parser.ParseFailure; import cd.frontend.semantic.SemanticFailure; import cd.ir.Ast.ClassDecl; @@ -25,13 +30,17 @@ abstract public class AbstractTestAgainstFrozenReference { public static final String PARSE_FAILURE = "ParseFailure"; public File file, sfile, binfile, infile; - public File parserreffile, semanticreffile, execreffile, cfgreffile, optreffile; + public File parserreffile, semanticreffile, execreffile, cfgreffile, rdreffile, + nnreffile, optreffile; public File errfile; public Main main; public static int counter = 0; - @Test(timeout=10000) + @Rule + public TestRule timeout = new DisableOnDebug(new Timeout(15, TimeUnit.SECONDS)); + + @Test public void test() throws Throwable { System.err.println("[" + counter++ + " = " + file + "]"); @@ -48,6 +57,12 @@ abstract public class AbstractTestAgainstFrozenReference { if (astRoots != null) { { boolean passedSemanticAnalysis = testSemanticAnalyzer(astRoots); + + { + if (passedSemanticAnalysis) { + testCodeGenerator(astRoots); + } + } } } } catch (org.junit.ComparisonFailure cf) { @@ -77,7 +92,7 @@ abstract public class AbstractTestAgainstFrozenReference { 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", + "-cp", "lib/frozenReferenceObf.jar" + colon + " lib/junit-4.12.jar" + colon + "lib/antlr-4.7.1-complete.jar", "cd.FrozenReferenceMain", file.getAbsolutePath()); Process proc = pb.start(); @@ -93,13 +108,24 @@ abstract public class AbstractTestAgainstFrozenReference { private static String referenceVersion() { { - return "CD_HW_SEMANTIC_SOL"; + return "CD_HW_CODEGEN_FULL_SOL"; } } + private String tryReadRefFile(File fileToFind) { + if (fileToFind.exists() && fileToFind.lastModified() >= file.lastModified()) { + try { + return FileUtil.read(fileToFind); + } catch (IOException e) { + throw new RuntimeException("ERROR: could not read file " + fileToFind.getPath()); + } + } + throw new RuntimeException("ERROR: could not find file " + fileToFind.getPath()); + } + /** Run the parser and compare the output against the reference results */ private List testParser() throws Exception { - String parserRef = findParserRef(); + String parserRef = tryReadRefFile(parserreffile); List astRoots = null; String parserOut; @@ -127,13 +153,6 @@ abstract public class AbstractTestAgainstFrozenReference { 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"); - } private boolean testSemanticAnalyzer(List astRoots) throws IOException { @@ -161,12 +180,7 @@ abstract public class AbstractTestAgainstFrozenReference { // The second one contains additional information that we log // to the debug file. - // Read in the result - String res; - if (semanticreffile.exists() && semanticreffile.lastModified() > file.lastModified()) - res = FileUtil.read(semanticreffile); - else - throw new RuntimeException("ERROR: could not find semantic .ref"); + String res = tryReadRefFile(semanticreffile); // Extract the first line: there should always be multiple lines, // but someone may have tinkered with the file or something @@ -181,6 +195,48 @@ abstract public class AbstractTestAgainstFrozenReference { } } + /** + * 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 = tryReadRefFile(execreffile); + + // 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 void assertEquals(String phase, String exp, String act_) { String act = act_.replace("\r\n", "\n"); // for windows machines if (!exp.equals(act)) { @@ -188,6 +244,16 @@ abstract public class AbstractTestAgainstFrozenReference { } } + /** + * 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)) { diff --git a/test/cd/test/Diff.java b/test/cd/Diff.java similarity index 99% rename from test/cd/test/Diff.java rename to test/cd/Diff.java index a86b5ee..148c1bd 100644 --- a/test/cd/test/Diff.java +++ b/test/cd/Diff.java @@ -1,4 +1,4 @@ -package cd.test; +package cd; // NOTE: This code was adapted from the code found at // http://www.cs.princeton.edu/introcs/96optimization/Diff.java.html diff --git a/test/cd/test/TestSamplePrograms.java b/test/cd/TestSamplePrograms.java similarity index 89% rename from test/cd/test/TestSamplePrograms.java rename to test/cd/TestSamplePrograms.java index 311c5dd..7d53fd5 100644 --- a/test/cd/test/TestSamplePrograms.java +++ b/test/cd/TestSamplePrograms.java @@ -1,4 +1,4 @@ -package cd.test; +package cd; import java.io.File; import java.io.StringWriter; @@ -10,8 +10,6 @@ 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) @@ -31,8 +29,8 @@ public class TestSamplePrograms extends AbstractTestAgainstFrozenReference { * particular directory, use sth. like: * {@code testDir = new File("javali_tests/HW2/")}. */ - public static final File testDir = new File("javali_tests/HW3_nop90"); -// public static final File testDir = new File("javali_tests"); +// 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() { @@ -64,6 +62,8 @@ public class TestSamplePrograms extends AbstractTestAgainstFrozenReference { 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.rdreffile = new File(file.getPath() + ".rd.ref"); + this.nnreffile = new File(file.getPath() + ".nn.ref"); this.optreffile = new File(file.getPath() + ".opt.ref"); this.errfile = new File(String.format("%s.err", file.getPath())); this.main = new Main();