|
|
CS 3721
Programming Languages
Spring 2014 |
Recitation 5.
Assignments
|
Week 5: Feb 11 - 13
|
Submit following directions at:
submissions
and rules at:
rules.
Deadlines are:
- 2014-02-18 23:59:59 (that's Tue, 18 Feb 2014, 11:59:59 pm)
for full credit.
- 2014-02-21 23:59:59 (that's Fri, 21 Feb 2014, 11:59:59 pm)
for 75% credit.
|
Compiler for Assignments, and for I/O:
Study the pages:
- Recursive-Descent Parsing:
- Tiny® Language:
- MIPS Assembly Language:
Overview:
For this recitation you are to do the following:
- A. Write an R-D parser for a subset of
Tiny.
- B. Add Code to make the program
into a compiler.
- C. Compile 4 Tiny programs to MIPS
and run the MIPS with SPIM.
A. Write an R-D Parser for a subset of
Tiny:
Write a program in Java or C that
will parse the following grammar, which is a subset of
the full language we want to compile:
Subset Tiny® Grammar |
M −−> S { S } '$'
S −−> A | P | G
A −−> lower-case '=' E ';'
P −−> '<' E ';' | '<' ('B' | 'N' | 'T') ';'
G −−> '>' lower-case ';'
E −−> T {('+' | '-') T}
T −−> F {('*' | '/') F}
F −−> '(' E ')' | lower-case | digit
|
This grammar adds just four statements to the grammar for
arithmetic expressions that we've been working on:
an assignment statement, two kinds of output statements, and an
input statement.
It also leaves off the hat operator ^, along with the grammar rule
for the hat. (This rule uses a non-terminal U in some places
and a non-terminal S in others. S is used above
for a "statement".) The next table gives
an list of these 4 constructs, with the grammar rules and with examples.
Features
Implemented by this Recitation |
Name of Feature |
Grammar Rule |
Tiny Examples |
Assignment Statement
(Insert value into a variable) |
A −−> lower-case '=' E ';' |
n = n + 1; a = 2*b/c-4; |
Output Expression
(Output value of an expression) |
P −−> '<' E ';' |
< n; < (a+b)/2; |
Output Blank, Newline, or Tab
(B for ' ', N for '\n', T for '\t') |
P −−> '<' ('B' | 'N' | 'T') ';' |
< B; < N; < T; |
Input Value (Insert value
from input into a variable) |
G −−> '>' lower-case ';' |
> n; > a; > p; |
You should test your parser with some of the Tiny programs below.
B. Add Code to Make the program
into a compiler:
How is Memory Mapped:
We need to be clear about the use of memory at run time.
Recall the .data part of the MIPS code we will use:
MIPS
Memory Allocation |
.data
.align 3
M: .double 0.,1.,2.,3.,4.,5.,6.
.double 7.,8.,9. # constants
.space 208 # variables a to z
.space 1000 # 125 temporaries
Blank: .asciiz " "
NewL: .asciiz "\n"
Tab: .asciiz "\t"
|
We use M[i] to stand for i*8 bytes past the start of M.
Then the locations of digits, variables and temporaries are given
in the table:
Memory Allocation Table.
The names "Blank", "NewL", and "Tab" above are my own
arbitrary choice.
Returning Addresses:
Start with your Tiny Parser from Part A above.
Each function related to expressions will
be returning an address where the value of the corresponding
part of the program can be found at run time. Thus functions
E, T, and F will each return such an address.
In my own code I return the proper index of the M
"array", and then multiply by 8 before using it, but you
could instead return the actual address.
For example, in your code, whenever you have a call to E,
this call should return the address where the value of that
expression will be at run time. It doesn't matter whether that
call to E processes a digit, an identifier, or an
arbitrarily complex expression.
Consider what the function F should return in your code.
- If F handles a digit, say stored in a variable
ch, then F should return (ch-'0')
(or this times 8).
- If F handles a lower-case letter, stored in ch,
then F should return (ch-'a'+10)
(or this times 8).
- If F handles an expression (in parens) by calling
E, the F should return whatever E returns
(will already be multiplied by 8, if that is the way you are
handling things).
How To Get Started.
I would start with the parser from Part A above.
Then initially I would make only a few changes:
- Change F so that it returns the proper integer
for a lower-case letter and the proper one for a digit.
- Change T and E so that they
pass the value coming to them from below on up by returning it.
- Add code to A so that it completely handles an assignment.
A first decides which address belongs
to the variable on the left side. Save this number inside A.
Then get the number returned from a call to E.
Multiply this by 8 to get byte offset of the expression from
the start of M. (Or you may be working with addresses that are
already multiplied by 8.)
Finally generate (print) two MIPS
instructions, the first to load the number at the locations
returned by E into a register, and second to store
this number into the location given by the identifier at
the right side of the assignment. So
start with the input: f = 7;$, which should be a legal
input to your parser. With the changes above, your program
should output the two MIPS instructions at the far left.
Then with the same program, try the second input.
Sample Input a: |
Input: f = 7; $
Should produce output:
# M[15] = M[7]
l.d $f2, 56($s1)
s.d $f2, 120($s1)
|
| |
Sample Input b: |
Input: f = 7; g = f; $
Should produce output:
# M[15] = M[7]
l.d $f2, 56($s1)
s.d $f2, 120($s1)
# M[16] = M[15]
l.d $f2, 120($s1)
s.d $f2, 128($s1)
|
|
- Start on the case of handling an
arithmetic operator in an expression.
This is by far the most difficult part, yet in the end it
doesn't take much coding.
Do just plus(+) first,
and focus on the following example.
(Once you have + done, the other three operators are essentially
the same.)
Sample Input c: |
Input: g = f + 8 ; $
Should produce output:
# M[36] = M[15] + M[8]
l.d $f2, 120($s1)
l.d $f4, 64($s1)
add.d $f6, $f2, $f4
s.d $f6, 288($s1)
# M[16] = M[36]
l.d $f2, 288($s1)
s.d $f2, 128($s1)
|
You must add to the function
E so that when there is an addition, it will produce two loads,
add, and a store instruction.
If there is a +, then inside E, there will be two calls to T,
each returning an address taking care of whatever in the tree
is below that T. These two addresses are used for the two loads.
Then you need to generate a new temporary memory location,
and that will be the address in the store instruction.
Finally, if there are only two operands in that call to E,
the address of the temporary is what E will return.
More about handling an addition:
An addition was discussed and illustrated in Step 4 directly above.
Let's go into it in more detail. Simplify the parser code
so that it will just handle a single addition, without worrying about
several of them. This is the function on the left below.
The code beside on the right shows E returning an int
to hold an address, the offset from $s1.
Function E for parser |
void E(void) {
T();
if (next == '+') {
scan();
T();
}
}
|
| |
Function E for compiler |
int E(void) {
int res, arg1, arg2;
arg1 = T();
if (next == '+') {
scan();
arg2 = T();
}
res = // temp address for sum
return res;
}
|
|
After E gets the two addresses from the two calls to T,
it can output an add instruction. First the two operands
from the two T's must be loaded. Then the add using registers,
and finally a store instruction to stick the result into
a new temporary memory location. The diagram below shows this.
(Diagram in PDF.)
Very important:
In problem 3 of Recitation 3, you translated something like
f = g + 5; into 4 MIPS instructions. This was possible
because you were looking at the entire statement at once.
Your compiler will need 6 statements (as shown above) to translate this.
The function E only handles expressions and addition/subtraction.
It is completely separate from the function A that handles assignments.
E doesn't know where its results are headed, and similarly,
A doesn't know where the expression to the right of the assignment
operator came from, or anything about this expression. As far
as A knows, it could be a single variable, a single constant,
or a very complex expression.
C. Compile 4 Tiny programs to MIPS
and run the MIPS with SPIM.
Sample Programs:
(In the end, the three inputs above, a, b, and c, are
for your own debugging.)
Here are sample programs that you should compile from Tiny to MIPS
assembly code. You then should execute the MIPS code.
For these you need to implement the two types of output
In order to successfully execute
these using spim, you must
add the stuff at the beginning and end of the complete sample
programs. Of course all four programs work with doubles,
but the doubles in Sample Input 1 are all exact integers.
Sample Input 0: |
a = 8 * 2;
b = 7 * a;
c = b + 1;
d = a / c;
e = 3 + d;
< e;
< N;
$
|
| |
Sample Input 1: |
f = 3*7;
g = f + (9 + 4);
h = f + g;
< h; < B;
a = f*f + g*g;
b = g*h + f*g;
c = g*g + h*h;
< c; < B;
f = a*a + b*b;
g = b*c + a*b;
h = c*c + b*b;
< h; < N;
a = f*f + g*g;
b = g*h + f*g;
c = g*g + h*h;
< c; < N; $
|
Output: |
55 4181 24157817
806515533049393
Fibonacci Nums:
F10 F19 F37 F73
|
| |
Sample Input 2: |
< 3+1/(7+1/(5*3+1/(1+
1/((4*(9*8+1))+1/(1+
1/(1+1/(1+1/(2+1/(1+
1/(3+1/(1+1/(2*7+1/(2+
1) ))))))))))));$
|
| |
Sample Input 3: |
p = 3 + (1/7);
> r;
v = (4/3)*p*r*r*r;
< p; < N;
< r; < N;
< v; < N;
$
|
Run of Program: |
% spim r3.s
3.5
3.14285714285714279
3.5
179.666666666666657
|
|
What you should submit:
- Give the complete Java or C source code for your compiler program.
- For each of the four sample program in turn, include:
- The original Tiny program.
- The complete MIPS code that results from compiling that Tiny program,
for Sample Inputs 0 and 3 only. (My MIPS code for
Sample Inputs 1 and 2 are 240 and 180 lines long respectively.)
- The output resulting from running that particular MIPS program
using spim.
Revision date: 2014-01-29.
(Use ISO 8601,
an International Standard.)
|