ddnet/docs/tool/Modules/NaturalDocs/Languages/CSharp.pm
2008-08-02 08:21:29 +00:00

1485 lines
42 KiB
Perl

###############################################################################
#
# 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;