Scoping nested variable declarations

Sep 25, 2011

(Pardon the title, I’m basically making post titles based on what feature I added/fixed, so there might be duplicates since I’m not actually bothering to go back and check whether there’s another post with a similar title. Only getting on an average of 10 hits a day or so, figure nobody will notice. Also, the code in the samples doesn’t really have terrible indentation, I just have to chop up the long grammar rules to make them somewhat fit in the page-width.)

Following fj-eclipse, I’d set up a grammar rule for handling nested variable declarations (A.B.C = 10 etc.) as follows:

QualifiedIdentifier: ( ( 'class' SQUOTE classRef=[ClassDecl] SQUOTE (DOT 'default')?
    ( {Selection.receiver=current} DOT (child=[DeclaredVariable|Word]) )* ) |
    ( ( (parent=[DeclaredVariable|Word]|
    parentFunctionCall=FunctionCall) (LSBRACK arrayIndex=Operand RSBRACK)? )
    ( {Selection.receiver=current} DOT
        ( (child=[DeclaredVariable|Word]|
        (LSBRACK arrayIndex=Operand RSBRACK)? ) )* ) );

As a result of which, given

struct Vector { float X, Y, Z; }
struct CrashAndBurn { Vector V, W; }

the following would work:

function Crash() {
    CrashAndBurn burn;
    burn.V = vect(0, 0, 0);

but the following wouldn’t:

burn.V.X = 10.0f;

The truth is, I’d basically stuck different pieces of code together to make the feature work, and Object.uc (being the base class of all classes in Unrealscript) doesn’t have any nested references like the above. While making coffee for an all-night coding session, I thought that I should test this feature to make sure it works, and yeah, it doesn’t.

So, I set out looking through the forums looking for assigned actions (the Selection.receiver = current part of my grammar rule), and I stumbled across this thread. Here Alexander Nittka describes exactly how assigned actions work (it’s an excellent explanation, I highly recommend you sit down and read through the whole thing if you’re implementing support for a language using Xtext). Keeping the description in mind, I headed out to clean up my own grammar and scope resolution code:


QualifiedIdentifier: ( ( 'class' SQUOTE classRef=[ClassDecl] SQUOTE (DOT 'default')?
    ( {QualifiedIdentifier.receiver=current} DOT (child=[DeclaredVariable|Word]) )* )
    | ( ( (child=[DeclaredVariable|Word] | childFunctionCall=FunctionCall)
        (LSBRACK arrayIndex=Operand RSBRACK)? )
        ( {QualifiedIdentifier.receiver=current} DOT
            ( (child=[DeclaredVariable|Word]| childFunctionCall=FunctionCall)
              (LSBRACK arrayIndex=Operand RSBRACK)? ) )* ) );


public class UnrealscriptScopeProvider extends AbstractDeclarativeScopeProvider {
    public IScope scope_QualifiedIdentifier_child(QualifiedIdentifier context, EReference ref) {
        EObject parent;
        IScope localScope = IScope.NULLSCOPE,
        argScope = IScope.NULLSCOPE,
        globalScope = IScope.NULLSCOPE;
        if (context.getReceiver() == null) {
            // This is the first/only variable in the chain, i.e. A in A.B.C
        else {
            parent = context.getReceiver().getChild();
            if (parent != null) {
                if (parent instanceof DeclaredVariable) {
                    DeclaredVariable dvar = (DeclaredVariable)parent;
                    argScope = getFieldScopeFor(dvar);
                else {
                    System.out.println("Parent = " + parent);

        return argScope;

And it now works perfectly:

Nested variables