<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">###############################################################################
#
#   Class: NaturalDocs::Languages::CSharp
#
###############################################################################
#
#   A subclass to handle the language variations of C#.
#
#
#   Topic: Language Support
#
#       Supported:
#
#       - Classes
#       - Namespaces (no topic generated)
#       - Functions
#       - Constructors and Destructors
#       - Properties
#       - Indexers
#       - Operators
#       - Delegates
#       - Variables
#       - Constants
#       - Events
#       - Enums
#
#       Not supported yet:
#
#       - Autodocumenting enum members
#       - Using alias
#
###############################################################################

# This file is part of Natural Docs, which is Copyright (C) 2003-2008 Greg Valure
# Natural Docs is licensed under the GPL

use strict;
use integer;

package NaturalDocs::Languages::CSharp;

use base 'NaturalDocs::Languages::Advanced';


###############################################################################
# Group: Package Variables

#
#   hash: classKeywords
#   An existence hash of all the acceptable class keywords.  The keys are in all lowercase.
#
my %classKeywords = ( 'class' =&gt; 1,
                                    'struct' =&gt; 1,
                                    'interface' =&gt; 1 );

#
#   hash: classModifiers
#   An existence hash of all the acceptable class modifiers.  The keys are in all lowercase.
#
my %classModifiers = ( 'new' =&gt; 1,
                                   'public' =&gt; 1,
                                   'protected' =&gt; 1,
                                   'internal' =&gt; 1,
                                   'private' =&gt; 1,
                                   'abstract' =&gt; 1,
                                   'sealed' =&gt; 1,
                                   'unsafe' =&gt; 1,
                                   'static' =&gt; 1 );

#
#   hash: functionModifiers
#   An existence hash of all the acceptable function modifiers.  Also applies to properties.  Also encompasses those for operators
#   and indexers, but have more than are valid for them.  The keys are in all lowercase.
#
my %functionModifiers = ( 'new' =&gt; 1,
                                       'public' =&gt; 1,
                                       'protected' =&gt; 1,
                                       'internal' =&gt; 1,
                                       'private' =&gt; 1,
                                       'static' =&gt; 1,
                                       'virtual' =&gt; 1,
                                       'sealed' =&gt; 1,
                                       'override' =&gt; 1,
                                       'abstract' =&gt; 1,
                                       'extern' =&gt; 1,
                                       'unsafe' =&gt; 1 );

#
#   hash: variableModifiers
#   An existence hash of all the acceptable variable modifiers.  The keys are in all lowercase.
#
my %variableModifiers = ( 'new' =&gt; 1,
                                       'public' =&gt; 1,
                                       'protected' =&gt; 1,
                                       'internal' =&gt; 1,
                                       'private' =&gt; 1,
                                       'static' =&gt; 1,
                                       'readonly' =&gt; 1,
                                       'volatile' =&gt; 1,
                                       'unsafe' =&gt; 1 );

#
#   hash: enumTypes
#   An existence hash of all the possible enum types.  The keys are in all lowercase.
#
my %enumTypes = ( 'sbyte' =&gt; 1,
                             'byte' =&gt; 1,
                             'short' =&gt; 1,
                             'ushort' =&gt; 1,
                             'int' =&gt; 1,
                             'uint' =&gt; 1,
                             'long' =&gt; 1,
                             'ulong' =&gt; 1 );

#
#   hash: impossibleTypeWords
#   An existence hash of all the reserved words that cannot be in a type.  This includes 'enum' and all modifiers.  The keys are in
#   all lowercase.
#
my %impossibleTypeWords = ( 'abstract' =&gt; 1, 'as' =&gt; 1, 'base' =&gt; 1, 'break' =&gt; 1, 'case' =&gt; 1, 'catch' =&gt; 1,
                                              'checked' =&gt; 1, 'class' =&gt; 1, 'const' =&gt; 1, 'continue' =&gt; 1, 'default' =&gt; 1, 'delegate' =&gt; 1,
                                              'do' =&gt; 1, 'else' =&gt; 1, 'enum' =&gt; 1, 'event' =&gt; 1, 'explicit' =&gt; 1, 'extern' =&gt; 1,
                                              'false' =&gt; 1, 'finally' =&gt; 1, 'fixed' =&gt; 1, 'for' =&gt; 1, 'foreach' =&gt; 1, 'goto' =&gt; 1, 'if' =&gt; 1,
                                              'implicit' =&gt; 1, 'in' =&gt; 1, 'interface' =&gt; 1, 'internal' =&gt; 1, 'is' =&gt; 1, 'lock' =&gt; 1,
                                              'namespace' =&gt; 1, 'new' =&gt; 1, 'null' =&gt; 1, 'operator' =&gt; 1, 'out' =&gt; 1, 'override' =&gt; 1,
                                              'params' =&gt; 1, 'private' =&gt; 1, 'protected' =&gt; 1, 'public' =&gt; 1, 'readonly' =&gt; 1, 'ref' =&gt; 1,
                                              'return' =&gt; 1, 'sealed' =&gt; 1, 'sizeof' =&gt; 1, 'stackalloc' =&gt; 1, 'static' =&gt; 1,
                                              'struct' =&gt; 1, 'switch' =&gt; 1, 'this' =&gt; 1, 'throw' =&gt; 1, 'true' =&gt; 1, 'try' =&gt; 1, 'typeof' =&gt; 1,
                                              'unchecked' =&gt; 1, 'unsafe' =&gt; 1, 'using' =&gt; 1, 'virtual' =&gt; 1, 'volatile' =&gt; 1, 'while' =&gt; 1 );
# Deleted from the list: object, string, bool, decimal, sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, void



###############################################################################
# Group: Interface Functions


#
#   Function: PackageSeparator
#   Returns the package separator symbol.
#
sub PackageSeparator
    {  return '.';  };


#
#   Function: EnumValues
#   Returns the &lt;EnumValuesType&gt; that describes how the language handles enums.
#
sub EnumValues
    {  return ::ENUM_UNDER_TYPE();  };


#
#   Function: ParseFile
#
#   Parses the passed source file, sending comments acceptable for documentation to &lt;NaturalDocs::Parser-&gt;OnComment()&gt;.
#
#   Parameters:
#
#       sourceFile - The &lt;FileName&gt; to parse.
#       topicList - A reference to the list of &lt;NaturalDocs::Parser::ParsedTopics&gt; being built by the file.
#
#   Returns:
#
#       The array ( autoTopics, scopeRecord ).
#
#       autoTopics - An arrayref of automatically generated topics from the file, or undef if none.
#       scopeRecord - An arrayref of &lt;NaturalDocs::Languages::Advanced::ScopeChanges&gt;, or undef if none.
#
sub ParseFile #(sourceFile, topicsList)
    {
    my ($self, $sourceFile, $topicsList) = @_;

    $self-&gt;ParseForCommentsAndTokens($sourceFile, [ '//' ], [ '/*', '*/' ], [ '///' ], [ '/**', '*/' ] );

    my $tokens = $self-&gt;Tokens();
    my $index = 0;
    my $lineNumber = 1;

    while ($index &lt; scalar @$tokens)
        {
        if ($self-&gt;TryToSkipWhitespace(\$index, \$lineNumber) ||
            $self-&gt;TryToGetNamespace(\$index, \$lineNumber) ||
            $self-&gt;TryToGetUsing(\$index, \$lineNumber) ||
            $self-&gt;TryToGetClass(\$index, \$lineNumber) ||
            $self-&gt;TryToGetFunction(\$index, \$lineNumber) ||
            $self-&gt;TryToGetOverloadedOperator(\$index, \$lineNumber) ||
            $self-&gt;TryToGetVariable(\$index, \$lineNumber) ||
            $self-&gt;TryToGetEnum(\$index, \$lineNumber) )
            {
            # The functions above will handle everything.
            }

        elsif ($tokens-&gt;[$index] eq '{')
            {
            $self-&gt;StartScope('}', $lineNumber, undef, undef, undef);
            $index++;
            }

        elsif ($tokens-&gt;[$index] eq '}')
            {
            if ($self-&gt;ClosingScopeSymbol() eq '}')
                {  $self-&gt;EndScope($lineNumber);  };

            $index++;
            }

        else
            {
            $self-&gt;SkipRestOfStatement(\$index, \$lineNumber);
            };
        };


    # Don't need to keep these around.
    $self-&gt;ClearTokens();


    my $autoTopics = $self-&gt;AutoTopics();

    my $scopeRecord = $self-&gt;ScopeRecord();
    if (defined $scopeRecord &amp;&amp; !scalar @$scopeRecord)
        {  $scopeRecord = undef;  };

    return ( $autoTopics, $scopeRecord );
    };



###############################################################################
# Group: Statement Parsing Functions
# All functions here assume that the current position is at the beginning of a statement.
#
# Note for developers: I am well aware that the code in these functions do not check if we're past the end of the tokens as
# often as it should.  We're making use of the fact that Perl will always return undef in these cases to keep the code simpler.


#
#   Function: TryToGetNamespace
#
#   Determines whether the position is at a namespace declaration statement, and if so, adjusts the scope, skips it, and returns
#   true.
#
#   Why no topic?:
#
#       The main reason we don't create a Natural Docs topic for a namespace is because in order to declare class A.B.C in C#,
#       you must do this:
#
#       &gt; namespace A.B
#       &gt;    {
#       &gt;    class C
#       &gt;        { ... }
#       &gt;    }
#
#       That would result in a namespace topic whose only purpose is really to qualify C.  It would take the default page title, and
#       thus the default menu title.  So if you have files for A.B.X, A.B.Y, and A.B.Z, they all will appear as A.B on the menu.
#
#       If something actually appears in the namespace besides a class, it will be handled by
#       &lt;NaturalDocs::Parser-&gt;AddPackageDelineators()&gt;.  That function will add a package topic to correct the scope.
#
#       If the user actually documented it, it will still appear because of the manual topic.
#
sub TryToGetNamespace #(indexRef, lineNumberRef)
    {
    my ($self, $indexRef, $lineNumberRef) = @_;
    my $tokens = $self-&gt;Tokens();

    if (lc($tokens-&gt;[$$indexRef]) ne 'namespace')
        {  return undef;  };

    my $index = $$indexRef + 1;
    my $lineNumber = $$lineNumberRef;

    if (!$self-&gt;TryToSkipWhitespace(\$index, \$lineNumber))
        {  return undef;  };

    my $name;

    while ($tokens-&gt;[$index] =~ /^[a-z_\.\@]/i)
        {
        $name .= $tokens-&gt;[$index];
        $index++;
        };

    if (!defined $name)
        {  return undef;  };

    $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

    if ($tokens-&gt;[$index] ne '{')
        {  return undef;  };

    $index++;


    # We found a valid one if we made it this far.

    my $autoTopic = NaturalDocs::Parser::ParsedTopic-&gt;New(::TOPIC_CLASS(), $name,
                                                                                         $self-&gt;CurrentScope(), $self-&gt;CurrentUsing(),
                                                                                         undef,
                                                                                         undef, undef, $$lineNumberRef);

    # We don't add an auto-topic for namespaces.  See the function documentation above.

    NaturalDocs::Parser-&gt;OnClass($autoTopic-&gt;Package());

    $self-&gt;StartScope('}', $lineNumber, $autoTopic-&gt;Package());

    $$indexRef = $index;
    $$lineNumberRef = $lineNumber;

    return 1;
    };


#
#   Function: TryToGetClass
#
#   Determines whether the position is at a class declaration statement, and if so, generates a topic for it, skips it, and
#   returns true.
#
#   Supported Syntaxes:
#
#       - Classes
#       - Structs
#       - Interfaces
#
sub TryToGetClass #(indexRef, lineNumberRef)
    {
    my ($self, $indexRef, $lineNumberRef) = @_;
    my $tokens = $self-&gt;Tokens();

    my $index = $$indexRef;
    my $lineNumber = $$lineNumberRef;

    my $startIndex = $index;
    my $startLine = $lineNumber;
    my $needsPrototype = 0;

    if ($self-&gt;TryToSkipAttributes(\$index, \$lineNumber))
        {  $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);  }

    my @modifiers;

    while ($tokens-&gt;[$index] =~ /^[a-z]/i &amp;&amp;
              !exists $classKeywords{lc($tokens-&gt;[$index])} &amp;&amp;
              exists $classModifiers{lc($tokens-&gt;[$index])} )
        {
        push @modifiers, lc($tokens-&gt;[$index]);
        $index++;

        $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);
        };

    if (!exists $classKeywords{lc($tokens-&gt;[$index])})
        {  return undef;  };

    my $lcClassKeyword = lc($tokens-&gt;[$index]);

    $index++;

    if (!$self-&gt;TryToSkipWhitespace(\$index, \$lineNumber))
        {  return undef;  };

    my $name;

    while ($tokens-&gt;[$index] =~ /^[a-z_\@]/i)
        {
        $name .= $tokens-&gt;[$index];
        $index++;
        };

    if (!defined $name)
        {  return undef;  };

    $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

    if ($tokens-&gt;[$index] eq '&lt;')
    	{
    	# XXX: This is half-assed.
    	$index++;
    	$needsPrototype = 1;

    	while ($index &lt; scalar @$tokens &amp;&amp; $tokens-&gt;[$index] ne '&gt;')
    		{
    		$index++;
    		}

    	if ($index &lt; scalar @$tokens)
    		{
    		$index++;
    		}
    	else
    		{  return undef;  }

        $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);
    	}

    my @parents;

    if ($tokens-&gt;[$index] eq ':')
        {
        do
            {
            $index++;

            $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

            my $parentName;

            while ($tokens-&gt;[$index] =~ /^[a-z_\.\@]/i)
                {
                $parentName .= $tokens-&gt;[$index];
                $index++;
                };

	        if ($tokens-&gt;[$index] eq '&lt;')
	            {
	            # XXX: This is still half-assed.
	            $index++;
	            $needsPrototype = 1;

	            while ($index &lt; scalar @$tokens &amp;&amp; $tokens-&gt;[$index] ne '&gt;')
	                {
	                $index++;
	                }

	            if ($index &lt; scalar @$tokens)
	                {
	                $index++;
	                }
	            else
	                {  return undef;  }

	            $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);
	            }

            if (!defined $parentName)
                {  return undef;  };

            push @parents, NaturalDocs::SymbolString-&gt;FromText($parentName);

            $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);
            }
        while ($tokens-&gt;[$index] eq ',')
        };

    if (lc($tokens-&gt;[$index]) eq 'where')
    	{
    	# XXX: This is also half-assed
    	$index++;

    	while ($index &lt; scalar @$tokens &amp;&amp; $tokens-&gt;[$index] ne '{')
    		{
    		$index++;
    		}
    	}

    if ($tokens-&gt;[$index] ne '{')
        {  return undef;  };


    # If we made it this far, we have a valid class declaration.

    my @scopeIdentifiers = NaturalDocs::SymbolString-&gt;IdentifiersOf($self-&gt;CurrentScope());
    $name = join('.', @scopeIdentifiers, $name);

    my $topicType;

    if ($lcClassKeyword eq 'interface')
        {  $topicType = ::TOPIC_INTERFACE();  }
    else
        {  $topicType = ::TOPIC_CLASS();  };

    my $prototype;

    if ($needsPrototype)
    	{
    	$prototype = $self-&gt;CreateString($startIndex, $index);
    	}

    my $autoTopic = NaturalDocs::Parser::ParsedTopic-&gt;New($topicType, $name,
                                                                                         undef, $self-&gt;CurrentUsing(),
                                                                                         $prototype,
                                                                                         undef, undef, $$lineNumberRef);

    $self-&gt;AddAutoTopic($autoTopic);
    NaturalDocs::Parser-&gt;OnClass($autoTopic-&gt;Package());

    foreach my $parent (@parents)
        {
        NaturalDocs::Parser-&gt;OnClassParent($autoTopic-&gt;Package(), $parent, $self-&gt;CurrentScope(), undef,
                                                               ::RESOLVE_RELATIVE());
        };

    $self-&gt;StartScope('}', $lineNumber, $autoTopic-&gt;Package());

    $index++;

    $$indexRef = $index;
    $$lineNumberRef = $lineNumber;

    return 1;
    };


#
#   Function: TryToGetUsing
#
#   Determines whether the position is at a using statement, and if so, adds it to the current scope, skips it, and returns
#	true.
#
#	Supported:
#
#       - Using
#
#	Unsupported:
#
#		- Using with alias
#
sub TryToGetUsing #(indexRef, lineNumberRef)
    {
    my ($self, $indexRef, $lineNumberRef) = @_;
    my $tokens = $self-&gt;Tokens();

    my $index = $$indexRef;
    my $lineNumber = $$lineNumberRef;

    if (lc($tokens-&gt;[$index]) ne 'using')
        {  return undef;  };

    $index++;
    $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

    my $name;

    while ($tokens-&gt;[$index] =~ /^[a-z_\@\.]/i)
        {
        $name .= $tokens-&gt;[$index];
        $index++;
        };

    if ($tokens-&gt;[$index] ne ';' ||
		!defined $name)
        {  return undef;  };

    $index++;


    $self-&gt;AddUsing( NaturalDocs::SymbolString-&gt;FromText($name) );

    $$indexRef = $index;
    $$lineNumberRef = $lineNumber;

    return 1;
    };



#
#   Function: TryToGetFunction
#
#   Determines if the position is on a function declaration, and if so, generates a topic for it, skips it, and returns true.
#
#   Supported Syntaxes:
#
#       - Functions
#       - Constructors
#       - Destructors
#       - Properties
#       - Indexers
#       - Delegates
#       - Events
#
sub TryToGetFunction #(indexRef, lineNumberRef)
    {
    my ($self, $indexRef, $lineNumberRef) = @_;
    my $tokens = $self-&gt;Tokens();

    my $index = $$indexRef;
    my $lineNumber = $$lineNumberRef;

    if ($self-&gt;TryToSkipAttributes(\$index, \$lineNumber))
        {  $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);  };

    my $startIndex = $index;
    my $startLine = $lineNumber;

    my @modifiers;

    while ($tokens-&gt;[$index] =~ /^[a-z]/i &amp;&amp;
              exists $functionModifiers{lc($tokens-&gt;[$index])} )
        {
        push @modifiers, lc($tokens-&gt;[$index]);
        $index++;

        $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);
        };

    my $isDelegate;
    my $isEvent;

    if (lc($tokens-&gt;[$index]) eq 'delegate')
        {
        $isDelegate = 1;
        $index++;
        $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);
        }
    elsif (lc($tokens-&gt;[$index]) eq 'event')
        {
        $isEvent = 1;
        $index++;
        $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);
        };

    my $returnType = $self-&gt;TryToGetType(\$index, \$lineNumber);

    $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

    my $name;
    my $lastNameWord;

    while ($tokens-&gt;[$index] =~ /^[a-z\_\@\.\~\&lt;]/i)
        {
        $name .= $tokens-&gt;[$index];

        # Ugly hack, but what else is new?  For explicit generic interface definitions, such as:
        # IDObjectType System.Collections.Generic.IEnumerator&lt;IDObjectType&gt;.Current

        if ($tokens-&gt;[$index] eq '&lt;')
        	{
        	do
        		{
	        	$index++;
        		$name .= $tokens-&gt;[$index];
        		}
        	while ($index &lt; @$tokens &amp;&amp; $tokens-&gt;[$index] ne '&gt;');
        	}

        $lastNameWord = $tokens-&gt;[$index];
        $index++;
        };

    if (!defined $name)
        {
        # Constructors and destructors don't have return types.  It's possible their names were mistaken for the return type.
        if (defined $returnType)
            {
            $name = $returnType;
            $returnType = undef;

            $name =~ /([a-z0-9_]+)$/i;
            $lastNameWord = $1;
            }
        else
            {  return undef;  };
        };

    # If there's no return type, make sure it's a constructor or destructor.
    if (!defined $returnType)
        {
        my @identifiers = NaturalDocs::SymbolString-&gt;IdentifiersOf( $self-&gt;CurrentScope() );

        if ($lastNameWord ne $identifiers[-1])
            {  return undef;  };
        };

    $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);


    # Skip the brackets on indexers.
    if ($tokens-&gt;[$index] eq '[' &amp;&amp; lc($lastNameWord) eq 'this')
        {
        # This should jump the brackets completely.
        $self-&gt;GenericSkip(\$index, \$lineNumber);
        $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

        $name .= '[]';
        };


    # Properties, indexers, events with braces

    if ($tokens-&gt;[$index] eq '{')
        {
        my $prototype = $self-&gt;CreateString($startIndex, $index);

        $index++;
        $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

        my ($aWord, $bWord, $hasA, $hasB);

        if ($isEvent)
            {
            $aWord = 'add';
            $bWord = 'remove';
            }
        else
            {
            $aWord = 'get';
            $bWord = 'set';
            };

        while ($index &lt; scalar @$tokens)
            {
            if ($self-&gt;TryToSkipAttributes(\$index, \$lineNumber))
                {  $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);  };

            if (lc($tokens-&gt;[$index]) eq $aWord)
                {  $hasA = 1;  }
            elsif (lc($tokens-&gt;[$index]) eq $bWord)
                {  $hasB = 1;  }
            elsif ($tokens-&gt;[$index] eq '}')
                {
                $index++;
                last;
                };

            $self-&gt;SkipRestOfStatement(\$index, \$lineNumber);
            $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);
            };

        if ($hasA &amp;&amp; $hasB)
            {  $prototype .= ' { ' . $aWord . ', ' . $bWord . ' }';  }
        elsif ($hasA)
            {  $prototype .= ' { ' . $aWord . ' }';  }
        elsif ($hasB)
            {  $prototype .= ' { ' . $bWord . ' }';  };

        $prototype = $self-&gt;NormalizePrototype($prototype);

        my $topicType = ( $isEvent ? ::TOPIC_EVENT() : ::TOPIC_PROPERTY() );

        $self-&gt;AddAutoTopic(NaturalDocs::Parser::ParsedTopic-&gt;New($topicType, $name,
                                                                                                  $self-&gt;CurrentScope(), $self-&gt;CurrentUsing(),
                                                                                                  $prototype,
                                                                                                  undef, undef, $startLine));
        }


    # Functions, constructors, destructors, delegates.

    elsif ($tokens-&gt;[$index] eq '(')
        {
        # This should jump the parenthesis completely.
        $self-&gt;GenericSkip(\$index, \$lineNumber);

        my $topicType = ( $isDelegate ? ::TOPIC_DELEGATE() : ::TOPIC_FUNCTION() );
        my $prototype = $self-&gt;NormalizePrototype( $self-&gt;CreateString($startIndex, $index) );

        $self-&gt;AddAutoTopic(NaturalDocs::Parser::ParsedTopic-&gt;New($topicType, $name,
                                                                                                  $self-&gt;CurrentScope(), $self-&gt;CurrentUsing(),
                                                                                                  $prototype,
                                                                                                  undef, undef, $startLine));

        $self-&gt;SkipRestOfStatement(\$index, \$lineNumber);
        }


    # Events without braces

    elsif ($isEvent &amp;&amp; $tokens-&gt;[$index] eq ';')
        {
        my $prototype = $self-&gt;NormalizePrototype( $self-&gt;CreateString($startIndex, $index) );

        $self-&gt;AddAutoTopic(NaturalDocs::Parser::ParsedTopic-&gt;New(::TOPIC_EVENT(), $name,
                                                                                                  $self-&gt;CurrentScope(), $self-&gt;CurrentUsing(),
                                                                                                  $prototype,
                                                                                                  undef, undef, $startLine));
        $index++;
        }

    else
        {  return undef;  };


    # We succeeded if we got this far.

    $$indexRef = $index;
    $$lineNumberRef = $lineNumber;

    return 1;
    };


#
#   Function: TryToGetOverloadedOperator
#
#   Determines if the position is on an operator overload declaration, and if so, generates a topic for it, skips it, and returns true.
#
sub TryToGetOverloadedOperator #(indexRef, lineNumberRef)
    {
    my ($self, $indexRef, $lineNumberRef) = @_;
    my $tokens = $self-&gt;Tokens();

    my $index = $$indexRef;
    my $lineNumber = $$lineNumberRef;

    if ($self-&gt;TryToSkipAttributes(\$index, \$lineNumber))
        {  $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);  };

    my $startIndex = $index;
    my $startLine = $lineNumber;

    my @modifiers;

    while ($tokens-&gt;[$index] =~ /^[a-z]/i &amp;&amp;
              exists $functionModifiers{lc($tokens-&gt;[$index])} )
        {
        push @modifiers, lc($tokens-&gt;[$index]);
        $index++;

        $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);
        };


    my $name;


    # Casting operators.

    if (lc($tokens-&gt;[$index]) eq 'implicit' || lc($tokens-&gt;[$index]) eq 'explicit')
        {
        $index++;

        $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

        if (lc($tokens-&gt;[$index]) ne 'operator')
            {  return undef;  };

        $index++;
        $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

        $name = $self-&gt;TryToGetType(\$index, \$lineNumber);

        if (!defined $name)
            {  return undef;  };
        }


    # Symbol operators.

    else
        {
        if (!$self-&gt;TryToGetType(\$index, \$lineNumber))
            {  return undef;  };

        $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

        if (lc($tokens-&gt;[$index]) ne 'operator')
            {  return undef;  };

        $index++;

        $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

        if (lc($tokens-&gt;[$index]) eq 'true' || lc($tokens-&gt;[$index]) eq 'false')
            {
            $name = $tokens-&gt;[$index];
            $index++;
            }
        else
            {
            while ($tokens-&gt;[$index] =~ /^[\+\-\!\~\*\/\%\&amp;\|\^\&lt;\&gt;\=]$/)
                {
                $name .= $tokens-&gt;[$index];
                $index++;
                };
            };
        };

    $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

    if ($tokens-&gt;[$index] ne '(')
        {  return undef;  };

    # This should skip the parenthesis completely.
    $self-&gt;GenericSkip(\$index, \$lineNumber);

    my $prototype = $self-&gt;NormalizePrototype( $self-&gt;CreateString($startIndex, $index) );

    $self-&gt;AddAutoTopic(NaturalDocs::Parser::ParsedTopic-&gt;New(::TOPIC_FUNCTION(), 'operator ' . $name,
                                                                                              $self-&gt;CurrentScope(), $self-&gt;CurrentUsing(),
                                                                                              $prototype,
                                                                                              undef, undef, $startLine));

    $self-&gt;SkipRestOfStatement(\$index, \$lineNumber);


    # We succeeded if we got this far.

    $$indexRef = $index;
    $$lineNumberRef = $lineNumber;

    return 1;
    };


#
#   Function: TryToGetVariable
#
#   Determines if the position is on a variable declaration statement, and if so, generates a topic for each variable, skips the
#   statement, and returns true.
#
#   Supported Syntaxes:
#
#       - Variables
#       - Constants
#
sub TryToGetVariable #(indexRef, lineNumberRef)
    {
    my ($self, $indexRef, $lineNumberRef) = @_;
    my $tokens = $self-&gt;Tokens();

    my $index = $$indexRef;
    my $lineNumber = $$lineNumberRef;

    if ($self-&gt;TryToSkipAttributes(\$index, \$lineNumber))
        {  $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);  };

    my $startIndex = $index;
    my $startLine = $lineNumber;

    my @modifiers;

    while ($tokens-&gt;[$index] =~ /^[a-z]/i &amp;&amp;
              exists $variableModifiers{lc($tokens-&gt;[$index])} )
        {
        push @modifiers, lc($tokens-&gt;[$index]);
        $index++;

        $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);
        };

    my $type;
    if (lc($tokens-&gt;[$index]) eq 'const')
        {
        $type = ::TOPIC_CONSTANT();
        $index++;
        $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);
        }
    else
        {
        $type = ::TOPIC_VARIABLE();
        };

    if (!$self-&gt;TryToGetType(\$index, \$lineNumber))
        {  return undef;  };

    my $endTypeIndex = $index;

    $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

    my @names;

    for (;;)
        {
        my $name;

        while ($tokens-&gt;[$index] =~ /^[a-z\@\_]/i)
            {
            $name .= $tokens-&gt;[$index];
            $index++;
            };

        $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

        if ($tokens-&gt;[$index] eq '=')
            {
            do
                {
                $self-&gt;GenericSkip(\$index, \$lineNumber);
                }
            while ($tokens-&gt;[$index] ne ',' &amp;&amp; $tokens-&gt;[$index] ne ';');
            };

        push @names, $name;

        if ($tokens-&gt;[$index] eq ';')
            {
            $index++;
            last;
            }
        elsif ($tokens-&gt;[$index] eq ',')
            {
            $index++;
            $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);
            }
        else
            {  return undef;  };
        };


    # We succeeded if we got this far.

    my $prototypePrefix = $self-&gt;CreateString($startIndex, $endTypeIndex);

    foreach my $name (@names)
        {
        my $prototype = $self-&gt;NormalizePrototype( $prototypePrefix . ' ' . $name );

        $self-&gt;AddAutoTopic(NaturalDocs::Parser::ParsedTopic-&gt;New($type, $name,
                                                                                                  $self-&gt;CurrentScope(), $self-&gt;CurrentUsing(),
                                                                                                  $prototype,
                                                                                                  undef, undef, $startLine));
        };

    $$indexRef = $index;
    $$lineNumberRef = $lineNumber;

    return 1;
    };


#
#   Function: TryToGetEnum
#
#   Determines if the position is on an enum declaration statement, and if so, generates a topic for it.
#
#   Supported Syntaxes:
#
#       - Enums
#       - Enums with declared types
#
#   Unsupported:
#
#       - Documenting the members automatically
#
sub TryToGetEnum #(indexRef, lineNumberRef)
    {
    my ($self, $indexRef, $lineNumberRef) = @_;
    my $tokens = $self-&gt;Tokens();

    my $index = $$indexRef;
    my $lineNumber = $$lineNumberRef;

    if ($self-&gt;TryToSkipAttributes(\$index, \$lineNumber))
        {  $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);  };

    my $startIndex = $index;
    my $startLine = $lineNumber;

    my @modifiers;

    while ($tokens-&gt;[$index] =~ /^[a-z]/i &amp;&amp;
              exists $variableModifiers{lc($tokens-&gt;[$index])} )
        {
        push @modifiers, lc($tokens-&gt;[$index]);
        $index++;

        $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);
        };

    if (lc($tokens-&gt;[$index]) ne 'enum')
        {  return undef;  }

    $index++;
    $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

    my $name;

    while ($tokens-&gt;[$index] =~ /^[a-z\@\_]/i)
        {
        $name .= $tokens-&gt;[$index];
        $index++;
        };

    $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

    if ($tokens-&gt;[$index] eq ':')
        {
        $index++;
        $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

        if (!exists $enumTypes{ lc($tokens-&gt;[$index]) })
            {  return undef;  }

        $index++;
        $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);
        }

    if ($tokens-&gt;[$index] ne '{')
        {  return undef;  }

    # We succeeded if we got this far.

    my $prototype = $self-&gt;CreateString($startIndex, $index);
    $prototype = $self-&gt;NormalizePrototype( $prototype );

    $self-&gt;SkipRestOfStatement(\$index, \$lineNumber);

    $self-&gt;AddAutoTopic(NaturalDocs::Parser::ParsedTopic-&gt;New(::TOPIC_ENUMERATION(), $name,
                                                                                              $self-&gt;CurrentScope(), $self-&gt;CurrentUsing(),
                                                                                              $prototype,
                                                                                              undef, undef, $startLine));

    $$indexRef = $index;
    $$lineNumberRef = $lineNumber;

    return 1;
    };


#
#   Function: TryToGetType
#
#   Determines if the position is on a type identifier, and if so, skips it and returns it as a string.  This function does _not_ allow
#   modifiers.  You must take care of those beforehand.
#
sub TryToGetType #(indexRef, lineNumberRef)
    {
    my ($self, $indexRef, $lineNumberRef) = @_;
    my $tokens = $self-&gt;Tokens();

    my $name;
    my $index = $$indexRef;
    my $lineNumber = $$lineNumberRef;

    while ($tokens-&gt;[$index] =~ /^[a-z\@\.\_]/i)
        {
        if (exists $impossibleTypeWords{ lc($tokens-&gt;[$index]) } &amp;&amp; $name !~ /\@$/)
            {  return undef;  };

        $name .= $tokens-&gt;[$index];
        $index++;
        };

    if (!defined $name)
        {  return undef;  };

	$self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

	if ($tokens-&gt;[$index] eq '?')
		{
		$name .= '?';
		$index++;

		$self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);
		}

    if ($tokens-&gt;[$index] eq '&lt;')
    	{
    	# XXX: This is half-assed.
    	$name .= '&lt;';
    	$index++;

    	while ($index &lt; scalar @$tokens &amp;&amp; $tokens-&gt;[$index] ne '&gt;')
    		{
    		$name .= $tokens-&gt;[$index];
    		$index++;
    		}

    	if ($index &lt; scalar @$tokens)
    		{
    		$name .= '&gt;';
    		$index++;
    		}
    	else
    		{  return undef;  }

		$self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);
    	}

    while ($tokens-&gt;[$index] eq '[')
        {
        $name .= '[';
        $index++;

        while ($tokens-&gt;[$index] eq ',')
            {
            $name .= ',';
            $index++;
            };

        if ($tokens-&gt;[$index] eq ']')
            {
            $name .= ']';
            $index++;
            }
        else
            {  return undef;  }
        };

    $$indexRef = $index;
    $$lineNumberRef = $lineNumber;

    return $name;
    };



###############################################################################
# Group: Low Level Parsing Functions


#
#   Function: GenericSkip
#
#   Advances the position one place through general code.
#
#   - If the position is on a string, it will skip it completely.
#   - If the position is on an opening symbol, it will skip until the past the closing symbol.
#   - If the position is on whitespace (including comments and preprocessing directives), it will skip it completely.
#   - Otherwise it skips one token.
#
#   Parameters:
#
#       indexRef - A reference to the current index.
#       lineNumberRef - A reference to the current line number.
#
sub GenericSkip #(indexRef, lineNumberRef)
    {
    my ($self, $indexRef, $lineNumberRef) = @_;
    my $tokens = $self-&gt;Tokens();

    # We can ignore the scope stack because we're just skipping everything without parsing, and we need recursion anyway.
    if ($tokens-&gt;[$$indexRef] eq '{')
        {
        $$indexRef++;
        $self-&gt;GenericSkipUntilAfter($indexRef, $lineNumberRef, '}');
        }
    elsif ($tokens-&gt;[$$indexRef] eq '(')
        {
        $$indexRef++;
        $self-&gt;GenericSkipUntilAfter($indexRef, $lineNumberRef, ')');
        }
    elsif ($tokens-&gt;[$$indexRef] eq '[')
        {
        $$indexRef++;
        $self-&gt;GenericSkipUntilAfter($indexRef, $lineNumberRef, ']');
        }

    elsif ($self-&gt;TryToSkipWhitespace($indexRef, $lineNumberRef) ||
            $self-&gt;TryToSkipString($indexRef, $lineNumberRef))
        {
        }

    else
        {  $$indexRef++;  };
    };


#
#   Function: GenericSkipUntilAfter
#
#   Advances the position via &lt;GenericSkip()&gt; until a specific token is reached and passed.
#
sub GenericSkipUntilAfter #(indexRef, lineNumberRef, token)
    {
    my ($self, $indexRef, $lineNumberRef, $token) = @_;
    my $tokens = $self-&gt;Tokens();

    while ($$indexRef &lt; scalar @$tokens &amp;&amp; $tokens-&gt;[$$indexRef] ne $token)
        {  $self-&gt;GenericSkip($indexRef, $lineNumberRef);  };

    if ($tokens-&gt;[$$indexRef] eq "\n")
        {  $$lineNumberRef++;  };
    $$indexRef++;
    };


#
#   Function: SkipRestOfStatement
#
#   Advances the position via &lt;GenericSkip()&gt; until after the end of the current statement, which is defined as a semicolon or
#   a brace group.  Of course, either of those appearing inside parenthesis, a nested brace group, etc. don't count.
#
sub SkipRestOfStatement #(indexRef, lineNumberRef)
    {
    my ($self, $indexRef, $lineNumberRef) = @_;
    my $tokens = $self-&gt;Tokens();

    while ($$indexRef &lt; scalar @$tokens &amp;&amp;
             $tokens-&gt;[$$indexRef] ne ';' &amp;&amp;
             $tokens-&gt;[$$indexRef] ne '{')
        {
        $self-&gt;GenericSkip($indexRef, $lineNumberRef);
        };

    if ($tokens-&gt;[$$indexRef] eq ';')
        {  $$indexRef++;  }
    elsif ($tokens-&gt;[$$indexRef] eq '{')
        {  $self-&gt;GenericSkip($indexRef, $lineNumberRef);  };
    };


#
#   Function: TryToSkipString
#   If the current position is on a string delimiter, skip past the string and return true.
#
#   Parameters:
#
#       indexRef - A reference to the index of the position to start at.
#       lineNumberRef - A reference to the line number of the position.
#
#   Returns:
#
#       Whether the position was at a string.
#
#   Syntax Support:
#
#       - Supports quotes, apostrophes, and at-quotes.
#
sub TryToSkipString #(indexRef, lineNumberRef)
    {
    my ($self, $indexRef, $lineNumberRef) = @_;
    my $tokens = $self-&gt;Tokens();

    # The three string delimiters.  All three are Perl variables when preceded by a dollar sign.
    if ($self-&gt;SUPER::TryToSkipString($indexRef, $lineNumberRef, '\'') ||
        $self-&gt;SUPER::TryToSkipString($indexRef, $lineNumberRef, '"') )
        {
        return 1;
        }
    elsif ($tokens-&gt;[$$indexRef] eq '@' &amp;&amp; $tokens-&gt;[$$indexRef+1] eq '"')
        {
        $$indexRef += 2;

        # We need to do at-strings manually because backslash characters are accepted as regular characters, and two consecutive
        # quotes are accepted as well.

        while ($$indexRef &lt; scalar @$tokens &amp;&amp; !($tokens-&gt;[$$indexRef] eq '"' &amp;&amp; $tokens-&gt;[$$indexRef+1] ne '"') )
            {
            if ($tokens-&gt;[$$indexRef] eq '"')
                {
                # This is safe because the while condition will only let through quote pairs.
                $$indexRef += 2;
                }
            elsif ($tokens-&gt;[$$indexRef] eq "\n")
                {
                $$indexRef++;
                $$lineNumberRef++;
                }
            else
                {
                $$indexRef++;
                };
            };

        # Skip the closing quote.
        if ($$indexRef &lt; scalar @$tokens)
            {  $$indexRef++;  };

        return 1;
        }
    else
        {  return undef;  };
    };


#
#   Function: TryToSkipAttributes
#   If the current position is on an attribute section, skip it and return true.  Skips multiple attribute sections if they appear
#   consecutively.
#
sub TryToSkipAttributes #(indexRef, lineNumberRef)
    {
    my ($self, $indexRef, $lineNumberRef) = @_;
    my $tokens = $self-&gt;Tokens();

    my $success;

    while ($tokens-&gt;[$$indexRef] eq '[')
        {
        $success = 1;
        $$indexRef++;
        $self-&gt;GenericSkipUntilAfter($indexRef, $lineNumberRef, ']');
        $self-&gt;TryToSkipWhitespace($indexRef, $lineNumberRef);
        };

    return $success;
    };


#
#   Function: TryToSkipWhitespace
#   If the current position is on a whitespace token, a line break token, a comment, or a preprocessing directive, it skips them
#   and returns true.  If there are a number of these in a row, it skips them all.
#
sub TryToSkipWhitespace #(indexRef, lineNumberRef)
    {
    my ($self, $indexRef, $lineNumberRef) = @_;
    my $tokens = $self-&gt;Tokens();

    my $result;

    while ($$indexRef &lt; scalar @$tokens)
        {
        if ($tokens-&gt;[$$indexRef] =~ /^[ \t]/)
            {
            $$indexRef++;
            $result = 1;
            }
        elsif ($tokens-&gt;[$$indexRef] eq "\n")
            {
            $$indexRef++;
            $$lineNumberRef++;
            $result = 1;
            }
        elsif ($self-&gt;TryToSkipComment($indexRef, $lineNumberRef) ||
                $self-&gt;TryToSkipPreprocessingDirective($indexRef, $lineNumberRef))
            {
            $result = 1;
            }
        else
            {  last;  };
        };

    return $result;
    };


#
#   Function: TryToSkipComment
#   If the current position is on a comment, skip past it and return true.
#
sub TryToSkipComment #(indexRef, lineNumberRef)
    {
    my ($self, $indexRef, $lineNumberRef) = @_;

    return ( $self-&gt;TryToSkipLineComment($indexRef, $lineNumberRef) ||
                $self-&gt;TryToSkipMultilineComment($indexRef, $lineNumberRef) );
    };


#
#   Function: TryToSkipLineComment
#   If the current position is on a line comment symbol, skip past it and return true.
#
sub TryToSkipLineComment #(indexRef, lineNumberRef)
    {
    my ($self, $indexRef, $lineNumberRef) = @_;
    my $tokens = $self-&gt;Tokens();

    if ($tokens-&gt;[$$indexRef] eq '/' &amp;&amp; $tokens-&gt;[$$indexRef+1] eq '/')
        {
        $self-&gt;SkipRestOfLine($indexRef, $lineNumberRef);
        return 1;
        }
    else
        {  return undef;  };
    };


#
#   Function: TryToSkipMultilineComment
#   If the current position is on an opening comment symbol, skip past it and return true.
#
sub TryToSkipMultilineComment #(indexRef, lineNumberRef)
    {
    my ($self, $indexRef, $lineNumberRef) = @_;
    my $tokens = $self-&gt;Tokens();

    if ($tokens-&gt;[$$indexRef] eq '/' &amp;&amp; $tokens-&gt;[$$indexRef+1] eq '*')
        {
        $self-&gt;SkipUntilAfter($indexRef, $lineNumberRef, '*', '/');
        return 1;
        }
    else
        {  return undef;  };
    };


#
#   Function: TryToSkipPreprocessingDirective
#   If the current position is on a preprocessing directive, skip past it and return true.
#
sub TryToSkipPreprocessingDirective #(indexRef, lineNumberRef)
    {
    my ($self, $indexRef, $lineNumberRef) = @_;
    my $tokens = $self-&gt;Tokens();

    if ($tokens-&gt;[$$indexRef] eq '#' &amp;&amp; $self-&gt;IsFirstLineToken($$indexRef))
        {
        $self-&gt;SkipRestOfLine($indexRef, $lineNumberRef);
        return 1;
        }
    else
        {  return undef;  };
    };


1;
</pre></body></html>