<html><head><meta name="color-scheme" content="light dark"></head><body><pre style="word-wrap: break-word; white-space: pre-wrap;">###############################################################################
#
#   Class: NaturalDocs::Languages::ActionScript
#
###############################################################################
#
#   A subclass to handle the language variations of Flash ActionScript.
#
###############################################################################

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

use base 'NaturalDocs::Languages::Advanced';


################################################################################
# Group: Constants and Types


#
#   Constants: XML Tag Type
#
#   XML_OPENING_TAG - The tag is an opening one, such as &lt;tag&gt;.
#   XML_CLOSING_TAG - The tag is a closing one, such as &lt;/tag&gt;.
#   XML_SELF_CONTAINED_TAG - The tag is self contained, such as &lt;tag /&gt;.
#
use constant XML_OPENING_TAG =&gt; 1;
use constant XML_CLOSING_TAG =&gt; 2;
use constant XML_SELF_CONTAINED_TAG =&gt; 3;


################################################################################
# Group: Package Variables

#
#   hash: classModifiers
#   An existence hash of all the acceptable class modifiers.  The keys are in all lowercase.
#
my %classModifiers = ( 'dynamic' =&gt; 1,
                                   'intrinsic' =&gt; 1,
                                   'final' =&gt; 1,
                                   'internal' =&gt; 1,
                                   'public' =&gt; 1 );

#
#   hash: memberModifiers
#   An existence hash of all the acceptable class member modifiers.  The keys are in all lowercase.
#
my %memberModifiers = ( 'public' =&gt; 1,
                                        'private' =&gt; 1,
                                        'protected' =&gt; 1,
                                        'static' =&gt; 1,
                                        'internal' =&gt; 1,
                                        'override' =&gt; 1 );


#
#   hash: declarationEnders
#   An existence hash of all the tokens that can end a declaration.  This is important because statements don't require a semicolon
#   to end.  The keys are in all lowercase.
#
my %declarationEnders = ( ';' =&gt; 1,
                                        '}' =&gt; 1,
                                        '{' =&gt; 1,
                                        'public' =&gt; 1,
                                        'private' =&gt; 1,
                                        'protected' =&gt; 1,
                                        'static' =&gt; 1,
                                        'internal' =&gt; 1,
                                        'dynamic' =&gt; 1,
                                        'intrinsic' =&gt; 1,
                                        'final' =&gt; 1,
                                        'override' =&gt; 1,
                                        'class' =&gt; 1,
                                        'interface' =&gt; 1,
                                        'var' =&gt; 1,
                                        'function' =&gt; 1,
                                        'const' =&gt; 1,
                                        'namespace' =&gt; 1,
                                        'import' =&gt; 1 );


#
#   var: isEscaped
#   Whether the current file being parsed uses escapement.
#
my $isEscaped;



################################################################################
# Group: Interface Functions


#
#   Function: PackageSeparator
#   Returns the package separator symbol.
#
sub PackageSeparator
    {  return '.';  };


#
#   Function: EnumValues
#   Returns the &lt;EnumValuesType&gt; that describes how the language handles enums.
#
sub EnumValues
    {  return ::ENUM_GLOBAL();  };


#
#   Function: ParseParameterLine
#   Parses a prototype parameter line and returns it as a &lt;NaturalDocs::Languages::Prototype::Parameter&gt; object.
#
sub ParseParameterLine #(line)
    {
    my ($self, $line) = @_;

    if ($line =~ /^ ?\.\.\.\ (.+)$/)
        {
        # This puts them in the wrong fields as $1 should be the name and ... should be the type.  However, this is necessary
        # because the order in the source is reversed from other parameter declarations and it's more important for the output
        # to match the source.
        return NaturalDocs::Languages::Prototype::Parameter-&gt;New($1, undef, '...', undef, undef, undef);
        }
    else
        {  return $self-&gt;ParsePascalParameterLine($line);  };
    };


#
#   Function: TypeBeforeParameter
#   Returns whether the type appears before the parameter in prototypes.
#
sub TypeBeforeParameter
    {  return 0;  };


#
#   Function: PreprocessFile
#
#   If the file is escaped, strips out all unescaped code.  Will translate any unescaped comments into comments surrounded by
#   "\x1C\x1D\x1E\x1F" and "\x1F\x1E\x1D" characters, so chosen because they are the same character lengths as &lt;!-- and --&gt;
#   and will not appear in normal code.
#
sub PreprocessFile
    {
    my ($self, $lines) = @_;

    if (!$isEscaped)
        {  return;  };

    use constant MODE_UNESCAPED_REGULAR =&gt; 1;
    use constant MODE_UNESCAPED_PI =&gt; 2;
    use constant MODE_UNESCAPED_CDATA =&gt; 3;
    use constant MODE_UNESCAPED_COMMENT =&gt; 4;
    use constant MODE_ESCAPED_UNKNOWN_CDATA =&gt; 5;
    use constant MODE_ESCAPED_CDATA =&gt; 6;
    use constant MODE_ESCAPED_NO_CDATA =&gt; 7;

    my $mode = MODE_UNESCAPED_REGULAR;

    for (my $i = 0; $i &lt; scalar @$lines; $i++)
        {
        my @tokens = split(/(&lt;[ \t]*\/?[ \t]*mx:Script[^&gt;]*&gt;|&lt;\?|\?&gt;|&lt;\!--|--&gt;|&lt;\!\[CDATA\[|\]\]\&gt;)/, $lines-&gt;[$i]);
        my $newLine;

        foreach my $token (@tokens)
            {
            if ($mode == MODE_UNESCAPED_REGULAR)
                {
                if ($token eq '&lt;?')
                    {  $mode = MODE_UNESCAPED_PI;  }
                elsif ($token eq '&lt;![CDATA[')
                    {  $mode = MODE_UNESCAPED_CDATA;  }
                elsif ($token eq '&lt;!--')
                    {
                    $mode = MODE_UNESCAPED_COMMENT;
                    $newLine .= "\x1C\x1D\x1E\x1F";
                    }
                elsif ($token =~ /^&lt;[ \t]*mx:Script/)
                    {  $mode = MODE_ESCAPED_UNKNOWN_CDATA;  };
                }

            elsif ($mode == MODE_UNESCAPED_PI)
                {
                if ($token eq '?&gt;')
                    {  $mode = MODE_UNESCAPED_REGULAR;  };
                }

            elsif ($mode == MODE_UNESCAPED_CDATA)
                {
                if ($token eq ']]&gt;')
                    {  $mode = MODE_UNESCAPED_REGULAR;  };
                }

            elsif ($mode == MODE_UNESCAPED_COMMENT)
                {
                if ($token eq '--&gt;')
                    {
                    $mode = MODE_UNESCAPED_REGULAR;
                    $newLine .= "\x1F\x1E\x1D";
                    }
                else
                    {  $newLine .= $token;  };
                }

            elsif ($mode == MODE_ESCAPED_UNKNOWN_CDATA)
                {
                if ($token eq '&lt;![CDATA[')
                    {  $mode = MODE_ESCAPED_CDATA;  }
                elsif ($token =~ /^&lt;[ \t]*\/[ \t]*mx:Script/)
                    {
                    $mode = MODE_UNESCAPED_REGULAR;
                    $newLine .= '; ';
                    }
                elsif ($token !~ /^[ \t]*$/)
                    {
                    $mode = MODE_ESCAPED_NO_CDATA;
                    $newLine .= $token;
                    };
                }

            elsif ($mode == MODE_ESCAPED_CDATA)
                {
                if ($token eq ']]&gt;')
                    {
                    $mode = MODE_UNESCAPED_REGULAR;
                    $newLine .= '; ';
                    }
                else
                    {  $newLine .= $token;  };
                }

            else #($mode == MODE_ESCAPED_NO_CDATA)
                {
                if ($token =~ /^&lt;[ \t]*\/[ \t]*mx:Script/)
                    {
                    $mode = MODE_UNESCAPED_REGULAR;
                    $newLine .= '; ';
                    }
                else
                    {  $newLine .= $token;  };
                };

            };

        $lines-&gt;[$i] = $newLine;
        };
    };


#
#   Function: ParseFile
#
#   Parses the passed source file, sending comments acceptable for documentation to &lt;NaturalDocs::Parser-&gt;OnComment()&gt;.
#
#   Parameters:
#
#       sourceFile - The &lt;FileName&gt; to parse.
#       topicList - A reference to the list of &lt;NaturalDocs::Parser::ParsedTopics&gt; 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 &lt;NaturalDocs::Languages::Advanced::ScopeChanges&gt;, or undef if none.
#
sub ParseFile #(sourceFile, topicsList)
    {
    my ($self, $sourceFile, $topicsList) = @_;

    # The \x1# comment symbols are inserted by PreprocessFile() to stand in for XML comments in escaped files.
    my @parseParameters = ( [ '//' ], [ '/*', '*/', "\x1C\x1D\x1E\x1F", "\x1F\x1E\x1D" ], [ '///' ], [ '/**', '*/' ] );

    my $extension = lc(NaturalDocs::File-&gt;ExtensionOf($sourceFile));
    $isEscaped = ($extension eq 'mxml');

    $self-&gt;ParseForCommentsAndTokens($sourceFile, @parseParameters);

    my $tokens = $self-&gt;Tokens();
    my $index = 0;
    my $lineNumber = 1;

    while ($index &lt; scalar @$tokens)
        {
        if ($self-&gt;TryToSkipWhitespace(\$index, \$lineNumber) ||
            $self-&gt;TryToGetImport(\$index, \$lineNumber) ||
            $self-&gt;TryToGetClass(\$index, \$lineNumber) ||
            $self-&gt;TryToGetFunction(\$index, \$lineNumber) ||
            $self-&gt;TryToGetVariable(\$index, \$lineNumber) )
            {
            # The functions above will handle everything.
            }

        elsif ($tokens-&gt;[$index] eq '{')
            {
            $self-&gt;StartScope('}', $lineNumber, undef, undef, undef);
            $index++;
            }

        elsif ($tokens-&gt;[$index] eq '}')
            {
            if ($self-&gt;ClosingScopeSymbol() eq '}')
                {  $self-&gt;EndScope($lineNumber);  };

            $index++;
            }

        else
            {
            $self-&gt;SkipToNextStatement(\$index, \$lineNumber);
            };
        };


    # Don't need to keep these around.
    $self-&gt;ClearTokens();


    my $autoTopics = $self-&gt;AutoTopics();

    my $scopeRecord = $self-&gt;ScopeRecord();
    if (defined $scopeRecord &amp;&amp; !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: TryToGetIdentifier
#
#   Determines whether the position is at an identifier, and if so, skips it and returns the complete identifier as a string.  Returns
#   undef otherwise.
#
#   Parameters:
#
#       indexRef - A reference to the current token index.
#       lineNumberRef - A reference to the current line number.
#       allowStar - If set, allows the last identifier to be a star.
#
sub TryToGetIdentifier #(indexRef, lineNumberRef, allowStar)
    {
    my ($self, $indexRef, $lineNumberRef, $allowStar) = @_;
    my $tokens = $self-&gt;Tokens();

    my $index = $$indexRef;

    use constant MODE_IDENTIFIER_START =&gt; 1;
    use constant MODE_IN_IDENTIFIER =&gt; 2;
    use constant MODE_AFTER_STAR =&gt; 3;

    my $identifier;
    my $mode = MODE_IDENTIFIER_START;

    while ($index &lt; scalar @$tokens)
        {
        if ($mode == MODE_IDENTIFIER_START)
            {
            if ($tokens-&gt;[$index] =~ /^[a-z\$\_]/i)
                {
                $identifier .= $tokens-&gt;[$index];
                $index++;

                $mode = MODE_IN_IDENTIFIER;
                }
            elsif ($allowStar &amp;&amp; $tokens-&gt;[$index] eq '*')
                {
                $identifier .= '*';
                $index++;

                $mode = MODE_AFTER_STAR;
                }
            else
                {  return undef;  };
            }

        elsif ($mode == MODE_IN_IDENTIFIER)
            {
            if ($tokens-&gt;[$index] eq '.')
                {
                $identifier .= '.';
                $index++;

                $mode = MODE_IDENTIFIER_START;
                }
            elsif ($tokens-&gt;[$index] =~ /^[a-z0-9\$\_]/i)
                {
                $identifier .= $tokens-&gt;[$index];
                $index++;
                }
            else
                {  last;  };
            }

        else #($mode == MODE_AFTER_STAR)
            {
            if ($tokens-&gt;[$index] =~ /^[a-z0-9\$\_\.]/i)
                {  return undef;  }
            else
                {  last;  };
            };
        };

    # We need to check again because we may have run out of tokens after a dot.
    if ($mode != MODE_IDENTIFIER_START)
        {
        $$indexRef = $index;
        return $identifier;
        }
    else
        {  return undef;  };
    };


#
#   Function: TryToGetImport
#
#   Determines whether the position is at a import statement, and if so, adds it as a Using statement to the current scope, skips
#   it, and returns true.
#
sub TryToGetImport #(indexRef, lineNumberRef)
    {
    my ($self, $indexRef, $lineNumberRef) = @_;
    my $tokens = $self-&gt;Tokens();

    my $index = $$indexRef;
    my $lineNumber = $$lineNumberRef;

    if ($tokens-&gt;[$index] ne 'import')
        {  return undef;  };

    $index++;
    $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

    my $identifier = $self-&gt;TryToGetIdentifier(\$index, \$lineNumber, 1);
    if (!$identifier)
        {  return undef;  };


    # Currently we implement importing by stripping the last package level and treating it as a using.  So "import p1.p2.p3" makes
    # p1.p2 the using path, which is over-tolerant but that's okay.  "import p1.p2.*" is treated the same way, but in this case it's
    # not over-tolerant.  If there's no dot, there's no point to including it.

    if (index($identifier, '.') != -1)
        {
        $identifier =~ s/\.[^\.]+$//;
        $self-&gt;AddUsing( NaturalDocs::SymbolString-&gt;FromText($identifier) );
        };

    $$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
#       - Interfaces
#       - Classes and interfaces with _global
#
sub TryToGetClass #(indexRef, lineNumberRef)
    {
    my ($self, $indexRef, $lineNumberRef) = @_;
    my $tokens = $self-&gt;Tokens();

    my $index = $$indexRef;
    my $lineNumber = $$lineNumberRef;

    my @modifiers;

    while ($tokens-&gt;[$index] =~ /^[a-z]/i &amp;&amp;
              exists $classModifiers{lc($tokens-&gt;[$index])} )
        {
        push @modifiers, lc($tokens-&gt;[$index]);
        $index++;

        $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);
        };

    my $type;

    if ($tokens-&gt;[$index] eq 'class' || $tokens-&gt;[$index] eq 'interface')
        {
        $type = $tokens-&gt;[$index];

        $index++;
        $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);
        }
    else
        {  return undef;  };

    my $className = $self-&gt;TryToGetIdentifier(\$index, \$lineNumber);

    if (!$className)
        {  return undef;  };

    $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

    my @parents;

    if ($tokens-&gt;[$index] eq 'extends')
        {
        $index++;
        $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

        my $parent = $self-&gt;TryToGetIdentifier(\$index, \$lineNumber);
        if (!$parent)
            {  return undef;  };

        push @parents, $parent;

        $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);
        };

    if ($type eq 'class' &amp;&amp; $tokens-&gt;[$index] eq 'implements')
        {
        $index++;
        $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

        for (;;)
            {
            my $parent = $self-&gt;TryToGetIdentifier(\$index, \$lineNumber);
            if (!$parent)
                {  return undef;  };

            push @parents, $parent;

            $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

            if ($tokens-&gt;[$index] ne ',')
                {  last;  }
            else
                {
                $index++;
                $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);
                };
            };
        };

    if ($tokens-&gt;[$index] ne '{')
        {  return undef;  };

    $index++;


    # If we made it this far, we have a valid class declaration.

    my $topicType;

    if ($type eq 'interface')
        {  $topicType = ::TOPIC_INTERFACE();  }
    else
        {  $topicType = ::TOPIC_CLASS();  };

    $className =~ s/^_global.//;

    my $autoTopic = NaturalDocs::Parser::ParsedTopic-&gt;New($topicType, $className,
                                                                                         undef, $self-&gt;CurrentUsing(),
                                                                                         undef,
                                                                                         undef, undef, $$lineNumberRef);

    $self-&gt;AddAutoTopic($autoTopic);
    NaturalDocs::Parser-&gt;OnClass($autoTopic-&gt;Package());

    foreach my $parent (@parents)
        {
        NaturalDocs::Parser-&gt;OnClassParent($autoTopic-&gt;Package(), NaturalDocs::SymbolString-&gt;FromText($parent),
                                                               undef, $self-&gt;CurrentUsing(), ::RESOLVE_ABSOLUTE());
        };

    $self-&gt;StartScope('}', $lineNumber, $autoTopic-&gt;Package());

    $$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
#       - Properties
#       - Functions with _global
#       - Functions with namespaces
#
sub TryToGetFunction #(indexRef, lineNumberRef)
    {
    my ($self, $indexRef, $lineNumberRef) = @_;
    my $tokens = $self-&gt;Tokens();

    my $index = $$indexRef;
    my $lineNumber = $$lineNumberRef;

    my $startIndex = $index;
    my $startLine = $lineNumber;

    my @modifiers;
    my $namespace;

    while ($tokens-&gt;[$index] =~ /^[a-z]/i)
        {
        if ($tokens-&gt;[$index] eq 'function')
            {  last;  }

        elsif (exists $memberModifiers{lc($tokens-&gt;[$index])})
            {
            push @modifiers, lc($tokens-&gt;[$index]);
            $index++;

            $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);
            }

        elsif (!$namespace)
            {
            do
                {
                $namespace .= $tokens-&gt;[$index];
                $index++;
                }
            while ($tokens-&gt;[$index] =~ /^[a-z0-9_]/i);

            $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);
            }

        else
            {  last;  };
        };

    if ($tokens-&gt;[$index] ne 'function')
        {  return undef;  };
    $index++;

    $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

    my $type;

    if ($tokens-&gt;[$index] eq 'get' || $tokens-&gt;[$index] eq 'set')
        {
        # This can either be a property ("function get Something()") or a function name ("function get()").

        my $nextIndex = $index;
        my $nextLineNumber = $lineNumber;

        $nextIndex++;
        $self-&gt;TryToSkipWhitespace(\$nextIndex, \$nextLineNumber);

        if ($tokens-&gt;[$nextIndex] eq '(')
            {
            $type = ::TOPIC_FUNCTION();
            # Ignore the movement and let the code ahead pick it up as the name.
            }
        else
            {
            $type = ::TOPIC_PROPERTY();
            $index = $nextIndex;
            $lineNumber = $nextLineNumber;
            };
        }
    else
        {  $type = ::TOPIC_FUNCTION();  };

    my $name = $self-&gt;TryToGetIdentifier(\$index, \$lineNumber);
    if (!$name)
        {  return undef;  };

    $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

    if ($tokens-&gt;[$index] ne '(')
        {  return undef;  };

    $index++;
    $self-&gt;GenericSkipUntilAfter(\$index, \$lineNumber, ')');

    $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

    if ($tokens-&gt;[$index] eq ':')
        {
        $index++;

        $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

        $self-&gt;TryToGetIdentifier(\$index, \$lineNumber, 1);

        $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);
        };


    my $prototype = $self-&gt;NormalizePrototype( $self-&gt;CreateString($startIndex, $index) );

    if ($tokens-&gt;[$index] eq '{')
        {  $self-&gt;GenericSkip(\$index, \$lineNumber);  }
    elsif (!exists $declarationEnders{$tokens-&gt;[$index]})
        {  return undef;  };


    my $scope = $self-&gt;CurrentScope();

    if ($name =~ s/^_global.//)
        {  $scope = undef;  };
    if ($namespace)
        {  $scope = NaturalDocs::SymbolString-&gt;Join($scope, $namespace);  };

    $self-&gt;AddAutoTopic(NaturalDocs::Parser::ParsedTopic-&gt;New($type, $name,
                                                                                              $scope, $self-&gt;CurrentUsing(),
                                                                                              $prototype,
                                                                                              undef, undef, $startLine));


    # 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
#       - Variables with _global
#       - Variables with type * (untyped)
#       - Constants
#       - Variables and constants with namespaces
#
sub TryToGetVariable #(indexRef, lineNumberRef)
    {
    my ($self, $indexRef, $lineNumberRef) = @_;
    my $tokens = $self-&gt;Tokens();

    my $index = $$indexRef;
    my $lineNumber = $$lineNumberRef;

    my $startIndex = $index;
    my $startLine = $lineNumber;

    my @modifiers;
    my $namespace;

    while ($tokens-&gt;[$index] =~ /^[a-z]/i)
        {
        if ($tokens-&gt;[$index] eq 'var' || $tokens-&gt;[$index] eq 'const')
            {  last;  }

        elsif (exists $memberModifiers{lc($tokens-&gt;[$index])})
            {
            push @modifiers, lc($tokens-&gt;[$index]);
            $index++;

            $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);
            }

        elsif (!$namespace)
            {
            do
                {
                $namespace .= $tokens-&gt;[$index];
                $index++;
                }
            while ($tokens-&gt;[$index] =~ /^[a-z0-9_]/i);

            $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);
            }

        else
            {  last;  };
        };

    my $type;

    if ($tokens-&gt;[$index] eq 'var')
        {  $type = ::TOPIC_VARIABLE();  }
    elsif ($tokens-&gt;[$index] eq 'const')
        {  $type = ::TOPIC_CONSTANT();  }
    else
        {  return undef;  };
    $index++;

    $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

    my $endTypeIndex = $index;
    my @names;
    my @types;

    for (;;)
        {
        my $name = $self-&gt;TryToGetIdentifier(\$index, \$lineNumber);
        if (!$name)
            {  return undef;  };

        $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

        my $type;

        if ($tokens-&gt;[$index] eq ':')
            {
            $index++;
            $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);

            $type = ': ' . $self-&gt;TryToGetIdentifier(\$index, \$lineNumber, 1);

            $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);
            };

        if ($tokens-&gt;[$index] eq '=')
            {
            do
                {
                $self-&gt;GenericSkip(\$index, \$lineNumber);
                }
            while ($tokens-&gt;[$index] ne ',' &amp;&amp; !exists $declarationEnders{$tokens-&gt;[$index]} &amp;&amp; $index &lt; scalar @$tokens);
            };

        push @names, $name;
        push @types, $type;

        if ($tokens-&gt;[$index] eq ',')
            {
            $index++;
            $self-&gt;TryToSkipWhitespace(\$index, \$lineNumber);
            }
        elsif (exists $declarationEnders{$tokens-&gt;[$index]})
            {  last;  }
        else
            {  return undef;  };
        };


    # We succeeded if we got this far.

    my $prototypePrefix = $self-&gt;CreateString($startIndex, $endTypeIndex);

    for (my $i = 0; $i &lt; scalar @names; $i++)
        {
        my $prototype = $self-&gt;NormalizePrototype( $prototypePrefix . ' ' . $names[$i] . $types[$i]);
        my $scope = $self-&gt;CurrentScope();

        if ($names[$i] =~ s/^_global.//)
            {  $scope = undef;  };
        if ($namespace)
            {  $scope = NaturalDocs::SymbolString-&gt;Join($scope, $namespace);  };

        $self-&gt;AddAutoTopic(NaturalDocs::Parser::ParsedTopic-&gt;New($type, $names[$i],
                                                                                                  $scope, $self-&gt;CurrentUsing(),
                                                                                                  $prototype,
                                                                                                  undef, undef, $startLine));
        };

    $$indexRef = $index;
    $$lineNumberRef = $lineNumber;

    return 1;
    };



################################################################################
# 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), 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-&gt;Tokens();

    # We can ignore the scope stack because we're just skipping everything without parsing, and we need recursion anyway.
    if ($tokens-&gt;[$$indexRef] eq '{')
        {
        $$indexRef++;
        $self-&gt;GenericSkipUntilAfter($indexRef, $lineNumberRef, '}');
        }
    elsif ($tokens-&gt;[$$indexRef] eq '(')
        {
        $$indexRef++;
        $self-&gt;GenericSkipUntilAfter($indexRef, $lineNumberRef, ')');
        }
    elsif ($tokens-&gt;[$$indexRef] eq '[')
        {
        $$indexRef++;
        $self-&gt;GenericSkipUntilAfter($indexRef, $lineNumberRef, ']');
        }

    elsif ($self-&gt;TryToSkipWhitespace($indexRef, $lineNumberRef) ||
            $self-&gt;TryToSkipString($indexRef, $lineNumberRef) ||
            $self-&gt;TryToSkipRegExp($indexRef, $lineNumberRef) ||
            $self-&gt;TryToSkipXML($indexRef, $lineNumberRef) )
        {
        }

    else
        {  $$indexRef++;  };
    };


#
#   Function: GenericSkipUntilAfter
#
#   Advances the position via &lt;GenericSkip()&gt; until a specific token is reached and passed.
#
sub GenericSkipUntilAfter #(indexRef, lineNumberRef, token)
    {
    my ($self, $indexRef, $lineNumberRef, $token) = @_;
    my $tokens = $self-&gt;Tokens();

    while ($$indexRef &lt; scalar @$tokens &amp;&amp; $tokens-&gt;[$$indexRef] ne $token)
        {  $self-&gt;GenericSkip($indexRef, $lineNumberRef);  };

    if ($tokens-&gt;[$$indexRef] eq "\n")
        {  $$lineNumberRef++;  };
    $$indexRef++;
    };


#
#   Function: IndiscriminateSkipUntilAfterSequence
#
#   Advances the position indiscriminately until a specific token sequence is reached and passed.
#
sub IndiscriminateSkipUntilAfterSequence #(indexRef, lineNumberRef, token, token, ...)
    {
    my ($self, $indexRef, $lineNumberRef, @sequence) = @_;
    my $tokens = $self-&gt;Tokens();

    while ($$indexRef &lt; scalar @$tokens &amp;&amp; !$self-&gt;IsAtSequence($$indexRef, @sequence))
        {
        if ($tokens-&gt;[$$indexRef] eq "\n")
            {  $$lineNumberRef++;  };
        $$indexRef++;
        };

    if ($self-&gt;IsAtSequence($$indexRef, @sequence))
        {
        $$indexRef += scalar @sequence;
        foreach my $token (@sequence)
            {
            if ($token eq "\n")
                {  $$lineNumberRef++;  };
            };
        };
    };


#
#   Function: SkipToNextStatement
#
#   Advances the position via &lt;GenericSkip()&gt; until the next statement, which is defined as anything in &lt;declarationEnders&gt; not
#   appearing in brackets or strings.  It will always advance at least one token.
#
sub SkipToNextStatement #(indexRef, lineNumberRef)
    {
    my ($self, $indexRef, $lineNumberRef) = @_;
    my $tokens = $self-&gt;Tokens();

    if ($tokens-&gt;[$$indexRef] eq ';')
        {  $$indexRef++;  }

    else
        {
        do
            {
            $self-&gt;GenericSkip($indexRef, $lineNumberRef);
            }
        while ( $$indexRef &lt; scalar @$tokens &amp;&amp;
                  !exists $declarationEnders{$tokens-&gt;[$$indexRef]} );
        };
    };


#
#   Function: TryToSkipRegExp
#   If the current position is on a regular expression, skip past it and return true.
#
sub TryToSkipRegExp #(indexRef, lineNumberRef)
    {
    my ($self, $indexRef, $lineNumberRef) = @_;
    my $tokens = $self-&gt;Tokens();

    if ($tokens-&gt;[$$indexRef] eq '/')
        {
        # A slash can either start a regular expression or be a divide symbol.  Skip backwards to see what the previous symbol is.
        my $index = $$indexRef - 1;

        while ($index &gt;= 0 &amp;&amp; $tokens-&gt;[$index] =~ /^(?: |\t|\n)/)
            {  $index--;  };

        if ($index &lt; 0 || $tokens-&gt;[$index] !~ /^\=\(\[\,]/)
            {  return 0;  };

        $$indexRef++;

        while ($$indexRef &lt; scalar @$tokens &amp;&amp; $tokens-&gt;[$$indexRef] ne '/')
            {
            if ($tokens-&gt;[$$indexRef] eq '\\')
                {  $$indexRef += 2;  }
            elsif ($tokens-&gt;[$$indexRef] eq "\n")
                {
                $$indexRef++;
                $$lineNumberRef++;
                }
            else
                {  $$indexRef++;  }
            };

        if ($$indexRef &lt; scalar @$tokens)
            {
            $$indexRef++;

            if ($tokens-&gt;[$$indexRef] =~ /^[gimsx]+$/i)
                {  $$indexRef++;  };
            };

        return 1;
        }
    else
        {  return 0;  };
    };


#
#   Function: TryToSkipXML
#   If the current position is on an XML literal, skip past it and return true.
#
sub TryToSkipXML #(indexRef, lineNumberRef)
    {
    my ($self, $indexRef, $lineNumberRef) = @_;
    my $tokens = $self-&gt;Tokens();

    if ($tokens-&gt;[$$indexRef] eq '&lt;')
        {
        # A &lt; can either start an XML literal or be a comparison or shift operator.  First check the next character for &lt;&lt; or &lt;=.

        my $index = $$indexRef + 1;

        while ($index &lt; scalar @$tokens &amp;&amp; $tokens-&gt;[$index] =~ /^[\=\&lt;]$/)
            {  return 0;  };


        # Next try the previous character.

        $index = $$indexRef - 1;

        while ($index &gt;= 0 &amp;&amp; $tokens-&gt;[$index] =~ /^[ |\t|\n]/)
            {  $index--;  };

        if ($index &lt; 0 || $tokens-&gt;[$index] !~ /^[\=\(\[\,\&gt;]/)
            {  return 0;  };
        }
    else
        {  return 0;  };


    # Only handle the tag here if it's not an irregular XML section.
    if (!$self-&gt;TryToSkipIrregularXML($indexRef, $lineNumberRef))
        {
        my @tagStack;

        my ($tagType, $tagIdentifier) = $self-&gt;GetAndSkipXMLTag($indexRef, $lineNumberRef);
        if ($tagType == XML_OPENING_TAG)
            {  push @tagStack, $tagIdentifier;  };

        while (scalar @tagStack &amp;&amp; $$indexRef &lt; scalar @$tokens)
            {
            $self-&gt;SkipToNextXMLTag($indexRef, $lineNumberRef);
            ($tagType, $tagIdentifier) = $self-&gt;GetAndSkipXMLTag($indexRef, $lineNumberRef);

            if ($tagType == XML_OPENING_TAG)
                {  push @tagStack, $tagIdentifier;  }
            elsif ($tagType == XML_CLOSING_TAG &amp;&amp; $tagIdentifier eq $tagStack[-1])
                {  pop @tagStack;  };
            };
        };


    return 1;
    };


#
#   Function: TryToSkipIrregularXML
#
#   If the current position is on an irregular XML tag, skip past it and return true.  Irregular XML tags are defined as
#
#       CDATA - &lt;![CDATA[ ... ]]&gt;
#       Comments - &lt;!-- ... --&gt;
#       PI - &lt;? ... ?&gt;
#
sub TryToSkipIrregularXML #(indexRef, lineNumberRef)
    {
    my ($self, $indexRef, $lineNumberRef) = @_;

    if ($self-&gt;IsAtSequence($$indexRef, '&lt;', '!', '[', 'CDATA', '['))
        {
        $$indexRef += 5;
        $self-&gt;IndiscriminateSkipUntilAfterSequence($indexRef, $lineNumberRef, ']', ']', '&gt;');
        return 1;
        }

    elsif ($self-&gt;IsAtSequence($$indexRef, '&lt;', '!', '-', '-'))
        {
        $$indexRef += 4;
        $self-&gt;IndiscriminateSkipUntilAfterSequence($indexRef, $lineNumberRef, '-', '-', '&gt;');
        return 1;
        }

    elsif ($self-&gt;IsAtSequence($$indexRef, '&lt;', '?'))
        {
        $$indexRef += 2;
        $self-&gt;IndiscriminateSkipUntilAfterSequence($indexRef, $lineNumberRef, '?', '&gt;');
        return 1;
        }

    else
        {  return 0;  };
    };


#
#   Function: GetAndSkipXMLTag
#
#   Processes the XML tag at the current position, moves beyond it, and returns information about it.  Assumes the position is on
#   the opening angle bracket of the tag and the tag is a normal XML tag, not one of the ones handled by
#   &lt;TryToSkipIrregularXML()&gt;.
#
#   Parameters:
#
#       indexRef - A reference to the index of the position of the opening angle bracket.
#       lineNumberRef - A reference to the line number of the position of the opening angle bracket.
#
#   Returns:
#
#       The array ( tagType, name ).
#
#       tagType - One of the &lt;XML Tag Type&gt; constants.
#       identifier - The identifier of the tag.  If it's an empty tag (&lt;&gt; or &lt;/&gt;), this will be "(anonymous)".
#
sub GetAndSkipXMLTag #(indexRef, lineNumberRef)
    {
    my ($self, $indexRef, $lineNumberRef) = @_;
    my $tokens = $self-&gt;Tokens();

    if ($$indexRef &lt; scalar @$tokens &amp;&amp; $tokens-&gt;[$$indexRef] ne '&lt;')
        {  die "Tried to call GetXMLTag when the position isn't on an opening bracket.";  };

    # Get the anonymous ones out of the way so we don't have to worry about them below, since they're rather exceptional.

    if ($self-&gt;IsAtSequence($$indexRef, '&lt;', '&gt;'))
        {
        $$indexRef += 2;
        return ( XML_OPENING_TAG, '(anonymous)' );
        }
    elsif ($self-&gt;IsAtSequence($$indexRef, '&lt;', '/', '&gt;'))
        {
        $$indexRef += 3;
        return ( XML_CLOSING_TAG, '(anonymous)' );
        };


    # Grab the identifier.

    my $tagType = XML_OPENING_TAG;
    my $identifier;

    $$indexRef++;

    if ($tokens-&gt;[$$indexRef] eq '/')
        {
        $$indexRef++;
        $tagType = XML_CLOSING_TAG;
        };

    $self-&gt;TryToSkipXMLWhitespace($indexRef, $lineNumberRef);


    # The identifier could be a native expression in braces.

    if ($tokens-&gt;[$$indexRef] eq '{')
        {
        my $startOfIdentifier = $$indexRef;

        $$indexRef++;
        $self-&gt;GenericSkipUntilAfter($indexRef, $lineNumberRef, '}');

        $identifier = $self-&gt;CreateString($startOfIdentifier, $$indexRef);
        }


    # Otherwise just grab content until whitespace or the end of the tag.

    else
        {
        while ($$indexRef &lt; scalar @$tokens &amp;&amp; $tokens-&gt;[$$indexRef] !~ /^[\/\&gt;\ \t]$/)
            {
            $identifier .= $tokens-&gt;[$$indexRef];
            $$indexRef++;
            };
        };


    # Skip to the end of the tag.

    while ($$indexRef &lt; scalar @$tokens &amp;&amp; $tokens-&gt;[$$indexRef] !~ /^[\/\&gt;]$/)
        {
        if ($tokens-&gt;[$$indexRef] eq '{')
            {
            $$indexRef++;
            $self-&gt;GenericSkipUntilAfter($indexRef, $lineNumberRef, '}');
            }

        elsif ($self-&gt;TryToSkipXMLWhitespace($indexRef, $lineNumberRef))
            {  }

        # We don't need to do special handling for attribute quotes or anything like that because there's no backslashing in
        # XML.  It's all handled with entity characters.
        else
            {  $$indexRef++;  };
        };


    if ($tokens-&gt;[$$indexRef] eq '/')
        {
        if ($tagType == XML_OPENING_TAG)
            {  $tagType = XML_SELF_CONTAINED_TAG;  };

        $$indexRef++;
        };

    if ($tokens-&gt;[$$indexRef] eq '&gt;')
        {  $$indexRef++;  };

    if (!$identifier)
        {  $identifier = '(anonymous)';  };


    return ( $tagType, $identifier );
    };


#
#   Function: SkipToNextXMLTag
#   Skips to the next normal XML tag.  It will not stop at elements handled by &lt;TryToSkipIrregularXML()&gt;.  Note that if the
#   position is already at an XML tag, it will not move.
#
sub SkipToNextXMLTag #(indexRef, lineNumberRef)
    {
    my ($self, $indexRef, $lineNumberRef) = @_;
    my $tokens = $self-&gt;Tokens();

    while ($$indexRef &lt; scalar @$tokens)
        {
        if ($tokens-&gt;[$$indexRef] eq '{')
            {
            $$indexRef++;
            $self-&gt;GenericSkipUntilAfter($indexRef, $lineNumberRef, '}');
            }

        elsif ($self-&gt;TryToSkipIrregularXML($indexRef, $lineNumberRef))
            {  }

        elsif ($tokens-&gt;[$$indexRef] eq '&lt;')
            {  last;  }

        else
            {
            if ($tokens-&gt;[$$indexRef] eq "\n")
                {  $$lineNumberRef++;  };

            $$indexRef++;
            };
        };
    };


#
#   Function: TryToSkipXMLWhitespace
#   If the current position is on XML whitespace, skip past it and return true.
#
sub TryToSkipXMLWhitespace #(indexRef, lineNumberRef)
    {
    my ($self, $indexRef, $lineNumberRef) = @_;
    my $tokens = $self-&gt;Tokens();

    my $result;

    while ($$indexRef &lt; scalar @$tokens)
        {
        if ($tokens-&gt;[$$indexRef] =~ /^[ \t]/)
            {
            $$indexRef++;
            $result = 1;
            }
        elsif ($tokens-&gt;[$$indexRef] eq "\n")
            {
            $$indexRef++;
            $$lineNumberRef++;
            $result = 1;
            }
        else
            {  last;  };
        };

    return $result;
    };


#
#   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 and apostrophes.
#
sub TryToSkipString #(indexRef, lineNumberRef)
    {
    my ($self, $indexRef, $lineNumberRef) = @_;

    return ($self-&gt;SUPER::TryToSkipString($indexRef, $lineNumberRef, '\'') ||
               $self-&gt;SUPER::TryToSkipString($indexRef, $lineNumberRef, '"') );
    };


#
#   Function: TryToSkipWhitespace
#   If the current position is on a whitespace token, a line break token, or a comment, 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-&gt;Tokens();

    my $result;

    while ($$indexRef &lt; scalar @$tokens)
        {
        if ($tokens-&gt;[$$indexRef] =~ /^[ \t]/)
            {
            $$indexRef++;
            $result = 1;
            }
        elsif ($tokens-&gt;[$$indexRef] eq "\n")
            {
            $$indexRef++;
            $$lineNumberRef++;
            $result = 1;
            }
        elsif ($self-&gt;TryToSkipComment($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-&gt;TryToSkipLineComment($indexRef, $lineNumberRef) ||
                $self-&gt;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-&gt;Tokens();

    if ($tokens-&gt;[$$indexRef] eq '/' &amp;&amp; $tokens-&gt;[$$indexRef+1] eq '/')
        {
        $self-&gt;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-&gt;Tokens();

    if ($tokens-&gt;[$$indexRef] eq '/' &amp;&amp; $tokens-&gt;[$$indexRef+1] eq '*')
        {
        $self-&gt;SkipUntilAfter($indexRef, $lineNumberRef, '*', '/');
        return 1;
        }
    else
        {  return undef;  };
    };


1;
</pre></body></html>