Skip to content

Commit f970b4a

Browse files
committed
Improve naming across the codebase and add comprehensive test suite
1 parent 597de99 commit f970b4a

23 files changed

Lines changed: 2698 additions & 133 deletions

README.md

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -106,16 +106,31 @@ To build and test this project you will need [Java 25](https://adoptium.net/temu
106106
one of the following commands:
107107

108108
* `gradlew lexer` (Windows) or `./gradlew lexer` (macOS and Linux) — to generate lexer (tokenizer) implementation
109-
(`MJLexer.java`) based on [lexer specification](src/main/resources/mjlexer.flex).
109+
(`Lexer.java`) based on [lexer specification](src/main/resources/mjlexer.flex).
110110
* `gradlew parser` (Windows) or `./gradlew parser` (macOS and Linux) — to generate parser implementation
111-
(`MJParser.java`, `sym.java`) and abstract syntax tree implementation (classes inside `ast` directory), based on
111+
(`Parser.java`, `sym.java`) and abstract syntax tree implementation (classes inside `ast` directory), based on
112112
[parser specification](src/main/resources/mjparser.cup).
113-
* `gradlew build` (Windows) or `./gradlew build` (macOS and Linux) — to build the project, test the compiler and run
114-
MicroJava VM. MicroJava Compiler will compile the test file [`simple_calculator.mj`](src/test/resources/simple_calculator.mj) and produce
115-
`simple_calculator.obj`. Then, the machine code inside `simple_calculator.obj` will be executed by MicroJava VM. Note
116-
that, for testing purposes, standard input has been substituted with a file named [`input_stream.txt`](src/test/resources/input_stream.txt).
117-
* `gradlew disassemble` (Windows) or `./gradlew disassemble` (macOS and Linux) — to disassemble `simple_calculator.obj`
118-
produced in the previous step, using [`rs.etf.pp1.mj.runtime.disasm`](libs/mj-runtime-1.1.jar).
113+
* `gradlew build` (Windows) or `./gradlew build` (macOS and Linux) — to build the project and run the test suite.
114+
* `gradlew disassemble` (Windows) or `./gradlew disassemble` (macOS and Linux) — to disassemble
115+
[`simple_calculator.obj`](src/test/resources/simple_calculator.obj) (compiled from
116+
[`simple_calculator.mj`](src/test/resources/simple_calculator.mj)) using
117+
[`rs.etf.pp1.mj.runtime.disasm`](libs/mj-runtime-1.1.jar).
118+
119+
## Test suite
120+
121+
The test suite covers the compiler pipeline at multiple levels:
122+
123+
* **Parsing tests** (`ParserTest`) — verifies that valid MicroJava programs parse without errors
124+
and that lexical/syntax errors are detected in malformed input.
125+
* **Semantic analysis tests** (`SemanticAnalyzerTest`) — tests detection of each semantic error kind
126+
(duplicate declarations, unresolved symbols, type mismatches, misplaced `break`/`continue`, etc.)
127+
as well as positive cases that should pass analysis without errors.
128+
* **Compiler tests** (`CompilerTest`) — compiles a wide range of MicroJava programs covering
129+
constants, variables, arithmetic, control flow, arrays, methods, classes, and inheritance,
130+
asserting that they compile without any errors.
131+
* **End-to-end tests** (`EndToEndTest`) — compiles MicroJava programs, runs the generated bytecode
132+
on the MicroJava VM, and verifies the exact output. This includes arithmetic, recursion (factorial,
133+
Fibonacci, GCD), arrays, class method invocation, polymorphism, I/O operations, and CLI behavior.
119134

120135
You can always run MicroJava Compiler as a standalone application.
121136
In order to achieve this, you just have to type `gradlew run <source-file-name> <obj-file-name>` (Windows) or

build.gradle.kts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ java {
1414
}
1515

1616
application {
17-
mainClass.set("dev.askov.mjcompiler.MJCompiler")
17+
mainClass.set("dev.askov.mjcompiler.Compiler")
1818
}
1919

2020
repositories {
@@ -78,12 +78,12 @@ tasks.register<JavaExec>("lexer") {
7878
mainClass.set("JFlex.Main")
7979
classpath = sourceSets.main.get().compileClasspath
8080

81-
val outputFile = jflexDir.file("dev/askov/mjcompiler/MJLexer.java")
81+
val outputFile = jflexDir.file("dev/askov/mjcompiler/Lexer.java")
8282

83-
inputs.file("src/main/resources/mjlexer.flex")
83+
inputs.file("src/main/resources/lexer.flex")
8484
outputs.file(outputFile)
8585

86-
args("-d", outputFile.asFile.parent, "src/main/resources/mjlexer.flex")
86+
args("-d", outputFile.asFile.parent, "src/main/resources/lexer.flex")
8787
}
8888

8989
tasks.register<JavaExec>("parser") {
@@ -100,7 +100,7 @@ tasks.register<JavaExec>("parser") {
100100

101101
workingDir = genSourceRoot
102102

103-
inputs.file("src/main/resources/mjparser.cup")
103+
inputs.file("src/main/resources/parser.cup")
104104
outputs.dir(genSourceRoot)
105105

106106
doFirst {
@@ -111,11 +111,11 @@ tasks.register<JavaExec>("parser") {
111111
"-destdir",
112112
"dev/askov/mjcompiler",
113113
"-parser",
114-
"MJParser",
114+
"Parser",
115115
"-ast",
116116
"dev.askov.mjcompiler.ast",
117117
"-buildtree",
118-
file("src/main/resources/mjparser.cup").absolutePath,
118+
file("src/main/resources/parser.cup").absolutePath,
119119
)
120120
}
121121

src/main/java/dev/askov/mjcompiler/CodeGenerator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
import dev.askov.mjcompiler.ast.*;
2323
import dev.askov.mjcompiler.inheritancetree.InheritanceTree;
2424
import dev.askov.mjcompiler.inheritancetree.InheritanceTreeNode;
25-
import dev.askov.mjcompiler.mjsymboltable.MJTab;
25+
import dev.askov.mjcompiler.symboltable.MJTab;
2626
import dev.askov.mjcompiler.util.MJUtils;
2727
import java.util.ArrayList;
2828
import java.util.HashMap;

src/main/java/dev/askov/mjcompiler/MJCompiler.java renamed to src/main/java/dev/askov/mjcompiler/Compiler.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
import dev.askov.mjcompiler.ast.Program;
2323
import dev.askov.mjcompiler.inheritancetree.InheritanceTree;
2424
import dev.askov.mjcompiler.inheritancetree.visitor.InheritanceTreePrinter;
25-
import dev.askov.mjcompiler.mjsymboltable.MJTab;
25+
import dev.askov.mjcompiler.symboltable.MJTab;
2626
import dev.askov.mjcompiler.vmt.VMTCodeGenerator;
2727
import dev.askov.mjcompiler.vmt.VMTCreator;
2828
import dev.askov.mjcompiler.vmt.VMTStartAddressGenerator;
@@ -37,17 +37,17 @@
3737
/**
3838
* @author Danijel Askov
3939
*/
40-
public class MJCompiler {
40+
public class Compiler {
4141

42-
private static final Logger LOGGER = LoggerFactory.getLogger(MJCompiler.class);
42+
private static final Logger LOGGER = LoggerFactory.getLogger(Compiler.class);
4343

4444
public static void dumpSymbolTable() {
4545
MJTab.dump(LOGGER);
4646
}
4747

4848
public static void main(String[] args) throws Exception {
4949
if (args.length < 2) {
50-
LOGGER.error("Too few arguments. Usage: MJCompiler <source-file> <obj-file>");
50+
LOGGER.error("Too few arguments. Usage: Compiler <source-file> <obj-file>");
5151
return;
5252
}
5353
var sourceFile = new File(args[0]);
@@ -57,8 +57,8 @@ public static void main(String[] args) throws Exception {
5757
}
5858
LOGGER.info("Compiling source file \"{}\"...", sourceFile.getAbsolutePath());
5959
try (var br = new BufferedReader(new FileReader(sourceFile))) {
60-
var lexer = new MJLexer(br);
61-
var parser = new MJParser(lexer);
60+
var lexer = new Lexer(br);
61+
var parser = new Parser(lexer);
6262
var symbol = parser.parse();
6363

6464
if (!parser.lexicalErrorDetected() && !parser.syntaxErrorDetected()) {

src/main/java/dev/askov/mjcompiler/SemanticAnalyzer.java

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -100,14 +100,14 @@
100100
import dev.askov.mjcompiler.ast.VoidReturnType;
101101
import dev.askov.mjcompiler.ast.VoidSuperclass;
102102
import dev.askov.mjcompiler.inheritancetree.InheritanceTree;
103-
import dev.askov.mjcompiler.loggers.SemanticErrorMJLogger;
104-
import dev.askov.mjcompiler.loggers.SemanticErrorMJLogger.SemanticErrorKind;
105-
import dev.askov.mjcompiler.loggers.SymbolUsageMJLogger;
103+
import dev.askov.mjcompiler.loggers.SemanticErrorLogger;
104+
import dev.askov.mjcompiler.loggers.SemanticErrorLogger.SemanticErrorKind;
105+
import dev.askov.mjcompiler.loggers.SymbolUsageLogger;
106106
import dev.askov.mjcompiler.methodsignature.ClassMethodSignature;
107107
import dev.askov.mjcompiler.methodsignature.GlobalMethodSignature;
108108
import dev.askov.mjcompiler.methodsignature.MethodSignature;
109109
import dev.askov.mjcompiler.methodsignature.MethodSignatureGenerator;
110-
import dev.askov.mjcompiler.mjsymboltable.MJTab;
110+
import dev.askov.mjcompiler.symboltable.MJTab;
111111
import dev.askov.mjcompiler.util.MJUtils;
112112
import java.util.Optional;
113113
import java.util.Stack;
@@ -124,8 +124,8 @@ public class SemanticAnalyzer extends VisitorAdaptor {
124124

125125
private boolean semanticErrorDetected = false;
126126

127-
private final SymbolUsageMJLogger symbolUsageMJLogger = new SymbolUsageMJLogger();
128-
private final SemanticErrorMJLogger semanticErrorMJLogger = new SemanticErrorMJLogger();
127+
private final SymbolUsageLogger symbolUsageLogger = new SymbolUsageLogger();
128+
private final SemanticErrorLogger semanticErrorLogger = new SemanticErrorLogger();
129129

130130
public boolean semanticErrorDetected() {
131131
return semanticErrorDetected;
@@ -137,7 +137,7 @@ private void detectSemanticError(
137137
SemanticErrorKind semanticErrorKind,
138138
Object... context) {
139139
semanticErrorDetected = true;
140-
semanticErrorMJLogger.log(symbolObj, syntaxNode.getLine(), null, semanticErrorKind, context);
140+
semanticErrorLogger.log(symbolObj, syntaxNode.getLine(), null, semanticErrorKind, context);
141141
}
142142

143143
private void detectSemanticError() {
@@ -1248,7 +1248,7 @@ public void visit(BoolFactor boolFactor) {
12481248
public void visit(NewScalarFactor newScalarFactor) {
12491249
newScalarFactor.obj = newScalarFactor.getType().obj;
12501250
if (newScalarFactor.obj.getType().getKind() == Struct.Class) {
1251-
symbolUsageMJLogger.log(newScalarFactor.obj, newScalarFactor.getLine(), null);
1251+
symbolUsageLogger.log(newScalarFactor.obj, newScalarFactor.getLine(), null);
12521252
}
12531253
}
12541254

@@ -1293,9 +1293,9 @@ public void visit(IdentDesignator identDesignator) {
12931293
} else {
12941294
detectSemanticError();
12951295
}
1296-
symbolUsageMJLogger.log(identObj, identDesignator.getLine(), null, currentMethodObj);
1296+
symbolUsageLogger.log(identObj, identDesignator.getLine(), null, currentMethodObj);
12971297
} else {
1298-
symbolUsageMJLogger.log(identObj, identDesignator.getLine(), null, currentMethodObj);
1298+
symbolUsageLogger.log(identObj, identDesignator.getLine(), null, currentMethodObj);
12991299
}
13001300

13011301
identDesignator.obj = identObj;
@@ -1332,7 +1332,7 @@ public void visit(ArrayElemAccessDesignator arrayElemAccessDesignator) {
13321332
"",
13331333
array.getType().getElemType() != null ? array.getType().getElemType() : MJTab.noType);
13341334
}
1335-
symbolUsageMJLogger.log(array, arrayElemAccessDesignator.getLine(), null, array);
1335+
symbolUsageLogger.log(array, arrayElemAccessDesignator.getLine(), null, array);
13361336
}
13371337

13381338
@Override
@@ -1356,12 +1356,12 @@ public void visit(MemberAccessDesignator memberAccessDesignator) {
13561356
} else {
13571357
memberAccessDesignatorObj = findNearestDeclaration(memberName, designatorStartObj);
13581358
if (memberAccessDesignatorObj != Tab.noObj) {
1359-
symbolUsageMJLogger.log(memberAccessDesignatorObj, memberAccessDesignator.getLine(), null);
1359+
symbolUsageLogger.log(memberAccessDesignatorObj, memberAccessDesignator.getLine(), null);
13601360
} else {
13611361
memberAccessDesignatorObj = new Obj(Obj.NO_VALUE, memberName, MJTab.noType);
13621362
detectSemanticError(
13631363
memberAccessDesignatorObj, memberAccessDesignator, SemanticErrorKind.UNRESOLVED_MEMBER);
1364-
symbolUsageMJLogger.log(memberAccessDesignatorObj, memberAccessDesignator.getLine(), null);
1364+
symbolUsageLogger.log(memberAccessDesignatorObj, memberAccessDesignator.getLine(), null);
13651365
}
13661366
}
13671367

@@ -1387,9 +1387,9 @@ public void visit(IdentDesignatorStart identDesignatorStart) {
13871387
|| identObj.getKind() == Obj.Prog) {
13881388
identObj = new Obj(Obj.NO_VALUE, identDesignatorStartIdent, MJTab.noType);
13891389
detectSemanticError(identObj, identDesignatorStart, SemanticErrorKind.UNRESOLVED_VARIABLE);
1390-
symbolUsageMJLogger.log(identObj, identDesignatorStart.getLine(), null, currentMethodObj);
1390+
symbolUsageLogger.log(identObj, identDesignatorStart.getLine(), null, currentMethodObj);
13911391
} else {
1392-
symbolUsageMJLogger.log(identObj, identDesignatorStart.getLine(), null, currentMethodObj);
1392+
symbolUsageLogger.log(identObj, identDesignatorStart.getLine(), null, currentMethodObj);
13931393
}
13941394

13951395
identDesignatorStart.obj = identObj;
@@ -1425,7 +1425,7 @@ public void visit(ArrayElemAccessDesignatorStart arrayElemAccessDesignatorStart)
14251425
"",
14261426
array.getType().getElemType() != null ? array.getType().getElemType() : MJTab.noType);
14271427
}
1428-
symbolUsageMJLogger.log(array, arrayElemAccessDesignatorStart.getLine(), null, array);
1428+
symbolUsageLogger.log(array, arrayElemAccessDesignatorStart.getLine(), null, array);
14291429
}
14301430

14311431
@Override
@@ -1449,15 +1449,15 @@ public void visit(MemberAccessDesignatorStart memberAccessDesignatorStart) {
14491449
} else {
14501450
memberAccessDesignatorStartObj = findNearestDeclaration(memberName, designatorStartObj);
14511451
if (memberAccessDesignatorStartObj != MJTab.noObj) {
1452-
symbolUsageMJLogger.log(
1452+
symbolUsageLogger.log(
14531453
memberAccessDesignatorStartObj, memberAccessDesignatorStart.getLine(), null);
14541454
} else {
14551455
memberAccessDesignatorStartObj = new Obj(Obj.NO_VALUE, memberName, MJTab.noType);
14561456
detectSemanticError(
14571457
memberAccessDesignatorStartObj,
14581458
memberAccessDesignatorStart,
14591459
SemanticErrorKind.UNRESOLVED_MEMBER);
1460-
symbolUsageMJLogger.log(
1460+
symbolUsageLogger.log(
14611461
memberAccessDesignatorStartObj, memberAccessDesignatorStart.getLine(), null);
14621462
}
14631463
}

src/main/java/dev/askov/mjcompiler/loggers/LexicalErrorMJLogger.java renamed to src/main/java/dev/askov/mjcompiler/loggers/LexicalErrorLogger.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@
2222
/**
2323
* @author Danijel Askov
2424
*/
25-
public class LexicalErrorMJLogger extends MJLogger<String> {
25+
public class LexicalErrorLogger extends MJLogger<String> {
2626

27-
public LexicalErrorMJLogger() {
27+
public LexicalErrorLogger() {
2828
super(Type.ERROR_LOGGER, "Lexical error");
2929
}
3030

src/main/java/dev/askov/mjcompiler/loggers/SemanticErrorMJLogger.java renamed to src/main/java/dev/askov/mjcompiler/loggers/SemanticErrorLogger.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
/**
3030
* @author Danijel Askov
3131
*/
32-
public class SemanticErrorMJLogger extends MJLogger<Obj> {
32+
public class SemanticErrorLogger extends MJLogger<Obj> {
3333

3434
public enum SemanticErrorKind {
3535
INAPPLICABLE_METHOD,
@@ -60,7 +60,7 @@ public enum SemanticErrorKind {
6060
NON_INVOCABLE_METHOD,
6161
}
6262

63-
public SemanticErrorMJLogger() {
63+
public SemanticErrorLogger() {
6464
super(Type.ERROR_LOGGER, "Semantic error");
6565
}
6666

src/main/java/dev/askov/mjcompiler/loggers/SymbolUsageMJLogger.java renamed to src/main/java/dev/askov/mjcompiler/loggers/SymbolUsageLogger.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,18 +20,18 @@
2020
package dev.askov.mjcompiler.loggers;
2121

2222
import dev.askov.mjcompiler.SemanticAnalyzer;
23-
import dev.askov.mjcompiler.mjsymboltable.MJDumpSymbolTableVisitor;
23+
import dev.askov.mjcompiler.symboltable.MJDumpSymbolTableVisitor;
2424
import rs.etf.pp1.symboltable.concepts.Obj;
2525
import rs.etf.pp1.symboltable.concepts.Struct;
2626

2727
/**
2828
* @author Danijel Askov
2929
*/
30-
public final class SymbolUsageMJLogger extends MJLogger<Obj> {
30+
public final class SymbolUsageLogger extends MJLogger<Obj> {
3131

3232
private final MJDumpSymbolTableVisitor symbolTableVisitor = new MJDumpSymbolTableVisitor(false);
3333

34-
public SymbolUsageMJLogger() {
34+
public SymbolUsageLogger() {
3535
super(Type.INFO_LOGGER, "Symbol usage");
3636
}
3737

src/main/java/dev/askov/mjcompiler/loggers/SyntaxErrorMJLogger.java renamed to src/main/java/dev/askov/mjcompiler/loggers/SyntaxErrorLogger.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
/**
2525
* @author Danijel Askov
2626
*/
27-
public class SyntaxErrorMJLogger extends MJLogger<Symbol> {
27+
public class SyntaxErrorLogger extends MJLogger<Symbol> {
2828

2929
public enum SyntaxErrorKind {
3030
INV_GLOBAL_VAR_DECL,
@@ -37,7 +37,7 @@ public enum SyntaxErrorKind {
3737
INV_DECL,
3838
}
3939

40-
public SyntaxErrorMJLogger() {
40+
public SyntaxErrorLogger() {
4141
super(Type.ERROR_LOGGER, "Syntax error");
4242
}
4343

src/main/java/dev/askov/mjcompiler/methodsignature/ClassMethodSignature.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
package dev.askov.mjcompiler.methodsignature;
2121

22-
import dev.askov.mjcompiler.mjsymboltable.MJTab;
22+
import dev.askov.mjcompiler.symboltable.MJTab;
2323
import dev.askov.mjcompiler.util.MJUtils;
2424
import java.util.Optional;
2525
import rs.etf.pp1.symboltable.concepts.Obj;

0 commit comments

Comments
 (0)