grammar t022scopes;

options {
    language=JavaScript;
}

/* global scopes */

scope aScope {
names
}

a
scope aScope;
    :   {$aScope::names = [];} ID*
    ;


/* rule scopes, from the book, final beta, p.147 */

b[v]
scope {x}
    : {$b::x = v;} b2
    ;

b2
    : b3
    ;

b3 
    : {$b::x}?=> ID // only visible, if b was called with True
    | NUM
    ;


/* rule scopes, from the book, final beta, p.148 */

c returns [res]
scope {
    symbols
}
@init {
    $c::symbols = {};
}
    : '{' c1* c2+ '}'
        { $res = $c::symbols; }
    ;

c1
    : 'int' ID {$c::symbols[$ID.text] = true;} ';'
    ;

c2
    : ID '=' NUM ';'
        {
            if (! $c::symbols[$ID.text]) {
                throw new Error($ID.text);
            }
        }
    ;

/* recursive rule scopes, from the book, final beta, p.150 */

d returns [res]
scope {
    symbols
}
@init {
    $d::symbols = {};
}
    : '{' d1* d2* '}'
        { $res = $d::symbols; }
    ;

d1
    : 'int' ID {$d::symbols[$ID.text] = true;} ';'
    ;

d2
    : ID '=' NUM ';'
        {
            var i, isDefined;
            for (i=$d.length-1, isDefined=false; i>=0; i--) {
                if ($d[i]::symbols[$ID.text]) {
                    isDefined = true;
                    break;
                }
            }
            if (!isDefined) {
                throw new Error("undefined variable "+$ID.text);
            }
        }
    | d
    ;

/* recursive rule scopes, access bottom-most scope */

e returns [res]
scope {
    a
}
@after {
    $res = $e::a;
}
    : NUM { $e[0]::a = parseInt($NUM.text, 10); }
    | '{' e '}'
    ;


/* recursive rule scopes, access with negative index */

f returns [res]
scope {
    a
}
@after {
    $res = $f::a;
}
    : NUM { var len = $f.length-2; $f[len>=0 ? len : 0]::a = parseInt($NUM.text, 10); }
    | '{' f '}'
    ;


/* tokens */
ID  :   ('a'..'z')+
    ;

NUM :   ('0'..'9')+
    ;

WS  :   (' '|'\n'|'\r')+ {$channel=HIDDEN;}
    ;