diff options
Diffstat (limited to 'docs/tool/Modules/NaturalDocs/Languages/CSharp.pm')
| -rw-r--r-- | docs/tool/Modules/NaturalDocs/Languages/CSharp.pm | 1484 |
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; |