1 ###############################################################################
3 # Class: NaturalDocs::Languages::CSharp
5 ###############################################################################
7 # A subclass to handle the language variations of C#.
10 # Topic: Language Support
15 # - Namespaces (no topic generated)
17 # - Constructors and Destructors
29 # - Autodocumenting enum members
32 ###############################################################################
34 # This file is part of Natural Docs, which is Copyright © 2003-2010 Greg Valure
35 # Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
36 # Refer to License.txt for the complete details
41 package NaturalDocs::Languages::CSharp;
43 use base 'NaturalDocs::Languages::Advanced';
46 ###############################################################################
47 # Group: Package Variables
51 # An existence hash of all the acceptable class keywords. The keys are in all lowercase.
53 my %classKeywords = ( 'class' => 1,
58 # hash: classModifiers
59 # An existence hash of all the acceptable class modifiers. The keys are in all lowercase.
61 my %classModifiers = ( 'new' => 1,
73 # hash: functionModifiers
74 # An existence hash of all the acceptable function modifiers. Also applies to properties. Also encompasses those for operators
75 # and indexers, but have more than are valid for them. The keys are in all lowercase.
77 my %functionModifiers = ( 'new' => 1,
91 # hash: variableModifiers
92 # An existence hash of all the acceptable variable modifiers. The keys are in all lowercase.
94 my %variableModifiers = ( 'new' => 1,
106 # An existence hash of all the possible enum types. The keys are in all lowercase.
108 my %enumTypes = ( 'sbyte' => 1,
118 # hash: impossibleTypeWords
119 # An existence hash of all the reserved words that cannot be in a type. This includes 'enum' and all modifiers. The keys are in
122 my %impossibleTypeWords = ( 'abstract' => 1, 'as' => 1, 'base' => 1, 'break' => 1, 'case' => 1, 'catch' => 1,
123 'checked' => 1, 'class' => 1, 'const' => 1, 'continue' => 1, 'default' => 1, 'delegate' => 1,
124 'do' => 1, 'else' => 1, 'enum' => 1, 'event' => 1, 'explicit' => 1, 'extern' => 1,
125 'false' => 1, 'finally' => 1, 'fixed' => 1, 'for' => 1, 'foreach' => 1, 'goto' => 1, 'if' => 1,
126 'implicit' => 1, 'in' => 1, 'interface' => 1, 'internal' => 1, 'is' => 1, 'lock' => 1,
127 'namespace' => 1, 'new' => 1, 'null' => 1, 'operator' => 1, 'out' => 1, 'override' => 1,
128 'params' => 1, 'private' => 1, 'protected' => 1, 'public' => 1, 'readonly' => 1, 'ref' => 1,
129 'return' => 1, 'sealed' => 1, 'sizeof' => 1, 'stackalloc' => 1, 'static' => 1,
130 'struct' => 1, 'switch' => 1, 'this' => 1, 'throw' => 1, 'true' => 1, 'try' => 1, 'typeof' => 1,
131 'unchecked' => 1, 'unsafe' => 1, 'using' => 1, 'virtual' => 1, 'volatile' => 1, 'while' => 1 );
132 # Deleted from the list: object, string, bool, decimal, sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, void
136 ###############################################################################
137 # Group: Interface Functions
141 # Function: PackageSeparator
142 # Returns the package separator symbol.
149 # Function: EnumValues
150 # Returns the <EnumValuesType> that describes how the language handles enums.
153 { return ::ENUM_UNDER_TYPE(); };
157 # Function: ParseFile
159 # Parses the passed source file, sending comments acceptable for documentation to <NaturalDocs::Parser->OnComment()>.
163 # sourceFile - The <FileName> to parse.
164 # topicList - A reference to the list of <NaturalDocs::Parser::ParsedTopics> being built by the file.
168 # The array ( autoTopics, scopeRecord ).
170 # autoTopics - An arrayref of automatically generated topics from the file, or undef if none.
171 # scopeRecord - An arrayref of <NaturalDocs::Languages::Advanced::ScopeChanges>, or undef if none.
173 sub ParseFile #(sourceFile, topicsList)
175 my ($self, $sourceFile, $topicsList) = @_;
177 $self->ParseForCommentsAndTokens($sourceFile, [ '//' ], [ '/*', '*/' ], [ '///' ], [ '/**', '*/' ] );
179 my $tokens = $self->Tokens();
183 while ($index < scalar @$tokens)
185 if ($self->TryToSkipWhitespace(\$index, \$lineNumber) ||
186 $self->TryToGetNamespace(\$index, \$lineNumber) ||
187 $self->TryToGetUsing(\$index, \$lineNumber) ||
188 $self->TryToGetClass(\$index, \$lineNumber) ||
189 $self->TryToGetFunction(\$index, \$lineNumber) ||
190 $self->TryToGetOverloadedOperator(\$index, \$lineNumber) ||
191 $self->TryToGetVariable(\$index, \$lineNumber) ||
192 $self->TryToGetEnum(\$index, \$lineNumber) )
194 # The functions above will handle everything.
197 elsif ($tokens->[$index] eq '{')
199 $self->StartScope('}', $lineNumber, undef, undef, undef);
203 elsif ($tokens->[$index] eq '}')
205 if ($self->ClosingScopeSymbol() eq '}')
206 { $self->EndScope($lineNumber); };
213 $self->SkipRestOfStatement(\$index, \$lineNumber);
218 # Don't need to keep these around.
219 $self->ClearTokens();
222 my $autoTopics = $self->AutoTopics();
224 my $scopeRecord = $self->ScopeRecord();
225 if (defined $scopeRecord && !scalar @$scopeRecord)
226 { $scopeRecord = undef; };
228 return ( $autoTopics, $scopeRecord );
233 ###############################################################################
234 # Group: Statement Parsing Functions
235 # All functions here assume that the current position is at the beginning of a statement.
237 # 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
238 # 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.
242 # Function: TryToGetNamespace
244 # Determines whether the position is at a namespace declaration statement, and if so, adjusts the scope, skips it, and returns
249 # 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#,
258 # That would result in a namespace topic whose only purpose is really to qualify C. It would take the default page title, and
259 # 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.
261 # If something actually appears in the namespace besides a class, it will be handled by
262 # <NaturalDocs::Parser->AddPackageDelineators()>. That function will add a package topic to correct the scope.
264 # If the user actually documented it, it will still appear because of the manual topic.
266 sub TryToGetNamespace #(indexRef, lineNumberRef)
268 my ($self, $indexRef, $lineNumberRef) = @_;
269 my $tokens = $self->Tokens();
271 if (lc($tokens->[$$indexRef]) ne 'namespace')
274 my $index = $$indexRef + 1;
275 my $lineNumber = $$lineNumberRef;
277 if (!$self->TryToSkipWhitespace(\$index, \$lineNumber))
282 while ($tokens->[$index] =~ /^[a-z_\.\@]/i)
284 $name .= $tokens->[$index];
291 $self->TryToSkipWhitespace(\$index, \$lineNumber);
293 if ($tokens->[$index] ne '{')
299 # We found a valid one if we made it this far.
301 my $autoTopic = NaturalDocs::Parser::ParsedTopic->New(::TOPIC_CLASS(), $name,
302 $self->CurrentScope(), $self->CurrentUsing(),
304 undef, undef, $$lineNumberRef);
306 # We don't add an auto-topic for namespaces. See the function documentation above.
308 NaturalDocs::Parser->OnClass($autoTopic->Package());
310 $self->StartScope('}', $lineNumber, $autoTopic->Package());
313 $$lineNumberRef = $lineNumber;
320 # Function: TryToGetClass
322 # Determines whether the position is at a class declaration statement, and if so, generates a topic for it, skips it, and
325 # Supported Syntaxes:
331 sub TryToGetClass #(indexRef, lineNumberRef)
333 my ($self, $indexRef, $lineNumberRef) = @_;
334 my $tokens = $self->Tokens();
336 my $index = $$indexRef;
337 my $lineNumber = $$lineNumberRef;
339 my $startIndex = $index;
340 my $startLine = $lineNumber;
341 my $needsPrototype = 0;
343 if ($self->TryToSkipAttributes(\$index, \$lineNumber))
344 { $self->TryToSkipWhitespace(\$index, \$lineNumber); }
348 while ($tokens->[$index] =~ /^[a-z]/i &&
349 !exists $classKeywords{lc($tokens->[$index])} &&
350 exists $classModifiers{lc($tokens->[$index])} )
352 push @modifiers, lc($tokens->[$index]);
355 $self->TryToSkipWhitespace(\$index, \$lineNumber);
358 if (!exists $classKeywords{lc($tokens->[$index])})
361 my $lcClassKeyword = lc($tokens->[$index]);
365 if (!$self->TryToSkipWhitespace(\$index, \$lineNumber))
370 while ($tokens->[$index] =~ /^[a-z_\@]/i)
372 $name .= $tokens->[$index];
379 $self->TryToSkipWhitespace(\$index, \$lineNumber);
381 if ($self->TryToSkipTemplateSpec(\$index, \$lineNumber))
384 $self->TryToSkipWhitespace(\$index, \$lineNumber);
386 if ($self->TryToSkipWhereClauses(\$index, \$lineNumber))
387 { $self->TryToSkipWhitespace(\$index, \$lineNumber); }
392 if ($tokens->[$index] eq ':')
394 my $inheritsTemplates;
400 $self->TryToSkipWhitespace(\$index, \$lineNumber);
404 while ($tokens->[$index] =~ /^[a-z_\.\@]/i)
406 $parentName .= $tokens->[$index];
410 if (!defined $parentName)
413 push @parents, NaturalDocs::SymbolString->FromText($parentName);
415 $self->TryToSkipWhitespace(\$index, \$lineNumber);
417 if ($self->TryToSkipTemplateSpec(\$index, \$lineNumber))
419 $inheritsTemplates = 1;
421 $self->TryToSkipWhitespace(\$index, \$lineNumber);
424 while ($tokens->[$index] eq ',');
426 if ($inheritsTemplates)
428 if ($self->TryToSkipWhereClauses(\$index, \$lineNumber))
429 { $self->TryToSkipWhitespace(\$index, \$lineNumber); }
433 if ($tokens->[$index] ne '{')
437 # If we made it this far, we have a valid class declaration.
439 my @scopeIdentifiers = NaturalDocs::SymbolString->IdentifiersOf($self->CurrentScope());
440 $name = join('.', @scopeIdentifiers, $name);
444 if ($lcClassKeyword eq 'interface')
445 { $topicType = ::TOPIC_INTERFACE(); }
447 { $topicType = ::TOPIC_CLASS(); };
453 $prototype = $self->CreateString($startIndex, $index);
456 my $autoTopic = NaturalDocs::Parser::ParsedTopic->New($topicType, $name,
457 undef, $self->CurrentUsing(),
459 undef, undef, $$lineNumberRef);
461 $self->AddAutoTopic($autoTopic);
462 NaturalDocs::Parser->OnClass($autoTopic->Package());
464 foreach my $parent (@parents)
466 NaturalDocs::Parser->OnClassParent($autoTopic->Package(), $parent, $self->CurrentScope(), undef,
467 ::RESOLVE_RELATIVE());
470 $self->StartScope('}', $lineNumber, $autoTopic->Package());
475 $$lineNumberRef = $lineNumber;
482 # Function: TryToGetUsing
484 # Determines whether the position is at a using statement, and if so, adds it to the current scope, skips it, and returns
495 sub TryToGetUsing #(indexRef, lineNumberRef)
497 my ($self, $indexRef, $lineNumberRef) = @_;
498 my $tokens = $self->Tokens();
500 my $index = $$indexRef;
501 my $lineNumber = $$lineNumberRef;
503 if (lc($tokens->[$index]) ne 'using')
507 $self->TryToSkipWhitespace(\$index, \$lineNumber);
511 while ($tokens->[$index] =~ /^[a-z_\@\.]/i)
513 $name .= $tokens->[$index];
517 if ($tokens->[$index] ne ';' ||
524 $self->AddUsing( NaturalDocs::SymbolString->FromText($name) );
527 $$lineNumberRef = $lineNumber;
535 # Function: TryToGetFunction
537 # Determines if the position is on a function declaration, and if so, generates a topic for it, skips it, and returns true.
539 # Supported Syntaxes:
549 sub TryToGetFunction #(indexRef, lineNumberRef)
551 my ($self, $indexRef, $lineNumberRef) = @_;
552 my $tokens = $self->Tokens();
554 my $index = $$indexRef;
555 my $lineNumber = $$lineNumberRef;
557 if ($self->TryToSkipAttributes(\$index, \$lineNumber))
558 { $self->TryToSkipWhitespace(\$index, \$lineNumber); };
560 my $startIndex = $index;
561 my $startLine = $lineNumber;
565 while ($tokens->[$index] =~ /^[a-z]/i &&
566 exists $functionModifiers{lc($tokens->[$index])} )
568 push @modifiers, lc($tokens->[$index]);
571 $self->TryToSkipWhitespace(\$index, \$lineNumber);
577 if (lc($tokens->[$index]) eq 'delegate')
581 $self->TryToSkipWhitespace(\$index, \$lineNumber);
583 elsif (lc($tokens->[$index]) eq 'event')
587 $self->TryToSkipWhitespace(\$index, \$lineNumber);
590 my $returnType = $self->TryToGetType(\$index, \$lineNumber);
592 $self->TryToSkipWhitespace(\$index, \$lineNumber);
598 while ($tokens->[$index] =~ /^[a-z\_\@\.\~]/i)
600 $name .= $tokens->[$index];
601 $lastNameWord = $tokens->[$index];
604 # For explicit generic interface definitions, such as
605 # IDObjectType System.Collections.Generic.IEnumerator<IDObjectType>.Current
606 # or templated functions.
608 if ($self->TryToSkipTemplateSpec(\$index, \$lineNumber))
611 $self->TryToSkipWhitespace(\$index, \$lineNumber);
617 # Constructors and destructors don't have return types. It's possible their names were mistaken for the return type.
618 if (defined $returnType)
623 $name =~ /([a-z0-9_]+)$/i;
630 # If there's no return type, make sure it's a constructor or destructor.
631 if (!defined $returnType)
633 my @identifiers = NaturalDocs::SymbolString->IdentifiersOf( $self->CurrentScope() );
635 if ($lastNameWord ne $identifiers[-1])
639 $self->TryToSkipWhitespace(\$index, \$lineNumber);
642 # Skip the brackets on indexers.
643 if ($tokens->[$index] eq '[' && lc($lastNameWord) eq 'this')
645 # This should jump the brackets completely.
646 $self->GenericSkip(\$index, \$lineNumber);
647 $self->TryToSkipWhitespace(\$index, \$lineNumber);
653 # Properties, indexers, events with braces
655 if ($tokens->[$index] eq '{')
657 my $prototype = $self->CreateString($startIndex, $index);
660 $self->TryToSkipWhitespace(\$index, \$lineNumber);
662 my ($aWord, $bWord, $hasA, $hasB);
675 while ($index < scalar @$tokens)
677 if ($self->TryToSkipAttributes(\$index, \$lineNumber))
678 { $self->TryToSkipWhitespace(\$index, \$lineNumber); };
680 if (lc($tokens->[$index]) eq $aWord)
682 elsif (lc($tokens->[$index]) eq $bWord)
684 elsif ($tokens->[$index] eq '}')
690 $self->SkipRestOfStatement(\$index, \$lineNumber);
691 $self->TryToSkipWhitespace(\$index, \$lineNumber);
695 { $prototype .= ' { ' . $aWord . ', ' . $bWord . ' }'; }
697 { $prototype .= ' { ' . $aWord . ' }'; }
699 { $prototype .= ' { ' . $bWord . ' }'; };
701 $prototype = $self->NormalizePrototype($prototype);
703 my $topicType = ( $isEvent ? ::TOPIC_EVENT() : ::TOPIC_PROPERTY() );
705 $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New($topicType, $name,
706 $self->CurrentScope(), $self->CurrentUsing(),
708 undef, undef, $startLine));
712 # Functions, constructors, destructors, delegates.
714 elsif ($tokens->[$index] eq '(')
716 # This should jump the parenthesis completely.
717 $self->GenericSkip(\$index, \$lineNumber);
718 $self->TryToSkipWhitespace(\$index, \$lineNumber);
720 if ($hasTemplates && $self->TryToSkipWhereClauses(\$index, \$lineNumber))
721 { $self->TryToSkipWhitespace(\$index, \$lineNumber); }
723 my $topicType = ( $isDelegate ? ::TOPIC_DELEGATE() : ::TOPIC_FUNCTION() );
724 my $prototype = $self->NormalizePrototype( $self->CreateString($startIndex, $index) );
726 $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New($topicType, $name,
727 $self->CurrentScope(), $self->CurrentUsing(),
729 undef, undef, $startLine));
731 $self->SkipRestOfStatement(\$index, \$lineNumber);
735 # Events without braces
737 elsif ($isEvent && $tokens->[$index] eq ';')
739 my $prototype = $self->NormalizePrototype( $self->CreateString($startIndex, $index) );
741 $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New(::TOPIC_EVENT(), $name,
742 $self->CurrentScope(), $self->CurrentUsing(),
744 undef, undef, $startLine));
752 # We succeeded if we got this far.
755 $$lineNumberRef = $lineNumber;
762 # Function: TryToGetOverloadedOperator
764 # Determines if the position is on an operator overload declaration, and if so, generates a topic for it, skips it, and returns true.
766 sub TryToGetOverloadedOperator #(indexRef, lineNumberRef)
768 my ($self, $indexRef, $lineNumberRef) = @_;
769 my $tokens = $self->Tokens();
771 my $index = $$indexRef;
772 my $lineNumber = $$lineNumberRef;
774 if ($self->TryToSkipAttributes(\$index, \$lineNumber))
775 { $self->TryToSkipWhitespace(\$index, \$lineNumber); };
777 my $startIndex = $index;
778 my $startLine = $lineNumber;
782 while ($tokens->[$index] =~ /^[a-z]/i &&
783 exists $functionModifiers{lc($tokens->[$index])} )
785 push @modifiers, lc($tokens->[$index]);
788 $self->TryToSkipWhitespace(\$index, \$lineNumber);
797 if (lc($tokens->[$index]) eq 'implicit' || lc($tokens->[$index]) eq 'explicit')
801 $self->TryToSkipWhitespace(\$index, \$lineNumber);
803 if (lc($tokens->[$index]) ne 'operator')
807 $self->TryToSkipWhitespace(\$index, \$lineNumber);
809 $name = $self->TryToGetType(\$index, \$lineNumber);
820 if (!$self->TryToGetType(\$index, \$lineNumber))
823 $self->TryToSkipWhitespace(\$index, \$lineNumber);
825 if (lc($tokens->[$index]) ne 'operator')
830 $self->TryToSkipWhitespace(\$index, \$lineNumber);
832 if (lc($tokens->[$index]) eq 'true' || lc($tokens->[$index]) eq 'false')
834 $name = $tokens->[$index];
839 while ($tokens->[$index] =~ /^[\+\-\!\~\*\/\%\&\|\^\<\>\=]$/)
841 $name .= $tokens->[$index];
847 $self->TryToSkipWhitespace(\$index, \$lineNumber);
849 if ($tokens->[$index] ne '(')
852 # This should skip the parenthesis completely.
853 $self->GenericSkip(\$index, \$lineNumber);
855 my $prototype = $self->NormalizePrototype( $self->CreateString($startIndex, $index) );
857 $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New(::TOPIC_FUNCTION(), 'operator ' . $name,
858 $self->CurrentScope(), $self->CurrentUsing(),
860 undef, undef, $startLine));
862 $self->SkipRestOfStatement(\$index, \$lineNumber);
865 # We succeeded if we got this far.
868 $$lineNumberRef = $lineNumber;
875 # Function: TryToGetVariable
877 # Determines if the position is on a variable declaration statement, and if so, generates a topic for each variable, skips the
878 # statement, and returns true.
880 # Supported Syntaxes:
885 sub TryToGetVariable #(indexRef, lineNumberRef)
887 my ($self, $indexRef, $lineNumberRef) = @_;
888 my $tokens = $self->Tokens();
890 my $index = $$indexRef;
891 my $lineNumber = $$lineNumberRef;
893 if ($self->TryToSkipAttributes(\$index, \$lineNumber))
894 { $self->TryToSkipWhitespace(\$index, \$lineNumber); };
896 my $startIndex = $index;
897 my $startLine = $lineNumber;
901 while ($tokens->[$index] =~ /^[a-z]/i &&
902 exists $variableModifiers{lc($tokens->[$index])} )
904 push @modifiers, lc($tokens->[$index]);
907 $self->TryToSkipWhitespace(\$index, \$lineNumber);
911 if (lc($tokens->[$index]) eq 'const')
913 $type = ::TOPIC_CONSTANT();
915 $self->TryToSkipWhitespace(\$index, \$lineNumber);
919 $type = ::TOPIC_VARIABLE();
922 if (!$self->TryToGetType(\$index, \$lineNumber))
925 my $endTypeIndex = $index;
927 $self->TryToSkipWhitespace(\$index, \$lineNumber);
935 while ($tokens->[$index] =~ /^[a-z\@\_]/i)
937 $name .= $tokens->[$index];
941 $self->TryToSkipWhitespace(\$index, \$lineNumber);
943 if ($tokens->[$index] eq '=')
947 $self->GenericSkip(\$index, \$lineNumber);
949 while ($tokens->[$index] ne ',' && $tokens->[$index] ne ';');
954 if ($tokens->[$index] eq ';')
959 elsif ($tokens->[$index] eq ',')
962 $self->TryToSkipWhitespace(\$index, \$lineNumber);
969 # We succeeded if we got this far.
971 my $prototypePrefix = $self->CreateString($startIndex, $endTypeIndex);
973 foreach my $name (@names)
975 my $prototype = $self->NormalizePrototype( $prototypePrefix . ' ' . $name );
977 $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New($type, $name,
978 $self->CurrentScope(), $self->CurrentUsing(),
980 undef, undef, $startLine));
984 $$lineNumberRef = $lineNumber;
991 # Function: TryToGetEnum
993 # Determines if the position is on an enum declaration statement, and if so, generates a topic for it.
995 # Supported Syntaxes:
998 # - Enums with declared types
1002 # - Documenting the members automatically
1004 sub TryToGetEnum #(indexRef, lineNumberRef)
1006 my ($self, $indexRef, $lineNumberRef) = @_;
1007 my $tokens = $self->Tokens();
1009 my $index = $$indexRef;
1010 my $lineNumber = $$lineNumberRef;
1012 if ($self->TryToSkipAttributes(\$index, \$lineNumber))
1013 { $self->TryToSkipWhitespace(\$index, \$lineNumber); };
1015 my $startIndex = $index;
1016 my $startLine = $lineNumber;
1020 while ($tokens->[$index] =~ /^[a-z]/i &&
1021 exists $variableModifiers{lc($tokens->[$index])} )
1023 push @modifiers, lc($tokens->[$index]);
1026 $self->TryToSkipWhitespace(\$index, \$lineNumber);
1029 if (lc($tokens->[$index]) ne 'enum')
1033 $self->TryToSkipWhitespace(\$index, \$lineNumber);
1037 while ($tokens->[$index] =~ /^[a-z\@\_]/i)
1039 $name .= $tokens->[$index];
1043 $self->TryToSkipWhitespace(\$index, \$lineNumber);
1045 if ($tokens->[$index] eq ':')
1048 $self->TryToSkipWhitespace(\$index, \$lineNumber);
1050 if (!exists $enumTypes{ lc($tokens->[$index]) })
1054 $self->TryToSkipWhitespace(\$index, \$lineNumber);
1057 if ($tokens->[$index] ne '{')
1060 # We succeeded if we got this far.
1062 my $prototype = $self->CreateString($startIndex, $index);
1063 $prototype = $self->NormalizePrototype( $prototype );
1065 $self->SkipRestOfStatement(\$index, \$lineNumber);
1067 $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New(::TOPIC_ENUMERATION(), $name,
1068 $self->CurrentScope(), $self->CurrentUsing(),
1070 undef, undef, $startLine));
1072 $$indexRef = $index;
1073 $$lineNumberRef = $lineNumber;
1080 # Function: TryToGetType
1082 # 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
1083 # modifiers. You must take care of those beforehand.
1085 sub TryToGetType #(indexRef, lineNumberRef)
1087 my ($self, $indexRef, $lineNumberRef) = @_;
1088 my $tokens = $self->Tokens();
1091 my $index = $$indexRef;
1092 my $lineNumber = $$lineNumberRef;
1094 while ($tokens->[$index] =~ /^[a-z\@\.\_]/i)
1096 # Case sensitive since you can declare a class Lock even though lock is a keyword.
1097 if (exists $impossibleTypeWords{ $tokens->[$index] } && $name !~ /\@$/)
1100 $name .= $tokens->[$index];
1107 $self->TryToSkipWhitespace(\$index, \$lineNumber);
1109 if ($tokens->[$index] eq '?')
1114 $self->TryToSkipWhitespace(\$index, \$lineNumber);
1117 if ($self->TryToSkipTemplateSpec(\$index, \$lineNumber))
1118 { $self->TryToSkipWhitespace(\$index, \$lineNumber); }
1120 while ($tokens->[$index] eq '[')
1125 while ($tokens->[$index] eq ',')
1131 if ($tokens->[$index] eq ']')
1140 $$indexRef = $index;
1141 $$lineNumberRef = $lineNumber;
1148 ###############################################################################
1149 # Group: Low Level Parsing Functions
1153 # Function: GenericSkip
1155 # Advances the position one place through general code.
1157 # - If the position is on a string, it will skip it completely.
1158 # - If the position is on an opening symbol, it will skip until the past the closing symbol.
1159 # - If the position is on whitespace (including comments and preprocessing directives), it will skip it completely.
1160 # - Otherwise it skips one token.
1164 # indexRef - A reference to the current index.
1165 # lineNumberRef - A reference to the current line number.
1167 sub GenericSkip #(indexRef, lineNumberRef)
1169 my ($self, $indexRef, $lineNumberRef) = @_;
1170 my $tokens = $self->Tokens();
1172 # We can ignore the scope stack because we're just skipping everything without parsing, and we need recursion anyway.
1173 if ($tokens->[$$indexRef] eq '{')
1176 $self->GenericSkipUntilAfter($indexRef, $lineNumberRef, '}');
1178 elsif ($tokens->[$$indexRef] eq '(')
1181 $self->GenericSkipUntilAfter($indexRef, $lineNumberRef, ')');
1183 elsif ($tokens->[$$indexRef] eq '[')
1186 $self->GenericSkipUntilAfter($indexRef, $lineNumberRef, ']');
1189 elsif ($self->TryToSkipWhitespace($indexRef, $lineNumberRef) ||
1190 $self->TryToSkipString($indexRef, $lineNumberRef))
1200 # Function: GenericSkipUntilAfter
1202 # Advances the position via <GenericSkip()> until a specific token is reached and passed.
1204 sub GenericSkipUntilAfter #(indexRef, lineNumberRef, token)
1206 my ($self, $indexRef, $lineNumberRef, $token) = @_;
1207 my $tokens = $self->Tokens();
1209 while ($$indexRef < scalar @$tokens && $tokens->[$$indexRef] ne $token)
1210 { $self->GenericSkip($indexRef, $lineNumberRef); };
1212 if ($tokens->[$$indexRef] eq "\n")
1213 { $$lineNumberRef++; };
1219 # Function: SkipRestOfStatement
1221 # Advances the position via <GenericSkip()> until after the end of the current statement, which is defined as a semicolon or
1222 # a brace group. Of course, either of those appearing inside parenthesis, a nested brace group, etc. don't count.
1224 sub SkipRestOfStatement #(indexRef, lineNumberRef)
1226 my ($self, $indexRef, $lineNumberRef) = @_;
1227 my $tokens = $self->Tokens();
1229 while ($$indexRef < scalar @$tokens &&
1230 $tokens->[$$indexRef] ne ';' &&
1231 $tokens->[$$indexRef] ne '{')
1233 $self->GenericSkip($indexRef, $lineNumberRef);
1236 if ($tokens->[$$indexRef] eq ';')
1238 elsif ($tokens->[$$indexRef] eq '{')
1239 { $self->GenericSkip($indexRef, $lineNumberRef); };
1244 # Function: TryToSkipString
1245 # If the current position is on a string delimiter, skip past the string and return true.
1249 # indexRef - A reference to the index of the position to start at.
1250 # lineNumberRef - A reference to the line number of the position.
1254 # Whether the position was at a string.
1258 # - Supports quotes, apostrophes, and at-quotes.
1260 sub TryToSkipString #(indexRef, lineNumberRef)
1262 my ($self, $indexRef, $lineNumberRef) = @_;
1263 my $tokens = $self->Tokens();
1265 # The three string delimiters. All three are Perl variables when preceded by a dollar sign.
1266 if ($self->SUPER::TryToSkipString($indexRef, $lineNumberRef, '\'') ||
1267 $self->SUPER::TryToSkipString($indexRef, $lineNumberRef, '"') )
1271 elsif ($tokens->[$$indexRef] eq '@' && $tokens->[$$indexRef+1] eq '"')
1275 # We need to do at-strings manually because backslash characters are accepted as regular characters, and two consecutive
1276 # quotes are accepted as well.
1278 while ($$indexRef < scalar @$tokens && !($tokens->[$$indexRef] eq '"' && $tokens->[$$indexRef+1] ne '"') )
1280 if ($tokens->[$$indexRef] eq '"')
1282 # This is safe because the while condition will only let through quote pairs.
1285 elsif ($tokens->[$$indexRef] eq "\n")
1296 # Skip the closing quote.
1297 if ($$indexRef < scalar @$tokens)
1308 # Function: TryToSkipAttributes
1309 # If the current position is on an attribute section, skip it and return true. Skips multiple attribute sections if they appear
1312 sub TryToSkipAttributes #(indexRef, lineNumberRef)
1314 my ($self, $indexRef, $lineNumberRef) = @_;
1315 my $tokens = $self->Tokens();
1319 while ($tokens->[$$indexRef] eq '[')
1323 $self->GenericSkipUntilAfter($indexRef, $lineNumberRef, ']');
1324 $self->TryToSkipWhitespace($indexRef, $lineNumberRef);
1332 # Function: TryToSkipTemplateSpec
1333 # If the current position is on a template spec (the part in angle brackets) skip it and return true. Can handle nested
1336 sub TryToSkipTemplateSpec #(indexRef, lineNumberRef)
1338 my ($self, $indexRef, $lineNumberRef) = @_;
1339 my $tokens = $self->Tokens();
1341 if ($tokens->[$$indexRef] eq '<')
1343 my $nestingLevel = 1;
1345 $self->TryToSkipWhitespace($indexRef, $lineNumberRef);
1347 while ($$indexRef < scalar @$tokens && $nestingLevel > 0)
1349 if ($tokens->[$$indexRef] eq '<')
1350 { $nestingLevel++; }
1351 elsif ($tokens->[$$indexRef] eq '>')
1352 { $nestingLevel--; }
1355 $self->TryToSkipWhitespace($indexRef, $lineNumberRef);
1366 # Function: TryToSkipWhereClauses
1367 # If the current position is on a "where" clause, skips it and returns true. Can handle multiple wheres in a row.
1369 sub TryToSkipWhereClauses #(indexRef, lineNumberRef)
1371 my ($self, $indexRef, $lineNumberRef) = @_;
1372 my $tokens = $self->Tokens();
1374 if ($tokens->[$$indexRef] ne 'where')
1377 my $index = $$indexRef;
1378 my $lineNumber = $$lineNumberRef;
1382 $index++; # Skip the where
1383 $self->TryToSkipWhitespace(\$index, \$lineNumber);
1385 $index++; # Skip the variable name
1386 $self->TryToSkipWhitespace(\$index, \$lineNumber);
1388 if ($tokens->[$index] ne ':')
1391 $index++; # Skip the colon
1392 $self->TryToSkipWhitespace(\$index, \$lineNumber);
1396 if ($index >= scalar @$tokens)
1398 elsif ($tokens->[$index] eq 'new')
1401 $self->TryToSkipWhitespace(\$index, \$lineNumber);
1403 if ($tokens->[$index] ne '(')
1407 $self->TryToSkipWhitespace(\$index, \$lineNumber);
1409 if ($tokens->[$index] ne ')')
1413 $self->TryToSkipWhitespace(\$index, \$lineNumber);
1417 while ($index < scalar @$tokens && $tokens->[$index] =~ /^[a-z0-9_\.\@]+$/i)
1420 $self->TryToSkipWhitespace(\$index, \$lineNumber);
1422 if ($self->TryToSkipTemplateSpec(\$index, \$lineNumber))
1423 { $self->TryToSkipWhitespace(\$index, \$lineNumber); }
1426 if ($tokens->[$index] eq ',')
1429 $self->TryToSkipWhitespace(\$index, \$lineNumber);
1435 while ($tokens->[$index] eq 'where');
1437 $$indexRef = $index;
1438 $$lineNumberRef = $lineNumber;
1445 # Function: TryToSkipWhitespace
1446 # If the current position is on a whitespace token, a line break token, a comment, or a preprocessing directive, it skips them
1447 # and returns true. If there are a number of these in a row, it skips them all.
1449 sub TryToSkipWhitespace #(indexRef, lineNumberRef)
1451 my ($self, $indexRef, $lineNumberRef) = @_;
1452 my $tokens = $self->Tokens();
1456 while ($$indexRef < scalar @$tokens)
1458 if ($tokens->[$$indexRef] =~ /^[ \t]/)
1463 elsif ($tokens->[$$indexRef] eq "\n")
1469 elsif ($self->TryToSkipComment($indexRef, $lineNumberRef) ||
1470 $self->TryToSkipPreprocessingDirective($indexRef, $lineNumberRef))
1483 # Function: TryToSkipComment
1484 # If the current position is on a comment, skip past it and return true.
1486 sub TryToSkipComment #(indexRef, lineNumberRef)
1488 my ($self, $indexRef, $lineNumberRef) = @_;
1490 return ( $self->TryToSkipLineComment($indexRef, $lineNumberRef) ||
1491 $self->TryToSkipMultilineComment($indexRef, $lineNumberRef) );
1496 # Function: TryToSkipLineComment
1497 # If the current position is on a line comment symbol, skip past it and return true.
1499 sub TryToSkipLineComment #(indexRef, lineNumberRef)
1501 my ($self, $indexRef, $lineNumberRef) = @_;
1502 my $tokens = $self->Tokens();
1504 if ($tokens->[$$indexRef] eq '/' && $tokens->[$$indexRef+1] eq '/')
1506 $self->SkipRestOfLine($indexRef, $lineNumberRef);
1515 # Function: TryToSkipMultilineComment
1516 # If the current position is on an opening comment symbol, skip past it and return true.
1518 sub TryToSkipMultilineComment #(indexRef, lineNumberRef)
1520 my ($self, $indexRef, $lineNumberRef) = @_;
1521 my $tokens = $self->Tokens();
1523 if ($tokens->[$$indexRef] eq '/' && $tokens->[$$indexRef+1] eq '*')
1525 $self->SkipUntilAfter($indexRef, $lineNumberRef, '*', '/');
1534 # Function: TryToSkipPreprocessingDirective
1535 # If the current position is on a preprocessing directive, skip past it and return true.
1537 sub TryToSkipPreprocessingDirective #(indexRef, lineNumberRef)
1539 my ($self, $indexRef, $lineNumberRef) = @_;
1540 my $tokens = $self->Tokens();
1542 if ($tokens->[$$indexRef] eq '#' && $self->IsFirstLineToken($$indexRef))
1544 $self->SkipRestOfLine($indexRef, $lineNumberRef);