Skip to content

Commit 467db45

Browse files
committed
Merge remote-tracking branch 'origin/codegen' into type-reordering
2 parents b4a5e2d + 0e38d4c commit 467db45

File tree

8 files changed

+68
-96
lines changed

8 files changed

+68
-96
lines changed

README.md

+16-20
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ A MiniJava Compiler written in Rust.
44

55
## MiniJava
66

7-
MiniJava is a subset of Java. It is a simple object-oriented language that supports classes, single inheritance(not
7+
MiniJava is a subset of Java. It is a simple object-oriented language that supports classes, single inheritance(not
88
supported in this compiler), and strong typing.
99

1010
## Build
1111

1212
### Requirements
1313

14-
- [Rust](https://rustup.rs/)
14+
- [Rust](https://rustup.rs/)
1515

1616
### Build
1717

@@ -22,29 +22,25 @@ cargo build --release
2222
## Usage
2323

2424
```bash
25-
cargo r -r -- <input_file> <output_file>
25+
cargo r -r -- <input_file> [<output_file>]
2626
```
2727

28-
# Disclaimer
29-
This compiler was done as a student project and doesn't support many language features and may contain bugs. It is not intended to be used in production.
28+
## Documentation
3029

31-
However, it was a great learning experience, and we hope it can be useful to someone else.
30+
A more detailed documentation on how to use the separate parts of the compiler is given in [User-Doc](./docs/User-Doc.md). A detailed documentation on how the project was done is given in [Project-Doc](./docs/Project-Doc.md).
3231

33-
## Parsing
34-
Parsing is done using a [Pest grammar](https://pest.rs/).
32+
## Disclaimer
3533

36-
## Type Checking
37-
I do not know what to write about type checking as i am clueless
34+
This compiler was done as a student project and doesn't support many language features and may contain bugs. It is not intended to be used in production.
3835

39-
## Code Generation
40-
Code generation is done manually to generate Java Bytecode. The code generation uses two passes.
41-
The first pass generates all instructions and the second one is used to convert relative jumps into absolute jumps.
36+
However, it was a great learning experience, and we hope it can be useful to someone else.
4237

4338
## Authors
44-
- Flippchen
45-
- Marion
46-
- mfloto
47-
- Nereuxofficial
48-
- Sander
49-
- Tori
50-
- Val
39+
40+
- Philipp Wolf (Flippchen)
41+
- Maximilian Floto (mfloto)
42+
- Marion Hinkel (MarionHinkel)
43+
- Benedikt Brandmaier (Nereuxofficial)
44+
- Tori Gönnheimer (ToriTheGreenOne)
45+
- Sander Stella (SanderForGodot)
46+
- Val Richter (ArtInLines)

docs/Project-Doc.md

+28-31
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
## Supported types
2-
- Welche Types, Strukturen und deren Kombinationen werden unterstützt?
2+
3+
- Welche Types, Strukturen und deren Kombinationen werden unterstützt?
34

45
## Parser
56

@@ -28,32 +29,33 @@ Funktionsweise des Typecheckers:
2829
Der Typechecker iteriert über alle übergebenen Klassen und prüft auf mehrfache Klassendeklarationen. Die Felddeklarationen werden in einem neuen getypten Klassenobjekt gespeichert, in dem alle weiteren getypten Methoden und deren Statements gespeichert werden. Anschließend iteriert der Typechecker über alle Methoden und prüft auf mehrfache Methodendeklarationen und typisiert die Methodenparameter. Nachdem alle Statements typisiert wurden, wird der Rückgabetyp der Methode geprüft und die Methode im getypten Klassenobjekt gespeichert. Nach dem Überprüfen und Typisieren der Klasse wird diese in einen Vektor an getypten Klassen gespeichert. Nachdem alle Klassen getypt wurden, wird der Vektor an getypten Klassen zurückgegeben.
2930

3031
Folgende Funktionen werden vom Typechecker übernommen:
31-
- Liest alle definierten Types/Strukturen
32-
- Typisierung aller Variablen und Ausdrücke
33-
- Checken von mehreren Klassen
34-
- Checken der Rückgabe-Typen von Methoden
35-
- Ersetzen von LocalOrFieldVar durch LocalVar oder FieldVar
3632

33+
- Liest alle definierten Types/Strukturen
34+
- Typisierung aller Variablen und Ausdrücke
35+
- Checken von mehreren Klassen
36+
- Checken der Rückgabe-Typen von Methoden
37+
- Ersetzen von LocalOrFieldVar durch LocalVar oder FieldVar
3738

3839
Folgende Fehler werden vom Typechecker erkannt:
39-
- Mehrfache Deklaration einer Klasse
40-
- Mehrfache Deklaration einer Methode
41-
- Type-Mismatch bei Methodenrückgabe
42-
- Type-Mismatch bei Methodenaufruf
43-
- Type-Mismatch bei Methodenparametern
44-
- Type-Mismatch bei FieldDecl
45-
- Type-Mismatch bei Unary/Binary-Operationen
46-
- Mehrfache Deklaration von FieldDecl
47-
- Mehrfache Deklaration von LocalOrFieldVar
48-
- Nicht deklarierte Variable
49-
- Unbekannte Methode bei Methodenaufruf
50-
- Verwendung einer nicht deklarierten Variable
51-
- Panic bei TypedExpr in AST
52-
- Bedingung von If/While-Statement ist kein Bool
40+
41+
- Mehrfache Deklaration einer Klasse
42+
- Mehrfache Deklaration einer Methode
43+
- Type-Mismatch bei Methodenrückgabe
44+
- Type-Mismatch bei Methodenaufruf
45+
- Type-Mismatch bei Methodenparametern
46+
- Type-Mismatch bei FieldDecl
47+
- Type-Mismatch bei Unary/Binary-Operationen
48+
- Mehrfache Deklaration von FieldDecl
49+
- Mehrfache Deklaration von LocalOrFieldVar
50+
- Nicht deklarierte Variable
51+
- Unbekannte Methode bei Methodenaufruf
52+
- Verwendung einer nicht deklarierten Variable
53+
- Panic bei TypedExpr in AST
54+
- Bedingung von If/While-Statement ist kein Bool
5355

5456
## Codegenerierung
5557

56-
ByteCode-Umwandlung, Bugfixes, StackSize und viele Improvements: Val Richter
58+
ByteCode-Umwandlung, Bugfixes, StackSize und viele Verbesserungen: Val Richter
5759

5860
Definition DIR(Duck Intermediate Representation), ConstantPool, LocalVarPool, Methoden zur Instruction-generierung, BugFixes, etwas ByteCode-Umwandlung und Umwandlung relativer in absolute Jumps: Marion Hinkel und Benedikt Brandmaier im Pair Programming
5961

@@ -69,7 +71,7 @@ hatten häufig off-by-one Errors.
6971

7072
Zudem musste eine StackMapTable implementiert werden, da die JVM sonst unsere Klassen nicht lädt.
7173
Das Troubleshooten von Testfehlern war auch sehr aufwending da oft javap gar nicht erst den Fehler im Klassencode ausgab
72-
und wir mit einem Hex-Editor die Klassen von Hand analysieren mussten, da es auch kein anderes Tool gab, um solche Fehler
74+
und wir mit einem Hex-Editor die Klassen von Hand analysieren mussten, da es auch kein anderes Tool gab, um solche Fehler
7375
auszugeben und die Zeit fehlte ein Eigenes zu schreiben.
7476

7577
Da es auch keine Dokumentation gibt, die in etwa zeigt, welcher Bytecode für welche Operationen genutzt wird, mussten wir
@@ -78,15 +80,13 @@ in die wir manuell Java Code eingeben und schauten, was für Bytecode bei versch
7880
wird, was sehr zeitaufwendig war.
7981

8082
Auch sehr schwierig war die Implementation einer StackMapTable, da Java diese erwartet. Diese ist eine Tabelle, die für
81-
jede Instruktion die Typen der Elemente auf dem Stack in komprimiertem Format angibt. Diese Tabelle muss manuell
83+
jede Instruktion die Typen der Elemente auf dem Stack in komprimiertem Format angibt. Diese Tabelle muss manuell
8284
erstellt werden und über die Typen aller Variablen, die in den Stack geschrieben wurden Bescheid wissen.
8385

8486
## Testing
8587

86-
- Wer hat welche Arbeit beim Testing gemacht?
87-
- Wie funktioniert das Testing genau?
88-
8988
Das Testen des Codegens war sehr aufwendig, er besteht aus diesen Schritten:
89+
9090
1. Ein handgeschriebener TAST wird geladen
9191
2. Eine Java Klasse wird erstellt die jede Methode im TAST aufruft
9292
3. Die java Klasse wird mit javac kompiliert und ausgeführt, wobei die Ausgabe in einer Variable gespeichert wird
@@ -99,9 +99,6 @@ Das Testen des Codegens war sehr aufwendig, er besteht aus diesen Schritten:
9999

100100
## AST-Definition
101101

102-
- Wer hat wie zur endgültigen Definition des ASTs beigetragen?
103-
- v.a. interessant, dass während der Arbeit der einzelnen Teams, der AST iterativ verändert wurde
104-
105-
## Projektmanagement
102+
Die genaue Definition des ASTs, die verwendet wurde, ist in [AST-Klassendiagramm](./AST-Klassendiagramm.png) als UML-Diagramm dargestellt. Zusätzlich lassen sich in [hier](./Notizen%20zum%20AST.md) einige Erklärungen zum Diagramm finden.
106103

107-
- Welche Arbeit lief von wem ins Projektmanagement?
104+
![Klassendiagramm](AST-Klassendiagramm.png)

docs/User-Doc.md

+19-12
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,43 @@
1-
TBD
2-
3-
- Jeweils erwähnen wie man die Teile kompiliert und laufen lassen kann. Nicht vergessen: Wie sollte die Eingabe aussehen und wie sieht die Ausgabe dann aus?
4-
- Vielleicht sollten wir Command-Line Befehle für jeden der Teile im Binary Crate zur Verfügung stellen?
5-
61
## Vorbedingungen
2+
73
[Rust installieren](https://rustup.rs/):
4+
85
```bash
96
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
107
```
11-
- [JDK 20](https://www.oracle.com/java/technologies/downloads/)(Wird in Tests für die Validierung des Codegens benötigt)
8+
9+
- [JDK 20](https://www.oracle.com/java/technologies/downloads/) (Wird in Tests für die Validierung des Codegens benötigt)
1210

1311
# Ausführen
12+
1413
Um eine .java in eine .class-datei zu kompilieren:
14+
1515
```bash
16-
cargo run -- <input_file>
16+
cargo r -r -- <input_file> [<output_file>]
1717
```
1818

1919
# Testen
20+
2021
1. Projekt bauen: `cargo build`
2122

2223
## Parser
2324

24-
2. Parser tests ausführen: `cargo test test_parser`
25+
2. Parser tests ausführen: `cargo test --lib test_parser`
26+
27+
Spezifischen Test ausführen: `cargo test --lib <test_name>::test_parser`
28+
2529
## Typchecker
2630

27-
2. Typechecker tests ausführen: `cargo test test_typechecker`
31+
2. Typechecker tests ausführen: `cargo test --lib test_typechecker`
2832

29-
Spezifischen Test ausführen: `cargo test <test_name>::test_typechecker`
33+
Spezifischen Test ausführen: `cargo test --lib <test_name>::test_typechecker`
3034

3135
## Codegenerierung
3236

33-
2. Codegenerierung tests ausführen: `cargo test test_codegen`
37+
2. Codegenerierung tests ausführen: `cargo test --lib test_codegen`
38+
39+
Spezifischen Test ausführen: `cargo test --lib <test_name>::test_codegen`
3440

3541
## Testing
36-
Zur ausführung aller tests: `cargo test`
42+
43+
Zur ausführung aller tests: `cargo test --lib`. Dabei sollte beachtet werden, dass die Tests teilweise die selben Dateien schreiben und entsprechend Probleme aufkommen können, wenn alle Tests auf einmal ausgeführt werden. Diese Probleme treten nicht auf, wenn die Teile des Compilers (Parser, Typchecker, Codegenerierung) einzeln getestet werden.

lib/src/codegen/ir.rs

-13
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,6 @@ impl ConstantPool {
256256
result.extend_from_slice(&self.add(Constant::Utf8(r#type)).to_be_bytes());
257257
}
258258
Constant::FieldRef(FieldRef { class, field }) => {
259-
//TODO: Maybe this should be moved Prio2
260259
result.push(9);
261260
result.extend_from_slice(
262261
&self
@@ -574,18 +573,6 @@ fn generate_class(class: &Class, dir: &mut DIR) -> IRClass {
574573
ir_class
575574
}
576575

577-
// @Cleanup this function is never used
578-
/// If this method is used the caller has to still set a NameAndType constant and a FieldRef
579-
/// constant, which is technically optional if the field is not used but we're lazy
580-
fn generate_field(field: &FieldDecl, constant_pool: &mut ConstantPool) -> IRFieldDecl {
581-
let name_index = constant_pool.add(Constant::Utf8(field.name.clone()));
582-
let type_index = constant_pool.add(Constant::Utf8(format!(
583-
"(){}",
584-
field.field_type.clone().to_ir_string()
585-
)));
586-
IRFieldDecl::new(type_index, name_index)
587-
}
588-
589576
/// Generates a Vector of instructions for a given method
590577
fn generate_method(
591578
method: &MethodDecl,

lib/src/codegen/stack.rs

+2-6
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,10 @@ impl StackMapTable {
8282
constant_pool: &mut ConstantPool,
8383
) {
8484
while instruction_idx < code.len() {
85-
// FIXME: When are locals added and how can we detect that from the instructions alone?
8685
match code.get(instruction_idx).unwrap() {
8786
Instruction::invokespecial(idx) => {
8887
bytes_idx += 3;
8988
if let Some(Constant::MethodRef(m)) = constant_pool.get(*idx) {
90-
// FIXME: Somehow figure out how many parameters are popped when calling the given function
9189
let pop_amount = 1;
9290
let l = current_stack.operands.len();
9391
current_stack.operands.splice(l - pop_amount..l, []);
@@ -124,7 +122,7 @@ impl StackMapTable {
124122
bytes_idx += 1;
125123
current_stack.operands.push(VerificationType::NULL)
126124
}
127-
Instruction::new(_) => todo!(), // requires special treatment, I think, see documentation VerificationType::UNINITIALIZED
125+
Instruction::new(_) => unimplemented!(),
128126
Instruction::aload_0 => {
129127
bytes_idx += 1;
130128
current_stack.operands.push(VerificationType::OBJECT(
@@ -477,8 +475,7 @@ pub(crate) enum VerificationType {
477475
INTEGER,
478476
NULL,
479477
UNINITIALIZED_THIS,
480-
OBJECT(u16), // index in constant pool
481-
UNINITIALIZED(u16), // offset (see docs for specific infos)
478+
OBJECT(u16), // index in constant pool
482479
}
483480

484481
impl VerificationType {
@@ -494,7 +491,6 @@ impl VerificationType {
494491
v.extend_from_slice(&cp_idx.to_be_bytes());
495492
v
496493
}
497-
VerificationType::UNINITIALIZED(offset) => todo!(),
498494
}
499495
}
500496
}

lib/src/parser/parser.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ fn parse_class(pair: Pair<Rule>) -> Class {
5252
methods,
5353
}
5454
}
55-
_ => todo!(),
55+
_ => unreachable!(),
5656
}
5757
}
5858
fn next_id(inners: &mut Pairs<Rule>) -> String {
@@ -182,7 +182,7 @@ fn parse_Stmt(pair: Pair<Rule>) -> Vec<Stmt> {
182182
pair.as_str()
183183
);
184184
match pair.as_rule() {
185-
Rule::Stmt => parse_Stmt(pair.into_inner().next().unwrap()), //@Notice this may be very wrong !!
185+
Rule::Stmt => parse_Stmt(pair.into_inner().next().unwrap()),
186186
Rule::WhileStmt => {
187187
let mut inners = pair.into_inner();
188188
let Expr = parse_expr(inners.next().unwrap());

lib/src/tests/mod.rs

+1-4
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,6 @@ fn normalize_str(s: std::string::String) -> std::string::String {
6161

6262
pub fn parser_test(ast: &Class, name: &str) {
6363
// Call parser with java code
64-
// TODO: Can only be done, once we have a parsing method that returns a Class
6564
let parse_res = parser::parse_programm(
6665
&read_to_string(File::open(format!("lib/testcases/{name}.java")).unwrap()).unwrap(),
6766
)
@@ -71,7 +70,6 @@ pub fn parser_test(ast: &Class, name: &str) {
7170
}
7271

7372
pub fn typechecker_test(ast: &Class, tast: &Class) {
74-
// TODO: Errors are just ignored for now, oops
7573
let mut tc = TypeChecker::new(vec![ast.clone()]).unwrap();
7674
let typed_classes = tc.check_and_type_program().unwrap();
7775
assert_eq!(typed_classes[0], *tast);
@@ -80,7 +78,6 @@ pub fn typechecker_test(ast: &Class, tast: &Class) {
8078
const TEST_VALS_AMOUNT: usize = 5;
8179
static BOOL_TEST_VALS: [&str; 4] = ["true", "false", "false", "true"];
8280
static CHAR_TEST_VALS: [&str; TEST_VALS_AMOUNT] = ["'c'", "'x'", "'!'", "'a'", "'f'"];
83-
static INT_TEST_VALS: [&str; TEST_VALS_AMOUNT] = ["5", "8", "257", "0", "69"];
8481
static STR_TEST_VALS: [&str; TEST_VALS_AMOUNT] = [
8582
"\"a\"",
8683
"\"test\"",
@@ -93,7 +90,7 @@ pub fn get_test_val(t: Type, i: usize) -> std::string::String {
9390
match t {
9491
Type::Bool => BOOL_TEST_VALS[xorshift().wrapping_add(i) % BOOL_TEST_VALS.len()].to_string(),
9592
Type::Char => CHAR_TEST_VALS[i % CHAR_TEST_VALS.len()].to_string(),
96-
Type::Int => INT_TEST_VALS[i % INT_TEST_VALS.len()].to_string(),
93+
Type::Int => (xorshift().wrapping_add(i) % 30).to_string(),
9794
Type::Null => "null".to_string(),
9895
Type::Void => panic!("can't create a test value for parameters of type 'void'"),
9996
Type::String => STR_TEST_VALS[i % STR_TEST_VALS.len()].to_string(),

lib/src/types.rs

-8
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,6 @@ pub enum Stmt {
9090
pub enum StmtExpr {
9191
Assign(Expr, Expr), // first the name of the variable, then the value it is being assigned to
9292
New(Type, Vec<Expr>), // first the class type, that should be instantiated, then the list of arguments for the constructor
93-
// FIXME: This needs to be changed to represent more how the JVM handles method calls. We need a class(at least name) and a method name with the typed arguments inside it, also the return type
94-
// #2 = Methodref #3.#17 // MethodTest.y:(I)I
95-
// #3 = Class #18 // MethodTest
96-
// #17 = NameAndType #19:#20 // y:(I)I
97-
// #18 = Utf8 MethodTest
98-
// #19 = Utf8 y
99-
// #20 = Utf8 (I)I
10093
MethodCall(Expr, String, Vec<Expr>), // first the object to which the method belongs (e.g. Expr::This), then the name of the method and lastly the list of arguments for the method call
10194
TypedStmtExpr(Box<StmtExpr>, Type),
10295
}
@@ -272,7 +265,6 @@ impl Type {
272265
Type::Bool => "Z",
273266
Type::String => "Ljava/lang/String;",
274267
Type::Void => "V",
275-
// FIXME: Either the class has the formatting `L<class>;' or we have to add it here.
276268
Type::Class(name) => name,
277269
_ => panic!("Invalid type: {}", self),
278270
}

0 commit comments

Comments
 (0)