CSC 533: Programming Languages
Spring 2024

HW3: Implementing Subroutines


The SILLY interpreter you wrote for HW2 extends the original version to allow for if-else statements, multi-value print statements, and for loops. For this third SILLY assignment, you are to extend the language further by implementing simple subroutines (without return values).

In order to implement simple subroutines, you will need to define two new classes, a subDecl class for declaring a subroutine and a subCall class for calling a subroutine. The grammar rules for these new statements are:

<subdecl> --> 'sub' <id> '(' [ <id> { ',' <id> } ] ')' <compound> <subcall> --> 'call' <id> '(' [ <expr> { ',' <expr> } ] ')'

A subroutine declaration specifies the name of the subroutine, an arbitrary number of parameters in parentheses (separated by commas), and a compound statement specifying the body of the subroutine. Executing a subroutine declaration when there already exists a variable or subroutine with that same name should result in a run-time error. Note: when a subroutine is declared, it is assumed to be in the global scope, regardless of where that declaration occurs.

A subroutine call specifies the subroutine name and the sequence of expressions (in parentheses and separated by commas) that correspond to the parameters. Executing a subroutine call should result in a run-time error if there is no declared subroutine with that name or if the number of expressions does not match the number of parameters. Note: when a subroutine is called, it defines a new, independent scope. Parameters are treated as local variables in that scope, initialized based on the expressions provided in the subroutine call. Because a subroutine's scope is independent, statements within a subroutine cannot access variables outside that subroutine.

SAMPLE CODE (output in red)
>>> sub foo( ) {
        print("foo")
    }
>>> call foo( )
foo
>>> sub add(val1, val2) {
        sum = (val1 + val2)
        print(val1, "+", val2, "=", sum)
    }
>>> call add(2, 4)
2 + 4 = 6 
>>> call add("foo", "bar")
foo + bar = foobar   
>>> sub sumTo(num) {
        sum = 0
        while (num > 0) {
            sum = (sum + num)
            num = (num ~ 1)
        }
        print(sum)
    }
>>> call sumTo(10)
55 
>>> call sumTo((10*10))
5050  
>>> sub stamp(word, times) {
        final = "" 
        while (times > 0) {
            final = (final + word)
            times = (times~1)
        }
        print(final)
    }
>>> call stamp("foo", 3)
foofoofoo 
>>> call stamp(("X"+"Y"), (4*2))
XYXYXYXYXYXYXYXY 
>>> sub hailstone(n) {
        if (n == 1) {
            print("1...STUCK")
        } else {
            print(n)
            if ((n % 2) == 0) {
                n = (n / 2)
            } else {
                n = ((n*3) + 1)
            }
            call hailstone(n) 
        }
    }
>>> call hailstone(5)
5
16
8
4
2
1...STUCK 
>>> call hailstone(52)
52 
26 
13 
40 
20 
10 
5 
16 
8 
4 
2 
1...STUCK

It is important to recognize that executing a subroutine declaration does NOT result in its code being executed. Declaring a subroutine simply stores the parameters and code associated with that subroutine in memory so that it can be called later. You will need to add an additional field to the MemorySpace class corresponding to the code segment. When a subroutine declaration is executed, the parameters and code for that subroutine should be stored in the code segment. Subsequently, when a subroutine call is executed, the corresponding parameters and statements must be accessed from the code segment so that they can be executed.