In this lab, you develop a simple code generator, that generates Java bytecode from simple MiniJava programs, which only print integer constants in their main methods.
- Write a Jasmin program which prints
42
. - Implement a code generator that transforms MiniJava programs into Java bytecode.
The code generator should include
- A transformation from MiniJava ASTs to Jasmin ASTs which handles
- MiniJava programs
- with only a main class
- with a single print statement with
- an integer constant expression.
- A menu action which invokes the transformation and pretty-prints the Jasmin AST to concrete syntax.
- A menu action which generates a Java class file instead of a Jasmin file.
- A menu action which runs the program (challenge).
- A transformation from MiniJava ASTs to Jasmin ASTs which handles
You need to submit your MiniJava project with a pull request against branch assignment10
on GitHub.
Your GitHub repository contains a step-by-step procedure how to file such a request.
This project should contain a README.md
with a short paragraph explaining the organisation of your Stratego files.
The deadline for submissions is December 10th, 17:59.
You can earn up to 10 points for your Jasmin program and up to 75 points for your code generator:
- transformation (50 points)
- program (2 points)
- main class (18 points)
- main method (13 points)
- print statement (17 points)
- menu actions (10 points)
- Jasmin (5 points)
- Java class file (5 points)
- challenges (15 points)
- target directories (4 points)
- automatic code generation (5 points)
- runner (6 points)
You can earn up to 5 points for the organisation of your Stratego files and up to 10 points for the quality of your code. We focus on readibility in general, meaningful variable names and the consistent use of Stratego paragdims. We will consider the fact that Stratego is new to you.
We provide you with an initial MiniJava project in the branch assignment10
.
Make sure you have this branch in your fork as well, before you start working on this assignment.
- Import the project into your workspace:
- right-click into the Package Explorer
- select Import... from the context menu
- choose General/Existing Projects into Workspace from the list
- select the MiniJava project
- press the Finish button
- Build the project:
- select the project folder
- select Build Project from the Project menu
- the console will report success or failure
This project contains the following implementations:
- MiniJava signatures in
common/src-gen/signatures/MiniJava-sig
. - MiniJava pretty-printer in
common/src-gen/pp/MiniJava-pp
- MiniJava syntactic completions in
common/src-gen/completions/MiniJava-esv
. - MiniJava desugarings in
common/desugar
. - MiniJava name analysis in
common/names
. - MiniJava type analysis in
common/types
. - JasminXT signatures in
common/src-gen/signatures/JasminXT*-sig
- JasminXT pretty-printer in
common/src-gen/pp/JasminXT*-pp
These implementations are already imported into the initial project.
Update: There is an inconsistency in the pretty-printing rules. You can fix this, by deleting common/src-gen/pp/JasminXT-Extra-PP-Rules.pp.str
.
Consider the following simple MiniJava program:
class Main {
public static void main(String[] args) {
System.out.println(42);
}
}
Write a Jasmin program simple.j
, which you expect to be the result of a MiniJava-to-Jasmin compiler.
Generate a Java class file from it and run it.
Improve your program until it runs without errors.
Code generation should be a service of your MiniJava editor. To achieve this, define a new strategy generate-jbc
, which will be associated with the Generate Java bytecode entry in the Generate menu. Implement this strategy in Stratego by following the interface for action strategies:
generate-jbc: (selected, position, ast, path, project-path) -> (filename, result)
The implementation should rely on a strategy program-to-jbc
, which transforms MiniJava programs into Java bytecode.
Next, you need to implement program-to-jbc
.
You will do this stepwise over the remaining labs.
During this lab, you should implement it for programs that contain only a main class, which prints a single integer constant.
To understand Jasmin's abstract syntax, you can either
study example ASTs generated by a Jasmin editor,
study the grammar in the Jasmin project,
or have a closer look into common/src-gen/signatures/JasminXT-sig.str
.
-
Provide a rule for
exp-to-jbc
, which translates an integer constant from MiniJava into a sequence of Java bytecode instructions, that loads this constant to the operand stack. Note that it is important to generate a sequence here, even if you only need a single instruction, because in general a MiniJava expression translates into a sequence of bytecode instructions. -
Provide a rule for
stmt-to-jbc
, which translates a print statement from MiniJava into a sequence of Java bytecode instructions. This rule should callexp-to-jbc
to translate the expression inside the print statement to a Java bytecode sequence. -
Provide a rule for
class-to-jbc
, which translates a main class from MiniJava into a Jasmin class file. This rule should callstmt-to-jbc
to translate the statement inside the main method to a Java bytecode sequence. -
Provide a rule for
program-to-jbc
, which translates a MiniJava program into a list of Jasmin class files. This rule should callclass-to-jbc
to translate the main class of the program into a Jasmin class file.
For testing purposes, you can define another menu entry which calls a strategy to-jbc
, which dispatches to your different implementation rules:
to-jbc = program-to-jbc + class-to-jbc + stmt-to-jbc + exp-to-jbc
This allows you to test your implementation by selecting a code fragment in the MiniJava editor and running your code generation builder.
You can represent sequences as lists. Sequences of bytecode instructions are never nested. Thus, your generated sequences should also be flat lists. This requires you to compose sequences from recursive calls with surrounding instructions. In general, there are four different approaches to this in Stratego:
-
Generate nested lists and flatten these lists afterwards by applying
flatten-list
(not recommended). -
Compose lists with a head and tails notation. For example,
seq
might be a sequence generated by a recursive call. You can precede this sequence with instructionsinstr1
, ...,instrn
by writing[ instr1, ..., instrn | seq ]
. -
Compose lists explicitly.
<conc> (l1, l2)
concatenates two listsl1
andl2
.<concat> [l1, ..., ln]
concatenates listsl1
...ln
.
-
Use special list variables (recommended). Stratego provides special variable names for sequences. These names end in
*
, for exampleinstr*
. When using such variables in a list, Stratego will inline the list elements at that position instead of creating a nested list. For example,instr1* := [LDC(Int("1")), LDC(Int("2"))]; instr2* := [LDC(Int("0")), instr1*, LDC(Int("3"))]
is equivalent to
instr1* := [LDC(Int("1")), LDC(Int("2"))]; instr2* := [LDC(Int("0")), LDC(Int("1")), LDC(Int("2")), LDC(Int("3"))]
As you may have noticed, your implementation follows the code generation by transformation paradigm. The result of to-jbc
is a Jasmin AST. Extend your builder strategy generate-jbc
to generate concrete syntax. Therefor, you need to pretty-print the Jasmin ASTs, which are generated by program-to-jbc
. You can find a pretty-printing strategy for Jasmin in trans/jasmin.str
.
At this point, you can generate Jasmin class files from simple MiniJava programs. To run your programs in the Java Virtual Machine, you need to generate Java class files.
Define a new builder Generate Java class files with a corresponding builder strategy generate-jc
, that generates the Jasmin file and turns it into a Java class file. To generate the class file, you first need to generate the Jasmin AST.
To translate a Jasmin AST into a Java class file, you can use the strategy jasmin-generate
defined in trans/jasmin.str
. This strategy will only work, if you specify the path of the source file in a Jasmin directive.
The source directive is used by the JVM for debugging purposes and error messages. The Jasmin builder uses it to figure out where to store the class file. Thus, make sure your Jasmin AST has the form JBCFile(JBCHeader(_, JBCSource(<double-quote> path), _, _, _, _, _, _, _, _), _, _)
. You can pass path
as an strategy argument from generate-jbc
to program-to-jbc
.
Even though in MiniJava some keywords are reserved, Jasmin has its own keywords. Some Jasmin implementations consider those as reserved, some do not. Typical examples are keywords such as field
or class
. In the Spoofax implementation, we do not reserve these, but other Jasmin implementations might do, particularly if they may not have the latest updates (Debian for example). See StackOverflow for more information about this.
Challenges are meant to distinguish excellent solutions from good solutions. Typically, they are less guided and require more investigation and programming skills.
Real-world compilers like the JDT place generated code in designated directories, such as bin/
or src-gen/
. Implement a similar behaviour for your MiniJava compiler. Make sure you can handle classes with the same name from different programs and files with the same name from different directories.
The following strategies deal with directories:
<file-exists ; filemode ; isdir> path
succeeds ifpath
is a path to an existing directory in the file system.<mkdir> path
creates a new directory.
Real-world compilers like the JDT generate code silently in the background. Implement a similar behaviour, using Spoofax's on-save
builder. In your main Stratego file, change editor-save
to run your compiler: editor-save = where(analysis-save-default(|<language>)); generate-java-class-files
. editor-save
has the same input interface as a builder, but the output should be None()
. This means that you cannot return a (filename, result)
tuple like a normal builder, but need to write to a file yourself.
Implement a strategy write-file
that rewrites a pair of file name and file content to itself. As a side effect, this strategy should create (if necessary) and write a file. These strategies might be useful:
<fopen> (filename, "w")
opens a file for writing and gives you a file descriptor of the open file.<fputs> (content, filedescriptor)
writes to a file.<fclose> filedescriptor
closes a file.<refresh-workspace-file> filename
refreshes a file in the Eclipse workspace.
Add a builder that runs a MiniJava program by running the main method from the generated class file.