Compare commits
No commits in common. "1348cea93f6a36dbf55fdf6f13d50ef9b9bbdf47" and "ae45251189d2579a17ea31773aa888d464b1de32" have entirely different histories.
1348cea93f
...
ae45251189
5
.gitignore
vendored
5
.gitignore
vendored
@ -26,8 +26,3 @@ cabal.project.local
|
|||||||
cabal.project.local~
|
cabal.project.local~
|
||||||
.HTF/
|
.HTF/
|
||||||
.ghc.environment.*
|
.ghc.environment.*
|
||||||
texput.log
|
|
||||||
doc/output/
|
|
||||||
doc/*.aux
|
|
||||||
doc/*.log
|
|
||||||
doc/*.out
|
|
@ -16,8 +16,7 @@ public class Main {
|
|||||||
TestRecursion recursion = new TestRecursion(10);
|
TestRecursion recursion = new TestRecursion(10);
|
||||||
TestMalicious malicious = new TestMalicious();
|
TestMalicious malicious = new TestMalicious();
|
||||||
TestLoop loop = new TestLoop();
|
TestLoop loop = new TestLoop();
|
||||||
TestMethodOverload overload = new TestMethodOverload();
|
|
||||||
|
|
||||||
// constructing a basic class works
|
// constructing a basic class works
|
||||||
assert empty != null;
|
assert empty != null;
|
||||||
// initializers (and default initializers to 0/null) work
|
// initializers (and default initializers to 0/null) work
|
||||||
@ -35,12 +34,6 @@ public class Main {
|
|||||||
// self-referencing methods work.
|
// self-referencing methods work.
|
||||||
assert recursion.fibonacci(15) == 610;
|
assert recursion.fibonacci(15) == 610;
|
||||||
assert loop.factorial(5) == 120;
|
assert loop.factorial(5) == 120;
|
||||||
// methods with the same name but different parameters work
|
|
||||||
assert overload.MethodOverload() == 42;
|
|
||||||
assert overload.MethodOverload(15) == 42 + 15;
|
|
||||||
// constructor overloading works, too.
|
|
||||||
assert (new TestConstructorOverload()).a == 42;
|
|
||||||
assert (new TestConstructorOverload(12)).a == 12;
|
|
||||||
// intentionally dodgy expressions work
|
// intentionally dodgy expressions work
|
||||||
assert malicious.assignNegativeIncrement(42) == -42;
|
assert malicious.assignNegativeIncrement(42) == -42;
|
||||||
assert malicious.tripleAddition(1, 2, 3) == 6;
|
assert malicious.tripleAddition(1, 2, 3) == 6;
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
public class TestConstructorOverload {
|
|
||||||
|
|
||||||
public int a = 42;
|
|
||||||
|
|
||||||
TestConstructorOverload() {
|
|
||||||
// nothing here, so a will assume the default value 42.
|
|
||||||
}
|
|
||||||
|
|
||||||
TestConstructorOverload(int a) {
|
|
||||||
this.a = a;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
public class TestMethodOverload {
|
|
||||||
|
|
||||||
public int MethodOverload() {
|
|
||||||
return 42;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int MethodOverload(int a) {
|
|
||||||
return 42 + a;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
public class TestSingleton {
|
|
||||||
|
|
||||||
TestSingleton instance;
|
|
||||||
|
|
||||||
TestSingleton() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public TestSingleton getInstance() {
|
|
||||||
if (instance == null) {
|
|
||||||
instance = new TestSingleton();
|
|
||||||
}
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -7,56 +7,56 @@ import Ast
|
|||||||
|
|
||||||
|
|
||||||
testSingleEmptyClass = TestCase $
|
testSingleEmptyClass = TestCase $
|
||||||
assertEqual "expect single empty class hello" [Class "Hello" [] [] []] $
|
assertEqual "expect single empty class hello" [Class "Hello" [] []] $
|
||||||
parse [CLASS, IDENTIFIER "Hello", LBRACKET, RBRACKET]
|
parse [CLASS, IDENTIFIER "Hello", LBRACKET, RBRACKET]
|
||||||
testTwoEmptyClasses = TestCase $
|
testTwoEmptyClasses = TestCase $
|
||||||
assertEqual "expect two empty classes" [Class "Class1" [] [] [], Class "Class2" [] [] []] $
|
assertEqual "expect two empty classes" [Class "Class1" [] [], Class "Class2" [] []] $
|
||||||
parse [CLASS,IDENTIFIER "Class1",LBRACKET,RBRACKET,CLASS,IDENTIFIER "Class2",LBRACKET,RBRACKET]
|
parse [CLASS,IDENTIFIER "Class1",LBRACKET,RBRACKET,CLASS,IDENTIFIER "Class2",LBRACKET,RBRACKET]
|
||||||
testBooleanField = TestCase $
|
testBooleanField = TestCase $
|
||||||
assertEqual "expect class with boolean field" [Class "WithBool" [] [] [VariableDeclaration "boolean" "value" Nothing]] $
|
assertEqual "expect class with boolean field" [Class "WithBool" [] [VariableDeclaration "boolean" "value" Nothing]] $
|
||||||
parse [CLASS,IDENTIFIER "WithBool",LBRACKET,BOOLEAN,IDENTIFIER "value",SEMICOLON,RBRACKET]
|
parse [CLASS,IDENTIFIER "WithBool",LBRACKET,BOOLEAN,IDENTIFIER "value",SEMICOLON,RBRACKET]
|
||||||
testIntField = TestCase $
|
testIntField = TestCase $
|
||||||
assertEqual "expect class with int field" [Class "WithInt" [] [] [VariableDeclaration "int" "value" Nothing]] $
|
assertEqual "expect class with int field" [Class "WithInt" [] [VariableDeclaration "int" "value" Nothing]] $
|
||||||
parse [CLASS,IDENTIFIER "WithInt",LBRACKET,INT,IDENTIFIER "value",SEMICOLON,RBRACKET]
|
parse [CLASS,IDENTIFIER "WithInt",LBRACKET,INT,IDENTIFIER "value",SEMICOLON,RBRACKET]
|
||||||
testCustomTypeField = TestCase $
|
testCustomTypeField = TestCase $
|
||||||
assertEqual "expect class with foo field" [Class "WithFoo" [] [] [VariableDeclaration "Foo" "value" Nothing]] $
|
assertEqual "expect class with foo field" [Class "WithFoo" [] [VariableDeclaration "Foo" "value" Nothing]] $
|
||||||
parse [CLASS,IDENTIFIER "WithFoo",LBRACKET,IDENTIFIER "Foo",IDENTIFIER "value",SEMICOLON,RBRACKET]
|
parse [CLASS,IDENTIFIER "WithFoo",LBRACKET,IDENTIFIER "Foo",IDENTIFIER "value",SEMICOLON,RBRACKET]
|
||||||
testMultipleDeclarationSameLine = TestCase $
|
testMultipleDeclarationSameLine = TestCase $
|
||||||
assertEqual "expect class with two int fields" [Class "TwoInts" [] [] [VariableDeclaration "int" "num1" Nothing, VariableDeclaration "int" "num2" Nothing]] $
|
assertEqual "expect class with two int fields" [Class "TwoInts" [] [VariableDeclaration "int" "num1" Nothing, VariableDeclaration "int" "num2" Nothing]] $
|
||||||
parse [CLASS,IDENTIFIER "TwoInts",LBRACKET,INT,IDENTIFIER "num1",COMMA,IDENTIFIER "num2",SEMICOLON,RBRACKET]
|
parse [CLASS,IDENTIFIER "TwoInts",LBRACKET,INT,IDENTIFIER "num1",COMMA,IDENTIFIER "num2",SEMICOLON,RBRACKET]
|
||||||
testMultipleDeclarations = TestCase $
|
testMultipleDeclarations = TestCase $
|
||||||
assertEqual "expect class with int and char field" [Class "Multiple" [] [] [VariableDeclaration "int" "value" Nothing, VariableDeclaration "char" "letter" Nothing]] $
|
assertEqual "expect class with int and char field" [Class "Multiple" [] [VariableDeclaration "int" "value" Nothing, VariableDeclaration "char" "letter" Nothing]] $
|
||||||
parse [CLASS,IDENTIFIER "Multiple",LBRACKET,INT,IDENTIFIER "value",SEMICOLON,CHAR,IDENTIFIER "letter",SEMICOLON,RBRACKET]
|
parse [CLASS,IDENTIFIER "Multiple",LBRACKET,INT,IDENTIFIER "value",SEMICOLON,CHAR,IDENTIFIER "letter",SEMICOLON,RBRACKET]
|
||||||
testWithModifier = TestCase $
|
testWithModifier = TestCase $
|
||||||
assertEqual "expect class with int field" [Class "WithInt" [] [] [VariableDeclaration "int" "value" Nothing]] $
|
assertEqual "expect class with int field" [Class "WithInt" [] [VariableDeclaration "int" "value" Nothing]] $
|
||||||
parse [ABSTRACT,CLASS,IDENTIFIER "WithInt",LBRACKET,PUBLIC,INT,IDENTIFIER "value",SEMICOLON,RBRACKET]
|
parse [ABSTRACT,CLASS,IDENTIFIER "WithInt",LBRACKET,PUBLIC,INT,IDENTIFIER "value",SEMICOLON,RBRACKET]
|
||||||
|
|
||||||
testEmptyMethod = TestCase $
|
testEmptyMethod = TestCase $
|
||||||
assertEqual "expect class with method" [Class "WithMethod" [] [MethodDeclaration "int" "foo" [] (Block [])] []] $
|
assertEqual "expect class with method" [Class "WithMethod" [MethodDeclaration "int" "foo" [] (Block [])] []] $
|
||||||
parse [CLASS,IDENTIFIER "WithMethod",LBRACKET,INT,IDENTIFIER "foo",LBRACE,RBRACE,SEMICOLON,RBRACKET]
|
parse [CLASS,IDENTIFIER "WithMethod",LBRACKET,INT,IDENTIFIER "foo",LBRACE,RBRACE,SEMICOLON,RBRACKET]
|
||||||
testEmptyPrivateMethod = TestCase $
|
testEmptyPrivateMethod = TestCase $
|
||||||
assertEqual "expect class with method" [Class "WithMethod" [] [MethodDeclaration "int" "foo" [] (Block [])] []] $
|
assertEqual "expect class with method" [Class "WithMethod" [MethodDeclaration "int" "foo" [] (Block [])] []] $
|
||||||
parse [CLASS,IDENTIFIER "WithMethod",LBRACKET,PRIVATE,INT,IDENTIFIER "foo",LBRACE,RBRACE,LBRACKET,RBRACKET,RBRACKET]
|
parse [CLASS,IDENTIFIER "WithMethod",LBRACKET,PRIVATE,INT,IDENTIFIER "foo",LBRACE,RBRACE,LBRACKET,RBRACKET,RBRACKET]
|
||||||
testEmptyVoidMethod = TestCase $
|
testEmptyVoidMethod = TestCase $
|
||||||
assertEqual "expect class with method" [Class "WithMethod" [] [MethodDeclaration "void" "foo" [] (Block [])] []] $
|
assertEqual "expect class with method" [Class "WithMethod" [MethodDeclaration "void" "foo" [] (Block [])] []] $
|
||||||
parse [CLASS,IDENTIFIER "WithMethod",LBRACKET,VOID,IDENTIFIER "foo",LBRACE,RBRACE,LBRACKET,RBRACKET,RBRACKET]
|
parse [CLASS,IDENTIFIER "WithMethod",LBRACKET,VOID,IDENTIFIER "foo",LBRACE,RBRACE,LBRACKET,RBRACKET,RBRACKET]
|
||||||
testEmptyMethodWithParam = TestCase $
|
testEmptyMethodWithParam = TestCase $
|
||||||
assertEqual "expect class with method with param" [Class "WithParam" [] [MethodDeclaration "void" "foo" [ParameterDeclaration "int" "param"] (Block [])] []] $
|
assertEqual "expect class with method with param" [Class "WithParam" [MethodDeclaration "void" "foo" [ParameterDeclaration "int" "param"] (Block [])] []] $
|
||||||
parse [CLASS,IDENTIFIER "WithParam",LBRACKET,VOID,IDENTIFIER "foo",LBRACE,INT,IDENTIFIER "param",RBRACE,SEMICOLON,RBRACKET]
|
parse [CLASS,IDENTIFIER "WithParam",LBRACKET,VOID,IDENTIFIER "foo",LBRACE,INT,IDENTIFIER "param",RBRACE,SEMICOLON,RBRACKET]
|
||||||
testEmptyMethodWithParams = TestCase $
|
testEmptyMethodWithParams = TestCase $
|
||||||
assertEqual "expect class with multiple params" [Class "WithParams" [] [MethodDeclaration "void" "foo" [ParameterDeclaration "int" "p1",ParameterDeclaration "Custom" "p2"] (Block [])] []] $
|
assertEqual "expect class with multiple params" [Class "WithParams" [MethodDeclaration "void" "foo" [ParameterDeclaration "int" "p1",ParameterDeclaration "Custom" "p2"] (Block [])] []] $
|
||||||
parse [CLASS,IDENTIFIER "WithParams",LBRACKET,VOID,IDENTIFIER "foo",LBRACE,INT,IDENTIFIER "p1",COMMA,IDENTIFIER "Custom",IDENTIFIER "p2",RBRACE,SEMICOLON,RBRACKET]
|
parse [CLASS,IDENTIFIER "WithParams",LBRACKET,VOID,IDENTIFIER "foo",LBRACE,INT,IDENTIFIER "p1",COMMA,IDENTIFIER "Custom",IDENTIFIER "p2",RBRACE,SEMICOLON,RBRACKET]
|
||||||
testClassWithMethodAndField = TestCase $
|
testClassWithMethodAndField = TestCase $
|
||||||
assertEqual "expect class with method and field" [Class "WithMethodAndField" [] [MethodDeclaration "void" "foo" [] (Block []), MethodDeclaration "int" "bar" [] (Block [])] [VariableDeclaration "int" "value" Nothing]] $
|
assertEqual "expect class with method and field" [Class "WithMethodAndField" [MethodDeclaration "void" "foo" [] (Block []), MethodDeclaration "int" "bar" [] (Block [])] [VariableDeclaration "int" "value" Nothing]] $
|
||||||
parse [CLASS,IDENTIFIER "WithMethodAndField",LBRACKET,VOID,IDENTIFIER "foo",LBRACE,RBRACE,LBRACKET,RBRACKET,INT,IDENTIFIER "value",SEMICOLON,INT,IDENTIFIER "bar",LBRACE,RBRACE,SEMICOLON,RBRACKET]
|
parse [CLASS,IDENTIFIER "WithMethodAndField",LBRACKET,VOID,IDENTIFIER "foo",LBRACE,RBRACE,LBRACKET,RBRACKET,INT,IDENTIFIER "value",SEMICOLON,INT,IDENTIFIER "bar",LBRACE,RBRACE,SEMICOLON,RBRACKET]
|
||||||
testClassWithConstructor = TestCase $
|
testClassWithConstructor = TestCase $
|
||||||
assertEqual "expect class with constructor" [Class "WithConstructor" [ConstructorDeclaration "WithConstructor" [] (Block [])] [] []] $
|
assertEqual "expect class with constructor" [Class "WithConstructor" [MethodDeclaration "void" "<init>" [] (Block [])] []] $
|
||||||
parse [CLASS,IDENTIFIER "WithConstructor",LBRACKET,IDENTIFIER "WithConstructor",LBRACE,RBRACE,LBRACKET,RBRACKET,RBRACKET]
|
parse [CLASS,IDENTIFIER "WithConstructor",LBRACKET,IDENTIFIER "WithConstructor",LBRACE,RBRACE,LBRACKET,RBRACKET,RBRACKET]
|
||||||
testConstructorWithParams = TestCase $
|
testConstructorWithParams = TestCase $
|
||||||
assertEqual "expect constructor with params" [Class "WithParams" [ConstructorDeclaration "WithParams" [ParameterDeclaration "int" "p1"] (Block [])] [] []] $
|
assertEqual "expect constructor with params" [Class "WithParams" [MethodDeclaration "void" "<init>" [ParameterDeclaration "int" "p1"] (Block [])] []] $
|
||||||
parse [CLASS,IDENTIFIER "WithParams",LBRACKET,IDENTIFIER "WithParams",LBRACE,INT,IDENTIFIER "p1",RBRACE,LBRACKET,RBRACKET,RBRACKET]
|
parse [CLASS,IDENTIFIER "WithParams",LBRACKET,IDENTIFIER "WithParams",LBRACE,INT,IDENTIFIER "p1",RBRACE,LBRACKET,RBRACKET,RBRACKET]
|
||||||
testConstructorWithStatements = TestCase $
|
testConstructorWithStatements = TestCase $
|
||||||
assertEqual "expect constructor with statement" [Class "WithConstructor" [ConstructorDeclaration "WithConstructor" [] (Block [Return Nothing])] [] []] $
|
assertEqual "expect constructor with statement" [Class "WithConstructor" [MethodDeclaration "void" "<init>" [] (Block [Return Nothing])] []] $
|
||||||
parse [CLASS,IDENTIFIER "WithConstructor",LBRACKET,IDENTIFIER "WithConstructor",LBRACE,RBRACE,LBRACKET,RETURN,SEMICOLON,RBRACKET,RBRACKET]
|
parse [CLASS,IDENTIFIER "WithConstructor",LBRACKET,IDENTIFIER "WithConstructor",LBRACE,RBRACE,LBRACKET,RETURN,SEMICOLON,RBRACKET,RBRACKET]
|
||||||
|
|
||||||
|
|
||||||
@ -78,13 +78,13 @@ testExpressionIntLiteral = TestCase $
|
|||||||
assertEqual "expect IntLiteral" (IntegerLiteral 3) $
|
assertEqual "expect IntLiteral" (IntegerLiteral 3) $
|
||||||
parseExpression [INTEGERLITERAL 3]
|
parseExpression [INTEGERLITERAL 3]
|
||||||
testFieldWithInitialization = TestCase $
|
testFieldWithInitialization = TestCase $
|
||||||
assertEqual "expect Class with initialized field" [Class "WithInitField" [] [] [VariableDeclaration "int" "number" $ Just $ IntegerLiteral 3]] $
|
assertEqual "expect Class with initialized field" [Class "WithInitField" [] [VariableDeclaration "int" "number" $ Just $ IntegerLiteral 3]] $
|
||||||
parse [CLASS,IDENTIFIER "WithInitField",LBRACKET,INT,IDENTIFIER "number",ASSIGN,INTEGERLITERAL 3,SEMICOLON,RBRACKET]
|
parse [CLASS,IDENTIFIER "WithInitField",LBRACKET,INT,IDENTIFIER "number",ASSIGN,INTEGERLITERAL 3,SEMICOLON,RBRACKET]
|
||||||
testLocalBoolWithInitialization = TestCase $
|
testLocalBoolWithInitialization = TestCase $
|
||||||
assertEqual "expect block with with initialized local var" [Block [LocalVariableDeclaration $ VariableDeclaration "boolean" "b" $ Just $ BooleanLiteral False]] $
|
assertEqual "expect block with with initialized local var" [Block [LocalVariableDeclaration $ VariableDeclaration "boolean" "b" $ Just $ BooleanLiteral False]] $
|
||||||
parseStatement [LBRACKET,BOOLEAN,IDENTIFIER "b",ASSIGN,BOOLLITERAL False,SEMICOLON,RBRACKET]
|
parseStatement [LBRACKET,BOOLEAN,IDENTIFIER "b",ASSIGN,BOOLLITERAL False,SEMICOLON,RBRACKET]
|
||||||
testFieldNullWithInitialization = TestCase $
|
testFieldNullWithInitialization = TestCase $
|
||||||
assertEqual "expect Class with initialized field" [Class "WithInitField" [] [] [VariableDeclaration "Object" "bar" $ Just NullLiteral]] $
|
assertEqual "expect Class with initialized field" [Class "WithInitField" [] [VariableDeclaration "Object" "bar" $ Just NullLiteral]] $
|
||||||
parse [CLASS,IDENTIFIER "WithInitField",LBRACKET,IDENTIFIER "Object",IDENTIFIER "bar",ASSIGN,NULLLITERAL,SEMICOLON,RBRACKET]
|
parse [CLASS,IDENTIFIER "WithInitField",LBRACKET,IDENTIFIER "Object",IDENTIFIER "bar",ASSIGN,NULLLITERAL,SEMICOLON,RBRACKET]
|
||||||
testReturnVoid = TestCase $
|
testReturnVoid = TestCase $
|
||||||
assertEqual "expect block with return nothing" [Block [Return Nothing]] $
|
assertEqual "expect block with return nothing" [Block [Return Nothing]] $
|
||||||
|
83
doc/bytecode.md
Normal file
83
doc/bytecode.md
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
# Bytecodegenerierung
|
||||||
|
|
||||||
|
Die Bytecodegenerierung ist letztendlich eine zweistufige Transformation:
|
||||||
|
|
||||||
|
`Getypter AST -> [ClassFile] -> [[Word8]]`
|
||||||
|
|
||||||
|
Vom AST, der bereits den Typcheck durchlaufen hat, wird zunächst eine Abbildung in die einzelnen ClassFiles vorgenommen. Diese ClassFiles werden anschließend in deren Byte-Repräsentation serialisiert.
|
||||||
|
|
||||||
|
## Codegenerierung
|
||||||
|
|
||||||
|
Für die erste der beiden Transformationen (`Getypter AST -> [ClassFile]`) werden die Konzepte der "Builder" und "Assembler" eingeführt. Sie sind wie folgt definiert:
|
||||||
|
```
|
||||||
|
type ClassFileBuilder a = a -> ClassFile -> ClassFile
|
||||||
|
type Assembler a = ([ConstantInfo], [Operation], [String]) -> a -> ([ConstantInfo], [Operation], [String])
|
||||||
|
```
|
||||||
|
|
||||||
|
Die Idee hinter beiden ist, dass sie jeweils zwei Inputs haben, wobei der Rückgabewert immer den gleichen Typ hat wie einer der inputs. Das erlaubt es, eine Faltung durchzuführen. Ein ClassFileBuilder z.B bekommt als ersten Parameter den AST, und als zweiten Parameter (und Rückgabewert) eine ClassFile. Soll nun eine Klasse gebaut werden, wird der ClassFileBuilder mit dem AST und einer leeren ClassFile aufgerufen. Der Zustand dieser anfangs leeren ClassFile wird durch alle folgenden Builder/Assembler durchgeschleift, was es erlaubt, nach und nach kleinere Transformationen auf sie anzuwenden.
|
||||||
|
|
||||||
|
Der Nutzer ruft beispielsweise die Funktion `classBuilder` auf. Diese wendet nach und nach folgende Transformationen an:
|
||||||
|
|
||||||
|
```
|
||||||
|
methodsWithInjectedConstructor = injectDefaultConstructor methods
|
||||||
|
methodsWithInjectedInitializers = injectFieldInitializers name fields methodsWithInjectedConstructor
|
||||||
|
|
||||||
|
classFileWithFields = foldr fieldBuilder nakedClassFile fields
|
||||||
|
classFileWithMethods = foldr methodBuilder classFileWithFields methodsWithInjectedInitializers
|
||||||
|
classFileWithAssembledMethods = foldr methodAssembler classFileWithMethods methodsWithInjectedInitializers
|
||||||
|
```
|
||||||
|
|
||||||
|
Zuerst wird (falls notwendig) ein leerer Defaultkonstruktor in die Classfile eingefügt. Anschließend wird der AST so modifiziert, dass die Initialisierungen für alle Klassenfelder in allen Konstruktoren stattfinden. Nun beginnen die Faltungen:
|
||||||
|
|
||||||
|
1. Hinzufügen aller Klassenfelder
|
||||||
|
2. Hinzufügen aller Methoden (nur Prototypen)
|
||||||
|
3. Hinzufügen des Bytecodes in allen Methoden
|
||||||
|
|
||||||
|
Die Unterteilung von Schritt 2 und 3 ist deswegen notwendig, weil der Code einer Methode auch eine andere, erst nachher deklarierte Methode aufrufen kann. Nach Schritt 2 sind alle Methoden der Klasse bekannt. Wie beschrieben wird auch hier der Zustand über alle Faltungen mitgenommen. Jeder Schritt hat Zugriff auf alle Daten, die aus dem vorherigen Schritt bleiben. Sukzessive wird eine korrekte ClassFile aufgebaut.
|
||||||
|
|
||||||
|
Besonders interessant ist hierbei Schritt 3. Dort wird das Verhalten jeder einzelnen Methode in Bytecode übersetzt. In diesem Schritt werden zusätzlich zu den `Buildern` noch die `Assembler` verwendet (Definition siehe oben.) Die Assembler funktionieren ähnlich wie die Builder, arbeiten allerdings nicht auf einer ClassFile, sondern auf dem Inhalt einer Methode: Sie verarbeiten jeweils ein Tupel:
|
||||||
|
|
||||||
|
`([ConstantInfo], [Operation], [String])`
|
||||||
|
|
||||||
|
Dieses repräsentiert:
|
||||||
|
|
||||||
|
`(Konstantenpool, Bytecode, Lokale Variablen)`
|
||||||
|
|
||||||
|
In der Praxis werden oft nur Bytecode und Konstanten hinzugefügt. Prinzipiell können Assembler auch Code/Konstanten entfernen oder modifizieren. Als Beispiel dient hier der Assembler `assembleExpression`:
|
||||||
|
|
||||||
|
```
|
||||||
|
assembleExpression (constants, ops, lvars) (TypedExpression _ NullLiteral) =
|
||||||
|
(constants, ops ++ [Opaconst_null], lvars)
|
||||||
|
```
|
||||||
|
|
||||||
|
Hier werden die Konstanten und lokalen Variablen des Inputs nicht berührt, dem Bytecode wird lediglich die Operation `aconst_null` hinzugefügt. Damit ist das Verhalten des gematchten Inputs - eines Nullliterals - abgebildet.
|
||||||
|
|
||||||
|
Die Assembler rufen sich teilweise rekursiv selbst auf, da ja auch der AST verschachteltes Verhalten abbilden kann. Der Startpunkt für die Assembly einer Methode ist der Builder `methodAssembler`. Dieser entspricht Schritt 3 in der obigen Übersicht.
|
||||||
|
|
||||||
|
## Serialisierung
|
||||||
|
|
||||||
|
Damit Bytecode generiert werden kann, braucht es Strukturen, die die Daten halten, die letztendlich serialisiert werden. Die JVM erwartet den kompilierten Code in handliche Pakete verpackt. Die Struktur dieser Pakete ist [so definiert](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html).
|
||||||
|
|
||||||
|
Jede Struktur, die in dieser übergreifenden Class File vorkommt, haben wir in Haskell abgebildet. Es gibt z.B die Struktur "ClassFile", die wiederum weitere Strukturen wie z.B Informationen über Felder oder Methoden der Klasse beinhaltet. Alle diese Strukturen implementieren folgende TypeClass:
|
||||||
|
|
||||||
|
```
|
||||||
|
class Serializable a where
|
||||||
|
serialize :: a -> [Word8]
|
||||||
|
```
|
||||||
|
|
||||||
|
Hier ist ein Beispiel anhand der Serialisierung der einzelnen Operationen:
|
||||||
|
|
||||||
|
```
|
||||||
|
instance Serializable Operation where
|
||||||
|
serialize Opiadd = [0x60]
|
||||||
|
serialize Opisub = [0x64]
|
||||||
|
serialize Opimul = [0x68]
|
||||||
|
...
|
||||||
|
serialize (Opgetfield index) = 0xB4 : unpackWord16 index
|
||||||
|
```
|
||||||
|
|
||||||
|
Die Struktur ClassFile ruft für deren Kinder rekursiv diese `serialize` Funktion auf und konkateniert die Ergebnisse. Am Ende bleibt eine flache Word8-Liste übrig, die Serialisierung ist damit abgeschlossen. Da der Typecheck sicherstellt, dass alle referenzierten Methoden/Felder gültig sind, kann die Übersetzung der einzelnen Klassen voneinander unabhängig geschehen.
|
||||||
|
|
||||||
|
## Aufgabenteilung
|
||||||
|
|
||||||
|
Die Bytecodegenerierung wurde von Matthias Raba und Christian Brier im Stile des Pair Programmings zu zweit erarbeitet. Durch bisher gute Erfahrungen in vorherigen Projekten, sowie dem Interesse, alle Teile der Bytecodegenerierung zu sehen, wurde diese Programmierungsform als die Beste ausgewählt. Insgesamt lief die Implementierungsphase wie geplant und ohne weitere Komplikationen ab.
|
107
doc/bytecode.tex
107
doc/bytecode.tex
@ -1,107 +0,0 @@
|
|||||||
\section{Bytecodegenerierung}
|
|
||||||
Die Bytecodegenerierung ist letztendlich eine zweistufige Transformation:
|
|
||||||
|
|
||||||
\vspace{20px}
|
|
||||||
\texttt{Getypter AST -> [ClassFile] -> [[Word8]]}
|
|
||||||
\vspace{20px}
|
|
||||||
|
|
||||||
Vom AST, der bereits den Typcheck durchlaufen hat, wird zunächst eine Abbildung in die einzelnen ClassFiles vorgenommen.
|
|
||||||
Diese ClassFiles werden anschließend in deren Byte-Repräsentation serialisiert.
|
|
||||||
|
|
||||||
\subsection{Codegenerierung}
|
|
||||||
Für die erste der beiden Transformationen (\texttt{Getypter AST -> [ClassFile]}) werden die Konzepte der ``Builder'' und ``Assembler'' eingeführt.
|
|
||||||
Sie sind wie folgt definiert:
|
|
||||||
|
|
||||||
\vspace{20px}
|
|
||||||
\begin{lstlisting}[language=haskell]
|
|
||||||
type ClassFileBuilder a = a -> ClassFile -> ClassFile
|
|
||||||
type Assembler a = ([ConstantInfo], [Operation], [String]) -> a
|
|
||||||
-> ([ConstantInfo], [Operation], [String])
|
|
||||||
\end{lstlisting}
|
|
||||||
\vspace{20px}
|
|
||||||
|
|
||||||
Die Idee hinter beiden ist, dass sie jeweils zwei Inputs haben, wobei der Rückgabewert immer den gleichen Typ hat wie einer der Inputs.
|
|
||||||
Das erlaubt es, eine Faltung durchzuführen. Ein ClassFileBuilder z.B bekommt als ersten Parameter den AST,
|
|
||||||
und als zweiten Parameter (und Rückgabewert) eine ClassFile. Soll nun eine Klasse gebaut werden,
|
|
||||||
wird der ClassFileBuilder mit dem AST und einer leeren ClassFile aufgerufen.
|
|
||||||
Der Zustand dieser anfangs leeren ClassFile wird durch alle folgenden Builder/Assembler durchgeschleift, was es erlaubt,
|
|
||||||
nach und nach kleinere Transformationen auf sie anzuwenden. Der Nutzer ruft beispielsweise die Funktion \texttt{classBuilder} auf.
|
|
||||||
Diese wendet nach und nach folgende Transformationen an:
|
|
||||||
|
|
||||||
\vspace{20px}
|
|
||||||
\begin{enumerate}
|
|
||||||
\item Allen Konstruktoren werden Initialisierer aller Felder hinzugefügt
|
|
||||||
\item Für jedes Feld der Klasse wird ein Eintrag im Konstantenpool \& der Classfile erstellt
|
|
||||||
\item Für jede Methode wird ein Eintrag im Konstantenpool \& der Classfile erstellt
|
|
||||||
\item Allen Methoden wird der zugehörige Bytecode erstellt und zugewiesen
|
|
||||||
\item Allen Konstruktoren wird der zugehörige Bytecode erstellt und zugewiesen
|
|
||||||
\end{enumerate}
|
|
||||||
\vspace{20px}
|
|
||||||
|
|
||||||
Die Unterteilung von Deklaration der Methoden/Konstruktoren und Bytecodeerzeugung ist deswegen notwendig,
|
|
||||||
weil der Code einer Methode auch eine andere, erst nachher deklarierte Methode aufrufen kann.
|
|
||||||
Nach dem Hinzufügen der Deklarationen sind alle Methoden/Konstruktoren der Klasse bekannt.
|
|
||||||
Wie oben beschrieben wird auch hier der Zustand über alle Faltungen mitgenommen.
|
|
||||||
Jeder Schritt hat Zugriff auf alle Daten, die aus dem vorherigen Schritt bleiben. Sukzessive wird eine korrekte ClassFile aufgebaut.
|
|
||||||
Besonders interessant sind hierbei die beiden letzten Schritte. Dort wird das Verhalten jeder einzelnen Methode/Konstruktor in Bytecode übersetzt.
|
|
||||||
In diesem Schritt werden zusätzlich zu den \texttt{Buildern} noch die \texttt{Assembler} verwendet (Definition siehe oben.).
|
|
||||||
Die Assembler funktionieren ähnlich wie die Builder, arbeiten allerdings nicht auf einer ClassFile, sondern auf dem Inhalt einer Methode;
|
|
||||||
Sie verarbeiten jeweils ein Tupel der Form:
|
|
||||||
|
|
||||||
\vspace{20px}
|
|
||||||
\texttt{([ConstantInfo], [Operation], [String])}
|
|
||||||
\vspace{20px}
|
|
||||||
|
|
||||||
Dieses repräsentiert:
|
|
||||||
|
|
||||||
\vspace{20px}
|
|
||||||
\texttt{(Konstantenpool, Bytecode, Lokale Variablen)}
|
|
||||||
\vspace{20px}
|
|
||||||
|
|
||||||
In der Praxis werden meist nur Bytecode und Konstanten hinzugefügt. Prinzipiell können Assembler auch Code/Konstanten entfernen oder modifizieren.
|
|
||||||
Als Beispiel dient hier der Assembler \texttt{assembleExpression}:
|
|
||||||
|
|
||||||
\vspace{20px}
|
|
||||||
\begin{lstlisting}[language=haskell]
|
|
||||||
assembleExpression (constants, ops, lvars) (TypedExpression _ NullLiteral)
|
|
||||||
= (constants, ops ++ [Opaconst_null], lvars)
|
|
||||||
\end{lstlisting}
|
|
||||||
\vspace{20px}
|
|
||||||
|
|
||||||
Hier werden die Konstanten und lokalen Variablen des Inputs nicht berührt, dem Bytecode wird lediglich die Operation \texttt{aconst\_null} hinzugefügt.
|
|
||||||
Damit ist das Verhalten des gematchten Inputs - eines Nullliterals - abgebildet.
|
|
||||||
Die Assembler rufen sich teilweise rekursiv selbst auf, da ja auch der AST verschachteltes Verhalten abbilden kann.
|
|
||||||
Der Startpunkt für die Assembly einer Methode ist der Builder \texttt{methodAssembler}. Dieser entspricht Schritt 3 in der obigen Übersicht.
|
|
||||||
|
|
||||||
\subsection{Serialisierung}
|
|
||||||
Damit Bytecode generiert werden kann, braucht es Strukturen, die die Daten halten, die letztendlich serialisiert werden.
|
|
||||||
Die JVM erwartet den kompilierten Code in handliche Pakete verpackt.
|
|
||||||
Die Struktur dieser Pakete ist \href{https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html}{hier dokumentiert}.
|
|
||||||
Jede Struktur, die in dieser übergreifenden Class File vorkommt, haben wir in Haskell abgebildet.
|
|
||||||
Es gibt z.B die Struktur "ClassFile", die wiederum weitere Strukturen wie z.B Informationen über Felder oder Methoden der Klasse beinhaltet.
|
|
||||||
Alle diese Strukturen implementieren folgende TypeClass:
|
|
||||||
|
|
||||||
\vspace{20px}
|
|
||||||
\begin{lstlisting}[language=haskell]
|
|
||||||
class Serializable a where
|
|
||||||
serialize :: a -> [Word8]
|
|
||||||
\end{lstlisting}
|
|
||||||
\vspace{20px}
|
|
||||||
|
|
||||||
Hier ist ein Beispiel anhand der Serialisierung der einzelnen Operationen:
|
|
||||||
|
|
||||||
\vspace{20px}
|
|
||||||
\begin{lstlisting}[language=haskell]
|
|
||||||
instance Serializable Operation where
|
|
||||||
serialize Opiadd = [0x60]
|
|
||||||
serialize Opisub = [0x64]
|
|
||||||
serialize Opimul = [0x68]
|
|
||||||
...
|
|
||||||
serialize (Opgetfield index) = 0xB4 : unpackWord16 index
|
|
||||||
\end{lstlisting}
|
|
||||||
\vspace{20px}
|
|
||||||
|
|
||||||
Die Struktur ClassFile ruft für deren Kinder rekursiv diese \texttt{serialize} Funktion auf und konkateniert die Ergebnisse.
|
|
||||||
Am Ende bleibt eine flache Word8-Liste übrig, die Serialisierung ist damit abgeschlossen.
|
|
||||||
Da der Typecheck sicherstellt, dass alle referenzierten Methoden/Felder gültig sind,
|
|
||||||
kann die Übersetzung der einzelnen Klassen voneinander unabhängig geschehen.
|
|
Binary file not shown.
@ -1,34 +0,0 @@
|
|||||||
\documentclass[12pt, parskip=half, headheight=12pt, BCOR=8mm, footheight=16pt]{extarticle}
|
|
||||||
|
|
||||||
% General document formatting
|
|
||||||
\usepackage[margin=1.0in]{geometry}
|
|
||||||
\usepackage[parfill]{parskip}
|
|
||||||
\usepackage[utf8]{inputenc}
|
|
||||||
\usepackage[german]{babel}
|
|
||||||
\usepackage{enumitem}
|
|
||||||
\usepackage{listings}
|
|
||||||
\usepackage{hyperref}
|
|
||||||
|
|
||||||
\renewcommand\descriptionlabel[1]{$\bullet$ \textbf{#1}}
|
|
||||||
\hypersetup{
|
|
||||||
colorlinks=true,
|
|
||||||
linkcolor=blue,
|
|
||||||
filecolor=magenta,
|
|
||||||
urlcolor=cyan,
|
|
||||||
}
|
|
||||||
\let\clearpage\relax
|
|
||||||
|
|
||||||
\begin{document}
|
|
||||||
|
|
||||||
\include{features}
|
|
||||||
\newpage
|
|
||||||
\include{parser}
|
|
||||||
\newpage
|
|
||||||
\include{typecheck}
|
|
||||||
\newpage
|
|
||||||
\include{bytecode}
|
|
||||||
\newpage
|
|
||||||
\include{whodunit}
|
|
||||||
\newpage
|
|
||||||
|
|
||||||
\end{document}
|
|
@ -1,34 +0,0 @@
|
|||||||
\section{Sprach-Features}
|
|
||||||
\begin{itemize}
|
|
||||||
\item Klassen
|
|
||||||
\item Felder
|
|
||||||
\item Methoden (mit Parametern)
|
|
||||||
\item Konstruktoren (mit Parametern)
|
|
||||||
\item Standardkonstruktoren
|
|
||||||
\item Lokale Variablen
|
|
||||||
\item Zuweisungen (Feld- und lokale Variablen)
|
|
||||||
\item Arithmetik (\texttt{+, -, *, /, \%,} Klammern, Korrekte Operations-Präzedenz)
|
|
||||||
\item Arithmetische Zuweisungen (\texttt{+=, -=, *=, /=, \%=, \&=, |=, \^{}=})
|
|
||||||
\item Vergleichsoperationen (\texttt{<, >, <=, >=, ==, !=})
|
|
||||||
\item Boolsche Operationen (\texttt{||, \&\&})
|
|
||||||
\item Unäre Operationen (\texttt{-, ~})
|
|
||||||
\item Binar-Operationen (\texttt{\&, |, \^})
|
|
||||||
\item Pre/Post-Inkrement \& Dekrement
|
|
||||||
\item Kontrollflussstrukturen:
|
|
||||||
\begin{itemize}
|
|
||||||
\item If/Else
|
|
||||||
\item While
|
|
||||||
\item For
|
|
||||||
\item Return (mit/ohne Rückgabewert)
|
|
||||||
\end{itemize}
|
|
||||||
\item Default-Werte für alle Klassenfelder
|
|
||||||
\item Mehrere Klassen in einer Datei
|
|
||||||
\item Implizites \texttt{this}
|
|
||||||
\item Beliebig verschachtelte Namensketten
|
|
||||||
\item Beliebige Deklarationsreihenfolge
|
|
||||||
\item Literale für Integer, Characters, Booleans
|
|
||||||
\item Platzhalter/Separatoren in Integerliteralen (z.B. \texttt{1\_000\_000})
|
|
||||||
\item Deklaration und Zuweisung in einer Anweisung
|
|
||||||
\item Beliebig verschachtelte Blöcke
|
|
||||||
\item Überladung von Methoden \& Konstruktoren
|
|
||||||
\end{itemize}
|
|
5
doc/generate.sh
Normal file → Executable file
5
doc/generate.sh
Normal file → Executable file
@ -1 +1,4 @@
|
|||||||
pdflatex documentation.tex
|
#!/usr/bin/sh
|
||||||
|
|
||||||
|
pandoc bytecode.md -o bytecode.docx
|
||||||
|
pandoc bytecode.md -o bytecode.pdf
|
19
doc/parser.md
Normal file
19
doc/parser.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Lexer
|
||||||
|
(Marvin Schlegel)
|
||||||
|
|
||||||
|
Der Lexer wurde mit dem Alex tool implementiert. Dieser ist dafür zuständig den langen String in einzelne Tokens umzuwandeln. In der Alex Datei gibt es für jedes Token einen regulären Ausdruck. Bei den meisten Tokens ist das einfach das Schlüsselwort. Etwas komplexer waren Identifier, Integerliterale Strings und Chars. Für die Definition wurde sich eng an die offizielle Java Language Specification gehalten. Es ist beispielsweise auch möglich Unterstriche in Integerliterale einzubauen (Bsp.: `234_343_000`) Es sind fast alle Schlüsselwörter von Java im Lexer implementiert, auch wenn nicht alle davon vom Parser geparst werden können. Whitespace und Kommentare werden direkt ignoriert und verworfen. Für Charliterale und Integerliterale gibt es auch spezielle Fehlermeldungen. Die meisten Tokens haben nur die Information, zu welchem Keyword sie gehören. Eine Ausnahme bilden der Identifier und die Literale. Für den Identifier wird noch der Name gespeichert und für die Literale der entsprechende Wert. Mit der Funktion alexScanTokens kann dann ein beliebiger String in Tokens umgewandelt werden.
|
||||||
|
|
||||||
|
Die komplexeren Tokens haben Unittests, welche mit dem Testframework HUnit geschrieben wurden. Es gibt Tests für Kommentare, Identifier, Literale und ein paar weitere Tokens.
|
||||||
|
|
||||||
|
# Parser
|
||||||
|
(Marvin Schlegel)
|
||||||
|
|
||||||
|
Der Parser wurde mit dem Happy tool implementiert. Er baut aus einer Liste von Tokens einen ungetypten AST. Wir haben bereits eine Grammatik bekommen und mussten diese noch in den AST umwandeln.
|
||||||
|
|
||||||
|
Um den Parser aufzubauen wurde zuerst ein Großteil der Grammatik auskommentiert und Stück für Stück wurden die Umwandlungen hinzugefügt. Immer wenn ein neues Feature umgesetzt wurde, wurde dafür ein weiterer Unit Test geschrieben. Es gibt also für jede komplexe Ableitungsregel mindestens einen Unittest.
|
||||||
|
|
||||||
|
Als erstes wurden leere Methoden und Felder umgesetzt. Da in Java Methoden und Felder durcheinander vorkommen können geben die Ableitungsregeln einen Datentype namens `MethodOrFieldDeclaration` zurück. Über Pattern Matching baut die classbodydeclarations Regel dann eine Tupel mit einer Liste aus Methoden und einer aus Feldern. Über pattern matching werden diese Listen dann erweitert und in der darüberliegenden Regel schließlich extrahiert. Die Konstruktoren sind in diesem Fall auch normale Methoden mit dem Rückgabewert `void` und dem Namen `<init>`. Auf diese Weise müssen sie nicht mehr vom Typcheck oder vom Bytecode verändert werden.
|
||||||
|
|
||||||
|
In Java ist es möglich mehrere Variablen in einer Zeile zu deklarieren (Bsp.: `int x, y;`). Beim Parsen ergiebt sich dann die Schwierigkeit, dass man in dem Moment, wo man die Variable parst nicht weiß welchen Datentyp diese hat. Aus diesem Grund gibt es den Datentyp Declarator, welcher nur den Identifier und eventuell eine Zuweisung enthält. In den darüberliegenden Regeln fielddeclaration und localvariabledeclaration wird dann die Typinformation hinzugefügt mithilfe der Funktion convertDeclarator.
|
||||||
|
|
||||||
|
Für die Zuweisung wird auch die Kombination mit Rechenoperatoren unterstützt. Das ganze ist als syntactic sugar im Parser umgesetzt. Wenn es einen Zuweisungsoperator gibt, dann wird der Ausdruck in eine Zuweisung und Rechnung aufgeteilt. Bsp.: `x += 3;` wird umgewandelt in `x = x + 3`.
|
@ -1,35 +0,0 @@
|
|||||||
\section{Lexer \& Parser}
|
|
||||||
\subsection{Lexer}
|
|
||||||
|
|
||||||
Der Lexer wurde mit dem Alex tool implementiert. Dieser ist dafür zuständig den langen String in einzelne Tokens umzuwandeln.
|
|
||||||
In der Alex Datei gibt es für jedes Token einen regulären Ausdruck. Bei den meisten Tokens ist das einfach das Schlüsselwort.
|
|
||||||
Etwas komplexer waren Identifier, Integerliterale Strings und Chars.
|
|
||||||
Für die Definition wurde sich eng an die offizielle Java Language Specification gehalten.
|
|
||||||
Es ist beispielsweise auch möglich Unterstriche in Integerliterale einzubauen (Bsp.: \texttt{234\_343\_000}).
|
|
||||||
Es sind fast alle Schlüsselwörter von Java im Lexer implementiert, auch wenn nicht alle davon vom Parser geparst werden können.
|
|
||||||
Whitespace und Kommentare werden direkt ignoriert und verworfen.
|
|
||||||
Für Charliterale und Integerliterale gibt es auch spezielle Fehlermeldungen. Die meisten Tokens haben nur die Information, zu welchem Keyword sie gehören.
|
|
||||||
Eine Ausnahme bilden der Identifier und die Literale. Für den Identifier wird noch der Name gespeichert und für die Literale der entsprechende Wert.
|
|
||||||
Mit der Funktion alexScanTokens kann dann ein beliebiger String in Tokens umgewandelt werden.
|
|
||||||
Die komplexeren Tokens haben Unittests, welche mit dem Testframework HUnit geschrieben wurden.
|
|
||||||
Es gibt Tests für Kommentare, Identifier, Literale und ein paar weitere Tokens.
|
|
||||||
|
|
||||||
\subsection{Parser}
|
|
||||||
|
|
||||||
Der Parser wurde mit dem Happy tool implementiert. Er baut aus einer Liste von Tokens einen ungetypten AST.
|
|
||||||
Wir haben bereits eine Grammatik bekommen und mussten diese noch in den AST umwandeln.
|
|
||||||
Um den Parser aufzubauen wurde zuerst ein Großteil der Grammatik auskommentiert und Stück für Stück wurden die Umwandlungen hinzugefügt.
|
|
||||||
Immer wenn ein neues Feature umgesetzt wurde, wurde dafür ein weiterer Unit Test geschrieben.
|
|
||||||
Es gibt also für jede komplexe Ableitungsregel mindestens einen Unittest. Als erstes wurden leere Methoden und Felder umgesetzt.
|
|
||||||
Da in Java Methoden und Felder durcheinander vorkommen können geben die Ableitungsregeln einen Datentype namens \texttt{MethodOrFieldDeclaration} zurück.
|
|
||||||
Über Pattern Matching baut die classbodydeclarations Regel dann eine Tupel mit einer Liste aus Methoden und einer aus Feldern.
|
|
||||||
Über pattern matching werden diese Listen dann erweitert und in der darüberliegenden Regel schließlich extrahiert.
|
|
||||||
Die Konstruktoren sind in diesem Fall auch normale Methoden mit dem Rückgabewert `void` und dem Namen \texttt{<init>}.
|
|
||||||
Auf diese Weise müssen sie nicht mehr vom Typcheck oder vom Bytecode verändert werden.
|
|
||||||
In Java ist es möglich mehrere Variablen in einer Zeile zu deklarieren (Bsp.: \texttt{int x, y;}).
|
|
||||||
Beim Parsen ergiebt sich dann die Schwierigkeit, dass man in dem Moment, wo man die Variable parst nicht weiß welchen Datentyp diese hat.
|
|
||||||
Aus diesem Grund gibt es den Datentyp Declarator, welcher nur den Identifier und eventuell eine Zuweisung enthält.
|
|
||||||
In den darüberliegenden Regeln fielddeclaration und localvariabledeclaration wird dann die Typinformation hinzugefügt mithilfe der Funktion convertDeclarator.
|
|
||||||
Für die Zuweisung wird auch die Kombination mit Rechenoperatoren unterstützt. Das Ganze ist als syntactic sugar im Parser umgesetzt.
|
|
||||||
Wenn es einen Zuweisungsoperator gibt, dann wird der Ausdruck in eine Zuweisung und Rechnung aufgeteilt.
|
|
||||||
Bsp.: \texttt{x += 3;} wird umgewandelt zu \texttt{x = x + 3}.
|
|
55
doc/typecheck.md
Normal file
55
doc/typecheck.md
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# Typcheck (Fabian Noll)
|
||||||
|
|
||||||
|
## Überblick und Struktur
|
||||||
|
|
||||||
|
Die Typprüfung beginnt mit der Funktion `typeCheckCompilationUnit`, die eine Kompilationseinheit als Eingabe erhält. Diese Kompilationseinheit besteht aus einer Liste von Klassen. Jede Klasse wird einzeln durch die Funktion `typeCheckClass` überprüft. Innerhalb dieser Funktion wird eine Symboltabelle erstellt, die den Namen der Klasse als Typ und `this` als Identifier enthält. Diese Symboltabelle wird verwendet, um Typinformationen nach dem Lokalitätsprinzip während der Typprüfung zugänglich zu machen und zu verwalten.
|
||||||
|
|
||||||
|
Die Typprüfung einer Klasse umfasst die Überprüfung aller Methoden und Felder. Die Methode `typeCheckMethodDeclaration` ist für die Typprüfung einzelner Methodendeklarationen verantwortlich. Sie überprüft den Rückgabetyp der Methode, die Parameter und den Methodenrumpf. Der Methodenrumpf wird durch rekursive Aufrufe von `typeCheckStatement` überprüft, die verschiedene Arten von Anweisungen wie If-Anweisungen, While-Schleifen, Rückgabeanweisungen und Blockanweisungen behandelt.
|
||||||
|
|
||||||
|
## Ablauf und Symboltabellen
|
||||||
|
|
||||||
|
Eine zentrale Komponente des Typecheckers ist die Symboltabelle (symtab), die Informationen über die Bezeichner und ihre zugehörigen Datentypen speichert. Die Symboltabelle wird kontinuierlich angepasst, während der Typechecker die verschiedenen Teile des Programms durchläuft.
|
||||||
|
|
||||||
|
### Anpassung der Symboltabelle
|
||||||
|
|
||||||
|
- **Klassenkontext**:
|
||||||
|
Beim Typcheck einer Klasse wird eine initiale Symboltabelle erstellt, die die `this`-Referenz enthält. Dies geschieht in der Funktion `typeCheckClass`.
|
||||||
|
|
||||||
|
- **Methodenkontext**:
|
||||||
|
Innerhalb einer Methode wird die Symboltabelle um die Parameter der Methode erweitert sowie den Rückgabetyp der Methode, um die einzelnen Returns dagegen zu prüfen. Dies geschieht in `typeCheckMethodDeclaration`.
|
||||||
|
|
||||||
|
- **Blockkontext**:
|
||||||
|
Bei der Überprüfung eines Blocks (`typeCheckStatement` für Block) wird die Symboltabelle für jede Anweisung innerhalb des Blocks aktualisiert. Lokale Variablen, die innerhalb des Blocks deklariert werden, werden zur Symboltabelle hinzugefügt. Das bedeutet, dass automatisch, sobald der Block zu Ende ist, alle dort deklarierten Variablen danach nicht mehr zugänglich sind.
|
||||||
|
|
||||||
|
### Unterscheidung zwischen lokalen und Feldvariablen
|
||||||
|
|
||||||
|
Bei der Typprüfung von Referenzen (`typeCheckExpression` für Reference) wird zuerst in der Symboltabelle nach dem Bezeichner gesucht. Sollte dieser gefunden werden, handelt es sich um eine lokale Variable. Wenn der Bezeichner nicht gefunden wird, wird angenommen, dass es sich um eine Feldvariable handelt. In diesem Fall wird die Klasse, zu der die `this`-Referenz gehört, durchsucht, um die Feldvariable zu finden. Dies ermöglicht die Unterscheidung zwischen lokalen Variablen und Feldvariablen. Dies ist auch nur möglich, da wir die Feldvariablen und Methoden nicht in die Symboltabelle gelegt haben und stattdessen nur die `this`-Referenz.
|
||||||
|
|
||||||
|
## Fehlerbehandlung
|
||||||
|
|
||||||
|
Ein zentraler Aspekt des Typecheckers ist die Fehlerbehandlung. Bei Typinkonsistenzen oder ungültigen Operationen werden aussagekräftige Fehlermeldungen generiert. Beispiele für solche Fehlermeldungen sind:
|
||||||
|
|
||||||
|
- **Typinkonsistenzen**:
|
||||||
|
Wenn der Rückgabetyp einer Methode nicht mit dem deklarierten Rückgabetyp übereinstimmt. Oder aber auch die Anzahl der Parameter nicht übereinstimmt.
|
||||||
|
|
||||||
|
- **Ungültige Operationen**:
|
||||||
|
Wenn eine arithmetische Operation auf inkompatiblen Typen durchgeführt wird.
|
||||||
|
|
||||||
|
- **Nicht gefundene Bezeichner**:
|
||||||
|
Wenn eine Referenz auf eine nicht definierte Variable verweist.
|
||||||
|
|
||||||
|
Diese Fehlermeldungen helfen Entwicklern, die Ursachen von Typfehlern schnell zu identifizieren und zu beheben. Generell sind diese oftmals sehr spezifisch, was das Problem recht schnell identifizieren sollte. Z.B. falsche Reihenfolge / falsche Typen der Parameter beim Methodenaufruf sind direkt erkennbar.
|
||||||
|
|
||||||
|
## Typprüfung von Kontrollstrukturen und Blöcken
|
||||||
|
|
||||||
|
### If-Anweisungen
|
||||||
|
|
||||||
|
Bei der Typprüfung einer If-Anweisung (`typeCheckStatement` für If) wird zuerst der Typ der Bedingung überprüft, um sicherzustellen, dass es sich um einen booleschen Ausdruck handelt. Anschließend werden die Then- und Else-Zweige geprüft. Der Typ der If-Anweisung selbst wird durch die Vereinheitlichung der Typen der Then- und Else-Zweige bestimmt. Falls einer der Zweige keinen Rückgabewert hat, wird angenommen, dass der Rückgabewert `void` ist. Dies wurde so gelöst, um im Typchecker feststellen zu können, ob beide Zweige einen Return haben. Wenn nur einer der Zweige ein Return hat, wird im umliegenden Block ein weiteres benötigt, was durch den Typ `void` erzwungen wird. Dadurch weiß der Typchecker Bescheid.
|
||||||
|
|
||||||
|
### Block-Anweisungen
|
||||||
|
|
||||||
|
Die Typprüfung eines Blocks erfolgt in `typeCheckStatement` für Block. Jede Anweisung im Block wird nacheinander überprüft und die Symboltabelle wird entsprechend aktualisiert. Der Typ des Blocks wird durch die Vereinheitlichung der Typen aller Anweisungen im Block bestimmt. Wenn der Block keine Anweisungen hat, wird der Typ `void` angenommen.
|
||||||
|
|
||||||
|
### Rückgabeanweisungen
|
||||||
|
|
||||||
|
Die Typprüfung einer Rückgabeanweisung (`typeCheckStatement` für Return) überprüft, ob der Rückgabewert der Anweisung mit dem deklarierten Rückgabetyp der Methode übereinstimmt. Dafür wurde zu Beginn der Methodentypprüfung der Rückgabetyp der Methode in die Symboltabelle eingetragen. Wenn der Rückgabewert `null` ist, wird überprüft, ob der deklarierte Rückgabetyp ein Objekttyp ist. Dies stellt sicher, dass Methoden immer den korrekten Typ zurückgeben. Generell wird bei der Prüfung nach dem UpperBound geschaut und ebenfalls wird nachgeschaut, ob, wenn der Rückgabetyp `Object` ist, der Return-Wert auch eine tatsächlich existierende Klasse ist, indem in die Klassentabelle geschaut wird.
|
@ -1,105 +0,0 @@
|
|||||||
\section{Typecheck}
|
|
||||||
\subsection{Überblick \& Struktur}
|
|
||||||
|
|
||||||
Die Typprüfung beginnt mit der Funktion \texttt{typeCheckCompilationUnit}, die eine Kompilationseinheit als Eingabe erhält.
|
|
||||||
Diese Kompilationseinheit besteht aus einer Liste von Klassen. Jede Klasse wird einzeln durch die Funktion \texttt{typeCheckClass} überprüft.
|
|
||||||
Innerhalb dieser Funktion wird eine Symboltabelle erstellt, die den Namen der Klasse als Typ und \texttt{this} als Identifier enthält.
|
|
||||||
Diese Symboltabelle wird verwendet, um Typinformationen nach dem Lokalitätsprinzip während der Typprüfung zugänglich zu machen und zu verwalten.
|
|
||||||
Die Typprüfung einer Klasse umfasst die Überprüfung aller Konstruktoren, Methoden und Felder.
|
|
||||||
Die Methode \texttt{typeCheckConstructorDeclaration} ist für die Typprüfung einzelner Konstruktordeklarationen verantwortlich,
|
|
||||||
während \texttt{typeCheckMethodDeclaration} für die Typprüfung einzelner Methodendeklarationen zuständig ist.
|
|
||||||
Beide Funktionen überprüfen die Parameter und den Rumpf der jeweiligen Konstruktoren bzw. Methoden.
|
|
||||||
Der Rumpf wird durch rekursive Aufrufe von \texttt{typeCheckStatement} überprüft, die verschiedene Arten von Anweisungen wie If-Anweisungen,
|
|
||||||
While-Schleifen, Rückgabeanweisungen und Blockanweisungen behandelt.
|
|
||||||
|
|
||||||
\subsection{Ablauf \& Symboltabellen}
|
|
||||||
|
|
||||||
Eine zentrale Komponente des Typecheckers ist die Symboltabelle ("symtab"), die Informationen über die Bezeichner und ihre zugehörigen Datentypen speichert.
|
|
||||||
Die Symboltabelle wird kontinuierlich angepasst, während der Typechecker die verschiedenen Teile des Programms durchläuft.
|
|
||||||
|
|
||||||
\subsubsection{Anpassung der Symboltabelle}
|
|
||||||
|
|
||||||
\begin{description}
|
|
||||||
\item[Klassenkontext] Beim Typcheck einer Klasse wird eine initiale Symboltabelle erstellt, die die \texttt{this}-Referenz enthält.
|
|
||||||
Dies geschieht in der Funktion \texttt{typeCheckClass}.
|
|
||||||
\item[Konstruktorkontext] Innerhalb eines Konstruktors wird die Symboltabelle um die Parameter des Konstruktors erweitert.
|
|
||||||
Dies geschieht in \texttt{typeCheckConstructorDeclaration}. Der Rückgabetyp eines Konstruktors ist implizit \texttt{void},
|
|
||||||
was überprüft wird, um sicherzustellen, dass kein Wert zurückgegeben wird.
|
|
||||||
\item[Methodenkontext] Innerhalb einer Methode wird die Symboltabelle um die Parameter der Methode erweitert sowie den Rückgabetyp der Methode,
|
|
||||||
um die einzelnen Returns dagegen zu prüfen. Dies geschieht in \texttt{typeCheckMethodDeclaration}.
|
|
||||||
\item[Blockkontext] Bei der Überprüfung eines Blocks (\texttt{typeCheckStatement} für Block) wird die Symboltabelle für jede Anweisung
|
|
||||||
innerhalb des Blocks aktualisiert. Lokale Variablen, die innerhalb des Blocks deklariert werden, werden zur Symboltabelle hinzugefügt.
|
|
||||||
Das bedeutet, dass automatisch, sobald der Block zu Ende ist, alle dort deklarierten Variablen danach nicht mehr zugänglich sind.
|
|
||||||
\end{description}
|
|
||||||
|
|
||||||
\subsubsection{Unterscheidung zwischen lokalen und Feldvariablen}
|
|
||||||
|
|
||||||
Bei der Typprüfung von Referenzen (\texttt{typeCheckExpression} für Reference) wird zuerst in der Symboltabelle nach dem Bezeichner gesucht.
|
|
||||||
Sollte dieser gefunden werden, handelt es sich um eine lokale Variable. Wenn der Bezeichner nicht gefunden wird, wird angenommen,
|
|
||||||
dass es sich um eine Feldvariable handelt. In diesem Fall wird die Klasse, zu der die \texttt{this}-Referenz gehört, durchsucht,
|
|
||||||
um die Feldvariable zu finden. Dies ermöglicht die Unterscheidung zwischen lokalen Variablen und Feldvariablen.
|
|
||||||
Dies ist auch nur möglich, da wir die Feldvariablen und Methoden nicht in die Symboltabelle gelegt haben und stattdessen nur die \texttt{this}-Referenz.
|
|
||||||
|
|
||||||
\subsection{Fehlerbehandlung}
|
|
||||||
|
|
||||||
Ein zentraler Aspekt des Typecheckers ist die Fehlerbehandlung. Bei Typinkonsistenzen oder ungültigen Operationen werden
|
|
||||||
aussagekräftige Fehlermeldungen generiert. Beispiele für solche Fehlermeldungen sind:
|
|
||||||
|
|
||||||
\begin{description}
|
|
||||||
\item[Typinkonsistenzen] Wenn der Rückgabetyp einer Methode nicht mit dem deklarierten Rückgabetyp übereinstimmt oder die Anzahl der Parameter nicht übereinstimmt.
|
|
||||||
\item[Ungültige Operationen] Wenn eine arithmetische Operation auf inkompatiblen Typen durchgeführt wird.
|
|
||||||
\item[Nicht gefundene Bezeichner] Wenn eine Referenz auf eine nicht definierte Variable verweist.
|
|
||||||
\end{description}
|
|
||||||
|
|
||||||
Diese Fehlermeldungen helfen Entwicklern, die Ursachen von Typfehlern schnell zu identifizieren und zu beheben.
|
|
||||||
Generell sind diese oftmals sehr spezifisch, was das Problem recht schnell identifizieren sollte.
|
|
||||||
Z.B. falsche Reihenfolge / falsche Typen der Parameter beim Methodenaufruf sind direkt erkennbar.
|
|
||||||
|
|
||||||
\subsection{Typprüfung von Kontrollstrukturen und Blöcken}
|
|
||||||
\subsubsection{If-Anweisungen}
|
|
||||||
Bei der Typprüfung einer If-Anweisung (\texttt{typeCheckStatement} für If) wird zuerst der Typ der Bedingung überprüft, um sicherzustellen,
|
|
||||||
dass es sich um einen booleschen Ausdruck handelt. Anschließend werden die Then- und Else-Zweige geprüft.
|
|
||||||
Der Typ der If-Anweisung selbst wird durch die Vereinheitlichung der Typen der Then- und Else-Zweige bestimmt.
|
|
||||||
Falls einer der Zweige keinen Rückgabewert hat, wird angenommen, dass der Rückgabewert \texttt{void} ist.
|
|
||||||
Dies wurde so gelöst, um im Typchecker feststellen zu können, ob beide Zweige einen Return haben.
|
|
||||||
Wenn nur einer der Zweige ein Return hat, wird im umliegenden Block ein weiteres benötigt, was durch den Typ \texttt{void} erzwungen wird.
|
|
||||||
Dadurch weiß der Typchecker Bescheid.
|
|
||||||
|
|
||||||
\subsubsection{Block-Anweisungen}
|
|
||||||
Die Typprüfung eines Blocks erfolgt in \texttt{typeCheckStatement} für Block.
|
|
||||||
Jede Anweisung im Block wird nacheinander überprüft und die Symboltabelle wird entsprechend aktualisiert.
|
|
||||||
Der Typ des Blocks wird durch die Vereinheitlichung der Typen aller Anweisungen im Block bestimmt.
|
|
||||||
Wenn der Block keine Anweisungen hat, wird der Typ \texttt{void} angenommen.
|
|
||||||
|
|
||||||
\subsubsection{Rückgabeanweisungen}
|
|
||||||
Die Typprüfung einer Rückgabeanweisung (\texttt{typeCheckStatement} für Return) überprüft,
|
|
||||||
ob der Rückgabewert der Anweisung mit dem deklarierten Rückgabetyp der Methode übereinstimmt.
|
|
||||||
Dafür wurde zu Beginn der Methodentypprüfung der Rückgabetyp der Methode in die Symboltabelle eingetragen. Wenn der Rückgabewert \texttt{null} ist,
|
|
||||||
wird überprüft, ob der deklarierte Rückgabetyp ein Objekttyp ist. Dies stellt sicher, dass Methoden immer den korrekten Typ zurückgeben.
|
|
||||||
Generell wird bei der Prüfung nach dem UpperBound geschaut und ebenfalls wird nachgeschaut, ob, wenn der Rückgabetyp \texttt{Object} ist,
|
|
||||||
der Return-Wert auch eine tatsächlich existierende Klasse ist, indem in die Klassentabelle geschaut wird.
|
|
||||||
|
|
||||||
\subsubsection{Konstruktorüberladung und -prüfung}
|
|
||||||
Die Typprüfung unterstützt Konstruktorüberladung. Bei der Typprüfung von Konstruktoraufrufen (\texttt{typeCheckStatementExpression}
|
|
||||||
für \texttt{ConstructorCall}) wird überprüft, ob es mehrere Konstruktoren mit derselben Anzahl von Parametern gibt.
|
|
||||||
Falls mehrere passende Konstruktoren gefunden werden, wird ein Fehler gemeldet.
|
|
||||||
|
|
||||||
\begin{description}
|
|
||||||
\item[Parameterabgleich] Die Parameter eines Konstruktors werden gegen die Argumente des Aufrufs abgeglichen.
|
|
||||||
Dies umfasst die Prüfung der Typen und, falls es sich um \texttt{null} handelt, die Überprüfung, ob der Parameter ein Objekttyp ist.
|
|
||||||
\item[Fehlerbehandlung] Wenn kein passender Konstruktor gefunden wird, wird eine detaillierte Fehlermeldung generiert,
|
|
||||||
die die erwarteten Signaturen und die tatsächlichen Argumenttypen anzeigt. Wenn mehrere passende Konstruktoren gefunden werden,
|
|
||||||
wird ebenfalls ein Fehler gemeldet.
|
|
||||||
\end{description}
|
|
||||||
|
|
||||||
\subsubsection{Methodenüberladung und -prüfung}
|
|
||||||
Die Typprüfung unterstützt auch Methodenüberladung. Bei der Typprüfung von Methodenaufrufen (\texttt{typeCheckStatementExpression} für \texttt{MethodCall})
|
|
||||||
wird überprüft, ob es mehrere Methoden mit demselben Namen, aber unterschiedlichen Parametertypen gibt.
|
|
||||||
|
|
||||||
\begin{description}
|
|
||||||
\item[Parameterabgleich] Die Parameter einer Methode werden gegen die Argumente des Aufrufs abgeglichen.
|
|
||||||
Dies umfasst die Prüfung der Typen und, falls es sich um \texttt{null} handelt, die Überprüfung, ob der Parameter ein Objekttyp ist.
|
|
||||||
\item[Fehlerbehandlung] Wenn keine passende Methode gefunden wird, wird eine detaillierte Fehlermeldung generiert,
|
|
||||||
die die erwarteten Signaturen und die tatsächlichen Argumenttypen anzeigt. Wenn mehrere passende Methoden gefunden werden,
|
|
||||||
wird ebenfalls ein Fehler gemeldet.
|
|
||||||
\end{description}
|
|
@ -1,19 +0,0 @@
|
|||||||
\section{Aufgabenverteilung}
|
|
||||||
\begin{description}
|
|
||||||
\item[Marvin Schlegel] Parser \& Lexer
|
|
||||||
\item[Fabian Noll] Semantik- \& Typcheck
|
|
||||||
\item[Christian Brier] Bytecodegenerierung
|
|
||||||
\item[Matthias Raba] Bytecodegenerierung
|
|
||||||
\end{description}
|
|
||||||
\vspace{20px}
|
|
||||||
|
|
||||||
Marvin Schlegel und Fabian Noll haben ihre Teilaufgaben eigenständig bearbeitet.
|
|
||||||
|
|
||||||
Die Bytecodegenerierung wurde von Matthias Raba und Christian Brier im Stile des Pair Programmings zu zweit erarbeitet.
|
|
||||||
Durch bisher gute Erfahrungen in vorherigen Projekten, sowie dem Interesse, alle Teile der Bytecodegenerierung zu sehen,
|
|
||||||
wurde diese Programmierungsform als die Beste ausgewählt.
|
|
||||||
|
|
||||||
Während der Implementierungsphase wurde viel zwischen den 3 einzelnen Teams kommuniziert.
|
|
||||||
Wurden Fehler in einer der Komponenten gefunden, wurden die jeweiligen Verantwortlichen informiert um das Problem zu beheben.
|
|
||||||
Jedes der Teams arbeitete auf einem eigenen Branch, die einzelnen Beiträge wurde regelmäßig auf dem master-Branch zusammengeführt.
|
|
||||||
Insgesamt lief die Implementierungsphase wie geplant und ohne weitere Komplikationen ab.
|
|
@ -18,6 +18,7 @@ executable compiler
|
|||||||
other-modules: Parser.Lexer,
|
other-modules: Parser.Lexer,
|
||||||
Parser.JavaParser,
|
Parser.JavaParser,
|
||||||
Ast,
|
Ast,
|
||||||
|
Example,
|
||||||
Typecheck,
|
Typecheck,
|
||||||
ByteCode.Util,
|
ByteCode.Util,
|
||||||
ByteCode.ByteUtil,
|
ByteCode.ByteUtil,
|
||||||
@ -41,4 +42,10 @@ test-suite tests
|
|||||||
Parser.JavaParser,
|
Parser.JavaParser,
|
||||||
Ast,
|
Ast,
|
||||||
TestLexer,
|
TestLexer,
|
||||||
TestParser
|
TestParser,
|
||||||
|
ByteCode.Util,
|
||||||
|
ByteCode.ByteUtil,
|
||||||
|
ByteCode.ClassFile,
|
||||||
|
ByteCode.Assembler,
|
||||||
|
ByteCode.Builder,
|
||||||
|
ByteCode.Constants
|
||||||
|
@ -6,9 +6,8 @@ type Identifier = String
|
|||||||
|
|
||||||
data ParameterDeclaration = ParameterDeclaration DataType Identifier deriving (Show, Eq)
|
data ParameterDeclaration = ParameterDeclaration DataType Identifier deriving (Show, Eq)
|
||||||
data VariableDeclaration = VariableDeclaration DataType Identifier (Maybe Expression) deriving (Show, Eq)
|
data VariableDeclaration = VariableDeclaration DataType Identifier (Maybe Expression) deriving (Show, Eq)
|
||||||
data Class = Class DataType [ConstructorDeclaration] [MethodDeclaration] [VariableDeclaration] deriving (Show, Eq)
|
|
||||||
data MethodDeclaration = MethodDeclaration DataType Identifier [ParameterDeclaration] Statement deriving (Show, Eq)
|
data MethodDeclaration = MethodDeclaration DataType Identifier [ParameterDeclaration] Statement deriving (Show, Eq)
|
||||||
data ConstructorDeclaration = ConstructorDeclaration Identifier [ParameterDeclaration] Statement deriving (Show, Eq)
|
data Class = Class DataType [MethodDeclaration] [VariableDeclaration] deriving (Show, Eq)
|
||||||
|
|
||||||
data Statement
|
data Statement
|
||||||
= If Expression Statement (Maybe Statement)
|
= If Expression Statement (Maybe Statement)
|
||||||
|
@ -56,13 +56,10 @@ methodBuilder (MethodDeclaration returntype name parameters statement) input = l
|
|||||||
methods = methods input ++ [method]
|
methods = methods input ++ [method]
|
||||||
}
|
}
|
||||||
|
|
||||||
constructorBuilder :: ClassFileBuilder ConstructorDeclaration
|
|
||||||
constructorBuilder (ConstructorDeclaration name parameters statement) = methodBuilder (MethodDeclaration "void" "<init>" parameters statement)
|
|
||||||
|
|
||||||
|
|
||||||
methodAssembler :: ClassFileBuilder MethodDeclaration
|
methodAssembler :: ClassFileBuilder MethodDeclaration
|
||||||
methodAssembler (MethodDeclaration returntype name parameters statement) input = let
|
methodAssembler (MethodDeclaration returntype name parameters statement) input = let
|
||||||
methodConstantIndex = findMethodIndex input (MethodDeclaration returntype name parameters statement)
|
methodConstantIndex = findMethodIndex input name
|
||||||
in case methodConstantIndex of
|
in case methodConstantIndex of
|
||||||
Nothing -> error ("Cannot find method entry in method pool for method: " ++ name)
|
Nothing -> error ("Cannot find method entry in method pool for method: " ++ name)
|
||||||
Just index -> let
|
Just index -> let
|
||||||
@ -87,12 +84,9 @@ methodAssembler (MethodDeclaration returntype name parameters statement) input =
|
|||||||
methods = pre ++ (assembledMethod : post)
|
methods = pre ++ (assembledMethod : post)
|
||||||
}
|
}
|
||||||
|
|
||||||
constructorAssembler :: ClassFileBuilder ConstructorDeclaration
|
|
||||||
constructorAssembler (ConstructorDeclaration name parameters statement) = methodAssembler (MethodDeclaration "void" "<init>" parameters statement)
|
|
||||||
|
|
||||||
|
|
||||||
classBuilder :: ClassFileBuilder Class
|
classBuilder :: ClassFileBuilder Class
|
||||||
classBuilder (Class name constructors methods fields) _ = let
|
classBuilder (Class name methods fields) _ = let
|
||||||
baseConstants = [
|
baseConstants = [
|
||||||
ClassInfo 4,
|
ClassInfo 4,
|
||||||
MethodRefInfo 1 3,
|
MethodRefInfo 1 3,
|
||||||
@ -114,15 +108,15 @@ classBuilder (Class name constructors methods fields) _ = let
|
|||||||
attributes = []
|
attributes = []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
-- if a class has no constructor, inject an empty one.
|
||||||
|
methodsWithInjectedConstructor = injectDefaultConstructor methods
|
||||||
-- for every constructor, prepend all initialization assignments for fields.
|
-- for every constructor, prepend all initialization assignments for fields.
|
||||||
constructorsWithInitializers = injectFieldInitializers name fields constructors
|
methodsWithInjectedInitializers = injectFieldInitializers name fields methodsWithInjectedConstructor
|
||||||
|
|
||||||
-- add fields, then method bodies, then constructor bodies to the classfile. After all referable names are known,
|
-- add fields, then method bodies to the classfile. After all referable names are known,
|
||||||
-- assemble the methods and constructors into bytecode.
|
-- assemble the methods into bytecode.
|
||||||
fieldsAdded = foldr fieldBuilder nakedClassFile fields
|
classFileWithFields = foldr fieldBuilder nakedClassFile fields
|
||||||
methodsAdded = foldr methodBuilder fieldsAdded methods
|
classFileWithMethods = foldr methodBuilder classFileWithFields methodsWithInjectedInitializers
|
||||||
constructorsAdded = foldr constructorBuilder methodsAdded constructorsWithInitializers
|
classFileWithAssembledMethods = foldr methodAssembler classFileWithMethods methodsWithInjectedInitializers
|
||||||
methodsAssembled = foldr methodAssembler constructorsAdded methods
|
|
||||||
constructorsAssembled = foldr constructorAssembler methodsAssembled constructorsWithInitializers
|
|
||||||
in
|
in
|
||||||
constructorsAssembled
|
classFileWithAssembledMethods
|
@ -1,5 +1,3 @@
|
|||||||
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
|
|
||||||
{-# HLINT ignore "Use lambda-case" #-}
|
|
||||||
module ByteCode.Util where
|
module ByteCode.Util where
|
||||||
|
|
||||||
import Data.Int
|
import Data.Int
|
||||||
@ -133,12 +131,11 @@ comparisonOffset anything_else = Nothing
|
|||||||
isComparisonOperation :: Operation -> Bool
|
isComparisonOperation :: Operation -> Bool
|
||||||
isComparisonOperation op = isJust (comparisonOffset op)
|
isComparisonOperation op = isJust (comparisonOffset op)
|
||||||
|
|
||||||
findMethodIndex :: ClassFile -> MethodDeclaration -> Maybe Int
|
findMethodIndex :: ClassFile -> String -> Maybe Int
|
||||||
findMethodIndex classFile (MethodDeclaration rtype name params stmt) = let
|
findMethodIndex classFile name = let
|
||||||
constants = constantPool classFile
|
constants = constantPool classFile
|
||||||
descriptor = methodDescriptor (MethodDeclaration rtype name params stmt)
|
|
||||||
in
|
in
|
||||||
findIndex (\method -> memberInfoIsMethod constants method && memberInfoName constants method == name && memberInfoDescriptor constants method == descriptor) (methods classFile)
|
findIndex (\method -> memberInfoIsMethod constants method && memberInfoName constants method == name) (methods classFile)
|
||||||
|
|
||||||
findClassIndex :: [ConstantInfo] -> String -> Maybe Int
|
findClassIndex :: [ConstantInfo] -> String -> Maybe Int
|
||||||
findClassIndex constants name = let
|
findClassIndex constants name = let
|
||||||
@ -214,11 +211,16 @@ findMemberIndex constants (cname, fname, ftype) = let
|
|||||||
allMembers = getKnownMembers constants
|
allMembers = getKnownMembers constants
|
||||||
desiredMember = find (\(index, (c, f, ft)) -> (c, f, ft) == (cname, fname, ftype)) allMembers
|
desiredMember = find (\(index, (c, f, ft)) -> (c, f, ft) == (cname, fname, ftype)) allMembers
|
||||||
in
|
in
|
||||||
fmap fst desiredMember
|
fmap (\(index, _) -> index) desiredMember
|
||||||
|
|
||||||
injectFieldInitializers :: String -> [VariableDeclaration] -> [ConstructorDeclaration] -> [ConstructorDeclaration]
|
injectDefaultConstructor :: [MethodDeclaration] -> [MethodDeclaration]
|
||||||
injectFieldInitializers classname vars constructors = let
|
injectDefaultConstructor pre
|
||||||
initializers = mapMaybe (\variable -> case variable of
|
| any (\(MethodDeclaration _ name _ _) -> name == "<init>") pre = pre
|
||||||
|
| otherwise = pre ++ [MethodDeclaration "void" "<init>" [] (TypedStatement "void" (Block []))]
|
||||||
|
|
||||||
|
injectFieldInitializers :: String -> [VariableDeclaration] -> [MethodDeclaration] -> [MethodDeclaration]
|
||||||
|
injectFieldInitializers classname vars pre = let
|
||||||
|
initializers = mapMaybe (\(variable) -> case variable of
|
||||||
VariableDeclaration dtype name (Just initializer) -> Just (
|
VariableDeclaration dtype name (Just initializer) -> Just (
|
||||||
TypedStatement dtype (
|
TypedStatement dtype (
|
||||||
StatementExpressionStatement (
|
StatementExpressionStatement (
|
||||||
@ -233,11 +235,10 @@ injectFieldInitializers classname vars constructors = let
|
|||||||
otherwise -> Nothing
|
otherwise -> Nothing
|
||||||
) vars
|
) vars
|
||||||
in
|
in
|
||||||
map (\con -> let
|
map (\method -> case method of
|
||||||
ConstructorDeclaration classname params (TypedStatement "void" (Block statements)) = con
|
MethodDeclaration "void" "<init>" params (TypedStatement "void" (Block statements)) -> MethodDeclaration "void" "<init>" params (TypedStatement "void" (Block (initializers ++ statements)))
|
||||||
in
|
_ -> method
|
||||||
ConstructorDeclaration classname params (TypedStatement "void" (Block (initializers ++ statements)))
|
) pre
|
||||||
) constructors
|
|
||||||
|
|
||||||
-- effect of one instruction/operation on the stack
|
-- effect of one instruction/operation on the stack
|
||||||
operationStackCost :: [ConstantInfo] -> Operation -> Int
|
operationStackCost :: [ConstantInfo] -> Operation -> Int
|
||||||
|
267
src/Example.hs
Normal file
267
src/Example.hs
Normal file
@ -0,0 +1,267 @@
|
|||||||
|
module Example where
|
||||||
|
|
||||||
|
import Ast
|
||||||
|
import Typecheck
|
||||||
|
import Control.Exception (catch, evaluate, SomeException, displayException)
|
||||||
|
import Control.Exception.Base
|
||||||
|
import System.IO (stderr, hPutStrLn)
|
||||||
|
import Data.Maybe
|
||||||
|
import Data.List
|
||||||
|
|
||||||
|
green, red, yellow, blue, magenta, cyan, white :: String -> String
|
||||||
|
green str = "\x1b[32m" ++ str ++ "\x1b[0m"
|
||||||
|
red str = "\x1b[31m" ++ str ++ "\x1b[0m"
|
||||||
|
yellow str = "\x1b[33m" ++ str ++ "\x1b[0m"
|
||||||
|
blue str = "\x1b[34m" ++ str ++ "\x1b[0m"
|
||||||
|
magenta str = "\x1b[35m" ++ str ++ "\x1b[0m"
|
||||||
|
cyan str = "\x1b[36m" ++ str ++ "\x1b[0m"
|
||||||
|
white str = "\x1b[37m" ++ str ++ "\x1b[0m"
|
||||||
|
|
||||||
|
printSuccess :: String -> IO ()
|
||||||
|
printSuccess msg = putStrLn $ green "Success:" ++ white msg
|
||||||
|
|
||||||
|
handleError :: SomeException -> IO ()
|
||||||
|
handleError e = hPutStrLn stderr $ red ("Error: " ++ displayException e)
|
||||||
|
|
||||||
|
printResult :: Show a => String -> a -> IO ()
|
||||||
|
printResult title result = do
|
||||||
|
putStrLn $ green title
|
||||||
|
print result
|
||||||
|
|
||||||
|
sampleClasses :: [Class]
|
||||||
|
sampleClasses = [
|
||||||
|
Class "Person" [
|
||||||
|
MethodDeclaration "void" "setAge" [ParameterDeclaration "int" "newAge"]
|
||||||
|
(Block [
|
||||||
|
LocalVariableDeclaration (VariableDeclaration "int" "age" (Just (Reference "newAge")))
|
||||||
|
]),
|
||||||
|
MethodDeclaration "int" "getAge" [] (Return (Just (Reference "age"))),
|
||||||
|
MethodDeclaration "Person" "Person" [ParameterDeclaration "int" "initialAge"] (Block [])
|
||||||
|
] [
|
||||||
|
VariableDeclaration "int" "age" (Just (IntegerLiteral 25))
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
initialSymtab :: [(DataType, Identifier)]
|
||||||
|
initialSymtab = []
|
||||||
|
|
||||||
|
exampleExpression :: Expression
|
||||||
|
exampleExpression = BinaryOperation NameResolution (Reference "bob") (Reference "age")
|
||||||
|
|
||||||
|
exampleAssignment :: Expression
|
||||||
|
exampleAssignment = StatementExpressionExpression (Assignment (Reference "a") (IntegerLiteral 30))
|
||||||
|
|
||||||
|
exampleMethodCall :: Statement
|
||||||
|
exampleMethodCall = StatementExpressionStatement (MethodCall (Reference "this") "setAge" [IntegerLiteral 30])
|
||||||
|
|
||||||
|
exampleConstructorCall :: Statement
|
||||||
|
exampleConstructorCall = LocalVariableDeclaration (VariableDeclaration "Person" "bob" (Just (StatementExpressionExpression (ConstructorCall "Person" [IntegerLiteral 30]))))
|
||||||
|
|
||||||
|
exampleNameResolution :: Expression
|
||||||
|
exampleNameResolution = BinaryOperation NameResolution (Reference "bob2") (Reference "age")
|
||||||
|
|
||||||
|
exampleBlockResolution :: Statement
|
||||||
|
exampleBlockResolution = Block [
|
||||||
|
LocalVariableDeclaration (VariableDeclaration "Person" "bob" (Just (StatementExpressionExpression (ConstructorCall "Person" [IntegerLiteral 30])))),
|
||||||
|
LocalVariableDeclaration (VariableDeclaration "int" "age" (Just (StatementExpressionExpression (MethodCall (Reference "bob") "getAge" [])))),
|
||||||
|
StatementExpressionStatement (MethodCall (Reference "bob") "setAge" [IntegerLiteral 30])
|
||||||
|
]
|
||||||
|
|
||||||
|
exampleBlockResolutionFail :: Statement
|
||||||
|
exampleBlockResolutionFail = Block [
|
||||||
|
LocalVariableDeclaration (VariableDeclaration "Person" "bob" (Just (StatementExpressionExpression (ConstructorCall "Person" [IntegerLiteral 30])))),
|
||||||
|
LocalVariableDeclaration (VariableDeclaration "bool" "age" (Just (StatementExpressionExpression (MethodCall (Reference "bob") "getAge" [])))),
|
||||||
|
StatementExpressionStatement (MethodCall (Reference "bob") "setAge" [IntegerLiteral 30])
|
||||||
|
]
|
||||||
|
|
||||||
|
exampleMethodCallAndAssignment :: Statement
|
||||||
|
exampleMethodCallAndAssignment = Block [
|
||||||
|
LocalVariableDeclaration (VariableDeclaration "Person" "bob" (Just (StatementExpressionExpression (ConstructorCall "Person" [IntegerLiteral 30])))),
|
||||||
|
LocalVariableDeclaration (VariableDeclaration "int" "age" (Just (StatementExpressionExpression (MethodCall (Reference "bob") "getAge" [])))),
|
||||||
|
StatementExpressionStatement (MethodCall (Reference "bob") "setAge" [IntegerLiteral 30]),
|
||||||
|
LocalVariableDeclaration (VariableDeclaration "int" "a" Nothing),
|
||||||
|
StatementExpressionStatement (Assignment (Reference "a") (Reference "age"))
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
exampleMethodCallAndAssignmentFail :: Statement
|
||||||
|
exampleMethodCallAndAssignmentFail = Block [
|
||||||
|
LocalVariableDeclaration (VariableDeclaration "Person" "bob" (Just (StatementExpressionExpression (ConstructorCall "Person" [IntegerLiteral 30])))),
|
||||||
|
LocalVariableDeclaration (VariableDeclaration "int" "age" (Just (StatementExpressionExpression (MethodCall (Reference "bob") "getAge" [])))),
|
||||||
|
StatementExpressionStatement (MethodCall (Reference "bob") "setAge" [IntegerLiteral 30]),
|
||||||
|
StatementExpressionStatement (Assignment (Reference "a") (Reference "age"))
|
||||||
|
]
|
||||||
|
|
||||||
|
exampleNameResolutionAssignment :: Statement
|
||||||
|
exampleNameResolutionAssignment = Block [
|
||||||
|
LocalVariableDeclaration (VariableDeclaration "Person" "bob" (Just (StatementExpressionExpression (ConstructorCall "Person" [IntegerLiteral 30])))),
|
||||||
|
StatementExpressionStatement (Assignment (BinaryOperation NameResolution (Reference "bob") (Reference "age")) (IntegerLiteral 30))
|
||||||
|
]
|
||||||
|
|
||||||
|
exampleCharIntOperation :: Expression
|
||||||
|
exampleCharIntOperation = BinaryOperation Addition (CharacterLiteral 'a') (IntegerLiteral 1)
|
||||||
|
|
||||||
|
exampleNullDeclaration :: Statement
|
||||||
|
exampleNullDeclaration = LocalVariableDeclaration (VariableDeclaration "Person" "bob" (Just NullLiteral))
|
||||||
|
|
||||||
|
exampleNullDeclarationFail :: Statement
|
||||||
|
exampleNullDeclarationFail = LocalVariableDeclaration (VariableDeclaration "int" "a" (Just NullLiteral))
|
||||||
|
|
||||||
|
exampleNullAssignment :: Statement
|
||||||
|
exampleNullAssignment = StatementExpressionStatement (Assignment (Reference "a") NullLiteral)
|
||||||
|
|
||||||
|
exampleIncrement :: Statement
|
||||||
|
exampleIncrement = StatementExpressionStatement (PostIncrement (Reference "a"))
|
||||||
|
|
||||||
|
testClasses :: [Class]
|
||||||
|
testClasses = [
|
||||||
|
Class "Person" [
|
||||||
|
MethodDeclaration "Person" "Person" [ParameterDeclaration "int" "initialAge"]
|
||||||
|
(Block [
|
||||||
|
Return (Just (Reference "this"))
|
||||||
|
]),
|
||||||
|
MethodDeclaration "void" "setAge" [ParameterDeclaration "int" "newAge"]
|
||||||
|
(Block [
|
||||||
|
LocalVariableDeclaration (VariableDeclaration "int" "age" (Just (Reference "newAge")))
|
||||||
|
]),
|
||||||
|
MethodDeclaration "int" "getAge" []
|
||||||
|
(Return (Just (Reference "age")))
|
||||||
|
] [
|
||||||
|
VariableDeclaration "int" "age" Nothing -- initially unassigned
|
||||||
|
],
|
||||||
|
Class "Main" [
|
||||||
|
MethodDeclaration "int" "main" []
|
||||||
|
(Block [
|
||||||
|
LocalVariableDeclaration (VariableDeclaration "Person" "bob" (Just (StatementExpressionExpression (ConstructorCall "Person" [IntegerLiteral 25])))),
|
||||||
|
StatementExpressionStatement (MethodCall (Reference "bob") "setAge" [IntegerLiteral 30]),
|
||||||
|
LocalVariableDeclaration (VariableDeclaration "int" "bobAge" (Just (StatementExpressionExpression (MethodCall (Reference "bob2") "getAge" [])))),
|
||||||
|
Return (Just (Reference "bobAge"))
|
||||||
|
])
|
||||||
|
] [
|
||||||
|
VariableDeclaration "Person" "bob2" Nothing
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
runTypeCheck :: IO ()
|
||||||
|
runTypeCheck = do
|
||||||
|
catch (do
|
||||||
|
print "====================================================================================="
|
||||||
|
evaluatedExpression <- evaluate (typeCheckExpression exampleExpression [("bob", "Person")] sampleClasses)
|
||||||
|
printSuccess "Type checking of expression completed successfully"
|
||||||
|
printResult "Result Expression:" evaluatedExpression
|
||||||
|
) handleError
|
||||||
|
|
||||||
|
catch (do
|
||||||
|
print "====================================================================================="
|
||||||
|
evaluatedAssignment <- evaluate (typeCheckExpression exampleAssignment [("a", "int")] sampleClasses)
|
||||||
|
printSuccess "Type checking of assignment completed successfully"
|
||||||
|
printResult "Result Assignment:" evaluatedAssignment
|
||||||
|
) handleError
|
||||||
|
|
||||||
|
catch (do
|
||||||
|
print "====================================================================================="
|
||||||
|
evaluatedMethodCall <- evaluate (typeCheckStatement exampleMethodCall [("this", "Person"), ("setAge", "Person"), ("getAge", "Person")] sampleClasses)
|
||||||
|
printSuccess "Type checking of method call this completed successfully"
|
||||||
|
printResult "Result MethodCall:" evaluatedMethodCall
|
||||||
|
) handleError
|
||||||
|
|
||||||
|
catch (do
|
||||||
|
print "====================================================================================="
|
||||||
|
evaluatedConstructorCall <- evaluate (typeCheckStatement exampleConstructorCall [] sampleClasses)
|
||||||
|
printSuccess "Type checking of constructor call completed successfully"
|
||||||
|
printResult "Result Constructor Call:" evaluatedConstructorCall
|
||||||
|
) handleError
|
||||||
|
|
||||||
|
catch (do
|
||||||
|
print "====================================================================================="
|
||||||
|
evaluatedNameResolution <- evaluate (typeCheckExpression exampleNameResolution [("this", "Main")] testClasses)
|
||||||
|
printSuccess "Type checking of name resolution completed successfully"
|
||||||
|
printResult "Result Name Resolution:" evaluatedNameResolution
|
||||||
|
) handleError
|
||||||
|
|
||||||
|
catch (do
|
||||||
|
print "====================================================================================="
|
||||||
|
evaluatedBlockResolution <- evaluate (typeCheckStatement exampleBlockResolution [] sampleClasses)
|
||||||
|
printSuccess "Type checking of block resolution completed successfully"
|
||||||
|
printResult "Result Block Resolution:" evaluatedBlockResolution
|
||||||
|
) handleError
|
||||||
|
|
||||||
|
catch (do
|
||||||
|
print "====================================================================================="
|
||||||
|
evaluatedBlockResolutionFail <- evaluate (typeCheckStatement exampleBlockResolutionFail [] sampleClasses)
|
||||||
|
printSuccess "Type checking of block resolution failed"
|
||||||
|
printResult "Result Block Resolution:" evaluatedBlockResolutionFail
|
||||||
|
) handleError
|
||||||
|
|
||||||
|
catch (do
|
||||||
|
print "====================================================================================="
|
||||||
|
evaluatedMethodCallAndAssignment <- evaluate (typeCheckStatement exampleMethodCallAndAssignment [] sampleClasses)
|
||||||
|
printSuccess "Type checking of method call and assignment completed successfully"
|
||||||
|
printResult "Result Method Call and Assignment:" evaluatedMethodCallAndAssignment
|
||||||
|
) handleError
|
||||||
|
|
||||||
|
catch (do
|
||||||
|
print "====================================================================================="
|
||||||
|
evaluatedMethodCallAndAssignmentFail <- evaluate (typeCheckStatement exampleMethodCallAndAssignmentFail [] sampleClasses)
|
||||||
|
printSuccess "Type checking of method call and assignment failed"
|
||||||
|
printResult "Result Method Call and Assignment:" evaluatedMethodCallAndAssignmentFail
|
||||||
|
) handleError
|
||||||
|
|
||||||
|
catch (do
|
||||||
|
print "====================================================================================="
|
||||||
|
let mainClass = fromJust $ find (\(Class className _ _) -> className == "Main") testClasses
|
||||||
|
case mainClass of
|
||||||
|
Class _ [mainMethod] _ -> do
|
||||||
|
let result = typeCheckMethodDeclaration mainMethod [("this", "Main")] testClasses
|
||||||
|
printSuccess "Full program type checking completed successfully."
|
||||||
|
printResult "Main method result:" result
|
||||||
|
) handleError
|
||||||
|
|
||||||
|
catch (do
|
||||||
|
print "====================================================================================="
|
||||||
|
let typedProgram = typeCheckCompilationUnit testClasses
|
||||||
|
printSuccess "Type checking of Program completed successfully"
|
||||||
|
printResult "Typed Program:" typedProgram
|
||||||
|
) handleError
|
||||||
|
|
||||||
|
catch (do
|
||||||
|
print "====================================================================================="
|
||||||
|
typedAssignment <- evaluate (typeCheckStatement exampleNameResolutionAssignment [] sampleClasses)
|
||||||
|
printSuccess "Type checking of name resolution assignment completed successfully"
|
||||||
|
printResult "Result Name Resolution Assignment:" typedAssignment
|
||||||
|
) handleError
|
||||||
|
|
||||||
|
catch (do
|
||||||
|
print "====================================================================================="
|
||||||
|
evaluatedCharIntOperation <- evaluate (typeCheckExpression exampleCharIntOperation [] sampleClasses)
|
||||||
|
printSuccess "Type checking of char int operation completed successfully"
|
||||||
|
printResult "Result Char Int Operation:" evaluatedCharIntOperation
|
||||||
|
) handleError
|
||||||
|
|
||||||
|
catch (do
|
||||||
|
print "====================================================================================="
|
||||||
|
evaluatedNullDeclaration <- evaluate (typeCheckStatement exampleNullDeclaration [] sampleClasses)
|
||||||
|
printSuccess "Type checking of null declaration completed successfully"
|
||||||
|
printResult "Result Null Declaration:" evaluatedNullDeclaration
|
||||||
|
) handleError
|
||||||
|
|
||||||
|
catch (do
|
||||||
|
print "====================================================================================="
|
||||||
|
evaluatedNullDeclarationFail <- evaluate (typeCheckStatement exampleNullDeclarationFail [] sampleClasses)
|
||||||
|
printSuccess "Type checking of null declaration failed"
|
||||||
|
printResult "Result Null Declaration:" evaluatedNullDeclarationFail
|
||||||
|
) handleError
|
||||||
|
|
||||||
|
catch (do
|
||||||
|
print "====================================================================================="
|
||||||
|
evaluatedNullAssignment <- evaluate (typeCheckStatement exampleNullAssignment [("a", "Person")] sampleClasses)
|
||||||
|
printSuccess "Type checking of null assignment completed successfully"
|
||||||
|
printResult "Result Null Assignment:" evaluatedNullAssignment
|
||||||
|
) handleError
|
||||||
|
|
||||||
|
catch (do
|
||||||
|
print "====================================================================================="
|
||||||
|
evaluatedIncrement <- evaluate (typeCheckStatement exampleIncrement [("a", "int")] sampleClasses)
|
||||||
|
printSuccess "Type checking of increment completed successfully"
|
||||||
|
printResult "Result Increment:" evaluatedIncrement
|
||||||
|
) handleError
|
@ -92,10 +92,10 @@ qualifiedname : name DOT IDENTIFIER { BinaryOperation NameResolution $1 (Ref
|
|||||||
|
|
||||||
simplename : IDENTIFIER { $1 }
|
simplename : IDENTIFIER { $1 }
|
||||||
|
|
||||||
classdeclaration : CLASS IDENTIFIER classbody { case $3 of (constructors, methods, fields) -> Class $2 constructors methods fields }
|
classdeclaration : CLASS IDENTIFIER classbody { case $3 of (methods, fields) -> Class $2 methods fields }
|
||||||
| modifiers CLASS IDENTIFIER classbody { case $4 of (constructors, methods, fields) -> Class $3 constructors methods fields }
|
| modifiers CLASS IDENTIFIER classbody { case $4 of (methods, fields) -> Class $3 methods fields }
|
||||||
|
|
||||||
classbody : LBRACKET RBRACKET { ([], [], []) }
|
classbody : LBRACKET RBRACKET { ([], []) }
|
||||||
| LBRACKET classbodydeclarations RBRACKET { $2 }
|
| LBRACKET classbodydeclarations RBRACKET { $2 }
|
||||||
|
|
||||||
modifiers : modifier { }
|
modifiers : modifier { }
|
||||||
@ -103,15 +103,13 @@ modifiers : modifier { }
|
|||||||
|
|
||||||
classbodydeclarations : classbodydeclaration {
|
classbodydeclarations : classbodydeclaration {
|
||||||
case $1 of
|
case $1 of
|
||||||
ConstructorDecl constructor -> ([constructor], [], [])
|
MethodDecl method -> ([method], [])
|
||||||
MethodDecl method -> ([], [method], [])
|
FieldDecls fields -> ([], fields)
|
||||||
FieldDecls fields -> ([], [], fields)
|
|
||||||
}
|
}
|
||||||
| classbodydeclarations classbodydeclaration {
|
| classbodydeclarations classbodydeclaration {
|
||||||
case ($1, $2) of
|
case ($1, $2) of
|
||||||
((constructors, methods, fields), ConstructorDecl constructor) -> ((constructors ++ [constructor]), methods, fields)
|
((methods, fields), MethodDecl method) -> ((methods ++ [method]), fields)
|
||||||
((constructors, methods, fields), MethodDecl method) -> (constructors, (methods ++ [method]), fields)
|
((methods, fields), FieldDecls newFields) -> (methods, (fields ++ newFields))
|
||||||
((constructors, methods, fields), FieldDecls newFields) -> (constructors, methods, (fields ++ newFields))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
modifier : PUBLIC { }
|
modifier : PUBLIC { }
|
||||||
@ -130,8 +128,8 @@ classorinterfacetype : simplename { $1 }
|
|||||||
classmemberdeclaration : fielddeclaration { $1 }
|
classmemberdeclaration : fielddeclaration { $1 }
|
||||||
| methoddeclaration { $1 }
|
| methoddeclaration { $1 }
|
||||||
|
|
||||||
constructordeclaration : constructordeclarator constructorbody { case $1 of (identifier, parameters) -> ConstructorDecl $ ConstructorDeclaration identifier parameters $2 }
|
constructordeclaration : constructordeclarator constructorbody { MethodDecl $ MethodDeclaration "void" "<init>" $1 $2 }
|
||||||
| modifiers constructordeclarator constructorbody { case $2 of (identifier, parameters) -> ConstructorDecl $ ConstructorDeclaration identifier parameters $3 }
|
| modifiers constructordeclarator constructorbody { MethodDecl $ MethodDeclaration "void" "<init>" $2 $3 }
|
||||||
|
|
||||||
fielddeclaration : type variabledeclarators SEMICOLON { FieldDecls $ map (convertDeclarator $1) $2 }
|
fielddeclaration : type variabledeclarators SEMICOLON { FieldDecls $ map (convertDeclarator $1) $2 }
|
||||||
| modifiers type variabledeclarators SEMICOLON { FieldDecls $ map (convertDeclarator $2) $3 }
|
| modifiers type variabledeclarators SEMICOLON { FieldDecls $ map (convertDeclarator $2) $3 }
|
||||||
@ -141,8 +139,8 @@ methoddeclaration : methodheader methodbody { case $1 of (returnType, (name, par
|
|||||||
block : LBRACKET RBRACKET { Block [] }
|
block : LBRACKET RBRACKET { Block [] }
|
||||||
| LBRACKET blockstatements RBRACKET { Block $2 }
|
| LBRACKET blockstatements RBRACKET { Block $2 }
|
||||||
|
|
||||||
constructordeclarator : simplename LBRACE RBRACE { ($1, []) }
|
constructordeclarator : simplename LBRACE RBRACE { [] }
|
||||||
| simplename LBRACE formalparameterlist RBRACE { ($1, $3) }
|
| simplename LBRACE formalparameterlist RBRACE { $3 }
|
||||||
|
|
||||||
constructorbody : LBRACKET RBRACKET { Block [] }
|
constructorbody : LBRACKET RBRACKET { Block [] }
|
||||||
-- | LBRACKET explicitconstructorinvocation RBRACKET { }
|
-- | LBRACKET explicitconstructorinvocation RBRACKET { }
|
||||||
@ -387,7 +385,6 @@ multiplicativeexpression : unaryexpression { $1 }
|
|||||||
{
|
{
|
||||||
|
|
||||||
data MethodOrFieldDeclaration = MethodDecl MethodDeclaration
|
data MethodOrFieldDeclaration = MethodDecl MethodDeclaration
|
||||||
| ConstructorDecl ConstructorDeclaration
|
|
||||||
| FieldDecls [VariableDeclaration]
|
| FieldDecls [VariableDeclaration]
|
||||||
|
|
||||||
data Declarator = Declarator Identifier (Maybe Expression)
|
data Declarator = Declarator Identifier (Maybe Expression)
|
||||||
|
171
src/Typecheck.hs
171
src/Typecheck.hs
@ -3,48 +3,18 @@ import Data.List (find)
|
|||||||
import Data.Maybe
|
import Data.Maybe
|
||||||
import Ast
|
import Ast
|
||||||
|
|
||||||
|
|
||||||
typeCheckCompilationUnit :: CompilationUnit -> CompilationUnit
|
typeCheckCompilationUnit :: CompilationUnit -> CompilationUnit
|
||||||
typeCheckCompilationUnit classes =
|
typeCheckCompilationUnit classes = map (`typeCheckClass` classes) classes
|
||||||
let
|
|
||||||
-- Helper function to add a default constructor if none are present
|
|
||||||
ensureDefaultConstructor :: Class -> Class
|
|
||||||
ensureDefaultConstructor (Class className constructors methods fields) =
|
|
||||||
let
|
|
||||||
defaultConstructor = ConstructorDeclaration className [] (Block [])
|
|
||||||
constructorsWithDefault = if null constructors then [defaultConstructor] else constructors
|
|
||||||
in Class className constructorsWithDefault methods fields
|
|
||||||
|
|
||||||
-- Inject default constructors into all classes
|
|
||||||
classesWithDefaultConstructors = map ensureDefaultConstructor classes
|
|
||||||
|
|
||||||
in map (`typeCheckClass` classesWithDefaultConstructors) classesWithDefaultConstructors
|
|
||||||
|
|
||||||
typeCheckClass :: Class -> [Class] -> Class
|
typeCheckClass :: Class -> [Class] -> Class
|
||||||
typeCheckClass (Class className constructors methods fields) classes =
|
typeCheckClass (Class className methods fields) classes =
|
||||||
let
|
let
|
||||||
-- Fields and methods dont need to be added to the symtab because they are looked upon automatically under "this"
|
-- Fields and methods dont need to be added to the symtab because they are looked upon automatically under "this"
|
||||||
-- if its not a declared local variable. Also shadowing wouldnt be possible then.
|
-- if its not a declared local variable. Also shadowing wouldnt be possible then.
|
||||||
initalSymTab = [("this", className)]
|
initalSymTab = [("this", className)]
|
||||||
checkedConstructors = map (\constructor -> typeCheckConstructorDeclaration constructor initalSymTab classes) constructors
|
|
||||||
checkedMethods = map (\method -> typeCheckMethodDeclaration method initalSymTab classes) methods
|
checkedMethods = map (\method -> typeCheckMethodDeclaration method initalSymTab classes) methods
|
||||||
checkedFields = map (\field -> typeCheckVariableDeclaration field initalSymTab classes) fields
|
checkedFields = map (\field -> typeCheckVariableDeclaration field initalSymTab classes) fields
|
||||||
in Class className checkedConstructors checkedMethods checkedFields
|
in Class className checkedMethods checkedFields
|
||||||
|
|
||||||
typeCheckConstructorDeclaration :: ConstructorDeclaration -> [(Identifier, DataType)] -> [Class] -> ConstructorDeclaration
|
|
||||||
typeCheckConstructorDeclaration (ConstructorDeclaration name params body) symtab classes =
|
|
||||||
let
|
|
||||||
constructorParams = [(identifier, dataType) | ParameterDeclaration dataType identifier <- params]
|
|
||||||
initialSymtab = symtab ++ constructorParams
|
|
||||||
className = fromMaybe (error "Constructor Declaration: 'this' not found in symtab") (lookup "this" symtab)
|
|
||||||
checkedBody = typeCheckStatement body initialSymtab classes
|
|
||||||
bodyType = getTypeFromStmt checkedBody
|
|
||||||
in if name == className
|
|
||||||
then if bodyType == "void"
|
|
||||||
then ConstructorDeclaration name params checkedBody
|
|
||||||
else error $ "Constructor Declaration: Return type mismatch in constructor " ++ name ++ ": expected void, found " ++ bodyType
|
|
||||||
else error $ "Constructor Declaration: Constructor name " ++ name ++ " does not match class name " ++ className
|
|
||||||
|
|
||||||
|
|
||||||
typeCheckMethodDeclaration :: MethodDeclaration -> [(Identifier, DataType)] -> [Class] -> MethodDeclaration
|
typeCheckMethodDeclaration :: MethodDeclaration -> [(Identifier, DataType)] -> [Class] -> MethodDeclaration
|
||||||
typeCheckMethodDeclaration (MethodDeclaration retType name params body) symtab classes =
|
typeCheckMethodDeclaration (MethodDeclaration retType name params body) symtab classes =
|
||||||
@ -93,9 +63,9 @@ typeCheckExpression (Reference id) symtab classes =
|
|||||||
Nothing ->
|
Nothing ->
|
||||||
case lookup "this" symtab of
|
case lookup "this" symtab of
|
||||||
Just className ->
|
Just className ->
|
||||||
let classDetails = find (\(Class name _ _ _) -> name == className) classes
|
let classDetails = find (\(Class name _ _) -> name == className) classes
|
||||||
in case classDetails of
|
in case classDetails of
|
||||||
Just (Class _ _ _ fields) ->
|
Just (Class _ _ fields) ->
|
||||||
let fieldTypes = [dt | VariableDeclaration dt fieldId _ <- fields, fieldId == id]
|
let fieldTypes = [dt | VariableDeclaration dt fieldId _ <- fields, fieldId == id]
|
||||||
-- this case only happens when its a field of its own class so the implicit this will be converted to explicit this
|
-- this case only happens when its a field of its own class so the implicit this will be converted to explicit this
|
||||||
in case fieldTypes of
|
in case fieldTypes of
|
||||||
@ -169,70 +139,70 @@ typeCheckStatementExpression (Assignment ref expr) symtab classes =
|
|||||||
|
|
||||||
|
|
||||||
typeCheckStatementExpression (ConstructorCall className args) symtab classes =
|
typeCheckStatementExpression (ConstructorCall className args) symtab classes =
|
||||||
case find (\(Class name _ _ _) -> name == className) classes of
|
case find (\(Class name _ _) -> name == className) classes of
|
||||||
Nothing -> error $ "Class '" ++ className ++ "' not found."
|
Nothing -> error $ "Class '" ++ className ++ "' not found."
|
||||||
Just (Class _ constructors _ _) ->
|
Just (Class _ methods _) ->
|
||||||
let
|
-- Find constructor matching the class name with void return type
|
||||||
matchParams (ParameterDeclaration paramType _) arg =
|
case find (\(MethodDeclaration _ name params _) -> name == "<init>") methods of
|
||||||
let argTyped = typeCheckExpression arg symtab classes
|
-- If no constructor is found, assume standard constructor with no parameters
|
||||||
argType = getTypeFromExpr argTyped
|
Nothing ->
|
||||||
in if argType == "null" && isObjectType paramType
|
if null args then
|
||||||
then Just (TypedExpression paramType NullLiteral)
|
TypedStatementExpression className (ConstructorCall className args)
|
||||||
else if argType == paramType
|
else
|
||||||
then Just argTyped
|
error $ "No valid constructor found for class '" ++ className ++ "', but arguments were provided."
|
||||||
else Nothing
|
Just (MethodDeclaration _ _ params _) ->
|
||||||
|
let args' = zipWith
|
||||||
matchConstructor (ConstructorDeclaration name params _) =
|
(\arg (ParameterDeclaration paramType _) ->
|
||||||
let matchedArgs = sequence $ zipWith matchParams params args
|
let argTyped = typeCheckExpression arg symtab classes
|
||||||
in fmap (\checkedArgs -> (params, checkedArgs)) matchedArgs
|
in if getTypeFromExpr argTyped == "null" && isObjectType paramType
|
||||||
|
then TypedExpression paramType NullLiteral
|
||||||
validConstructors = filter (\(params, _) -> length params == length args) $ mapMaybe matchConstructor constructors
|
else argTyped
|
||||||
|
) args params
|
||||||
expectedSignatures = [ map (\(ParameterDeclaration t _) -> t) params | ConstructorDeclaration _ params _ <- constructors ]
|
expectedTypes = [dataType | ParameterDeclaration dataType _ <- params]
|
||||||
actualSignature = map (\arg -> getTypeFromExpr (typeCheckExpression arg symtab classes)) args
|
argTypes = map getTypeFromExpr args'
|
||||||
mismatchDetails = "Constructor not found for class '" ++ className ++ "' with given arguments.\n" ++
|
typeMatches = zipWith
|
||||||
"Expected signatures:\n" ++ show expectedSignatures ++
|
(\expType argType -> (expType == argType || (argType == "null" && isObjectType expType), expType, argType))
|
||||||
"\nActual arguments:" ++ show actualSignature
|
expectedTypes argTypes
|
||||||
|
mismatches = filter (not . fst3) typeMatches
|
||||||
in case validConstructors of
|
fst3 (a, _, _) = a
|
||||||
[(_, checkedArgs)] ->
|
in
|
||||||
TypedStatementExpression className (ConstructorCall className checkedArgs)
|
if null mismatches && length args == length params then
|
||||||
[] -> error mismatchDetails
|
TypedStatementExpression className (ConstructorCall className args')
|
||||||
_ -> error $ "Multiple matching constructors found for class '" ++ className ++ "' with given arguments."
|
else if not (null mismatches) then
|
||||||
|
error $ unlines $ ("Type mismatch in constructor arguments for class '" ++ className ++ "':")
|
||||||
|
: [ "Expected: " ++ expType ++ ", Found: " ++ argType | (_, expType, argType) <- mismatches ]
|
||||||
|
else
|
||||||
|
error $ "Incorrect number of arguments for constructor of class '" ++ className ++ "'. Expected " ++ show (length expectedTypes) ++ ", found " ++ show (length args) ++ "."
|
||||||
|
|
||||||
typeCheckStatementExpression (MethodCall expr methodName args) symtab classes =
|
typeCheckStatementExpression (MethodCall expr methodName args) symtab classes =
|
||||||
let objExprTyped = typeCheckExpression expr symtab classes
|
let objExprTyped = typeCheckExpression expr symtab classes
|
||||||
in case objExprTyped of
|
in case objExprTyped of
|
||||||
TypedExpression objType _ ->
|
TypedExpression objType _ ->
|
||||||
case find (\(Class className _ _ _) -> className == objType) classes of
|
case find (\(Class className _ _) -> className == objType) classes of
|
||||||
Just (Class _ _ methods _) ->
|
Just (Class _ methods _) ->
|
||||||
let matchParams (ParameterDeclaration paramType _) arg =
|
case find (\(MethodDeclaration retType name params _) -> name == methodName) methods of
|
||||||
let argTyped = typeCheckExpression arg symtab classes
|
Just (MethodDeclaration retType _ params _) ->
|
||||||
argType = getTypeFromExpr argTyped
|
let args' = zipWith
|
||||||
in if argType == "null" && isObjectType paramType
|
(\arg (ParameterDeclaration paramType _) ->
|
||||||
then Just (TypedExpression paramType NullLiteral)
|
let argTyped = typeCheckExpression arg symtab classes
|
||||||
else if argType == paramType
|
in if getTypeFromExpr argTyped == "null" && isObjectType paramType
|
||||||
then Just argTyped
|
then TypedExpression paramType NullLiteral
|
||||||
else Nothing
|
else argTyped
|
||||||
|
) args params
|
||||||
matchMethod (MethodDeclaration retType name params _) =
|
expectedTypes = [dataType | ParameterDeclaration dataType _ <- params]
|
||||||
let matchedArgs = sequence $ zipWith matchParams params args
|
argTypes = map getTypeFromExpr args'
|
||||||
in fmap (\checkedArgs -> (MethodDeclaration retType name params (Block []), checkedArgs)) matchedArgs
|
typeMatches = zipWith
|
||||||
|
(\expType argType -> (expType == argType || (argType == "null" && isObjectType expType), expType, argType))
|
||||||
validMethods = filter (\(MethodDeclaration _ name params _, _) -> name == methodName && length params == length args) $ mapMaybe matchMethod methods
|
expectedTypes argTypes
|
||||||
|
mismatches = filter (not . fst3) typeMatches
|
||||||
expectedSignatures = [ map (\(ParameterDeclaration t _) -> t) params | MethodDeclaration _ name params _ <- methods, name == methodName ]
|
fst3 (a, _, _) = a
|
||||||
actualSignature = map (\arg -> getTypeFromExpr (typeCheckExpression arg symtab classes)) args
|
in if null mismatches && length args == length params
|
||||||
mismatchDetails = "Method not found for class '" ++ objType ++ "' with given arguments.\n" ++
|
then TypedStatementExpression retType (MethodCall objExprTyped methodName args')
|
||||||
"Expected signatures for method '" ++ methodName ++ "':\n" ++ unlines (map show expectedSignatures) ++
|
else if not (null mismatches)
|
||||||
"Actual arguments:\n" ++ show actualSignature
|
then error $ unlines $ ("Argument type mismatches for method '" ++ methodName ++ "':")
|
||||||
|
: [ "Expected: " ++ expType ++ ", Found: " ++ argType | (_, expType, argType) <- mismatches ]
|
||||||
in case validMethods of
|
else error $ "Incorrect number of arguments for method '" ++ methodName ++ "'. Expected " ++ show (length expectedTypes) ++ ", found " ++ show (length args) ++ "."
|
||||||
[(MethodDeclaration retType _ params _, checkedArgs)] ->
|
Nothing -> error $ "Method '" ++ methodName ++ "' not found in class '" ++ objType ++ "'."
|
||||||
TypedStatementExpression retType (MethodCall objExprTyped methodName checkedArgs)
|
|
||||||
[] -> error mismatchDetails
|
|
||||||
_ -> error $ "Multiple matching methods found for class '" ++ objType ++ "' and method '" ++ methodName ++ "' with given arguments."
|
|
||||||
|
|
||||||
Nothing -> error $ "Class for object type '" ++ objType ++ "' not found."
|
Nothing -> error $ "Class for object type '" ++ objType ++ "' not found."
|
||||||
_ -> error "Invalid object type for method call. Object must have a class type."
|
_ -> error "Invalid object type for method call. Object must have a class type."
|
||||||
|
|
||||||
@ -385,7 +355,7 @@ isSubtype subType superType classes
|
|||||||
| otherwise = False
|
| otherwise = False
|
||||||
|
|
||||||
isUserDefinedClass :: DataType -> [Class] -> Bool
|
isUserDefinedClass :: DataType -> [Class] -> Bool
|
||||||
isUserDefinedClass dt classes = dt `elem` map (\(Class name _ _ _) -> name) classes
|
isUserDefinedClass dt classes = dt `elem` map (\(Class name _ _) -> name) classes
|
||||||
|
|
||||||
isObjectType :: DataType -> Bool
|
isObjectType :: DataType -> Bool
|
||||||
isObjectType dt = dt /= "int" && dt /= "boolean" && dt /= "char"
|
isObjectType dt = dt /= "int" && dt /= "boolean" && dt /= "char"
|
||||||
@ -438,14 +408,9 @@ checkComparisonOperation op expr1' expr2' type1 type2
|
|||||||
|
|
||||||
checkEqualityOperation :: BinaryOperator -> Expression -> Expression -> DataType -> DataType -> Expression
|
checkEqualityOperation :: BinaryOperator -> Expression -> Expression -> DataType -> DataType -> Expression
|
||||||
checkEqualityOperation op expr1' expr2' type1 type2
|
checkEqualityOperation op expr1' expr2' type1 type2
|
||||||
| type1 == type2 || (type1 == "null" && isObjectType type2) || (type2 == "null" && isObjectType type1) =
|
| type1 == type2 =
|
||||||
TypedExpression "boolean" (BinaryOperation op expr1' expr2')
|
TypedExpression "boolean" (BinaryOperation op expr1' expr2')
|
||||||
| type1 /= type2 =
|
| otherwise = error $ "Equality operation " ++ show op ++ " requires operands of the same type"
|
||||||
error $ "Equality operation " ++ show op ++ " requires operands of the same type. Found types: " ++ type1 ++ " and " ++ type2
|
|
||||||
| (type1 == "null" && not (isObjectType type2)) || (type2 == "null" && not (isObjectType type1)) =
|
|
||||||
error $ "Equality operation " ++ show op ++ " requires that null can only be compared with object types. Found types: " ++ type1 ++ " and " ++ type2
|
|
||||||
| otherwise = error $ "Equality operation " ++ show op ++ " encountered unexpected types: " ++ type1 ++ " and " ++ type2
|
|
||||||
|
|
||||||
|
|
||||||
checkLogicalOperation :: BinaryOperator -> Expression -> Expression -> DataType -> DataType -> Expression
|
checkLogicalOperation :: BinaryOperator -> Expression -> Expression -> DataType -> DataType -> Expression
|
||||||
checkLogicalOperation op expr1' expr2' type1 type2
|
checkLogicalOperation op expr1' expr2' type1 type2
|
||||||
@ -457,8 +422,8 @@ resolveNameResolution :: Expression -> Expression -> [(Identifier, DataType)] ->
|
|||||||
resolveNameResolution expr1' (Reference ident2) symtab classes =
|
resolveNameResolution expr1' (Reference ident2) symtab classes =
|
||||||
case getTypeFromExpr expr1' of
|
case getTypeFromExpr expr1' of
|
||||||
objType ->
|
objType ->
|
||||||
case find (\(Class className _ _ _) -> className == objType) classes of
|
case find (\(Class className _ _) -> className == objType) classes of
|
||||||
Just (Class _ _ _ fields) ->
|
Just (Class _ _ fields) ->
|
||||||
let fieldTypes = [dt | VariableDeclaration dt id _ <- fields, id == ident2]
|
let fieldTypes = [dt | VariableDeclaration dt id _ <- fields, id == ident2]
|
||||||
in case fieldTypes of
|
in case fieldTypes of
|
||||||
[resolvedType] -> TypedExpression resolvedType (BinaryOperation NameResolution expr1' (TypedExpression resolvedType (FieldVariable ident2)))
|
[resolvedType] -> TypedExpression resolvedType (BinaryOperation NameResolution expr1' (TypedExpression resolvedType (FieldVariable ident2)))
|
||||||
|
Loading…
Reference in New Issue
Block a user