CSC 533: Programming Languages
Spring 2023

HW3: Implementing Scopes


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 Scopes. 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