The Postscript is a dynamically scoped language. In this assignment, you will be modifying your SPS interpreter (Assignment 4 - Simple Post Script Interpreter) to handle a slightly different language which supports static scoping. We will call this language Scoped Simple PostScript - SSPS. SSPS has no dict, begin or end operations. Instead, each time a postscript function is called a new dictionary is automatically pushed on the dictionary stack. And when the function execution is complete, this dictionary will be popped out of the stack. The dictionary must be able to hold an arbitrary number of names.
Turning in your assignment
All code should be developed in the file called HW5.py.
At the top of the file in a comment, please include your name and the names of the students with whom you discussed any of the problems in this homework. This is an individual assignment and the final writing in the submitted file should be *solely yours*. You may NOT copy another student’s code or work together on writing code. You may not copy code from the web, or anything else that lets you avoid solving the problems for yourself.
You may turn in your assignment up to 3 times. Only the last one submitted will be graded.
Project Description
You will be modifying your SPS interpreter (Assignment 4 - Simple Post Script Interpreter) to handle a slightly different language which we will call Scoped Simple PostScript - SSPS. SSPS has no dict, begin or end operations. Instead, each time a postscript function is called a new dictionary is automatically pushed on the dictionary stack.
Compared to your SPS interpreter function, the SPSS interpreter will take an addition argument whose value will be either the string “static” or the string “dynamic”, to indicate whether it should behave using static scope rules or dynamic scope rules. For example:
# This is the recursive function to interpret a given code array.
# code is a code array; scope is a string (either “static” or “dynamic”) def interpretSPS(code,scope):
pass
# add an argument to interpret to specify the scoping rule that will be
# applied
# s is a string; scope is a string (either “static” or “dynamic”) def interpreter(s,scope):
s scope
interpretSPS(parse(tokenize( )), )
Since if, ifelse,repeat, and forall operators also call interpretSPS (to execute their code arrays), you should also add the “scope” argument to these operator implementations. To interpret code using static scoping rules call interpreter(s,'static') and to interpret code “s” using dynamic scoping rules call interpreter(s,'dynamic').
• Each time a postscript function is about to be called push a new dictionary and when a postscript function is about to return pop the dictionary.
• To implement static scope rules you need a static chain which is the set of dictionaries visited by following the static links (also called the access links) we discussed in class. You already have a
stack of dictionaries, you don’t need to have explicit dynamic links. The dynamic chain is implicit in the order of the dictionaries in the list. To search for a declaration you search from the top of the stack.
• How can you implement the static chain? I suggest making the stack be a stack of tuples (instead of just a stack of dictionaries) where each tuple contains a dictionary and an integer index . The integer index represents the static link that tells you the position in the list of the (dictionary, static-link-index) tuple for the parent scope.
• Where do static-link values come from? As we saw in class, at the point when a function is called, the static link in the new stack entry needs to be set to point to the stack entry where the function’s definition was found. (Note that with the stack being a list, this “pointer” is just an index in the list.) So when calling a postscript function you create a new dictionary automatically and what you push on the dictionary stack is a pair : (dictionary, index-of-definition’s stack entry).
§ Hint: In an effective implementation this should all take only a handful of lines of new code but it is tricky to get all the pieces right. My advice is think more, write less for this part of the project.
§ As discussed in class, variable lookups using static scope rules proceed by looking in the current dictionary at the top of the dictionary stack and then following the static-link fields to other dictionaries (instead of just looking at the dictionaries in order).
§ Note: In Assignment 3, you already implemented the lookup function using static scoping rule, where you search the dictionaries following the index links in the tuples (i.e., following the static links).
• Using dynamic scope rules, the SPSS interpreter will behave very much like SPS except that it won't support dict, begin or end operations in programs (indeed, there will be no implementation of these operations in SSPS).
§ I suggest you to use the same dictstack for both static and dynamic scope implementations.
§ When the scoping rule is dynamic, the lookup should just look at the dictionaries on the dictstack starting from top (ignoring the static links).
Additional Notes:
You should not have two separate interpret functions for the static and dynamic scoping implementations. Please include the scoping rule as an argument as suggested above and customize the interpretation if static scoping is specified.
For each test input we will test your code for static and dynamic scoping as follows (assume “input1” is the SPS code that we will interpret):
interpreter(input1, "static") interpreter(input1, "dynamic")
- As explained before, your interpreter should store 2-tuples in dictstack where first value in the tuple is the dictionary and second value is the static link index.
- A new tuple will be pushed onto the dictstack whenever a function call is made.
- “Static link index” is the index of the dictionary (in the dictstack) where the function is defined. I will discuss the algorithm for finding this in class.
- Change your lookup function for static scoping. If the scoping rule is dynamic, perform lookup by searching the activation record (tuples) top to down. If it is static, use static links for the search.
- When a function call returns, remember to pop the tuple for that function call from the dictstack.
- Change your stack operator implementation as explained below.
Output of the Interpreter
Whenever the stack operation is executed, the contents of the operand and dictionary stacks are printed. (Remember that stack only printed the contents of the operand stack in Assignment-4) § Print a line containing "==============” to separate the stack from what has come before.
§ Print the operand stack one value per line; print the top-of-stack element first.
§ Print a line containing “==============” to separate the stack from the dictionary stack. § Print the contents of the dictionary stack, beginning with the top-of-stack dictionary one name and value per line with a line containing {---- m---- n ----} before each dictionary. m is the index that will identify the dictionary printed (dictionary index) and n is the index that represents the static link for the dictionary printed (in the case that static scoping is being used). Please see below for an example.
§ Print a line containing “==============” to separate the dictionary stack from any subsequent output.
Remember please the difference between a dictionary and a dictionary entry.
What if my SPS interpreter didn’t work correctly?
You will need to fix it so it works. You can visit with the TA or me for help.
How can I tell if my static scoping code is working correctly?
You will have to create some test cases for which you can predict the correct answers. Below are couple examples for initial tests. Please create additional tests (at least 3) to test your interpreter. When we grade your assignment, we will compare the outputs of your interpreter prints.
/x 4 def
/g { x stack } def /f { /x 7 def g } def f
The above SPS code will leave 7 on the stack using dynamic scoping and 4 using static scoping. The output from the stack operator in function g would look like this when using static scoping:
Static
==============
4
============== ----2----0----
----1----0----
/x 7
----0----0----
/x 4
/g {'codearray': ['x', 'stack']}
/f {'codearray': ['/x', 7, 'def', 'g']} ==============
And using dynamic scoping, the output from the stack operator will look like the following:
Dynamic
==============
7
============== ----2----0----
----1----0----
/x 7
----0----0----
/x 4
/g {'codearray': ['x', 'stack']}
/f {'codearray': ['/x', 7, 'def', 'g']} ==============
Additional Test Cases:
2)
testinput2 = """
/x 4 def
[1 1 1] 1 [2 3] putinterval /arr exch def
/g { x stack } def
/f { 0 arr {7 mul add} forall /x exch def g } def f """
Expected Output
Using static scoping
Static
==============
4
============== ----2----0----
----1----0----
/x 42
----0----0---- /x 4
/arr [1, 2, 3]
/g {'codearray': ['x', 'stack']}
/f {'codearray': [0, 'arr', {'codearray': [7, 'mul', 'add']}, 'forall',
'/x', 'exch', 'def', 'g']}
==============
Using dynamic scoping
Dynamic
==============
42
============== ----2----0----
----1----0----
/x 42
----0----0---- /x 4
/arr [1, 2, 3]
/g {'codearray': ['x', 'stack']}
/f {'codearray': [0, 'arr', {'codearray': [7, 'mul', 'add']}, 'forall',
'/x', 'exch', 'def', 'g']} ==============
"""
/m 50 def
/n 100 def
/egg1 {/m 25 def n} def
/chic
{ /n 1 def /egg2 { n stack} def m n egg1 egg2 } def n chic
"""
Expected Output
Using static scoping
Static
==============
1
100 1
50
100
============== ----2----1----
----1----0----
/n 1
/egg2 {'codearray': ['n', 'stack']}
----0----0----
/m 50
/n 100
/egg1 {'codearray': ['/m', 25, 'def', 'n']}
/chic {'codearray': ['/n', 1, 'def', '/egg2', {'codearray': ['n',
'stack']}, 'def', 'm', 'n', 'egg1', 'egg2']}
==============
Using dynamic scoping
Dynamic
==============
1
1
1
50
100
============== ----2----1----
----1----0----
/n 1
/egg2 {'codearray': ['n', 'stack']}
----0----0----
/m 50
/n 100
/egg1 {'codearray': ['/m', 25, 'def', 'n']}
/chic {'codearray': ['/n', 1, 'def', '/egg2', {'codearray': ['n',
'stack']}, 'def', 'm', 'n', 'egg1', 'egg2']}
==============
4)
testinput4 = """
/x 10 def
/A { x } def
/C { /x 40 def A stack } def
/B { /x 30 def /A { x } def C } def
B """
Expected Output
Using static scoping
Static
==============
10
============== ----2----0----
/x 40
----1----0----
/x 30
/A {'codearray': ['x']}
----0----0----
/x 10
/A {'codearray': ['x']}
/C {'codearray': ['/x', 40, 'def', 'A', 'stack']}
/B {'codearray': ['/x', 30, 'def', '/A', {'codearray': ['x']}, 'def', 'C']} ==============
Using dynamic scoping
Dynamic
==============
40
============== ----2----0----
/x 40
----1----0----
/x 30
/A {'codearray': ['x']}
----0----0----
/x 10
/A {'codearray': ['x']}
/C {'codearray': ['/x', 40, 'def', 'A', 'stack']}
/B {'codearray': ['/x', 30, 'def', '/A', {'codearray': ['x']}, 'def', 'C']}
==============
/x 10 def
/n 5 def
/A { 0 n {x add} repeat} def
/C { /n 3 def /x 40 def A stack } def
/B { /x 30 def /A { x } def C } def
B """
Expected Output
Using static scoping
Static
==============
50
============== ----2----0----
/n 3
/x 40
----1----0----
/x 30
/A {'codearray': ['x']}
----0----0----
/x 10
/n 5
/A {'codearray': [0, 'n', {'codearray': ['x', 'add']}, 'repeat']}
/C {'codearray': ['/n', 3, 'def', '/x', 40, 'def', 'A', 'stack']}
/B {'codearray': ['/x', 30, 'def', '/A', {'codearray': ['x']}, 'def', 'C']} ============== Using dynamic scoping
Dynamic
==============
40
============== ----2----0----
/n 3
/x 40
----1----0----
/x 30
/A {'codearray': ['x']}
----0----0----
/x 10
/n 5
/A {'codearray': [0, 'n', {'codearray': ['x', 'add']}, 'repeat']}
/C {'codearray': ['/n', 3, 'def', '/x', 40, 'def', 'A', 'stack']}
/B {'codearray': ['/x', 30, 'def', '/A', {'codearray': ['x']}, 'def', 'C']} ==============
/out true def
/xand { true eq {pop false} {true eq { false } { true } ifelse} ifelse dup /x exch def stack} def
/myput { out dup /x exch def xand } def /f { /out false def myput } def false f
"""
Expected Output
Using static scoping
Static
==============
False
============== ----3----0----
/x False
----2----0----
/x True
----1----0----
/out False
----0----0----
/out True
/xand {'codearray': [True, 'eq', {'codearray': ['pop', False]},
{'codearray': [True, 'eq', {'codearray': [False]}, {'codearray': [True]},
'ifelse']}, 'ifelse', 'dup', '/x', 'exch', 'def', 'stack']}
/myput {'codearray': ['out', 'dup', '/x', 'exch', 'def', 'xand']}
/f {'codearray': ['/out', False, 'def', 'myput']} ==============
Using dynamic scoping
Dynamic
==============
True
============== ----3----0----
/x True
----2----0----
/x False
----1----0----
/out False
----0----0----
/out True
/xand {'codearray': [True, 'eq', {'codearray': ['pop', False]},
{'codearray': [True, 'eq', {'codearray': [False]}, {'codearray': [True]},
'ifelse']}, 'ifelse', 'dup', '/x', 'exch', 'def', 'stack']}
/myput {'codearray': ['out', 'dup', '/x', 'exch', 'def', 'xand']}
/f {'codearray': ['/out', False, 'def', 'myput']}
==============
/x [1 2 3 4] def
/A { x length } def
/C { /x [10 20 30 40 50 60] def A stack } def
/B { /x [6 7 8 9] def /A { x 0 get} def C } def
B
"""
Expected Output
Using static scoping
Static
==============
4
============== ----2----0----
/x [10, 20, 30, 40, 50, 60]
----1----0----
/x [6, 7, 8, 9]
/A {'codearray': ['x', 0, 'get']}
----0----0----
/x [1, 2, 3, 4]
/A {'codearray': ['x', 'length']}
/C {'codearray': ['/x', [10, 20, 30, 40, 50, 60], 'def', 'A', 'stack']}
/B {'codearray': ['/x', [6, 7, 8, 9], 'def', '/A', {'codearray': ['x', 0,
'get']}, 'def', 'C']} ==============
Using dynamic scoping
Dynamic
==============
10
============== ----2----0----
/x [10, 20, 30, 40, 50, 60]
----1----0----
/x [6, 7, 8, 9]
/A {'codearray': ['x', 0, 'get']}
----0----0----
/x [1, 2, 3, 4]
/A {'codearray': ['x', 'length']}
/C {'codearray': ['/x', [10, 20, 30, 40, 50, 60], 'def', 'A', 'stack']}
/B {'codearray': ['/x', [6, 7, 8, 9], 'def', '/A', {'codearray': ['x', 0,
'get']}, 'def', 'C']} ==============
"""
[0 1 2 3 4 5 6 7 8 9 10] 3 4 getinterval /x exch def
/a 10 def
/A { x length } def
/C { /x [a 2 mul a 3 mul dup a 4 mul] def A a x stack } def
/B { /x [6 7 8 9] def /A { x 0 get} def /a 5 def C } def
B
"""
Expected Output
Using static scoping
Static
==============
[20, 30, 30, 40]
10
4
============== ----2----0----
/x [20, 30, 30, 40] ----1----0----
/x [6, 7, 8, 9]
/A {'codearray': ['x', 0, 'get']}
/a 5
----0----0----
/x [3, 4, 5, 6]
/a 10
/A {'codearray': ['x', 'length']}
/C {'codearray': ['/x', ['a', 2, 'mul', 'a', 3, 'mul', 'dup', 'a', 4,
'mul'], 'def', 'A', 'a', 'x', 'stack']}
/B {'codearray': ['/x', [6, 7, 8, 9], 'def', '/A', {'codearray': ['x', 0,
'get']}, 'def', '/a', 5, 'def', 'C']} ==============
Using dynamic scoping
Dynamic
==============
[10, 15, 15, 20]
5
10
============== ----2----0----
/x [10, 15, 15, 20] ----1----0----
/x [6, 7, 8, 9]
/A {'codearray': ['x', 0, 'get']}
/a 5
----0----0----
/x [3, 4, 5, 6]
/a 10
/A {'codearray': ['x', 'length']}
/C {'codearray': ['/x', ['a', 2, 'mul', 'a', 3, 'mul', 'dup', 'a', 4,
'mul'], 'def', 'A', 'a', 'x', 'stack']}
/B {'codearray': ['/x', [6, 7, 8, 9], 'def', '/A', {'codearray': ['x', 0,
'get']}, 'def', '/a', 5, 'def', 'C']}
==============