Static and Dynamic Scoping

Scope

Static vs. Dynamic Scoping

Implementation of Dynamic Scoping

Implementation of Static Scoping

Implementing Recursive Functions

Lab

???

Step 1: Bash

Step 1: Bash (Continued)

  1. Make a file test1.bash with the contents
    x="2"

    function f {
    echo $x
    }

    function main {
    local x="3" ;
    f ;
    }

    main
  2. Start the bash shell (i.e. Cygwin). Run the program: source test1.bash. What does it print?
  3. What does that tell you about variable scoping in the bash language?

Step 2: More Bash (If you have time)

  1. Make a file test2.bash with the contents.
    x="2"

    function f {
    echo $x
    }

    function g {
    local x="3" ;
    f ;
    }

    function main {
    local x="4" ;
    f ;
    g ;
    f ;
    }

    f ;
    main

    What do you expect this program to print?

  2. Start the bash shell (i.e. Cygwin). Run the program: source test2.bash. What does it print?
  3. Explain the behavior by drawing diagrams of the environment.

Step 3: Understanding Closures in SL1

  1. Consider the SL1 program
    val a = 3;
    val f = { x => x * a };
    val g = { a => a * f(a)};
    g(1)

    What result output do you expect?

  2. Start the SL1 interpreter and run the program. What output did you get?
  3. Place a breakpoint in the line
    case Valdef(name, Function(params, body)) => { (name, Closure(params, body, symbols)) :: symbols }

    Run the debugger with the same program as input. When the breakpoint is hit, inspect symbols. What do you get for symbols when the definitions of f and g are evaluated?

  4. Change the code for
    case Funcall(fun, args) => eval(fun, symbols) match

    to

    case Funcall(fun, args) => val funval = eval(fun, symbols); funval match

    Debug the program again, this time with a breakpoint in the line

    evalBlock(body, params.zip(args.map(eval(_, symbols))) ::: syms)

    The breakpoint will be triggered when g and f are executed/ What are the values of symbols and the syms in the closure object when g is executed?

  5. With what environment is the body of f executed?

Step 4 (If you have more time)

I had to work a bit to make SL1 use static scoping. Undo this to make an interpreter that uses dynamic scoping.

  1. Remove the case class Closure
  2. Remove the line
    case Valdef(name, Function(params, body)) => { (name, Closure(params, body, symbols)) :: symbols }

    so that the line below will simply add the Function object to the table

  3. Remove the line
     case Function(params, body) => Closure(params, body, symbols)

    so that the line below will simply cause a Function to evaluate to itself

  4. Remove the Defdef case of evalDef for now. (It mutates the env field of the closure. This would need to be solved by mutating the symbol table entry, as in homework 3, but in this lab step, we just won't use def.)
  5. Change
    case Closure(params, body, syms) =>
      evalBlock(body, params.zip(args.map(eval(_, symbols))) ::: syms)    

    to

    case Function(params, body) =>
      evalBlock(body, params.zip(args.map(eval(_, symbols))) ::: symbols)    
  6. Translate test1.bash into SL1dyn. What is your test case? What is the result?