OSDN Git Service

Version 5.91
[vbslib/main.git] / GPL_bin_fullset / NaturalDocs / Modules / NaturalDocs / Languages / CSharp.pm
1 ###############################################################################
2 #
3 #   Class: NaturalDocs::Languages::CSharp
4 #
5 ###############################################################################
6 #
7 #   A subclass to handle the language variations of C#.
8 #
9 #
10 #   Topic: Language Support
11 #
12 #       Supported:
13 #
14 #       - Classes
15 #       - Namespaces (no topic generated)
16 #       - Functions
17 #       - Constructors and Destructors
18 #       - Properties
19 #       - Indexers
20 #       - Operators
21 #       - Delegates
22 #       - Variables
23 #       - Constants
24 #       - Events
25 #       - Enums
26 #
27 #       Not supported yet:
28 #
29 #       - Autodocumenting enum members
30 #       - Using alias
31 #
32 ###############################################################################
33
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
37
38 use strict;
39 use integer;
40
41 package NaturalDocs::Languages::CSharp;
42
43 use base 'NaturalDocs::Languages::Advanced';
44
45
46 ###############################################################################
47 # Group: Package Variables
48
49 #
50 #   hash: classKeywords
51 #   An existence hash of all the acceptable class keywords.  The keys are in all lowercase.
52 #
53 my %classKeywords = ( 'class' => 1,
54                                     'struct' => 1,
55                                     'interface' => 1 );
56
57 #
58 #   hash: classModifiers
59 #   An existence hash of all the acceptable class modifiers.  The keys are in all lowercase.
60 #
61 my %classModifiers = ( 'new' => 1,
62                                    'public' => 1,
63                                    'protected' => 1,
64                                    'internal' => 1,
65                                    'private' => 1,
66                                    'abstract' => 1,
67                                    'sealed' => 1,
68                                    'unsafe' => 1,
69                                    'static' => 1,
70                                    'partial' => 1 );
71
72 #
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.
76 #
77 my %functionModifiers = ( 'new' => 1,
78                                        'public' => 1,
79                                        'protected' => 1,
80                                        'internal' => 1,
81                                        'private' => 1,
82                                        'static' => 1,
83                                        'virtual' => 1,
84                                        'sealed' => 1,
85                                        'override' => 1,
86                                        'abstract' => 1,
87                                        'extern' => 1,
88                                        'unsafe' => 1 );
89
90 #
91 #   hash: variableModifiers
92 #   An existence hash of all the acceptable variable modifiers.  The keys are in all lowercase.
93 #
94 my %variableModifiers = ( 'new' => 1,
95                                        'public' => 1,
96                                        'protected' => 1,
97                                        'internal' => 1,
98                                        'private' => 1,
99                                        'static' => 1,
100                                        'readonly' => 1,
101                                        'volatile' => 1,
102                                        'unsafe' => 1 );
103
104 #
105 #   hash: enumTypes
106 #   An existence hash of all the possible enum types.  The keys are in all lowercase.
107 #
108 my %enumTypes = ( 'sbyte' => 1,
109                              'byte' => 1,
110                              'short' => 1,
111                              'ushort' => 1,
112                              'int' => 1,
113                              'uint' => 1,
114                              'long' => 1,
115                              'ulong' => 1 );
116
117 #
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
120 #   all lowercase.
121 #
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
133
134
135
136 ###############################################################################
137 # Group: Interface Functions
138
139
140 #
141 #   Function: PackageSeparator
142 #   Returns the package separator symbol.
143 #
144 sub PackageSeparator
145     {  return '.';  };
146
147
148 #
149 #   Function: EnumValues
150 #   Returns the <EnumValuesType> that describes how the language handles enums.
151 #
152 sub EnumValues
153     {  return ::ENUM_UNDER_TYPE();  };
154
155
156 #
157 #   Function: ParseFile
158 #
159 #   Parses the passed source file, sending comments acceptable for documentation to <NaturalDocs::Parser->OnComment()>.
160 #
161 #   Parameters:
162 #
163 #       sourceFile - The <FileName> to parse.
164 #       topicList - A reference to the list of <NaturalDocs::Parser::ParsedTopics> being built by the file.
165 #
166 #   Returns:
167 #
168 #       The array ( autoTopics, scopeRecord ).
169 #
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.
172 #
173 sub ParseFile #(sourceFile, topicsList)
174     {
175     my ($self, $sourceFile, $topicsList) = @_;
176
177     $self->ParseForCommentsAndTokens($sourceFile, [ '//' ], [ '/*', '*/' ], [ '///' ], [ '/**', '*/' ] );
178
179     my $tokens = $self->Tokens();
180     my $index = 0;
181     my $lineNumber = 1;
182
183     while ($index < scalar @$tokens)
184         {
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) )
193             {
194             # The functions above will handle everything.
195             }
196
197         elsif ($tokens->[$index] eq '{')
198             {
199             $self->StartScope('}', $lineNumber, undef, undef, undef);
200             $index++;
201             }
202
203         elsif ($tokens->[$index] eq '}')
204             {
205             if ($self->ClosingScopeSymbol() eq '}')
206                 {  $self->EndScope($lineNumber);  };
207
208             $index++;
209             }
210
211         else
212             {
213             $self->SkipRestOfStatement(\$index, \$lineNumber);
214             };
215         };
216
217
218     # Don't need to keep these around.
219     $self->ClearTokens();
220
221
222     my $autoTopics = $self->AutoTopics();
223
224     my $scopeRecord = $self->ScopeRecord();
225     if (defined $scopeRecord && !scalar @$scopeRecord)
226         {  $scopeRecord = undef;  };
227
228     return ( $autoTopics, $scopeRecord );
229     };
230
231
232
233 ###############################################################################
234 # Group: Statement Parsing Functions
235 # All functions here assume that the current position is at the beginning of a statement.
236 #
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.
239
240
241 #
242 #   Function: TryToGetNamespace
243 #
244 #   Determines whether the position is at a namespace declaration statement, and if so, adjusts the scope, skips it, and returns
245 #   true.
246 #
247 #   Why no topic?:
248 #
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#,
250 #       you must do this:
251 #
252 #       > namespace A.B
253 #       >    {
254 #       >    class C
255 #       >        { ... }
256 #       >    }
257 #
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.
260 #
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.
263 #
264 #       If the user actually documented it, it will still appear because of the manual topic.
265 #
266 sub TryToGetNamespace #(indexRef, lineNumberRef)
267     {
268     my ($self, $indexRef, $lineNumberRef) = @_;
269     my $tokens = $self->Tokens();
270
271     if (lc($tokens->[$$indexRef]) ne 'namespace')
272         {  return undef;  };
273
274     my $index = $$indexRef + 1;
275     my $lineNumber = $$lineNumberRef;
276
277     if (!$self->TryToSkipWhitespace(\$index, \$lineNumber))
278         {  return undef;  };
279
280     my $name;
281
282     while ($tokens->[$index] =~ /^[a-z_\.\@]/i)
283         {
284         $name .= $tokens->[$index];
285         $index++;
286         };
287
288     if (!defined $name)
289         {  return undef;  };
290
291     $self->TryToSkipWhitespace(\$index, \$lineNumber);
292
293     if ($tokens->[$index] ne '{')
294         {  return undef;  };
295
296     $index++;
297
298
299     # We found a valid one if we made it this far.
300
301     my $autoTopic = NaturalDocs::Parser::ParsedTopic->New(::TOPIC_CLASS(), $name,
302                                                                                          $self->CurrentScope(), $self->CurrentUsing(),
303                                                                                          undef,
304                                                                                          undef, undef, $$lineNumberRef);
305
306     # We don't add an auto-topic for namespaces.  See the function documentation above.
307
308     NaturalDocs::Parser->OnClass($autoTopic->Package());
309
310     $self->StartScope('}', $lineNumber, $autoTopic->Package());
311
312     $$indexRef = $index;
313     $$lineNumberRef = $lineNumber;
314
315     return 1;
316     };
317
318
319 #
320 #   Function: TryToGetClass
321 #
322 #   Determines whether the position is at a class declaration statement, and if so, generates a topic for it, skips it, and
323 #   returns true.
324 #
325 #   Supported Syntaxes:
326 #
327 #       - Classes
328 #       - Structs
329 #       - Interfaces
330 #
331 sub TryToGetClass #(indexRef, lineNumberRef)
332     {
333     my ($self, $indexRef, $lineNumberRef) = @_;
334     my $tokens = $self->Tokens();
335
336     my $index = $$indexRef;
337     my $lineNumber = $$lineNumberRef;
338
339     my $startIndex = $index;
340     my $startLine = $lineNumber;
341     my $needsPrototype = 0;
342
343     if ($self->TryToSkipAttributes(\$index, \$lineNumber))
344         {  $self->TryToSkipWhitespace(\$index, \$lineNumber);  }
345
346     my @modifiers;
347
348     while ($tokens->[$index] =~ /^[a-z]/i &&
349               !exists $classKeywords{lc($tokens->[$index])} &&
350               exists $classModifiers{lc($tokens->[$index])} )
351         {
352         push @modifiers, lc($tokens->[$index]);
353         $index++;
354
355         $self->TryToSkipWhitespace(\$index, \$lineNumber);
356         };
357
358     if (!exists $classKeywords{lc($tokens->[$index])})
359         {  return undef;  };
360
361     my $lcClassKeyword = lc($tokens->[$index]);
362
363     $index++;
364
365     if (!$self->TryToSkipWhitespace(\$index, \$lineNumber))
366         {  return undef;  };
367
368     my $name;
369
370     while ($tokens->[$index] =~ /^[a-z_\@]/i)
371         {
372         $name .= $tokens->[$index];
373         $index++;
374         };
375
376     if (!defined $name)
377         {  return undef;  };
378
379     $self->TryToSkipWhitespace(\$index, \$lineNumber);
380
381         if ($self->TryToSkipTemplateSpec(\$index, \$lineNumber))
382                 {
383                 $needsPrototype = 1;
384                 $self->TryToSkipWhitespace(\$index, \$lineNumber);
385
386                 if ($self->TryToSkipWhereClauses(\$index, \$lineNumber))
387                         {  $self->TryToSkipWhitespace(\$index, \$lineNumber);  }
388                 }
389
390     my @parents;
391
392     if ($tokens->[$index] eq ':')
393         {
394         my $inheritsTemplates;
395
396         do
397             {
398             $index++;
399
400             $self->TryToSkipWhitespace(\$index, \$lineNumber);
401
402             my $parentName;
403
404             while ($tokens->[$index] =~ /^[a-z_\.\@]/i)
405                 {
406                 $parentName .= $tokens->[$index];
407                 $index++;
408                 };
409
410             if (!defined $parentName)
411                 {  return undef;  };
412
413             push @parents, NaturalDocs::SymbolString->FromText($parentName);
414
415             $self->TryToSkipWhitespace(\$index, \$lineNumber);
416
417             if ($self->TryToSkipTemplateSpec(\$index, \$lineNumber))
418                 {
419                 $inheritsTemplates = 1;
420                 $needsPrototype = 1;
421                 $self->TryToSkipWhitespace(\$index, \$lineNumber);
422                 }
423             }
424         while ($tokens->[$index] eq ',');
425
426         if ($inheritsTemplates)
427                 {
428                 if ($self->TryToSkipWhereClauses(\$index, \$lineNumber))
429                         {  $self->TryToSkipWhitespace(\$index, \$lineNumber);  }
430                 }
431         };
432
433     if ($tokens->[$index] ne '{')
434         {  return undef;  };
435
436
437     # If we made it this far, we have a valid class declaration.
438
439     my @scopeIdentifiers = NaturalDocs::SymbolString->IdentifiersOf($self->CurrentScope());
440     $name = join('.', @scopeIdentifiers, $name);
441
442     my $topicType;
443
444     if ($lcClassKeyword eq 'interface')
445         {  $topicType = ::TOPIC_INTERFACE();  }
446     else
447         {  $topicType = ::TOPIC_CLASS();  };
448
449     my $prototype;
450
451     if ($needsPrototype)
452             {
453             $prototype = $self->CreateString($startIndex, $index);
454             }
455
456     my $autoTopic = NaturalDocs::Parser::ParsedTopic->New($topicType, $name,
457                                                                                          undef, $self->CurrentUsing(),
458                                                                                          $prototype,
459                                                                                          undef, undef, $$lineNumberRef);
460
461     $self->AddAutoTopic($autoTopic);
462     NaturalDocs::Parser->OnClass($autoTopic->Package());
463
464     foreach my $parent (@parents)
465         {
466         NaturalDocs::Parser->OnClassParent($autoTopic->Package(), $parent, $self->CurrentScope(), undef,
467                                                                ::RESOLVE_RELATIVE());
468         };
469
470     $self->StartScope('}', $lineNumber, $autoTopic->Package());
471
472     $index++;
473
474     $$indexRef = $index;
475     $$lineNumberRef = $lineNumber;
476
477     return 1;
478     };
479
480
481 #
482 #   Function: TryToGetUsing
483 #
484 #   Determines whether the position is at a using statement, and if so, adds it to the current scope, skips it, and returns
485 #        true.
486 #
487 #        Supported:
488 #
489 #       - Using
490 #
491 #        Unsupported:
492 #
493 #                - Using with alias
494 #
495 sub TryToGetUsing #(indexRef, lineNumberRef)
496     {
497     my ($self, $indexRef, $lineNumberRef) = @_;
498     my $tokens = $self->Tokens();
499
500     my $index = $$indexRef;
501     my $lineNumber = $$lineNumberRef;
502
503     if (lc($tokens->[$index]) ne 'using')
504         {  return undef;  };
505
506     $index++;
507     $self->TryToSkipWhitespace(\$index, \$lineNumber);
508
509     my $name;
510
511     while ($tokens->[$index] =~ /^[a-z_\@\.]/i)
512         {
513         $name .= $tokens->[$index];
514         $index++;
515         };
516
517     if ($tokens->[$index] ne ';' ||
518                 !defined $name)
519         {  return undef;  };
520
521     $index++;
522
523
524     $self->AddUsing( NaturalDocs::SymbolString->FromText($name) );
525
526     $$indexRef = $index;
527     $$lineNumberRef = $lineNumber;
528
529     return 1;
530     };
531
532
533
534 #
535 #   Function: TryToGetFunction
536 #
537 #   Determines if the position is on a function declaration, and if so, generates a topic for it, skips it, and returns true.
538 #
539 #   Supported Syntaxes:
540 #
541 #       - Functions
542 #       - Constructors
543 #       - Destructors
544 #       - Properties
545 #       - Indexers
546 #       - Delegates
547 #       - Events
548 #
549 sub TryToGetFunction #(indexRef, lineNumberRef)
550     {
551     my ($self, $indexRef, $lineNumberRef) = @_;
552     my $tokens = $self->Tokens();
553
554     my $index = $$indexRef;
555     my $lineNumber = $$lineNumberRef;
556
557     if ($self->TryToSkipAttributes(\$index, \$lineNumber))
558         {  $self->TryToSkipWhitespace(\$index, \$lineNumber);  };
559
560     my $startIndex = $index;
561     my $startLine = $lineNumber;
562
563     my @modifiers;
564
565     while ($tokens->[$index] =~ /^[a-z]/i &&
566               exists $functionModifiers{lc($tokens->[$index])} )
567         {
568         push @modifiers, lc($tokens->[$index]);
569         $index++;
570
571         $self->TryToSkipWhitespace(\$index, \$lineNumber);
572         };
573
574     my $isDelegate;
575     my $isEvent;
576
577     if (lc($tokens->[$index]) eq 'delegate')
578         {
579         $isDelegate = 1;
580         $index++;
581         $self->TryToSkipWhitespace(\$index, \$lineNumber);
582         }
583     elsif (lc($tokens->[$index]) eq 'event')
584         {
585         $isEvent = 1;
586         $index++;
587         $self->TryToSkipWhitespace(\$index, \$lineNumber);
588         };
589
590     my $returnType = $self->TryToGetType(\$index, \$lineNumber);
591
592     $self->TryToSkipWhitespace(\$index, \$lineNumber);
593
594     my $name;
595     my $lastNameWord;
596     my $hasTemplates;
597
598     while ($tokens->[$index] =~ /^[a-z\_\@\.\~]/i)
599         {
600         $name .= $tokens->[$index];
601         $lastNameWord = $tokens->[$index];
602         $index++;
603
604         # For explicit generic interface definitions, such as
605         # IDObjectType System.Collections.Generic.IEnumerator<IDObjectType>.Current
606         # or templated functions.
607
608         if ($self->TryToSkipTemplateSpec(\$index, \$lineNumber))
609                 {
610                 $hasTemplates = 1;
611                 $self->TryToSkipWhitespace(\$index, \$lineNumber);
612                 }
613         };
614
615     if (!defined $name)
616         {
617         # Constructors and destructors don't have return types.  It's possible their names were mistaken for the return type.
618         if (defined $returnType)
619             {
620             $name = $returnType;
621             $returnType = undef;
622
623             $name =~ /([a-z0-9_]+)$/i;
624             $lastNameWord = $1;
625             }
626         else
627             {  return undef;  };
628         };
629
630     # If there's no return type, make sure it's a constructor or destructor.
631     if (!defined $returnType)
632         {
633         my @identifiers = NaturalDocs::SymbolString->IdentifiersOf( $self->CurrentScope() );
634
635         if ($lastNameWord ne $identifiers[-1])
636             {  return undef;  };
637         };
638
639     $self->TryToSkipWhitespace(\$index, \$lineNumber);
640
641
642     # Skip the brackets on indexers.
643     if ($tokens->[$index] eq '[' && lc($lastNameWord) eq 'this')
644         {
645         # This should jump the brackets completely.
646         $self->GenericSkip(\$index, \$lineNumber);
647         $self->TryToSkipWhitespace(\$index, \$lineNumber);
648
649         $name .= '[]';
650         };
651
652
653     # Properties, indexers, events with braces
654
655     if ($tokens->[$index] eq '{')
656         {
657         my $prototype = $self->CreateString($startIndex, $index);
658
659         $index++;
660         $self->TryToSkipWhitespace(\$index, \$lineNumber);
661
662         my ($aWord, $bWord, $hasA, $hasB);
663
664         if ($isEvent)
665             {
666             $aWord = 'add';
667             $bWord = 'remove';
668             }
669         else
670             {
671             $aWord = 'get';
672             $bWord = 'set';
673             };
674
675         while ($index < scalar @$tokens)
676             {
677             if ($self->TryToSkipAttributes(\$index, \$lineNumber))
678                 {  $self->TryToSkipWhitespace(\$index, \$lineNumber);  };
679
680             if (lc($tokens->[$index]) eq $aWord)
681                 {  $hasA = 1;  }
682             elsif (lc($tokens->[$index]) eq $bWord)
683                 {  $hasB = 1;  }
684             elsif ($tokens->[$index] eq '}')
685                 {
686                 $index++;
687                 last;
688                 };
689
690             $self->SkipRestOfStatement(\$index, \$lineNumber);
691             $self->TryToSkipWhitespace(\$index, \$lineNumber);
692             };
693
694         if ($hasA && $hasB)
695             {  $prototype .= ' { ' . $aWord . ', ' . $bWord . ' }';  }
696         elsif ($hasA)
697             {  $prototype .= ' { ' . $aWord . ' }';  }
698         elsif ($hasB)
699             {  $prototype .= ' { ' . $bWord . ' }';  };
700
701         $prototype = $self->NormalizePrototype($prototype);
702
703         my $topicType = ( $isEvent ? ::TOPIC_EVENT() : ::TOPIC_PROPERTY() );
704
705         $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New($topicType, $name,
706                                                                                                   $self->CurrentScope(), $self->CurrentUsing(),
707                                                                                                   $prototype,
708                                                                                                   undef, undef, $startLine));
709         }
710
711
712     # Functions, constructors, destructors, delegates.
713
714     elsif ($tokens->[$index] eq '(')
715         {
716         # This should jump the parenthesis completely.
717         $self->GenericSkip(\$index, \$lineNumber);
718                 $self->TryToSkipWhitespace(\$index, \$lineNumber);
719
720         if ($hasTemplates && $self->TryToSkipWhereClauses(\$index, \$lineNumber))
721                 {  $self->TryToSkipWhitespace(\$index, \$lineNumber);  }
722
723         my $topicType = ( $isDelegate ? ::TOPIC_DELEGATE() : ::TOPIC_FUNCTION() );
724         my $prototype = $self->NormalizePrototype( $self->CreateString($startIndex, $index) );
725
726         $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New($topicType, $name,
727                                                                                                   $self->CurrentScope(), $self->CurrentUsing(),
728                                                                                                   $prototype,
729                                                                                                   undef, undef, $startLine));
730
731         $self->SkipRestOfStatement(\$index, \$lineNumber);
732         }
733
734
735     # Events without braces
736
737     elsif ($isEvent && $tokens->[$index] eq ';')
738         {
739         my $prototype = $self->NormalizePrototype( $self->CreateString($startIndex, $index) );
740
741         $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New(::TOPIC_EVENT(), $name,
742                                                                                                   $self->CurrentScope(), $self->CurrentUsing(),
743                                                                                                   $prototype,
744                                                                                                   undef, undef, $startLine));
745         $index++;
746         }
747
748     else
749         {  return undef;  };
750
751
752     # We succeeded if we got this far.
753
754     $$indexRef = $index;
755     $$lineNumberRef = $lineNumber;
756
757     return 1;
758     };
759
760
761 #
762 #   Function: TryToGetOverloadedOperator
763 #
764 #   Determines if the position is on an operator overload declaration, and if so, generates a topic for it, skips it, and returns true.
765 #
766 sub TryToGetOverloadedOperator #(indexRef, lineNumberRef)
767     {
768     my ($self, $indexRef, $lineNumberRef) = @_;
769     my $tokens = $self->Tokens();
770
771     my $index = $$indexRef;
772     my $lineNumber = $$lineNumberRef;
773
774     if ($self->TryToSkipAttributes(\$index, \$lineNumber))
775         {  $self->TryToSkipWhitespace(\$index, \$lineNumber);  };
776
777     my $startIndex = $index;
778     my $startLine = $lineNumber;
779
780     my @modifiers;
781
782     while ($tokens->[$index] =~ /^[a-z]/i &&
783               exists $functionModifiers{lc($tokens->[$index])} )
784         {
785         push @modifiers, lc($tokens->[$index]);
786         $index++;
787
788         $self->TryToSkipWhitespace(\$index, \$lineNumber);
789         };
790
791
792     my $name;
793
794
795     # Casting operators.
796
797     if (lc($tokens->[$index]) eq 'implicit' || lc($tokens->[$index]) eq 'explicit')
798         {
799         $index++;
800
801         $self->TryToSkipWhitespace(\$index, \$lineNumber);
802
803         if (lc($tokens->[$index]) ne 'operator')
804             {  return undef;  };
805
806         $index++;
807         $self->TryToSkipWhitespace(\$index, \$lineNumber);
808
809         $name = $self->TryToGetType(\$index, \$lineNumber);
810
811         if (!defined $name)
812             {  return undef;  };
813         }
814
815
816     # Symbol operators.
817
818     else
819         {
820         if (!$self->TryToGetType(\$index, \$lineNumber))
821             {  return undef;  };
822
823         $self->TryToSkipWhitespace(\$index, \$lineNumber);
824
825         if (lc($tokens->[$index]) ne 'operator')
826             {  return undef;  };
827
828         $index++;
829
830         $self->TryToSkipWhitespace(\$index, \$lineNumber);
831
832         if (lc($tokens->[$index]) eq 'true' || lc($tokens->[$index]) eq 'false')
833             {
834             $name = $tokens->[$index];
835             $index++;
836             }
837         else
838             {
839             while ($tokens->[$index] =~ /^[\+\-\!\~\*\/\%\&\|\^\<\>\=]$/)
840                 {
841                 $name .= $tokens->[$index];
842                 $index++;
843                 };
844             };
845         };
846
847     $self->TryToSkipWhitespace(\$index, \$lineNumber);
848
849     if ($tokens->[$index] ne '(')
850         {  return undef;  };
851
852     # This should skip the parenthesis completely.
853     $self->GenericSkip(\$index, \$lineNumber);
854
855     my $prototype = $self->NormalizePrototype( $self->CreateString($startIndex, $index) );
856
857     $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New(::TOPIC_FUNCTION(), 'operator ' . $name,
858                                                                                               $self->CurrentScope(), $self->CurrentUsing(),
859                                                                                               $prototype,
860                                                                                               undef, undef, $startLine));
861
862     $self->SkipRestOfStatement(\$index, \$lineNumber);
863
864
865     # We succeeded if we got this far.
866
867     $$indexRef = $index;
868     $$lineNumberRef = $lineNumber;
869
870     return 1;
871     };
872
873
874 #
875 #   Function: TryToGetVariable
876 #
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.
879 #
880 #   Supported Syntaxes:
881 #
882 #       - Variables
883 #       - Constants
884 #
885 sub TryToGetVariable #(indexRef, lineNumberRef)
886     {
887     my ($self, $indexRef, $lineNumberRef) = @_;
888     my $tokens = $self->Tokens();
889
890     my $index = $$indexRef;
891     my $lineNumber = $$lineNumberRef;
892
893     if ($self->TryToSkipAttributes(\$index, \$lineNumber))
894         {  $self->TryToSkipWhitespace(\$index, \$lineNumber);  };
895
896     my $startIndex = $index;
897     my $startLine = $lineNumber;
898
899     my @modifiers;
900
901     while ($tokens->[$index] =~ /^[a-z]/i &&
902               exists $variableModifiers{lc($tokens->[$index])} )
903         {
904         push @modifiers, lc($tokens->[$index]);
905         $index++;
906
907         $self->TryToSkipWhitespace(\$index, \$lineNumber);
908         };
909
910     my $type;
911     if (lc($tokens->[$index]) eq 'const')
912         {
913         $type = ::TOPIC_CONSTANT();
914         $index++;
915         $self->TryToSkipWhitespace(\$index, \$lineNumber);
916         }
917     else
918         {
919         $type = ::TOPIC_VARIABLE();
920         };
921
922     if (!$self->TryToGetType(\$index, \$lineNumber))
923         {  return undef;  };
924
925     my $endTypeIndex = $index;
926
927     $self->TryToSkipWhitespace(\$index, \$lineNumber);
928
929     my @names;
930
931     for (;;)
932         {
933         my $name;
934
935         while ($tokens->[$index] =~ /^[a-z\@\_]/i)
936             {
937             $name .= $tokens->[$index];
938             $index++;
939             };
940
941         $self->TryToSkipWhitespace(\$index, \$lineNumber);
942
943         if ($tokens->[$index] eq '=')
944             {
945             do
946                 {
947                 $self->GenericSkip(\$index, \$lineNumber);
948                 }
949             while ($tokens->[$index] ne ',' && $tokens->[$index] ne ';');
950             };
951
952         push @names, $name;
953
954         if ($tokens->[$index] eq ';')
955             {
956             $index++;
957             last;
958             }
959         elsif ($tokens->[$index] eq ',')
960             {
961             $index++;
962             $self->TryToSkipWhitespace(\$index, \$lineNumber);
963             }
964         else
965             {  return undef;  };
966         };
967
968
969     # We succeeded if we got this far.
970
971     my $prototypePrefix = $self->CreateString($startIndex, $endTypeIndex);
972
973     foreach my $name (@names)
974         {
975         my $prototype = $self->NormalizePrototype( $prototypePrefix . ' ' . $name );
976
977         $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New($type, $name,
978                                                                                                   $self->CurrentScope(), $self->CurrentUsing(),
979                                                                                                   $prototype,
980                                                                                                   undef, undef, $startLine));
981         };
982
983     $$indexRef = $index;
984     $$lineNumberRef = $lineNumber;
985
986     return 1;
987     };
988
989
990 #
991 #   Function: TryToGetEnum
992 #
993 #   Determines if the position is on an enum declaration statement, and if so, generates a topic for it.
994 #
995 #   Supported Syntaxes:
996 #
997 #       - Enums
998 #       - Enums with declared types
999 #
1000 #   Unsupported:
1001 #
1002 #       - Documenting the members automatically
1003 #
1004 sub TryToGetEnum #(indexRef, lineNumberRef)
1005     {
1006     my ($self, $indexRef, $lineNumberRef) = @_;
1007     my $tokens = $self->Tokens();
1008
1009     my $index = $$indexRef;
1010     my $lineNumber = $$lineNumberRef;
1011
1012     if ($self->TryToSkipAttributes(\$index, \$lineNumber))
1013         {  $self->TryToSkipWhitespace(\$index, \$lineNumber);  };
1014
1015     my $startIndex = $index;
1016     my $startLine = $lineNumber;
1017
1018     my @modifiers;
1019
1020     while ($tokens->[$index] =~ /^[a-z]/i &&
1021               exists $variableModifiers{lc($tokens->[$index])} )
1022         {
1023         push @modifiers, lc($tokens->[$index]);
1024         $index++;
1025
1026         $self->TryToSkipWhitespace(\$index, \$lineNumber);
1027         };
1028
1029     if (lc($tokens->[$index]) ne 'enum')
1030         {  return undef;  }
1031
1032     $index++;
1033     $self->TryToSkipWhitespace(\$index, \$lineNumber);
1034
1035     my $name;
1036
1037     while ($tokens->[$index] =~ /^[a-z\@\_]/i)
1038         {
1039         $name .= $tokens->[$index];
1040         $index++;
1041         };
1042
1043     $self->TryToSkipWhitespace(\$index, \$lineNumber);
1044
1045     if ($tokens->[$index] eq ':')
1046         {
1047         $index++;
1048         $self->TryToSkipWhitespace(\$index, \$lineNumber);
1049
1050         if (!exists $enumTypes{ lc($tokens->[$index]) })
1051             {  return undef;  }
1052
1053         $index++;
1054         $self->TryToSkipWhitespace(\$index, \$lineNumber);
1055         }
1056
1057     if ($tokens->[$index] ne '{')
1058         {  return undef;  }
1059
1060     # We succeeded if we got this far.
1061
1062     my $prototype = $self->CreateString($startIndex, $index);
1063     $prototype = $self->NormalizePrototype( $prototype );
1064
1065     $self->SkipRestOfStatement(\$index, \$lineNumber);
1066
1067     $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New(::TOPIC_ENUMERATION(), $name,
1068                                                                                               $self->CurrentScope(), $self->CurrentUsing(),
1069                                                                                               $prototype,
1070                                                                                               undef, undef, $startLine));
1071
1072     $$indexRef = $index;
1073     $$lineNumberRef = $lineNumber;
1074
1075     return 1;
1076     };
1077
1078
1079 #
1080 #   Function: TryToGetType
1081 #
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.
1084 #
1085 sub TryToGetType #(indexRef, lineNumberRef)
1086     {
1087     my ($self, $indexRef, $lineNumberRef) = @_;
1088     my $tokens = $self->Tokens();
1089
1090     my $name;
1091     my $index = $$indexRef;
1092     my $lineNumber = $$lineNumberRef;
1093
1094     while ($tokens->[$index] =~ /^[a-z\@\.\_]/i)
1095         {
1096                 # Case sensitive since you can declare a class Lock even though lock is a keyword.
1097         if (exists $impossibleTypeWords{ $tokens->[$index] } && $name !~ /\@$/)
1098             {  return undef;  };
1099
1100         $name .= $tokens->[$index];
1101         $index++;
1102         };
1103
1104     if (!defined $name)
1105         {  return undef;  };
1106
1107         $self->TryToSkipWhitespace(\$index, \$lineNumber);
1108
1109         if ($tokens->[$index] eq '?')
1110                 {
1111                 $name .= '?';
1112                 $index++;
1113
1114                 $self->TryToSkipWhitespace(\$index, \$lineNumber);
1115                 }
1116
1117     if ($self->TryToSkipTemplateSpec(\$index, \$lineNumber))
1118         {  $self->TryToSkipWhitespace(\$index, \$lineNumber);  }
1119
1120     while ($tokens->[$index] eq '[')
1121         {
1122         $name .= '[';
1123         $index++;
1124
1125         while ($tokens->[$index] eq ',')
1126             {
1127             $name .= ',';
1128             $index++;
1129             };
1130
1131         if ($tokens->[$index] eq ']')
1132             {
1133             $name .= ']';
1134             $index++;
1135             }
1136         else
1137             {  return undef;  }
1138         };
1139
1140     $$indexRef = $index;
1141     $$lineNumberRef = $lineNumber;
1142
1143     return $name;
1144     };
1145
1146
1147
1148 ###############################################################################
1149 # Group: Low Level Parsing Functions
1150
1151
1152 #
1153 #   Function: GenericSkip
1154 #
1155 #   Advances the position one place through general code.
1156 #
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.
1161 #
1162 #   Parameters:
1163 #
1164 #       indexRef - A reference to the current index.
1165 #       lineNumberRef - A reference to the current line number.
1166 #
1167 sub GenericSkip #(indexRef, lineNumberRef)
1168     {
1169     my ($self, $indexRef, $lineNumberRef) = @_;
1170     my $tokens = $self->Tokens();
1171
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 '{')
1174         {
1175         $$indexRef++;
1176         $self->GenericSkipUntilAfter($indexRef, $lineNumberRef, '}');
1177         }
1178     elsif ($tokens->[$$indexRef] eq '(')
1179         {
1180         $$indexRef++;
1181         $self->GenericSkipUntilAfter($indexRef, $lineNumberRef, ')');
1182         }
1183     elsif ($tokens->[$$indexRef] eq '[')
1184         {
1185         $$indexRef++;
1186         $self->GenericSkipUntilAfter($indexRef, $lineNumberRef, ']');
1187         }
1188
1189     elsif ($self->TryToSkipWhitespace($indexRef, $lineNumberRef) ||
1190             $self->TryToSkipString($indexRef, $lineNumberRef))
1191         {
1192         }
1193
1194     else
1195         {  $$indexRef++;  };
1196     };
1197
1198
1199 #
1200 #   Function: GenericSkipUntilAfter
1201 #
1202 #   Advances the position via <GenericSkip()> until a specific token is reached and passed.
1203 #
1204 sub GenericSkipUntilAfter #(indexRef, lineNumberRef, token)
1205     {
1206     my ($self, $indexRef, $lineNumberRef, $token) = @_;
1207     my $tokens = $self->Tokens();
1208
1209     while ($$indexRef < scalar @$tokens && $tokens->[$$indexRef] ne $token)
1210         {  $self->GenericSkip($indexRef, $lineNumberRef);  };
1211
1212     if ($tokens->[$$indexRef] eq "\n")
1213         {  $$lineNumberRef++;  };
1214     $$indexRef++;
1215     };
1216
1217
1218 #
1219 #   Function: SkipRestOfStatement
1220 #
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.
1223 #
1224 sub SkipRestOfStatement #(indexRef, lineNumberRef)
1225     {
1226     my ($self, $indexRef, $lineNumberRef) = @_;
1227     my $tokens = $self->Tokens();
1228
1229     while ($$indexRef < scalar @$tokens &&
1230              $tokens->[$$indexRef] ne ';' &&
1231              $tokens->[$$indexRef] ne '{')
1232         {
1233         $self->GenericSkip($indexRef, $lineNumberRef);
1234         };
1235
1236     if ($tokens->[$$indexRef] eq ';')
1237         {  $$indexRef++;  }
1238     elsif ($tokens->[$$indexRef] eq '{')
1239         {  $self->GenericSkip($indexRef, $lineNumberRef);  };
1240     };
1241
1242
1243 #
1244 #   Function: TryToSkipString
1245 #   If the current position is on a string delimiter, skip past the string and return true.
1246 #
1247 #   Parameters:
1248 #
1249 #       indexRef - A reference to the index of the position to start at.
1250 #       lineNumberRef - A reference to the line number of the position.
1251 #
1252 #   Returns:
1253 #
1254 #       Whether the position was at a string.
1255 #
1256 #   Syntax Support:
1257 #
1258 #       - Supports quotes, apostrophes, and at-quotes.
1259 #
1260 sub TryToSkipString #(indexRef, lineNumberRef)
1261     {
1262     my ($self, $indexRef, $lineNumberRef) = @_;
1263     my $tokens = $self->Tokens();
1264
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, '"') )
1268         {
1269         return 1;
1270         }
1271     elsif ($tokens->[$$indexRef] eq '@' && $tokens->[$$indexRef+1] eq '"')
1272         {
1273         $$indexRef += 2;
1274
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.
1277
1278         while ($$indexRef < scalar @$tokens && !($tokens->[$$indexRef] eq '"' && $tokens->[$$indexRef+1] ne '"') )
1279             {
1280             if ($tokens->[$$indexRef] eq '"')
1281                 {
1282                 # This is safe because the while condition will only let through quote pairs.
1283                 $$indexRef += 2;
1284                 }
1285             elsif ($tokens->[$$indexRef] eq "\n")
1286                 {
1287                 $$indexRef++;
1288                 $$lineNumberRef++;
1289                 }
1290             else
1291                 {
1292                 $$indexRef++;
1293                 };
1294             };
1295
1296         # Skip the closing quote.
1297         if ($$indexRef < scalar @$tokens)
1298             {  $$indexRef++;  };
1299
1300         return 1;
1301         }
1302     else
1303         {  return undef;  };
1304     };
1305
1306
1307 #
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
1310 #   consecutively.
1311 #
1312 sub TryToSkipAttributes #(indexRef, lineNumberRef)
1313     {
1314     my ($self, $indexRef, $lineNumberRef) = @_;
1315     my $tokens = $self->Tokens();
1316
1317     my $success;
1318
1319     while ($tokens->[$$indexRef] eq '[')
1320         {
1321         $success = 1;
1322         $$indexRef++;
1323         $self->GenericSkipUntilAfter($indexRef, $lineNumberRef, ']');
1324         $self->TryToSkipWhitespace($indexRef, $lineNumberRef);
1325         };
1326
1327     return $success;
1328     };
1329
1330
1331 #
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
1334 #       templates.
1335 #
1336 sub TryToSkipTemplateSpec #(indexRef, lineNumberRef)
1337         {
1338         my ($self, $indexRef, $lineNumberRef) = @_;
1339         my $tokens = $self->Tokens();
1340
1341     if ($tokens->[$$indexRef] eq '<')
1342         {
1343         my $nestingLevel = 1;
1344         $$indexRef++;
1345         $self->TryToSkipWhitespace($indexRef, $lineNumberRef);
1346
1347         while ($$indexRef < scalar @$tokens && $nestingLevel > 0)
1348                 {
1349                 if ($tokens->[$$indexRef] eq '<')
1350                         {  $nestingLevel++;  }
1351                 elsif ($tokens->[$$indexRef] eq '>')
1352                         {  $nestingLevel--;  }
1353
1354             $$indexRef++;
1355                 $self->TryToSkipWhitespace($indexRef, $lineNumberRef);
1356                         }
1357
1358                 return 1;
1359         }
1360     else
1361         {  return undef;  }
1362         }
1363
1364
1365 #
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.
1368 #
1369 sub TryToSkipWhereClauses #(indexRef, lineNumberRef)
1370         {
1371         my ($self, $indexRef, $lineNumberRef) = @_;
1372         my $tokens = $self->Tokens();
1373
1374     if ($tokens->[$$indexRef] ne 'where')
1375         {  return undef;  }
1376
1377         my $index = $$indexRef;
1378         my $lineNumber = $$lineNumberRef;
1379
1380     do
1381         {
1382         $index++;  # Skip the where
1383         $self->TryToSkipWhitespace(\$index, \$lineNumber);
1384
1385         $index++;  # Skip the variable name
1386         $self->TryToSkipWhitespace(\$index, \$lineNumber);
1387
1388         if ($tokens->[$index] ne ':')
1389                 {  return undef;  }
1390
1391         $index++;  # Skip the colon
1392         $self->TryToSkipWhitespace(\$index, \$lineNumber);
1393
1394         for (;;)
1395                 {
1396                 if ($index >= scalar @$tokens)
1397                         {  return undef;  }
1398                 elsif ($tokens->[$index] eq 'new')
1399                         {
1400                         $index++;
1401                         $self->TryToSkipWhitespace(\$index, \$lineNumber);
1402
1403                         if ($tokens->[$index] ne '(')
1404                                 {  return undef;  }
1405
1406                 $index++;
1407                         $self->TryToSkipWhitespace(\$index, \$lineNumber);
1408
1409                         if ($tokens->[$index] ne ')')
1410                                 {  return undef;  }
1411
1412                         $index++;
1413                         $self->TryToSkipWhitespace(\$index, \$lineNumber);
1414                         }
1415                 else
1416                         {
1417                         while ($index < scalar @$tokens && $tokens->[$index] =~ /^[a-z0-9_\.\@]+$/i)
1418                                 {  $index++;  }
1419
1420                         $self->TryToSkipWhitespace(\$index, \$lineNumber);
1421
1422                         if ($self->TryToSkipTemplateSpec(\$index, \$lineNumber))
1423                                 {  $self->TryToSkipWhitespace(\$index, \$lineNumber);  }
1424                         }
1425
1426                 if ($tokens->[$index] eq ',')
1427                         {
1428                         $index++;
1429                         $self->TryToSkipWhitespace(\$index, \$lineNumber);
1430                         }
1431                 else
1432                         {  last;  }
1433                 }
1434         }
1435     while ($tokens->[$index] eq 'where');
1436
1437     $$indexRef = $index;
1438     $$lineNumberRef = $lineNumber;
1439
1440     return 1;
1441         }
1442
1443
1444 #
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.
1448 #
1449 sub TryToSkipWhitespace #(indexRef, lineNumberRef)
1450     {
1451     my ($self, $indexRef, $lineNumberRef) = @_;
1452     my $tokens = $self->Tokens();
1453
1454     my $result;
1455
1456     while ($$indexRef < scalar @$tokens)
1457         {
1458         if ($tokens->[$$indexRef] =~ /^[ \t]/)
1459             {
1460             $$indexRef++;
1461             $result = 1;
1462             }
1463         elsif ($tokens->[$$indexRef] eq "\n")
1464             {
1465             $$indexRef++;
1466             $$lineNumberRef++;
1467             $result = 1;
1468             }
1469         elsif ($self->TryToSkipComment($indexRef, $lineNumberRef) ||
1470                 $self->TryToSkipPreprocessingDirective($indexRef, $lineNumberRef))
1471             {
1472             $result = 1;
1473             }
1474         else
1475             {  last;  };
1476         };
1477
1478     return $result;
1479     };
1480
1481
1482 #
1483 #   Function: TryToSkipComment
1484 #   If the current position is on a comment, skip past it and return true.
1485 #
1486 sub TryToSkipComment #(indexRef, lineNumberRef)
1487     {
1488     my ($self, $indexRef, $lineNumberRef) = @_;
1489
1490     return ( $self->TryToSkipLineComment($indexRef, $lineNumberRef) ||
1491                 $self->TryToSkipMultilineComment($indexRef, $lineNumberRef) );
1492     };
1493
1494
1495 #
1496 #   Function: TryToSkipLineComment
1497 #   If the current position is on a line comment symbol, skip past it and return true.
1498 #
1499 sub TryToSkipLineComment #(indexRef, lineNumberRef)
1500     {
1501     my ($self, $indexRef, $lineNumberRef) = @_;
1502     my $tokens = $self->Tokens();
1503
1504     if ($tokens->[$$indexRef] eq '/' && $tokens->[$$indexRef+1] eq '/')
1505         {
1506         $self->SkipRestOfLine($indexRef, $lineNumberRef);
1507         return 1;
1508         }
1509     else
1510         {  return undef;  };
1511     };
1512
1513
1514 #
1515 #   Function: TryToSkipMultilineComment
1516 #   If the current position is on an opening comment symbol, skip past it and return true.
1517 #
1518 sub TryToSkipMultilineComment #(indexRef, lineNumberRef)
1519     {
1520     my ($self, $indexRef, $lineNumberRef) = @_;
1521     my $tokens = $self->Tokens();
1522
1523     if ($tokens->[$$indexRef] eq '/' && $tokens->[$$indexRef+1] eq '*')
1524         {
1525         $self->SkipUntilAfter($indexRef, $lineNumberRef, '*', '/');
1526         return 1;
1527         }
1528     else
1529         {  return undef;  };
1530     };
1531
1532
1533 #
1534 #   Function: TryToSkipPreprocessingDirective
1535 #   If the current position is on a preprocessing directive, skip past it and return true.
1536 #
1537 sub TryToSkipPreprocessingDirective #(indexRef, lineNumberRef)
1538     {
1539     my ($self, $indexRef, $lineNumberRef) = @_;
1540     my $tokens = $self->Tokens();
1541
1542     if ($tokens->[$$indexRef] eq '#' && $self->IsFirstLineToken($$indexRef))
1543         {
1544         $self->SkipRestOfLine($indexRef, $lineNumberRef);
1545         return 1;
1546         }
1547     else
1548         {  return undef;  };
1549     };
1550
1551
1552 1;