about summary refs log tree commit diff
path: root/docs/tool/Modules/NaturalDocs/Languages/CSharp.pm
diff options
context:
space:
mode:
Diffstat (limited to 'docs/tool/Modules/NaturalDocs/Languages/CSharp.pm')
-rw-r--r--docs/tool/Modules/NaturalDocs/Languages/CSharp.pm1484
1 files changed, 1484 insertions, 0 deletions
diff --git a/docs/tool/Modules/NaturalDocs/Languages/CSharp.pm b/docs/tool/Modules/NaturalDocs/Languages/CSharp.pm
new file mode 100644
index 00000000..5bcd50be
--- /dev/null
+++ b/docs/tool/Modules/NaturalDocs/Languages/CSharp.pm
@@ -0,0 +1,1484 @@
+###############################################################################
+#
+#   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' => 1,
+                                    'struct' => 1,
+                                    'interface' => 1 );
+
+#
+#   hash: classModifiers
+#   An existence hash of all the acceptable class modifiers.  The keys are in all lowercase.
+#
+my %classModifiers = ( 'new' => 1,
+                                   'public' => 1,
+                                   'protected' => 1,
+                                   'internal' => 1,
+                                   'private' => 1,
+                                   'abstract' => 1,
+                                   'sealed' => 1,
+                                   'unsafe' => 1,
+                                   'static' => 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' => 1,
+                                       'public' => 1,
+                                       'protected' => 1,
+                                       'internal' => 1,
+                                       'private' => 1,
+                                       'static' => 1,
+                                       'virtual' => 1,
+                                       'sealed' => 1,
+                                       'override' => 1,
+                                       'abstract' => 1,
+                                       'extern' => 1,
+                                       'unsafe' => 1 );
+
+#
+#   hash: variableModifiers
+#   An existence hash of all the acceptable variable modifiers.  The keys are in all lowercase.
+#
+my %variableModifiers = ( 'new' => 1,
+                                       'public' => 1,
+                                       'protected' => 1,
+                                       'internal' => 1,
+                                       'private' => 1,
+                                       'static' => 1,
+                                       'readonly' => 1,
+                                       'volatile' => 1,
+                                       'unsafe' => 1 );
+
+#
+#   hash: enumTypes
+#   An existence hash of all the possible enum types.  The keys are in all lowercase.
+#
+my %enumTypes = ( 'sbyte' => 1,
+                             'byte' => 1,
+                             'short' => 1,
+                             'ushort' => 1,
+                             'int' => 1,
+                             'uint' => 1,
+                             'long' => 1,
+                             'ulong' => 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' => 1, 'as' => 1, 'base' => 1, 'break' => 1, 'case' => 1, 'catch' => 1,
+                                              'checked' => 1, 'class' => 1, 'const' => 1, 'continue' => 1, 'default' => 1, 'delegate' => 1,
+                                              'do' => 1, 'else' => 1, 'enum' => 1, 'event' => 1, 'explicit' => 1, 'extern' => 1,
+                                              'false' => 1, 'finally' => 1, 'fixed' => 1, 'for' => 1, 'foreach' => 1, 'goto' => 1, 'if' => 1,
+                                              'implicit' => 1, 'in' => 1, 'interface' => 1, 'internal' => 1, 'is' => 1, 'lock' => 1,
+                                              'namespace' => 1, 'new' => 1, 'null' => 1, 'operator' => 1, 'out' => 1, 'override' => 1,
+                                              'params' => 1, 'private' => 1, 'protected' => 1, 'public' => 1, 'readonly' => 1, 'ref' => 1,
+                                              'return' => 1, 'sealed' => 1, 'sizeof' => 1, 'stackalloc' => 1, 'static' => 1,
+                                              'struct' => 1, 'switch' => 1, 'this' => 1, 'throw' => 1, 'true' => 1, 'try' => 1, 'typeof' => 1,
+                                              'unchecked' => 1, 'unsafe' => 1, 'using' => 1, 'virtual' => 1, 'volatile' => 1, 'while' => 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 <EnumValuesType> 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 <NaturalDocs::Parser->OnComment()>.
+#
+#   Parameters:
+#
+#       sourceFile - The <FileName> to parse.
+#       topicList - A reference to the list of <NaturalDocs::Parser::ParsedTopics> 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 <NaturalDocs::Languages::Advanced::ScopeChanges>, or undef if none.
+#
+sub ParseFile #(sourceFile, topicsList)
+    {
+    my ($self, $sourceFile, $topicsList) = @_;
+
+    $self->ParseForCommentsAndTokens($sourceFile, [ '//' ], [ '/*', '*/' ], [ '///' ], [ '/**', '*/' ] );
+
+    my $tokens = $self->Tokens();
+    my $index = 0;
+    my $lineNumber = 1;
+
+    while ($index < scalar @$tokens)
+        {
+        if ($self->TryToSkipWhitespace(\$index, \$lineNumber) ||
+            $self->TryToGetNamespace(\$index, \$lineNumber) ||
+            $self->TryToGetUsing(\$index, \$lineNumber) ||
+            $self->TryToGetClass(\$index, \$lineNumber) ||
+            $self->TryToGetFunction(\$index, \$lineNumber) ||
+            $self->TryToGetOverloadedOperator(\$index, \$lineNumber) ||
+            $self->TryToGetVariable(\$index, \$lineNumber) ||
+            $self->TryToGetEnum(\$index, \$lineNumber) )
+            {
+            # The functions above will handle everything.
+            }
+
+        elsif ($tokens->[$index] eq '{')
+            {
+            $self->StartScope('}', $lineNumber, undef, undef, undef);
+            $index++;
+            }
+
+        elsif ($tokens->[$index] eq '}')
+            {
+            if ($self->ClosingScopeSymbol() eq '}')
+                {  $self->EndScope($lineNumber);  };
+
+            $index++;
+            }
+
+        else
+            {
+            $self->SkipRestOfStatement(\$index, \$lineNumber);
+            };
+        };
+
+
+    # Don't need to keep these around.
+    $self->ClearTokens();
+
+
+    my $autoTopics = $self->AutoTopics();
+
+    my $scopeRecord = $self->ScopeRecord();
+    if (defined $scopeRecord && !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:
+#
+#       > namespace A.B
+#       >    {
+#       >    class C
+#       >        { ... }
+#       >    }
+#
+#       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
+#       <NaturalDocs::Parser->AddPackageDelineators()>.  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->Tokens();
+
+    if (lc($tokens->[$$indexRef]) ne 'namespace')
+        {  return undef;  };
+
+    my $index = $$indexRef + 1;
+    my $lineNumber = $$lineNumberRef;
+
+    if (!$self->TryToSkipWhitespace(\$index, \$lineNumber))
+        {  return undef;  };
+
+    my $name;
+
+    while ($tokens->[$index] =~ /^[a-z_\.\@]/i)
+        {
+        $name .= $tokens->[$index];
+        $index++;
+        };
+
+    if (!defined $name)
+        {  return undef;  };
+
+    $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+    if ($tokens->[$index] ne '{')
+        {  return undef;  };
+
+    $index++;
+
+
+    # We found a valid one if we made it this far.
+
+    my $autoTopic = NaturalDocs::Parser::ParsedTopic->New(::TOPIC_CLASS(), $name,
+                                                                                         $self->CurrentScope(), $self->CurrentUsing(),
+                                                                                         undef,
+                                                                                         undef, undef, $$lineNumberRef);
+
+    # We don't add an auto-topic for namespaces.  See the function documentation above.
+
+    NaturalDocs::Parser->OnClass($autoTopic->Package());
+
+    $self->StartScope('}', $lineNumber, $autoTopic->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->Tokens();
+
+    my $index = $$indexRef;
+    my $lineNumber = $$lineNumberRef;
+
+    my $startIndex = $index;
+    my $startLine = $lineNumber;
+    my $needsPrototype = 0;
+
+    if ($self->TryToSkipAttributes(\$index, \$lineNumber))
+        {  $self->TryToSkipWhitespace(\$index, \$lineNumber);  }
+
+    my @modifiers;
+
+    while ($tokens->[$index] =~ /^[a-z]/i &&
+              !exists $classKeywords{lc($tokens->[$index])} &&
+              exists $classModifiers{lc($tokens->[$index])} )
+        {
+        push @modifiers, lc($tokens->[$index]);
+        $index++;
+
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+        };
+
+    if (!exists $classKeywords{lc($tokens->[$index])})
+        {  return undef;  };
+
+    my $lcClassKeyword = lc($tokens->[$index]);
+
+    $index++;
+
+    if (!$self->TryToSkipWhitespace(\$index, \$lineNumber))
+        {  return undef;  };
+
+    my $name;
+
+    while ($tokens->[$index] =~ /^[a-z_\@]/i)
+        {
+        $name .= $tokens->[$index];
+        $index++;
+        };
+
+    if (!defined $name)
+        {  return undef;  };
+
+    $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+    if ($tokens->[$index] eq '<')
+    	{
+    	# XXX: This is half-assed.
+    	$index++;
+    	$needsPrototype = 1;
+
+    	while ($index < scalar @$tokens && $tokens->[$index] ne '>')
+    		{
+    		$index++;
+    		}
+
+    	if ($index < scalar @$tokens)
+    		{
+    		$index++;
+    		}
+    	else
+    		{  return undef;  }
+
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+    	}
+
+    my @parents;
+
+    if ($tokens->[$index] eq ':')
+        {
+        do
+            {
+            $index++;
+
+            $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+            my $parentName;
+
+            while ($tokens->[$index] =~ /^[a-z_\.\@]/i)
+                {
+                $parentName .= $tokens->[$index];
+                $index++;
+                };
+
+	        if ($tokens->[$index] eq '<')
+	            {
+	            # XXX: This is still half-assed.
+	            $index++;
+	            $needsPrototype = 1;
+
+	            while ($index < scalar @$tokens && $tokens->[$index] ne '>')
+	                {
+	                $index++;
+	                }
+
+	            if ($index < scalar @$tokens)
+	                {
+	                $index++;
+	                }
+	            else
+	                {  return undef;  }
+
+	            $self->TryToSkipWhitespace(\$index, \$lineNumber);
+	            }
+
+            if (!defined $parentName)
+                {  return undef;  };
+
+            push @parents, NaturalDocs::SymbolString->FromText($parentName);
+
+            $self->TryToSkipWhitespace(\$index, \$lineNumber);
+            }
+        while ($tokens->[$index] eq ',')
+        };
+
+    if (lc($tokens->[$index]) eq 'where')
+    	{
+    	# XXX: This is also half-assed
+    	$index++;
+
+    	while ($index < scalar @$tokens && $tokens->[$index] ne '{')
+    		{
+    		$index++;
+    		}
+    	}
+
+    if ($tokens->[$index] ne '{')
+        {  return undef;  };
+
+
+    # If we made it this far, we have a valid class declaration.
+
+    my @scopeIdentifiers = NaturalDocs::SymbolString->IdentifiersOf($self->CurrentScope());
+    $name = join('.', @scopeIdentifiers, $name);
+
+    my $topicType;
+
+    if ($lcClassKeyword eq 'interface')
+        {  $topicType = ::TOPIC_INTERFACE();  }
+    else
+        {  $topicType = ::TOPIC_CLASS();  };
+
+    my $prototype;
+
+    if ($needsPrototype)
+    	{
+    	$prototype = $self->CreateString($startIndex, $index);
+    	}
+
+    my $autoTopic = NaturalDocs::Parser::ParsedTopic->New($topicType, $name,
+                                                                                         undef, $self->CurrentUsing(),
+                                                                                         $prototype,
+                                                                                         undef, undef, $$lineNumberRef);
+
+    $self->AddAutoTopic($autoTopic);
+    NaturalDocs::Parser->OnClass($autoTopic->Package());
+
+    foreach my $parent (@parents)
+        {
+        NaturalDocs::Parser->OnClassParent($autoTopic->Package(), $parent, $self->CurrentScope(), undef,
+                                                               ::RESOLVE_RELATIVE());
+        };
+
+    $self->StartScope('}', $lineNumber, $autoTopic->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->Tokens();
+
+    my $index = $$indexRef;
+    my $lineNumber = $$lineNumberRef;
+
+    if (lc($tokens->[$index]) ne 'using')
+        {  return undef;  };
+
+    $index++;
+    $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+    my $name;
+
+    while ($tokens->[$index] =~ /^[a-z_\@\.]/i)
+        {
+        $name .= $tokens->[$index];
+        $index++;
+        };
+
+    if ($tokens->[$index] ne ';' ||
+		!defined $name)
+        {  return undef;  };
+
+    $index++;
+
+
+    $self->AddUsing( NaturalDocs::SymbolString->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->Tokens();
+
+    my $index = $$indexRef;
+    my $lineNumber = $$lineNumberRef;
+
+    if ($self->TryToSkipAttributes(\$index, \$lineNumber))
+        {  $self->TryToSkipWhitespace(\$index, \$lineNumber);  };
+
+    my $startIndex = $index;
+    my $startLine = $lineNumber;
+
+    my @modifiers;
+
+    while ($tokens->[$index] =~ /^[a-z]/i &&
+              exists $functionModifiers{lc($tokens->[$index])} )
+        {
+        push @modifiers, lc($tokens->[$index]);
+        $index++;
+
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+        };
+
+    my $isDelegate;
+    my $isEvent;
+
+    if (lc($tokens->[$index]) eq 'delegate')
+        {
+        $isDelegate = 1;
+        $index++;
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+        }
+    elsif (lc($tokens->[$index]) eq 'event')
+        {
+        $isEvent = 1;
+        $index++;
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+        };
+
+    my $returnType = $self->TryToGetType(\$index, \$lineNumber);
+
+    $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+    my $name;
+    my $lastNameWord;
+
+    while ($tokens->[$index] =~ /^[a-z\_\@\.\~\<]/i)
+        {
+        $name .= $tokens->[$index];
+
+        # Ugly hack, but what else is new?  For explicit generic interface definitions, such as:
+        # IDObjectType System.Collections.Generic.IEnumerator<IDObjectType>.Current
+
+        if ($tokens->[$index] eq '<')
+        	{
+        	do
+        		{
+	        	$index++;
+        		$name .= $tokens->[$index];
+        		}
+        	while ($index < @$tokens && $tokens->[$index] ne '>');
+        	}
+
+        $lastNameWord = $tokens->[$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->IdentifiersOf( $self->CurrentScope() );
+
+        if ($lastNameWord ne $identifiers[-1])
+            {  return undef;  };
+        };
+
+    $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+
+    # Skip the brackets on indexers.
+    if ($tokens->[$index] eq '[' && lc($lastNameWord) eq 'this')
+        {
+        # This should jump the brackets completely.
+        $self->GenericSkip(\$index, \$lineNumber);
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+        $name .= '[]';
+        };
+
+
+    # Properties, indexers, events with braces
+
+    if ($tokens->[$index] eq '{')
+        {
+        my $prototype = $self->CreateString($startIndex, $index);
+
+        $index++;
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+        my ($aWord, $bWord, $hasA, $hasB);
+
+        if ($isEvent)
+            {
+            $aWord = 'add';
+            $bWord = 'remove';
+            }
+        else
+            {
+            $aWord = 'get';
+            $bWord = 'set';
+            };
+
+        while ($index < scalar @$tokens)
+            {
+            if ($self->TryToSkipAttributes(\$index, \$lineNumber))
+                {  $self->TryToSkipWhitespace(\$index, \$lineNumber);  };
+
+            if (lc($tokens->[$index]) eq $aWord)
+                {  $hasA = 1;  }
+            elsif (lc($tokens->[$index]) eq $bWord)
+                {  $hasB = 1;  }
+            elsif ($tokens->[$index] eq '}')
+                {
+                $index++;
+                last;
+                };
+
+            $self->SkipRestOfStatement(\$index, \$lineNumber);
+            $self->TryToSkipWhitespace(\$index, \$lineNumber);
+            };
+
+        if ($hasA && $hasB)
+            {  $prototype .= ' { ' . $aWord . ', ' . $bWord . ' }';  }
+        elsif ($hasA)
+            {  $prototype .= ' { ' . $aWord . ' }';  }
+        elsif ($hasB)
+            {  $prototype .= ' { ' . $bWord . ' }';  };
+
+        $prototype = $self->NormalizePrototype($prototype);
+
+        my $topicType = ( $isEvent ? ::TOPIC_EVENT() : ::TOPIC_PROPERTY() );
+
+        $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New($topicType, $name,
+                                                                                                  $self->CurrentScope(), $self->CurrentUsing(),
+                                                                                                  $prototype,
+                                                                                                  undef, undef, $startLine));
+        }
+
+
+    # Functions, constructors, destructors, delegates.
+
+    elsif ($tokens->[$index] eq '(')
+        {
+        # This should jump the parenthesis completely.
+        $self->GenericSkip(\$index, \$lineNumber);
+
+        my $topicType = ( $isDelegate ? ::TOPIC_DELEGATE() : ::TOPIC_FUNCTION() );
+        my $prototype = $self->NormalizePrototype( $self->CreateString($startIndex, $index) );
+
+        $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New($topicType, $name,
+                                                                                                  $self->CurrentScope(), $self->CurrentUsing(),
+                                                                                                  $prototype,
+                                                                                                  undef, undef, $startLine));
+
+        $self->SkipRestOfStatement(\$index, \$lineNumber);
+        }
+
+
+    # Events without braces
+
+    elsif ($isEvent && $tokens->[$index] eq ';')
+        {
+        my $prototype = $self->NormalizePrototype( $self->CreateString($startIndex, $index) );
+
+        $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New(::TOPIC_EVENT(), $name,
+                                                                                                  $self->CurrentScope(), $self->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->Tokens();
+
+    my $index = $$indexRef;
+    my $lineNumber = $$lineNumberRef;
+
+    if ($self->TryToSkipAttributes(\$index, \$lineNumber))
+        {  $self->TryToSkipWhitespace(\$index, \$lineNumber);  };
+
+    my $startIndex = $index;
+    my $startLine = $lineNumber;
+
+    my @modifiers;
+
+    while ($tokens->[$index] =~ /^[a-z]/i &&
+              exists $functionModifiers{lc($tokens->[$index])} )
+        {
+        push @modifiers, lc($tokens->[$index]);
+        $index++;
+
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+        };
+
+
+    my $name;
+
+
+    # Casting operators.
+
+    if (lc($tokens->[$index]) eq 'implicit' || lc($tokens->[$index]) eq 'explicit')
+        {
+        $index++;
+
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+        if (lc($tokens->[$index]) ne 'operator')
+            {  return undef;  };
+
+        $index++;
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+        $name = $self->TryToGetType(\$index, \$lineNumber);
+
+        if (!defined $name)
+            {  return undef;  };
+        }
+
+
+    # Symbol operators.
+
+    else
+        {
+        if (!$self->TryToGetType(\$index, \$lineNumber))
+            {  return undef;  };
+
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+        if (lc($tokens->[$index]) ne 'operator')
+            {  return undef;  };
+
+        $index++;
+
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+        if (lc($tokens->[$index]) eq 'true' || lc($tokens->[$index]) eq 'false')
+            {
+            $name = $tokens->[$index];
+            $index++;
+            }
+        else
+            {
+            while ($tokens->[$index] =~ /^[\+\-\!\~\*\/\%\&\|\^\<\>\=]$/)
+                {
+                $name .= $tokens->[$index];
+                $index++;
+                };
+            };
+        };
+
+    $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+    if ($tokens->[$index] ne '(')
+        {  return undef;  };
+
+    # This should skip the parenthesis completely.
+    $self->GenericSkip(\$index, \$lineNumber);
+
+    my $prototype = $self->NormalizePrototype( $self->CreateString($startIndex, $index) );
+
+    $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New(::TOPIC_FUNCTION(), 'operator ' . $name,
+                                                                                              $self->CurrentScope(), $self->CurrentUsing(),
+                                                                                              $prototype,
+                                                                                              undef, undef, $startLine));
+
+    $self->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->Tokens();
+
+    my $index = $$indexRef;
+    my $lineNumber = $$lineNumberRef;
+
+    if ($self->TryToSkipAttributes(\$index, \$lineNumber))
+        {  $self->TryToSkipWhitespace(\$index, \$lineNumber);  };
+
+    my $startIndex = $index;
+    my $startLine = $lineNumber;
+
+    my @modifiers;
+
+    while ($tokens->[$index] =~ /^[a-z]/i &&
+              exists $variableModifiers{lc($tokens->[$index])} )
+        {
+        push @modifiers, lc($tokens->[$index]);
+        $index++;
+
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+        };
+
+    my $type;
+    if (lc($tokens->[$index]) eq 'const')
+        {
+        $type = ::TOPIC_CONSTANT();
+        $index++;
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+        }
+    else
+        {
+        $type = ::TOPIC_VARIABLE();
+        };
+
+    if (!$self->TryToGetType(\$index, \$lineNumber))
+        {  return undef;  };
+
+    my $endTypeIndex = $index;
+
+    $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+    my @names;
+
+    for (;;)
+        {
+        my $name;
+
+        while ($tokens->[$index] =~ /^[a-z\@\_]/i)
+            {
+            $name .= $tokens->[$index];
+            $index++;
+            };
+
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+        if ($tokens->[$index] eq '=')
+            {
+            do
+                {
+                $self->GenericSkip(\$index, \$lineNumber);
+                }
+            while ($tokens->[$index] ne ',' && $tokens->[$index] ne ';');
+            };
+
+        push @names, $name;
+
+        if ($tokens->[$index] eq ';')
+            {
+            $index++;
+            last;
+            }
+        elsif ($tokens->[$index] eq ',')
+            {
+            $index++;
+            $self->TryToSkipWhitespace(\$index, \$lineNumber);
+            }
+        else
+            {  return undef;  };
+        };
+
+
+    # We succeeded if we got this far.
+
+    my $prototypePrefix = $self->CreateString($startIndex, $endTypeIndex);
+
+    foreach my $name (@names)
+        {
+        my $prototype = $self->NormalizePrototype( $prototypePrefix . ' ' . $name );
+
+        $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New($type, $name,
+                                                                                                  $self->CurrentScope(), $self->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->Tokens();
+
+    my $index = $$indexRef;
+    my $lineNumber = $$lineNumberRef;
+
+    if ($self->TryToSkipAttributes(\$index, \$lineNumber))
+        {  $self->TryToSkipWhitespace(\$index, \$lineNumber);  };
+
+    my $startIndex = $index;
+    my $startLine = $lineNumber;
+
+    my @modifiers;
+
+    while ($tokens->[$index] =~ /^[a-z]/i &&
+              exists $variableModifiers{lc($tokens->[$index])} )
+        {
+        push @modifiers, lc($tokens->[$index]);
+        $index++;
+
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+        };
+
+    if (lc($tokens->[$index]) ne 'enum')
+        {  return undef;  }
+
+    $index++;
+    $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+    my $name;
+
+    while ($tokens->[$index] =~ /^[a-z\@\_]/i)
+        {
+        $name .= $tokens->[$index];
+        $index++;
+        };
+
+    $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+    if ($tokens->[$index] eq ':')
+        {
+        $index++;
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+        if (!exists $enumTypes{ lc($tokens->[$index]) })
+            {  return undef;  }
+
+        $index++;
+        $self->TryToSkipWhitespace(\$index, \$lineNumber);
+        }
+
+    if ($tokens->[$index] ne '{')
+        {  return undef;  }
+
+    # We succeeded if we got this far.
+
+    my $prototype = $self->CreateString($startIndex, $index);
+    $prototype = $self->NormalizePrototype( $prototype );
+
+    $self->SkipRestOfStatement(\$index, \$lineNumber);
+
+    $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New(::TOPIC_ENUMERATION(), $name,
+                                                                                              $self->CurrentScope(), $self->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->Tokens();
+
+    my $name;
+    my $index = $$indexRef;
+    my $lineNumber = $$lineNumberRef;
+
+    while ($tokens->[$index] =~ /^[a-z\@\.\_]/i)
+        {
+        if (exists $impossibleTypeWords{ lc($tokens->[$index]) } && $name !~ /\@$/)
+            {  return undef;  };
+
+        $name .= $tokens->[$index];
+        $index++;
+        };
+
+    if (!defined $name)
+        {  return undef;  };
+
+	$self->TryToSkipWhitespace(\$index, \$lineNumber);
+
+	if ($tokens->[$index] eq '?')
+		{
+		$name .= '?';
+		$index++;
+
+		$self->TryToSkipWhitespace(\$index, \$lineNumber);
+		}
+
+    if ($tokens->[$index] eq '<')
+    	{
+    	# XXX: This is half-assed.
+    	$name .= '<';
+    	$index++;
+
+    	while ($index < scalar @$tokens && $tokens->[$index] ne '>')
+    		{
+    		$name .= $tokens->[$index];
+    		$index++;
+    		}
+
+    	if ($index < scalar @$tokens)
+    		{
+    		$name .= '>';
+    		$index++;
+    		}
+    	else
+    		{  return undef;  }
+
+		$self->TryToSkipWhitespace(\$index, \$lineNumber);
+    	}
+
+    while ($tokens->[$index] eq '[')
+        {
+        $name .= '[';
+        $index++;
+
+        while ($tokens->[$index] eq ',')
+            {
+            $name .= ',';
+            $index++;
+            };
+
+        if ($tokens->[$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->Tokens();
+
+    # We can ignore the scope stack because we're just skipping everything without parsing, and we need recursion anyway.
+    if ($tokens->[$$indexRef] eq '{')
+        {
+        $$indexRef++;
+        $self->GenericSkipUntilAfter($indexRef, $lineNumberRef, '}');
+        }
+    elsif ($tokens->[$$indexRef] eq '(')
+        {
+        $$indexRef++;
+        $self->GenericSkipUntilAfter($indexRef, $lineNumberRef, ')');
+        }
+    elsif ($tokens->[$$indexRef] eq '[')
+        {
+        $$indexRef++;
+        $self->GenericSkipUntilAfter($indexRef, $lineNumberRef, ']');
+        }
+
+    elsif ($self->TryToSkipWhitespace($indexRef, $lineNumberRef) ||
+            $self->TryToSkipString($indexRef, $lineNumberRef))
+        {
+        }
+
+    else
+        {  $$indexRef++;  };
+    };
+
+
+#
+#   Function: GenericSkipUntilAfter
+#
+#   Advances the position via <GenericSkip()> until a specific token is reached and passed.
+#
+sub GenericSkipUntilAfter #(indexRef, lineNumberRef, token)
+    {
+    my ($self, $indexRef, $lineNumberRef, $token) = @_;
+    my $tokens = $self->Tokens();
+
+    while ($$indexRef < scalar @$tokens && $tokens->[$$indexRef] ne $token)
+        {  $self->GenericSkip($indexRef, $lineNumberRef);  };
+
+    if ($tokens->[$$indexRef] eq "\n")
+        {  $$lineNumberRef++;  };
+    $$indexRef++;
+    };
+
+
+#
+#   Function: SkipRestOfStatement
+#
+#   Advances the position via <GenericSkip()> 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->Tokens();
+
+    while ($$indexRef < scalar @$tokens &&
+             $tokens->[$$indexRef] ne ';' &&
+             $tokens->[$$indexRef] ne '{')
+        {
+        $self->GenericSkip($indexRef, $lineNumberRef);
+        };
+
+    if ($tokens->[$$indexRef] eq ';')
+        {  $$indexRef++;  }
+    elsif ($tokens->[$$indexRef] eq '{')
+        {  $self->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->Tokens();
+
+    # The three string delimiters.  All three are Perl variables when preceded by a dollar sign.
+    if ($self->SUPER::TryToSkipString($indexRef, $lineNumberRef, '\'') ||
+        $self->SUPER::TryToSkipString($indexRef, $lineNumberRef, '"') )
+        {
+        return 1;
+        }
+    elsif ($tokens->[$$indexRef] eq '@' && $tokens->[$$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 < scalar @$tokens && !($tokens->[$$indexRef] eq '"' && $tokens->[$$indexRef+1] ne '"') )
+            {
+            if ($tokens->[$$indexRef] eq '"')
+                {
+                # This is safe because the while condition will only let through quote pairs.
+                $$indexRef += 2;
+                }
+            elsif ($tokens->[$$indexRef] eq "\n")
+                {
+                $$indexRef++;
+                $$lineNumberRef++;
+                }
+            else
+                {
+                $$indexRef++;
+                };
+            };
+
+        # Skip the closing quote.
+        if ($$indexRef < 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->Tokens();
+
+    my $success;
+
+    while ($tokens->[$$indexRef] eq '[')
+        {
+        $success = 1;
+        $$indexRef++;
+        $self->GenericSkipUntilAfter($indexRef, $lineNumberRef, ']');
+        $self->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->Tokens();
+
+    my $result;
+
+    while ($$indexRef < scalar @$tokens)
+        {
+        if ($tokens->[$$indexRef] =~ /^[ \t]/)
+            {
+            $$indexRef++;
+            $result = 1;
+            }
+        elsif ($tokens->[$$indexRef] eq "\n")
+            {
+            $$indexRef++;
+            $$lineNumberRef++;
+            $result = 1;
+            }
+        elsif ($self->TryToSkipComment($indexRef, $lineNumberRef) ||
+                $self->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->TryToSkipLineComment($indexRef, $lineNumberRef) ||
+                $self->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->Tokens();
+
+    if ($tokens->[$$indexRef] eq '/' && $tokens->[$$indexRef+1] eq '/')
+        {
+        $self->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->Tokens();
+
+    if ($tokens->[$$indexRef] eq '/' && $tokens->[$$indexRef+1] eq '*')
+        {
+        $self->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->Tokens();
+
+    if ($tokens->[$$indexRef] eq '#' && $self->IsFirstLineToken($$indexRef))
+        {
+        $self->SkipRestOfLine($indexRef, $lineNumberRef);
+        return 1;
+        }
+    else
+        {  return undef;  };
+    };
+
+
+1;