The goal of this project is to build an interpreter for the Core language discussed in class. Your submission should compile and run in the standard environment on stdlinux. If you work in some other environment, it is your responsibility to port your code to stdlinux and
make sure it works there.
How you interpreter should represent variables/memory, and the semantics of the Input
and Output statements are given in the sections below. Other semantic details of this language should be for the most part obvious; if any aspects of the language are not, please bring it up on Piazza for discussion. Building from the project 2 parser, you need to write an executor for the language described. Every valid input program for this language should be executed correctly, and every invalid input program for this language should be rejected with an error message.
The interpreter should have three main components: a lexical analyzer (a.k.a scanner from project 1), a syntax analyzer (a.k.a. parser from project 2), and an executor. The executor should be written using the recursive descent approach discussed in class, similar to how the parser/printer in project 2 worked.
Your project should have a main procedure in a file called main, which does the following in order:
1. Initializes the scanner.
2. Parses the input program. 3. Execute the input program.
You can add extra steps here if you like, for example you may choose to add some extra checks or modify the parse tree after parsing and before executing. You do not need to print the program or run your semantic checks from project 2.
The Executor
The goal of this project is to use recursive descent to walk over the parse tree built by your parser, and “execute” the Core program by performing the actions described. Essentially this means that for each “parse” function, you will need to make a corresponding “execute” function, which will execute its children and perform any action needed on the result of that execution.
For example, for your Procedure class you will want execute function that is something like this:
execute () {
// Do something to initialize memory
// Execute the declaration sequence , // i . e . create space in memory for the
if (ds != null) { ds . execute ( ) ;
// Execute the statement sequence
ss . execute ();
Many of the execute functions will be this simple. Others will require something more complicated and you may decide to pass in values or have values returned. For example, for Expr, Term, Factor, and similar things it probably makes sense to have a return value so the execute function can look something like this:
int execute () {
int value = term.execute(); if (option==1) {
value = value + expr . execute (); } else if (option==2) {
value = value = expr . execute (); }
return value ; }
Memory and Variables
Your interpreter needs to simulate memory for our variables. Conceptually, the Core lan- guage has 3 “regions” of memory: Global, Local, and Heap. The Java heap can serve as the Core heap, but you will need to explicitly represent the Global and Local regions. Your Global space can just be represented with a map, but the Local space representation will need something more to handle nested local scopes.
I suggest putting these data structures in their own file, along with any helper functions you create to interact with them.
With memory structured in this way, we have the following semantics for how variables are used:
1. An integer variable uses the value model of variables, and has similar semantics to the “int” type in C.
(a) When an integer variable is declared, it has initial value 0. 2
2. An array variable uses the reference model of variables, and has similar semantics as an “array of ints” in Java.
(a) When a array variable is declared, it is initially a null reference.
(b) The “id[
(c) The “id = new integer[
(d) When an array variable is used in a factor without “[
(e) When an array variable appears on the left hand side of an “id :=
(f) The “id := array id” type assignment should result in both ids having the same reference value as the id on the right hand side (i.e. both variables “point” to the same array).
Input To Your Interpreter, The In Function in Core
The input to the interpreter will come from two ASCII text files. The names of these files will be given as command line arguments to the interpreter.
The first file will be a .code file that contains the program to be executed.
The second file will be a .data file that contains integer constants separated by spaces. Creating another instance of your scanner should make getting these constants easy.
During execution, each Core in function will read the next data value from the .data file each time it is executed, to simulate user input.
Output From Your Interpreter, The Out Statement in Core
All output should go to stdout. This includes error messages – do not print to stderr.
The scanner and parser should only produce output in the case of an error.
For the executor, each Core out statement should print on a new line the integer value
of the expression, without any spaces/tabs before or after it. Other than that, the executor should only have output if there is an error. The output for error cases is described below.
Invalid Input
We will not be rechecking the error handling of your scanner and parser, you can focus on just catching runtime errors in your executor.
There are just a few kinds of runtime errors possible in the current version of Core:
1. An error is possible with the input function. If an input function is executing but all values in the .data file have already been used then your executor should print a meaningful error message and halt execution.
2. An attempted division by 0 should result in an error.
3. Errors around the use of arrays:
(a) Accessing any index of a null array should result in an error. (b) Accessing outside of the valid indexes should result in an error.
Testing Your Project
I will provide some test cases. The test cases I will provide are rather weak. You should do additional testing testing with your own cases. For each test case I provide there are three files: a .code file, a .data file, and a .expected file.
I will provide a “tester.sh” script that works similar to how the script from project 2 worked, and it will use the diff command to compare your output to the expected output. Your output should exactly match what is in the .expected files.
Suggestions/Notes
There are many ways to approach this project. Here are some suggestions:
Just like I suggested for the parser, take the executor in stages. Implement just enough to execute a very simple program, then test, then implement more, ect.
Before starting, spend some time thinking about how to to keep track of the value of each variable (symbol table)? This will be the difficult part of this project.
While constants in Core have a limited, positive range, variables in Core can store positive and negative integer values. You do not need to worry about the range or overflow here, just represent them using the built in int representation for Java.
Post questions on piazza, and read the questions other students post. You may find details you missed on your own. You are encouraged to share test cases with the class on piazza.
Remember that the grammar enforces right-associativity. In Core we have 1−2−3 = 2, not −4.
程序代写 CS代考 加微信: cstutorcs
Please note this is a language like C or Java where whitespaces have no meaning, and whitespace can be inserted between keywords, identifiers, constants, and specials to accommodate programmer style. This grammar does not include formal rules about whitespace because that would add immense clutter.
Recall epsilon is the empty string.
| if
Computer Science Tutoring
CS Help, Email: tutorcs@163.com