[antlr-interest] Small example of local variable semantic analysis for an IDE

Sam Harwell sharwell at pixelminegames.com
Thu Mar 19 12:59:08 PDT 2009


For those of you that don't want to read this long message, a screenshot
of what I did is at the end of this message. Syntax highlighting is
available if you read this message in HTML format, and there is a lot of
code so it's probably helpful to do so. :) This code isn't optimal, but
it shows how I was able to drop in some useful messages in Visual Studio
in about 20 minutes from when I decided to try it. My goal was to detect
and report the following items. Since my first task is getting feedback
about the new features from our users, it was more important to "show
the possibilities" with a simple implementation than have a perfect
analysis.

 

*         Unreferenced local variables

*         Local variables that are assigned but not used

*         Local variables that are referenced before they are
initialized

 

I started by adding a scope to the functionBlock rule in my tree walker
that keeps track of the first time each local is initialized and
referenced.

 

functionBlock

scope

{

        // store the first location where the local is initialized

        Dictionary<CommonToken, CommonToken> initializedLocals;

        // store the first location where the local is referenced

        Dictionary<CommonToken, CommonToken> referencedLocals;

}

@init

{

        $functionBlock::initializedLocals = new Dictionary<CommonToken,
CommonToken>();

        $functionBlock::referencedLocals = new Dictionary<CommonToken,
CommonToken>();

}

@after

{

        CheckVariableUsage();

}

 

Assuming these are properly manipulated (which I'll show in a bit), the
CheckVariableUsage() function is quickly implemented like this. Note the
comments about some (big) remaining issues; also keep in mind the
language is case-insensitive.

 

void CheckVariableUsage()

{

    var scope = functionBlock_stack.Peek();

    var referencedLocals = scope.referencedLocals;

    var initializedLocals = scope.initializedLocals;

 

    // check for unreferenced local variables

    foreach ( var unreferencedLocal in referencedLocals.Where( local =>
local.Value == null && initializedLocals[local.Key] == null ) )

    {

        // this warning is positioned at the declaration

        AddWarning( string.Format( "'{0}' : unreferenced local
variable", unreferencedLocal.Key.Text.ToUpperInvariant() ),
unreferencedLocal.Key );

    }

    // now check referenced but not initialized

    // TODO: this needs to handle right-to-left operators (r = r + 3;)
and check whether the initialization occurred before the reference

    // TODO: this should be a better check inside conditional blocks

    foreach ( var referencedLocal in referencedLocals.Where( local =>
local.Value != null && initializedLocals[local.Key] == null ) )

    {

        // this warning is positioned at the first usage of the
uninitialized variable

        AddWarning( string.Format( "'{0}' : local variable not
initialized before use", referencedLocal.Key.Text.ToUpperInvariant() ),
referencedLocal.Value );

    }

    // now check assigned but not used

    foreach ( var unreferencedLocal in referencedLocals.Where( local =>
local.Value == null && initializedLocals[local.Key] != null ) )

    {

        // this warning is positioned at the declaration

        AddWarning( string.Format( "'{0}' : local variable assigned but
not used", unreferencedLocal.Key.Text.ToUpperInvariant() ),
unreferencedLocal.Key );

    }

    // TODO: check for assignment to self

}

 

When the tree walker encounters a variable declaration, it adds the
declaration token to the list of variables for the functionBlock.

 

functionLocal

scope

{

        CommonToken name;

}

        :       ^(

                        UK_LOCAL

                        varType

                        docComment

                        (

                                // the scope variable 'name' is
initialized in localNameAndDim so I

                                // can handle combined declarations like
"int i, j;"

                                localNameAndDim

                                {

 
$functionBlock::referencedLocals.Add( $functionLocal::name, null );

 
$functionBlock::initializedLocals.Add( $functionLocal::name, null );

                                }

                        )+

                )

        ;

 

The actual analysis is then handled after the tree walker parses an
expression.

 

expression

@after

{

        CheckLocalReferences( $start );

}

        :       // stuff goes here

        ;

 

void CheckLocalReferences( CommonTree tree )

{

    string varname = tree.Token.Text;

    CommonToken vartoken = null;

    if ( functionBlock_stack.Count > 0 )

    {

        var scope = functionBlock_stack.Peek();

        vartoken = scope.referencedLocals.Keys.FirstOrDefault( local =>
local.Text.Equals( varname, StringComparison.OrdinalIgnoreCase ) );

    }

 

    // return if no variable matches the name of this expression token

    if ( vartoken == null )

        return;

 

    // make sure we don't treat 'someObject.i' as a reference to local
variable 'i'

    if ( tree.Parent.Type != UcGrammarLexer.AST_MEMBER ||
tree.ChildIndex == 0 )

    {

        bool assignment = false;

        switch ( tree.Parent.Type )

        {

        // include all of the language's assignment operators

        // TODO: check for initialization via 'out' parameters of
function calls

        case UcGrammarLexer.EQ:

        case UcGrammarLexer.PLUSEQ:

        case UcGrammarLexer.MINUSEQ:

        case UcGrammarLexer.TIMESEQ:

            if ( tree.ChildIndex == 0 )

            {

                assignment = true;

                if (
functionBlock_stack.Peek().initializedLocals[vartoken] == null )

 
functionBlock_stack.Peek().initializedLocals[vartoken] =
(CommonToken)tree.Token;

            }

 

            break;

 

        default:

            break;

        }

 

        if ( !assignment &&
functionBlock_stack.Peek().referencedLocals[vartoken] == null )

            functionBlock_stack.Peek().referencedLocals[vartoken] =
(CommonToken)tree.Token;

    }

}

 

And here is the result:

 

 

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://www.antlr.org/pipermail/antlr-interest/attachments/20090319/bcadd748/attachment.html 
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: image/png
Size: 27417 bytes
Desc: image001.png
Url : http://www.antlr.org/pipermail/antlr-interest/attachments/20090319/bcadd748/attachment.png 


More information about the antlr-interest mailing list