In the HW2 version of the SILLY interpreter, all the variables in a SILLY program were treated as being in the same, global memory space. When a new variable was declared, an entry was stored for that variable and any subsequent reference to that variable would access that same entry. While this model works fine for simple assignments, consider what happens when a variable declaration occurs inside a loop:
int num = 1 while (num < 1000) do { int temp = (2*num) num = (temp+1) }
During the first pass through the loop, the variable temp
is declared and initialized. However, the second pass produces a runtime error since the variable temp
already exists when the variable declaration is executed again. In most languages, control structures such as while
and if
define new scopes. Variables that are declared within the structure are considered local to that scope. The lifetime of such a variable begins when its declaration is reached and ends when the enclosing scope ends. Using scopes, the above example would execute without error since each pass through the loop would define a new scope and so no conflicts would occur.
For this assignment, you are to extend the SILLY interpreter so that each compound statement defines a new scope. Any variable that is declared inside a compound statement belongs to the scope defined by that statement. That means it exists only while executing that scope (and any enclosed scopes) and it disappears after the scope is finished. Note that a variable declaration can override an existing variable with the same name, as long as that variable was declared outside of the current scope. For example, consider the following code segment:
int x = 100 int y = 200 if (x > 0) then { str x = "ok" output {x, y} } noelse output {x, y}
The first two assignments declare and assign values to variables x
and y
in the global scope. The if
statement defines a nested scope that contains a new variable x
, which effectively overrides the global x
while within this scope. When evaluating expressions in SILLY, static scoping is used. Thus, if a variable is declared within the current scope, the corresponding value should be accessed. If it is not declared in the current scope, the enclosing scope should be checked next, then the scope that encloses that scope, etc. In this example, the first output statement would display ok 200
, whereas the second would display 100 200
.
To implement nested scopes, it is strongly recommended that you define a Scope
class that manages the variable/binding map associated with a single scope. Then, you should modify the MemorySpace
class so that the stack segment is implemented as a stack of Scope
s. New methods should be added to the MemorySpace
class for beginning a new scope (i.e., pushing it on the stack) and ending the current scope (i.e., popping it off the stack). When a compound statement is executed, a new scope should begin so that local variables are stored within that scope. Once the compound statement is done executing, that scope should end (removing any local variables).
The methods for declaring, storing, and looking up variables in the memory space will also need to be modified so that they access and modify the appropriate scope in the runtime stack.
SAMPLE CODE (output in red) >>> int num = 1 >>> while (num < 1000) do { int temp = (2*num) num = (temp+1) } >>> output num 1023 >>> int x = 100 >>> int y = 200 >>> if (x > 0) then { str x = "ok" output {x, y} } noelse ok 200 >>> output {x, y} 100 200 >>> int reps = 2 >>> while (reps > 0) do { str temp = "inside" output {"temp", "=", temp} reps = (reps-1) } temp = inside temp = inside >>> str temp = "outside" >>> output {"temp", "=", temp} temp = outside >>> str a = "global" >>> reps = 4 >>> while (reps > 0) do { output a str b = "local" output b reps = (reps-1) } global local global local global local global local >>> output a global >>> str testA = "outer" >>> if true then { str testB = "middle" if true then { str testC = "inner" output {testA, testB, testC} } noelse output {testA, testB} } noelse outer middle inner outer middle >>> output testA outer >>> int testB = 1010 >>> output testB 1010 >>> int i = 1 >>> while (i < 5) do { int sum = 0 int j = 1 while (j <= i) do { sum = (sum + j) j = (j + 1) } output {i, sum} i = (i + 1) } 1 1 2 3 3 6 4 10 >>> output i 5 >>> i = (0-i) >>> if (i > 0) then { int double = (2 * i) output double } else { int double = ((0-i) * 2) output double } 10 >>> str double = "new" >>> output double new