JLite Compiler
Requirements
- Java 14
- Bash shell
- Connection to the internet
This project uses the gradle build system, which can self-bootstrap via
gradle/wrapper/graddle-wrapper.jar
.
As such, all the Makefile
does is invoke gradle to build the jar artifact.
If gradle has not been set up before, the self-bootstrapping process will
download all the requirements, freeing you from having to install anything
besides Java 14.
Java 14 Preview Features
This project uses Java 14 features, so you will need to pass --enable-preview
if you are running the jar file yourself.
This should be handled by the ./jlitec
wrapper script.
How to compile
In your shell, run:
$ make
This actually simply invokes:
$ ./gradlew jar
How to run
In your shell, run:
$ ./jlitec
I recommend trying out list.j
, which is an interactive program to manipulate a linked-list.
Assuming you have installed GCC ARM cross-compiler and QEMU user binfmt, simply run:
./jlitec arm -O3 test/arm/list.j > /tmp/a.s
arm-linux-gnueabi-gcc /tmp/a.s -o /tmp/a.out --static
/tmp/a.out
Subcommands
To get the subcommands and options available, run:
$ ./jlitec --help
You can also get the details of a particular subcommand, for example:
$ ./jlitec reg --help
Compile to ARM assembly
Use arm
to generate ARM assembly.
It also takes an optimization flag -O
:
-O0
is used if not specified, which disables any optimization.-O2
for optimizations on the Lower intermediate representation-O3
for additional peephole optimizations on the generated ARM assembly.
For example:
$ ./jlitec arm -O3 test/arm/gcd_fixed.j
.cpu cortex-a7
.global main
.type main, %function
main:
STMFD SP!, {R4, LR}
LDR R0, .S0
BL puts
MOV R1, #60
MOV R2, #50
BL Gcd_0
MOV R1, R0
LDR R0, .S1
BL printf
MOV R0, #0
LDMFD SP!, {R4, PC}
.global Gcd_0
.type Gcd_0, %function
Gcd_0:
L1:
CMP R1, R2
BEQ L2
CMP R1, R2
BLE L3
SUB R1, R1, R2
B L1
L3:
SUB R2, R2, R1
B L1
L2:
MOV R0, R1
BX LR
.S0:
.word .S0S
.S1:
.word .S1S
.section .rodata
.S0S:
.asciz "The GCD of 60 and 50 is:"
.section .rodata
.S1S:
.asciz "%d\n"
Lower Intermediate Code
Use lower
to generate Lower intermediate code.
You can also use the flag --opt
to generate Lower intermediate code after optimization.
For example:
$ ./jlitec lower --opt test/arm/gcd_fixed.j
======= Data =======
class Main {
}
class Gcd {
}
======= Methods =======
Void main() {
// START OF SPILLED TO STACK
// END OF SPILLED TO STACK
Int a;
Int b;
Gcd _t1;
Int _t2;
R0 = "The GCD of 60 and 50 is:";
CALL puts;
R1 = 60;
R2 = 50;
CALL %Gcd_0;
_t2 <- R0;
R0 = "%d\n";
R1 <- _t2;
CALL printf;
return;
}
Int Gcd_0(Gcd this, Int a, Int b) {
// START OF SPILLED TO STACK
// END OF SPILLED TO STACK
a <- R1;
b <- R2;
L1:
if (a == b) goto L2;
if (a <= b) goto L3;
a = a - b;
goto L1;
L3:
b = b - a;
goto L1;
L2:
R0 <- a;
return;
}
Static checks
Use check
to perform static check (distinct-name checking and type checking).
For example:
$ ./jlitec check test/arm/gcd_fixed.j
Static check succeeded!
Or in the case of failure:
$ ./jlitec check test/type/fail/overload.j
--> test/type/fail/overload.j:4:5
Semantic error: Call with signature `f(null)' on class `C' is ambiguous. Possible method overloads: f(B), f(C)
2 | Void main(){
3 | C c;
4 | c.f(null);
| ~~~~^^^ ambiguous method call `f(null)'
5 | return;
6 | }
IR3 Intermediate Code
Use ir3
to generate the IR3 intermediate code.
IR3 is actually not really used much in the compiler and what you probably want is the Lower intermediate code.
For example:
$ ./jlitec ir3 test/arm/gcd_fixed.j
======= CData3 =======
class Main {
}
class Gcd {
}
======= CMtd3 =======
Void main(Main this) {
Int a;
Int b;
Gcd _t1;
Int _t2;
a = 60;
b = 50;
println("The GCD of 60 and 50 is:");
_t1 = new Gcd();
_t2 = %Gcd_0(_t1, a, b);
println(_t2);
}
Int %Gcd_0(Gcd this, Int a, Int b) {
L1:
if (a == b) goto L2;
if (a <= b) goto L3;
a = a - b;
goto L4;
L3:
b = b - a;
L4:
goto L1;
L2:
return a;
}
Pretty printing
To pretty print a JLite file, you can use the pretty_print
subcommand.
For example:
$ ./jlitec pretty_print test/arm/gcd_fixed.j
class Main {
Void main() {
Int a;
Int b;
a = 60;
b = 50;
println("The GCD of 60 and 50 is:");
println(new Gcd().gcd(a, b));
}
}
class Gcd {
Int gcd(Int a, Int b) {
while (a != b) {
if (a > b) {
a = a - b;
} else {
b = b - a;
}
}
return a;
}
}
There are 2 other subcommands available that you may find useful:
lex
to print out the CUP symbols generated by the lexer.parse_tree
to print out the parse tree in JSON format.