[Journal - Implementing Nested Procedures]

Implementing Nested Procedures

Saturday, June 18, 2005

.NET Console supports nesting of user functions. Part of this feature is capturing variables declared in blocks enclosing the definition of the nested function:

foo()
{
    x = 5;
    bar()
    {
        print x;
    }

    if(true)
    {
        y = 7:
        bar();
    }
}

The "bar" function should have access to "x", but not "y". Which may seem obvious: the stack of object scopes includes the "if" block, and thus "y", but what matters here is the scope in which "bar" is declared. The following diagram contrasts the relationship of declaration scopes and the "runtime" scope stack:

foo()           MemberHeader ------------ MemberScope (Header)
{               MemberBlock --------------- BlockScope
  bar()           MemberHeader -----+    +----- BlockScope (Header)
  {               MemberBlock ----+ |    | +----- BlockScope
  }                               | +----|-|------- MemberScope (Header)
                                  +------|-|--------- BlockScope
  if(true)        ControlHeader ---------+ |
  {               ControlBlock ------------+
    bar();          Statement
  }
}

So what does the code that manages these scopes look like? The following CEvaluationContext method (in Gregor.NetConsole.Engine) tells whether the code block associated with a given scope is declared within the code block associated with another scope:

private bool IsEnclosedBy(CScope scope, CScope candidate){
    CCodeNode node = scope.CodeNode;
    while(node != null){
        if(node == candidate.CodeNode){
           return true;
        }
        // go up or left (members/controls-structs adjace, not nest, header and block)
        CCodeNode prevNode = node.PreviousNode;
        if(prevNode is CBlockHeader){
            node = prevNode;
        }else{
            node = node.ParentNode;		
        }
    }
    return false;
}

Variable capture is especially important for local callback functions, since .NET Console does not, at least not yet, support the definition of complex types, which are often needed for context data in callbacks. Of course, if you have Whidbey, you can use generic tuples.