Due 11:59pm Monday Sept 18
In this project, we will enrich the language we defined in the first two projects.
At the end of the project, we will be able to parse full programs, including functions and arrays. Our parser will generate an intermediate representation in the form of an Abstract Syntax Tree (AST), and our Semantic Analyzer will be able to verify that the typing rules are being enforced.
With functions now in our language, we can add a little bit of I/O. When writing code in our programming language, the programmer will have access to two primitives:
The project has been designed and tested for Linux/Mac OS. If you only have Windows installed on your personal laptop, consider running Linux in a VM or using the lab machines for the project.
If you use remote access to work on your project, please use one of the lab machines pod1-1 to pod1-20 with the suffix cs.purdue.edu (e.g. pod1-1.cs.purdue.edu)
Download the skeleton file here.
tar xzvf proj3.tar.gz cd proj3
You can use your editor or IDE of choice. The Scala website has instructions for a range of alternatives. If you are using Eclipse, you first need to generate an Eclipse project. Add a file plugins.sbt in the project/ folder with the line: addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.1") and execute:
sbt eclipseand then import the Project into Eclipse: File > Import > Existing project into Workspace. Select the root folder proj3 and hit Finish.
A description of the different files is provided below. The files you need to complete are Parser.scala, SemanticAnalyzer.scala, Interpreter.scala, and Compiler.scala. Following the path of the first project, we are going to implement the parser step by step. Follow the comments in the code and find the TODOs. The other part are also independent and can be develop separately. In order to implement the new features more easily, we have changed some of the previous class/function definition we had. You can search for the string CHANGE into the source code to see these modifications.
This file defines multiple classes that are used to generate the code and run it on your machine. Nothing needs to be modified, but it is recommended to read it and have an idea of what is happening behind the scenes.
As we are generating assembly code which is OS dependent, we are using GCC to do the heavy lifting for us. The bootstrap file is a generic C file that is allocation a chunk of memory called heap. This memory region is sent to our program through an argument of the function entry_point. The exit code of our program is then printed on the screen. Our compiler will generate the file gen/gen.s and will be assembled and bootstrapped by gcc:
gcc bootstrap.c gen.s -o out
out will then execute our program.
The main function is defined in this file. The data flow in this file can be seen as:
Read from File / Read from Command Line -> Parser -> Semantic Analyzer -> Interpreter/Compiler
You will be implementing many different parsers in order to test them through the main function.
The AST generated will then go through the semantic analyzer. If there are no errors, then the code is going to be interpreted and then compiled. We provide you with one interpreter. You will have to write one interpreter and one compiler.
As our language becomes more complex, it will become more useful to read the code from a source file. A folder with sample files sample/ has been provided. Here some examples of how to run the code:
sbt > run sample/arithm.scala ---> interpreter provided and x86 compiler > run sample/arithm.scala st_int ---> stack interpreter (your code)
However, it is still possible to run code from the command line, as in project 1:
sbt > run "val x = 5; x" > run "val x = 5; x" st_int
This class contains the definition of our intermediate language.
As we discussed in class, we are introducing types in our language. In addition to Integer, we will also have the types Boolean and Unit. You will need to modify the Scanner class in order to tokenize the constants of type Boolean correctly. There is a single Unit constant: (). Because this construct can be used for other reasons (e.g. a function without parameters), the () will still be parsed into two delimiters: '(' and ')'. The function parseAtom will be handling the Unit literal.
The rest of the file is the definition of each parser we are building, starting from a base parser that is an implementation of the parser we implemented in project 2. What you have to do for this project is highlighted with TODOs in the code. We encourage you to read the comments and code carefully before proceeding.
While the parser is in charge of verifying that the input string follows the defined syntax, the semantic analyzer verifies that the program described is meaningful and follows the rules. The skeleton code already contains the semantic checks we implemented in project 2.
However as we have seen in class, we are upgrading our semantic analyzer into a type checker. Your job is to complete the typeInfer function using the rules we have seen in class.
As we have seen in class, now that our programing language accepts more than just mathematical expressions, we need to define the required behavior of our language. An interpreter can be used to define this meaning.
We provide you with an interpreter that is fully functional. Your job is to complete the Stack-based interpreter.
The x86_64 compiler handling all features up to loops is already implemented for you. You need to complete the compiler with the new features.
In the previous project, we considered two different ways of generating the assembly code, each of which has pros and cons: Stack-based code, which is not very efficient, but can handle arbitrarily complex expressions; and Register-based code, which is efficient, but can not handle arbitrarily complex expressions. For this assignment, we will use a register-based approach.
These files contain some unit tests for the first parsers. You will have to write your own tests for the others. There are some functions given to you in order to make the implementation easier.
The programmer can use two functions to interact with the console: getchar and putchar. However, we do not have characters, so in order to print hello world, we would have to write the code:
putchar(72); putchar(101); putchar(108); putchar(108); putchar(111); ...This is not really convenient; we would rather do:
putchar(toInt('H')); putchar(toInt('e')); putchar(toInt('l')); putchar(toInt('l'))...You are therefore required to add a new type in the language: Char. All letters, numbers, punctuation signs and spaces should be handled, e.g. you don't have to handle the escaping characters '\n', '\r'... In the program, a programmer will be able to use the single quote notation to enter a character e.g. 'H'. You will also provide two primitives operations:
mov loc, %rax movzbq %al, locNote: this is not extra credit. Hint: The parser shouldn't need to be modified; only the Scanner class. And for the primitive: look at the putchar implementation within the code.
In the Compiler.scala file, there are some places marked as Extra Credit. These are the places where we need to generate code for functions used as arguments, stored into variables, or returned from a function. While you are required to parse this kind of code correctly and be able to type check it, you are not required to generate the corresponding x86 code. Implementing such generation correctly will be counted as extra credit.
You should turn in the proj3 directory. Please run an 'sbt clean' before submitting. You can also remove the target/.
To turn in your project, make sure that you are in the directory that contains the proj3 directory. Then, type the following:
turnin -c cs352 -p proj3 proj3
To verify your turned-in work, do:
turnin -c cs352 -p proj3 -v
Your project will be tested against a set of unit tests. The weights of each task will be distributed as follow:
|BaseParser: parse types||2.5%|