mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-19 14:38:18 +00:00
1485 lines
42 KiB
Perl
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;
|