Menu

C/C++ extended support

TTK-Bandit
2009-07-20
2012-09-14
  • TTK-Bandit

    TTK-Bandit - 2009-07-20

    Can't really call this full language support, since this is more a hack and it might not work for all kinds of c++ code, but here you go:
    http://roughael.net/Cpp.pm

    to add it, change the cpp part in the language.txt to:
    Language: C/C++

    Extensions: c cpp h hpp cxx hxx
    Ignore Function Prefix in Index: ~
    Full Language Support: NaturalDocs::Languages::Cpp

    and in languages.pm add this to the list:
    use NaturalDocs::Languages::Cpp;

    Not sure if I'm going to continue work on it, I haven't done anything with Perl before and I'm not sure I'll have the time to get into it.
    Honestly, I just wanted to get some documentation tool, and now I'm spending hours on something I dont even know if it will work out >-<

     
  • Ken MacKay

    Ken MacKay - 2009-09-18

    I have updated this to be more complete. Some complicated types involving
    function pointers might not work, but everything else seems to work OK.

    Class: NaturalDocs::Languages::Cpp

    A subclass to handle the language variations of C++.

    Topic: Language Support

    Supported:

    - Classes, Structs, Unions

    - Namespaces (no topic generated)

    - Functions

    - Constructors and Destructors

    - Variables

    - Constants

    - Enums

    Not supported yet:

    - Autodocumenting enum members

    - Using alias

    Notes: Complicated types involving function pointers might not be recognized

    properly.

    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;

    use List::Util 'max';

    use Scalar::Util 'looks_like_number';

    package NaturalDocs::Languages::Cpp;

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

    Group: Package Variables

    hash: classKeywords

    An existence hash of all the acceptable class keywords. The keys are in all

    lowercase.

    my %classKeywords = ( 'class' => 1,

    'struct' => 1,

    'union' => 1);

    hash: classModifiers

    An existence hash of all the acceptable class modifiers. The keys are in all

    lowercase.

    my %classModifiers = ( 'public' => 1,

    'protected' => 1,

    'private' => 1,

    'virtual' => 1);

    hash: classSectionModifiers

    An existence hash of all the acceptable class section modifiers. The keys

    are in all lowercase.

    my %classSectionModifiers = ( 'public' => 1,

    'protected' => 1,

    'private' => 1);

    hash: functionModifiers

    An existence hash of all the acceptable function modifiers. The keys are in

    all lowercase.

    my %functionModifiers = ( 'static' => 1,

    'virtual' => 1,

    'explicit' => 1);

    hash: impossibleTypeWords

    An existence hash of all the reserved words that cannot be in a type. This

    includes 'enum' and all modifiers. The keys are in

    all lowercase.

    my %impossibleTypeWords = ( 'break' => 1, 'case' => 1, 'catch' => 1,

    'continue' => 1, 'default' => 1,

    'do' => 1, 'else' => 1, 'explicit' => 1,

    'false' => 1, 'for' => 1, 'friend' => 1, 'goto' => 1, 'if' =>
    1,

    'namespace' => 1, 'new' => 1, 'operator' => 1,

    'private' => 1, 'protected' => 1, 'public' => 1,

    'return' => 1,'sizeof' => 1, 'static' => 1,

    'switch' => 1, 'this' => 1, 'throw' => 1, 'true' => 1, 'try' =>
    1, 'typeof' => 1,

    'using' => 1, 'while' => 1, '=' => 1);

    Deleted from the list: object, string, bool, decimal, sbyte, byte, short,

    ushort, int, uint, long, ulong, char, float, double, void

    Group: Interface Functions

    Function: PackageSeparator

    Returns the package separator symbol.

    sub PackageSeparator

    { return '::'; };

    Function: EnumValues

    Returns the <EnumValuesType> that describes how the language handles

    enums.

    sub EnumValues

    { return ::ENUM_UNDER_PARENT(); }; #true for now... changes for C++0x (where
    you can have class enums that would be ENUM_UNDER_TYPE)

    Function: ParseFile

    Parses the passed source file, sending comments acceptable for documentation

    to <NaturalDocs::Parser->OnComment()>.

    Parameters:

    sourceFile - The <FileName> to parse.

    topicList - A reference to the list of

    <NaturalDocs::Parser::ParsedTopics> being built by the file.

    Returns:

    The array ( autoTopics, scopeRecord ).

    autoTopics - An arrayref of automatically generated topics from the file, or

    undef if none.

    scopeRecord - An arrayref of

    <NaturalDocs::Languages::Advanced::ScopeChanges>, or undef if none.

    sub ParseFile #(sourceFile, topicsList)

    {

    my ($self, $sourceFile, $topicsList) = @_;

    $self->ParseForCommentsAndTokens($sourceFile, , , , );

    my $tokens = $self->Tokens();

    my $index = 0;

    my $lineNumber = 1;

    my @currentTypedef;

    my @currentVisibility;

    push(@currentVisibility, 0);

    while ($index < scalar @$tokens)

    {

    if ($self->TryToSkipWhitespace(\$index, \$lineNumber) ||

    $self->TryToGetTypeDef(\$index, \$lineNumber, \@currentVisibility) ||

    $self->TryToGetEnum(\$index, \$lineNumber, \@currentVisibility) ||

    $self->TryToGetNamespace(\$index, \$lineNumber, \@currentVisibility,
    \@currentTypedef) ||

    $self->TryToGetUsing(\$index, \$lineNumber) ||

    $self->TryToGetClassSectionModifier(\$index, \$lineNumber,
    \@currentVisibility) ||

    $self->TryToGetClass(\$index, \$lineNumber, \@currentVisibility,
    \@currentTypedef) ||

    $self->TryToGetFunction(\$index, \$lineNumber, \@currentVisibility) ||

    $self->TryToGetVariable(\$index, \$lineNumber, \@currentVisibility) )

    {

    The functions above will handle everything.

    }

    elsif ($tokens-> eq '{')

    {

    $self->StartScope('}', $lineNumber, undef, undef, undef);

    push(@currentVisibility, $currentVisibility);

    push(@currentTypedef, undef);

    $index++;

    }

    elsif ($tokens-> eq '}')

    {

    if ($self->ClosingScopeSymbol() eq '}')

    {

    $self->EndScope($lineNumber);

    pop(@currentVisibility);

    my $typedef = pop(@currentTypedef);

    if(defined $typedef)

    {

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    my $name;

    my $startIndex = $index;

    while($tokens-> ne ';' && $index < scalar @$tokens)

    {

    $name = $tokens->;

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    }

    if($tokens-> eq ';')

    {

    my $endIndex = $index;

    my $prototype = "typedef $typedef {...} " .
    $self->CreateString($startIndex, $endIndex);

    if($currentVisibility == 1)

    {

    $prototype = "protected: " . $prototype;

    }

    if(::max(@currentVisibility) < 2)

    {

    $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New(::TOPIC_TYPE()
    , $name,

    $self->CurrentScope(), $self->CurrentUsing(),

    $prototype,

    undef, undef, $lineNumber)); #fixme line number is probably wrong

    }

    }

    }

    };

    $index++;

    }

    else

    {

    $self->SkipRestOfStatement(\$index, \$lineNumber);

    };

    };

    Don't need to keep these around.

    $self->ClearTokens();

    my $autoTopics = $self->AutoTopics();

    my $scopeRecord = $self->ScopeRecord();

    if (defined $scopeRecord && !scalar @$scopeRecord)

    { $scopeRecord = undef; };

    return ( $autoTopics, $scopeRecord );

    };

    Group: Statement Parsing Functions

    All functions here assume that the current position is at the beginning of a

    statement.

    Note for developers: I am well aware that the code in these functions do not

    check if we're past the end of the tokens as

    often as it should. We're making use of the fact that Perl will always

    return undef in these cases to keep the code simpler.

    Function: TryToGetNamespace

    Determines whether the position is at a namespace declaration statement, and

    if so, adjusts the scope, skips it, and returns

    true.

    Why no topic?:

    The main reason we don't create a Natural Docs topic for a namespace is

    because in order to declare class A.B.C in C#,

    you must do this:

    > namespace A.B

    > {

    > class C

    > { ... }

    > }

    That would result in a namespace topic whose only purpose is really to

    qualify C. It would take the default page title, and

    thus the default menu title. So if you have files for A.B.X, A.B.Y, and

    A.B.Z, they all will appear as A.B on the menu.

    If something actually appears in the namespace besides a class, it will be

    handled by

    <NaturalDocs::Parser->AddPackageDelineators()>. That function will

    add a package topic to correct the scope.

    If the user actually documented it, it will still appear because of the

    manual topic.

    sub TryToGetNamespace #(indexRef, lineNumberRef, visibilityRef, typedefRef)

    {

    my ($self, $indexRef, $lineNumberRef, $visibilityRef, $typedefRef) = @_;

    my $tokens = $self->Tokens();

    if (lc($tokens->) ne 'namespace')

    { return undef; };

    my $index = $$indexRef + 1;

    my $lineNumber = $$lineNumberRef;

    if (!$self->TryToSkipWhitespace(\$index, \$lineNumber))

    { return undef; };

    my $name;

    while ($tokens-> =~ /^/i)

    {

    $name .= $tokens->;

    $index++;

    };

    if (!defined $name)

    { return undef; };

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    if ($tokens-> ne '{')

    { return undef; };

    $index++;

    my @scopeIdentifiers =
    NaturalDocs::SymbolString->IdentifiersOf($self->CurrentScope());

    $name = join('::', @scopeIdentifiers, $name);

    We found a valid one if we made it this far.

    my $autoTopic = NaturalDocs::Parser::ParsedTopic->New(::TOPIC_CLASS(),
    $name,

    undef, $self->CurrentUsing(),

    undef,

    undef, undef, $$lineNumberRef);

    $self->AddAutoTopic($autoTopic);

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

    push(@$visibilityRef, $$visibilityRef);

    push(@$typedefRef, undef);

    $$indexRef = $index;

    $$lineNumberRef = $lineNumber;

    return 1;

    };

    sub ExtractTemplate #(indexRef, lineNumberRef)

    {

    my ($self, $indexRef, $lineNumberRef) = @_;

    my $tokens = $self->Tokens();

    my $result = 'template <';

    $self->TryToSkipWhitespace($indexRef, $lineNumberRef);

    if ($tokens-> ne 'template')

    {

    return undef;

    }

    $$indexRef++;

    $self->TryToSkipWhitespace($indexRef, $lineNumberRef);

    if ($tokens-> ne '<')

    {

    return undef;

    };

    $$indexRef++;

    $self->TryToSkipWhitespace($indexRef, $lineNumberRef);

    while ($$indexRef < scalar @$tokens)

    {

    if ($tokens-> eq 'template')

    {

    $result .= $self->ExtractTemplate($indexRef, $lineNumberRef);

    $self->TryToSkipWhitespace($indexRef, $lineNumberRef);

    $result .= ' ';

    }

    elsif ($tokens-> =~ /^[a-z0-9_:*&]/i)

    {

    $result .= $tokens->;

    $$indexRef++;

    $self->TryToSkipWhitespace($indexRef, $lineNumberRef);

    if ($tokens-> eq ',')

    {

    $$indexRef++;

    $self->TryToSkipWhitespace($indexRef, $lineNumberRef);

    $result .= ', ';

    }

    elsif ($tokens-> eq ">")

    {

    $$indexRef++;

    $result .= '>';

    return $result;

    }

    else

    {

    $result .= ' ';

    };

    }

    elsif ($tokens-> eq ">")

    {

    $$indexRef++;

    $result .= '>';

    return $result;

    }

    else

    {

    return undef;

    };

    };

    return undef;

    };

    sub ExtractSpecialization #(indexRef, lineNumberRef)

    {

    my ($self, $indexRef, $lineNumberRef) = @_;

    my $tokens = $self->Tokens();

    my $result = '<';

    $self->TryToSkipWhitespace($indexRef, $lineNumberRef);

    if ($tokens-> ne '<')

    {

    return undef;

    }

    $$indexRef++;

    $self->TryToSkipWhitespace($indexRef, $lineNumberRef);

    while ($$indexRef < scalar @$tokens)

    {

    if ($tokens-> eq '<')

    {

    $result .= $self->ExtractSpecialization($indexRef, $lineNumberRef);

    $self->TryToSkipWhitespace($indexRef, $lineNumberRef);

    $result .= ' ';

    }

    elsif ($tokens-> =~ /^[a-z0-9_:*&\()]/i)

    {

    $result .= $tokens->;

    $$indexRef++;

    $self->TryToSkipWhitespace($indexRef, $lineNumberRef);

    if ($tokens-> eq ',')

    {

    $$indexRef++;

    $self->TryToSkipWhitespace($indexRef, $lineNumberRef);

    $result .= ', ';

    }

    elsif ($tokens-> eq ">")

    {

    $$indexRef++;

    $result .= '>';

    return $result;

    }

    else

    {

    $result .= ' ';

    };

    }

    elsif ($tokens-> eq ">")

    {

    $$indexRef++;

    $result .= '>';

    return $result;

    }

    else

    {

    return undef;

    };

    };

    return undef;

    };

    Function: TryToGetClass

    Determines whether the position is at a class declaration statement, and if

    so, generates a topic for it, skips it, and

    returns true.

    Supported Syntaxes:

    - Classes

    - Structs

    - Interfaces

    sub TryToGetClass #(indexRef, lineNumberRef, visibilityRef, typedefRef)

    {

    my ($self, $indexRef, $lineNumberRef, $visibilityRef, $typedefRef) = @_;

    my $tokens = $self->Tokens();

    my $index = $$indexRef;

    my $lineNumber = $$lineNumberRef;

    my $startIndex = $index;

    my $startLine = $lineNumber;

    my $isTypeDef = 0;

    my $isTemplate = 0;

    my $templateName;

    if (lc($tokens->) eq 'typedef')

    {

    $isTypeDef = 1;

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    $startIndex = $index;

    $startLine = $lineNumber;

    if (!exists $classKeywords{lc($tokens->)}) { return undef; };

    }

    elsif (lc($tokens->) eq 'template')

    {

    $isTemplate = 1;

    $templateName = $self->ExtractTemplate(\$index, \$lineNumber);

    if (!defined $templateName)

    {

    return undef;

    }

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    if (!exists $classKeywords{lc($tokens->)}) { return undef; };

    }

    elsif (!exists $classKeywords{lc($tokens->)})

    {

    return undef;

    };

    my $lcClassKeyword = lc($tokens->);

    $index++;

    if (!$self->TryToSkipWhitespace(\$index, \$lineNumber))

    { return undef; };

    my $name;

    my @scopes;

    while ($tokens-> =~ /^/i)

    {

    $name .= $tokens->;

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    if ($tokens-> eq ':' && $tokens-> eq ':')

    {

    push(@scopes, $name);

    $name = '';

    $index += 2;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    }

    };

    if (!defined $name)

    {

    return undef;

    };

    my $typedefName = "$lcClassKeyword " . join('::', @scopes, $name);

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    if ($tokens-> eq '<')

    {

    my $specialization = $self->ExtractSpecialization(\$index, \$lineNumber);

    if (!defined $specialization)

    {

    return undef;

    }

    $name .= $specialization;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    }

    no class body.. can be ignored.

    if ($tokens-> eq ';') {

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    $$indexRef = $index;

    $$lineNumberRef = $lineNumber;

    return 1;

    }

    my @parents;

    if ($tokens-> eq ':')

    {

    do

    {

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    my @modifiers;

    while ($tokens-> =~ /^/i && exists
    $classModifiers{lc($tokens->)} )

    {

    fixme: dunno if this would work, probably not..

    push @modifiers, lc($tokens->);

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    };

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    my $parentName;

    while ($tokens-> =~ /^/i)

    {

    $parentName .= $tokens->;

    $index++;

    };

    if ($tokens-> eq '<')

    {

    my $specialization = $self->ExtractSpecialization(\$index, \$lineNumber);

    if (!defined $specialization)

    {

    return undef;

    }

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    }

    if (!defined $parentName)

    { return undef; };

    push @parents, NaturalDocs::SymbolString->FromText($parentName);

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    }

    while ($tokens-> eq ',')

    };

    if ($tokens-> ne '{')

    {

    return undef;

    };

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

    my @scopeIdentifiers =
    NaturalDocs::SymbolString->IdentifiersOf($self->CurrentScope());

    $name = join('::', @scopeIdentifiers, @scopes, $name);

    my $prototype = $self->CreateString($startIndex, $index);

    if ($$visibilityRef == 1)

    {

    $prototype = "protected: " . $prototype;

    }

    my $autoTopic = NaturalDocs::Parser::ParsedTopic->New(::TOPIC_CLASS(),
    $name,

    undef, $self->CurrentUsing(),

    $prototype,

    undef, undef, $$lineNumberRef);

    if (::max(@$visibilityRef) < 2)

    { # only add classes that aren't private

    $self->AddAutoTopic($autoTopic);

    NaturalDocs::Parser->OnClass($autoTopic->Package());

    foreach my $parent (@parents)

    {

    NaturalDocs::Parser->OnClassParent($autoTopic->Package(), $parent,
    $self->CurrentScope(), undef,

    ::RESOLVE_RELATIVE());

    };

    }

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

    if($isTypeDef)

    {

    push(@$typedefRef, $typedefName);

    }

    else

    {

    push(@$typedefRef, undef);

    }

    my $newVisibility = 0;

    if($lcClassKeyword eq "class")

    {

    $newVisibility = 2;

    }

    push(@$visibilityRef, $newVisibility);

    $index++;

    $$indexRef = $index;

    $$lineNumberRef = $lineNumber;

    return 1;

    };

    Function: TryToGetUsing

    Determines whether the position is at a using statement, and if so, adds it

    to the current scope, skips it, and returns

    true.

    Supported:

    - Using

    Unsupported:

    - Using with alias

    sub TryToGetUsing #(indexRef, lineNumberRef)

    {

    my ($self, $indexRef, $lineNumberRef) = @_;

    my $tokens = $self->Tokens();

    my $index = $$indexRef;

    my $lineNumber = $$lineNumberRef;

    if (lc($tokens->) ne 'using')

    { return undef; };

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    my $name;

    while ($tokens-> =~ /^/i)

    { # not sure how this 'using' thing works exactly

    if ($tokens-> ne 'namespace')

    {

    $name .= $tokens->;

    }

    $index++;

    };

    namespace aliases are not supported.

    if ($tokens-> ne ';' ||

    !defined $name)

    { return undef; };

    $index++;

    $self->AddUsing( NaturalDocs::SymbolString->FromText($name) );

    $$indexRef = $index;

    $$lineNumberRef = $lineNumber;

    return 1;

    };

    Function: TryToGetUsing

    Get class section (public, private, ..)

    sub TryToGetClassSectionModifier #(indexRef, lineNumberRef, visibilityRef)

    {

    my ($self, $indexRef, $lineNumberRef, $visibilityRef) = @_;

    my $tokens = $self->Tokens();

    my $index = $$indexRef;

    my $lineNumber = $$lineNumberRef;

    if (!exists $classSectionModifiers{ lc($tokens->) })

    { return undef; };

    my $modifier = lc($tokens->);

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    if ($tokens-> ne ':' )

    { return undef; };

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    if ($modifier eq 'public')

    {

    $$visibilityRef = 0;

    }

    elsif ($modifier eq 'protected')

    {

    $$visibilityRef = 1;

    }

    elsif ($modifier eq 'private')

    {

    $$visibilityRef = 2;

    }

    $$indexRef = $index;

    $$lineNumberRef = $lineNumber;

    return 1;

    };

    Function: TryToGetFunction

    Determines if the position is on a function declaration, and if so,

    generates a topic for it, skips it, and returns true.

    Supported Syntaxes:

    - Functions

    - Constructors

    - Destructors

    - Properties

    - Indexers

    - Delegates

    - Events

    sub TryToGetFunction #(indexRef, lineNumberRef, visibilityRef)

    {

    my ($self, $indexRef, $lineNumberRef, $visibilityRef) = @_;

    my $tokens = $self->Tokens();

    my $index = $$indexRef;

    my $lineNumber = $$lineNumberRef;

    if ($self->TryToSkipAttributes(\$index, \$lineNumber))

    { $self->TryToSkipWhitespace(\$index, \$lineNumber); };

    my $startIndex = $index;

    my $startLine = $lineNumber;

    my $isOperator = 0;

    my $isTemplate = 0;

    my $templateName;

    if ($tokens-> eq 'template')

    {

    $isTemplate = 1;

    $templateName = $self->ExtractTemplate(\$index, \$lineNumber);

    if (!defined $templateName)

    {

    return undef;

    }

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    }

    while ($tokens-> =~ /^/i && exists
    $functionModifiers{lc($tokens->)} )

    {

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    };

    my $isFunctionPtr = 0;

    my $returnType = $self->TryToGetType(\$index, \$lineNumber,
    \$isFunctionPtr);

    fixme: need to handle cases where the return type is a function ptr

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    my $name;

    my $lastNameWord;

    if ( $tokens-> eq 'operator' )

    {

    $isOperator = 1;

    my $startOp = $index;

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    if($tokens-> eq '(')

    {

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    if($tokens-> ne ')')

    {

    return undef;

    }

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    }

    else

    {

    while ($index < scalar @$tokens && $tokens-> ne '(')

    {

    $index++;

    }

    }

    if ( $tokens-> ne '(' )

    {

    return undef;

    }

    $name = $self->NormalizePrototype( $self->CreateString($startOp, $index)
    );

    }

    elsif ( $tokens-> ne '(' )

    {

    $name = '';

    if ($tokens-> eq '~')

    {

    $name = '~';

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    }

    if (!defined $returnType)

    {

    $lastNameWord = $tokens->;

    }

    $name .= $tokens->;

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    if ( $tokens-> eq '<' )

    {

    my $specialization = $self->ExtractSpecialization(\$index, \$lineNumber);

    if (!defined $specialization)

    {

    return undef;

    }

    $name .= $specialization;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    }

    }

    else

    {

    Constructors and destructors don't have return types. It's possible their

    names were mistaken for the return type.

    if (defined $returnType)

    {

    $name = $returnType;

    $returnType = undef;

    $name =~ /(+)$/i;

    $lastNameWord = $1;

    }

    else

    {

    return undef;

    };

    };

    If there's no return type, make sure it's a constructor or destructor.

    if (!defined $returnType && !$isOperator)

    {

    my @identifiers = NaturalDocs::SymbolString->IdentifiersOf(
    $self->CurrentScope() );

    if ($lastNameWord ne $identifiers)

    {

    return undef;

    };

    };

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    Functions, constructors, destructors, delegates.

    if ($tokens-> eq '(')

    {

    This should jump the parenthesis completely.

    my $argStart = $index;

    $self->GenericSkip(\$index, \$lineNumber);

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

    my $addMore = 0;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    while ($tokens-> eq 'const' || $tokens-> eq 'volatile' || $tokens->
    eq 'throw')

    {

    $addMore = 1;

    if($tokens-> eq 'throw')

    {

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    if ($tokens-> ne '(') { return undef };

    This should jump the parenthesis completely.

    $self->GenericSkip(\$index, \$lineNumber);

    }

    else

    {

    $index++;

    }

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    }

    if ($tokens-> eq '=')

    {

    $addMore = 1;

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    if ($tokens-> ne '0') { return undef };

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    }

    if ($addMore)

    {

    $prototype = $self->NormalizePrototype( $self->CreateString($startIndex,
    $index) );

    }

    if($$visibilityRef == 1)

    {

    $prototype = "protected: " . $prototype;

    }

    if(::max(@$visibilityRef) < 2)

    {

    $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New(::TOPIC_FUNCTI
    ON(), $name,

    $self->CurrentScope(), $self->CurrentUsing(),

    $prototype,

    undef, undef, $startLine));

    }

    $self->SkipRestOfStatement(\$index, \$lineNumber);

    }

    else

    { return undef; };

    We succeeded if we got this far.

    $$indexRef = $index;

    $$lineNumberRef = $lineNumber;

    return 1;

    };

    Function: TryToGetVariable

    Determines if the position is on a variable declaration statement, and if

    so, generates a topic for each variable, skips the

    statement, and returns true.

    Supported Syntaxes:

    - Variables

    - Constants

    sub TryToGetVariable #(indexRef, lineNumberRef, visibilityRef)

    {

    my ($self, $indexRef, $lineNumberRef, $visibilityRef) = @_;

    my $tokens = $self->Tokens();

    my $index = $$indexRef;

    my $lineNumber = $$lineNumberRef;

    if ($self->TryToSkipAttributes(\$index, \$lineNumber))

    { $self->TryToSkipWhitespace(\$index, \$lineNumber); };

    my $startIndex = $index;

    my $startLine = $lineNumber;

    my @modifiers;

    if (lc($tokens->) eq 'static')

    {

    push @modifiers, lc('static');

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    };

    my $type;

    if (lc($tokens->) eq 'const')

    {

    $type = ::TOPIC_CONSTANT();

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    }

    else

    {

    $type = ::TOPIC_VARIABLE();

    };

    my $isFunctionPtr = 0;

    if (!$self->TryToGetType(\$index, \$lineNumber, \$isFunctionPtr) &&
    !$isFunctionPtr)

    {

    return undef;

    };

    my $endTypeIndex = $index;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    my $valueIndex;

    my $valueStr;

    my @names;

    my @values;

    my $paramStr;

    for (;;)

    {

    my $name;

    while ($tokens-> =~ /^/i)

    {

    $name .= $tokens->;

    $index++;

    };

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    if($isFunctionPtr)

    {

    if($tokens-> ne ')')

    {

    return undef;

    }

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    $paramStr .= ' ) ';

    my $paramStart = $index;

    if($tokens-> ne '(')

    {

    return undef;

    }

    should skip to after parens

    $self->GenericSkip(\$index, \$lineNumber);

    $paramStr .= $self->CreateString($paramStart, $index);

    }

    if ($tokens-> eq ';

    $index++;

    } while( $tokens-> ne ']');

    $name .= ']';

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    }

    if ($tokens-> eq '=')

    {

    $valueIndex = $index;

    do

    {

    $self->GenericSkip(\$index, \$lineNumber);

    }

    while ($tokens-> ne ',' && $tokens-> ne ';');

    my $valueStr = $self->CreateString($valueIndex, $index);

    push @values, $valueStr;

    }

    else {

    push @values, '';

    }

    push @names, $name;

    if ($tokens-> eq ';')

    {

    $index++;

    last;

    }

    elsif ($tokens-> eq ',')

    {

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    }

    else

    { return undef; };

    };

    We succeeded if we got this far.

    @values = reverse(@values);

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

    foreach my $name (@names)

    {

    my $prototype = $self->NormalizePrototype( $prototypePrefix . ' ' . $name .
    $paramStr . ' ' . pop(@values) );

    if($$visibilityRef == 1)

    {

    $prototype = "protected: " . $prototype;

    }

    if(::max(@$visibilityRef) < 2)

    {

    $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New($type, $name,

    $self->CurrentScope(), $self->CurrentUsing(),

    $prototype,

    undef, undef, $startLine));

    }

    };

    $$indexRef = $index;

    $$lineNumberRef = $lineNumber;

    return 1;

    };

    Function: TryToGetTypeDef

    sub TryToGetTypeDef #(indexRef, lineNumberRef, visibilityRef)

    {

    my ($self, $indexRef, $lineNumberRef, $visibilityRef) = @_;

    my $tokens = $self->Tokens();

    my $index = $$indexRef;

    my $lineNumber = $$lineNumberRef;

    my $startIndex = $index;

    my $startLine = $lineNumber;

    if ($tokens-> ne 'typedef')

    { return undef; }

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    enum and struct/class/union are handled differently..

    if ( $tokens-> eq 'enum' || (exists $classKeywords{lc($tokens->)}))

    { return undef; }

    my $name;

    my $isFunctionPtr = 0;

    while($tokens-> ne ';' && $index < scalar @$tokens)

    {

    if(!$isFunctionPtr && $tokens-> eq '(')

    {

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    if($tokens-> eq '*')

    {

    $isFunctionPtr = 1;

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    $name = $tokens->;

    }

    }

    if(!$isFunctionPtr)

    {

    $name = $tokens->;

    }

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    }

    if($tokens-> ne ';')

    {

    return undef;

    }

    my $endIndex = $index;

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    my $prototype = $self->CreateString($startIndex, $endIndex);

    if($$visibilityRef == 1)

    {

    $prototype = "protected: " . $prototype;

    }

    if(::max(@$visibilityRef) < 2)

    {

    $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New(::TOPIC_TYPE()
    , $name,

    $self->CurrentScope(), $self->CurrentUsing(),

    $prototype,

    undef, undef, $startLine));

    }

    $$indexRef = $index;

    $$lineNumberRef = $lineNumber;

    return 1;

    };

    Function: TryToGetEnum

    Determines if the position is on an enum declaration statement, and if so,

    generates a topic for it.

    Supported Syntaxes:

    - Enums

    Unsupported:

    - Documenting the members automatically

    sub TryToGetEnum #(indexRef, lineNumberRef, visibilityRef)

    {

    my ($self, $indexRef, $lineNumberRef, $visibilityRef) = @_;

    my $tokens = $self->Tokens();

    my $index = $$indexRef;

    my $lineNumber = $$lineNumberRef;

    my $istypedef = 0;

    my $startIndex = $index;

    my $startLine = $lineNumber;

    if ($tokens-> eq 'enum')

    { $index++; }

    elsif ($tokens-> eq 'typedef' )

    {

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    if ( $tokens-> ne 'enum')

    { return undef; }

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    $istypedef = 1;

    }

    else

    { return undef; }

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    my $name = '';

    if ($tokens-> eq '{')

    {

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    } else {

    $name = $tokens->;

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    if ($tokens-> ne '{')

    {

    return undef;

    }

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    }

    We succeeded if we got this far.

    my @names;

    my @values;

    my $currentValue = 0;

    my $valueHelper = 0;

    while($tokens-> ne '}' && $index < scalar @$tokens)

    {

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    if($tokens-> =~ /^/i)

    {

    my $ename = $tokens->;

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    if($tokens-> eq '=')

    {

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    my $value = '';

    while($tokens-> ne ',' && $tokens-> ne '}' && $index
    < scalar @$tokens)

    {

    $value .= $tokens->;

    $index++;

    }

    $currentValue = $value;

    $valueHelper = 0;

    }

    push(@names, $ename);

    if($valueHelper > 0)

    {

    push(@values, $currentValue . " + " . $valueHelper);

    }

    else

    {

    push(@values, $currentValue);

    }

    if(::looks_like_number($currentValue))

    {

    $currentValue++;

    }

    else

    {

    $valueHelper++;

    }

    }

    elsif($tokens-> eq ',')

    {

    $index++;

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    }

    else

    {

    print "failed to get enum value\n";

    return undef;

    }

    }

    if($tokens-> ne '}')

    {

    return undef;

    }

    if ( $istypedef ) {

    $self->GenericSkipUntilAfter(\$index, \$lineNumber, '}');

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    $name = $tokens->;

    }

    $self->GenericSkipUntilAfter(\$index, \$lineNumber, ';');

    my $prototype = $self->CreateString($startIndex, $index);

    $prototype = $self->NormalizePrototype( $prototype );

    if($$visibilityRef == 1)

    {

    $prototype = "protected: " . $prototype;

    }

    if(::max(@$visibilityRef) < 2)

    {

    $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New(::TOPIC_ENUMER
    ATION(), $name,

    $self->CurrentScope(), $self->CurrentUsing(),

    $prototype,

    undef, undef, $startLine));

    fixme: Not sure how to add the values properly

    foreach my $ename (@names)

    {

    my $eprototype = $self->NormalizePrototype( $ename . ' = ' .

    shift(@values) );

    $self->AddAutoTopic(NaturalDocs::Parser::ParsedTopic->New(::TOPIC_CONS

    TANT(), $ename,

    $self->CurrentScope(), $self->CurrentUsing(),

    $eprototype,

    undef, undef, $startLine));

    }

    }

    $$indexRef = $index;

    $$lineNumberRef = $lineNumber;

    return 1;

    };

    Function: TryToGetType

    Determines if the position is on a type identifier, and if so, skips it and

    returns it as a string. This function does not allow

    modifiers. You must take care of those beforehand.

    sub TryToGetType #(indexRef, lineNumberRef, isFunctionPtrRef)

    {

    my ($self, $indexRef, $lineNumberRef, $isFunctionPtrRef) = @_;

    my $tokens = $self->Tokens();

    my $index = $$indexRef;

    my $lineNumber = $$lineNumberRef;

    my $startIndex = $index;

    my $endIndex = $index;

    my $endLine = $lineNumber;

    my $hasTemplate = 0;

    while (($tokens-> =~ /^/i) && !(exists $impossibleTypeWords{
    lc($tokens->) }))

    {

    my $didTemplate = 0;

    if ($tokens-> eq '<')

    {

    if($hasTemplate)

    {

    last;

    }

    my $specialization = $self->ExtractSpecialization(\$index, \$lineNumber);

    if (!defined $specialization)

    {

    return undef;

    }

    $hasTemplate = 1;

    $didTemplate = 1;

    }

    $endIndex = $index;

    $endLine = $lineNumber;

    if (!$didTemplate)

    {

    $index++;

    }

    $self->TryToSkipWhitespace(\$index, \$lineNumber);

    };

    my $testIndex = $index;

    if($tokens-> eq '(')

    {

    $testIndex++;

    $self->TryToSkipWhitespace(\$testIndex, \$lineNumber);

    if($tokens-> eq '*')

    {

    $$isFunctionPtrRef = 1;

    $testIndex++;

    $self->TryToSkipWhitespace(\$testIndex, \$lineNumber);

    $$indexRef = $testIndex;

    $$lineNumberRef = $lineNumber;

    return undef;

    }

    }

    if (($index-$startIndex) < 2) { return undef; };

    if ((exists $impossibleTypeWords{ lc($tokens->) }))

    {

    $endIndex = $index;

    }

    my $name = $self->CreateString($startIndex, $endIndex);

    if (!defined $name)

    { return undef; };

    $$indexRef = $endIndex;

    $$lineNumberRef = $endLine;

    return $name;

    };

    Function: PreprocessFile

    Overridden in a hideous hack to deal with this module's inability to handle

    extern "c". TODO - replace with something better.

    sub PreprocessFile #(lines)

    {

    my ($self, $lines) = @_;

    for (my $i = 0; $i < scalar @$lines; $i++)

    {

    if ($lines-> =~ /^extern ""$/i)

    {

    $lines-> = '';

    }

    };

    };

    Group: Low Level Parsing Functions

    Function: GenericSkip

    Advances the position one place through general code.

    - If the position is on a string, it will skip it completely.

    - If the position is on an opening symbol, it will skip until the past the

    closing symbol.

    - If the position is on whitespace (including comments and preprocessing

    directives), it will skip it completely.

    - Otherwise it skips one token.

    Parameters:

    indexRef - A reference to the current index.

    lineNumberRef - A reference to the current line number.

    sub GenericSkip #(indexRef, lineNumberRef)

    {

    my ($self, $indexRef, $lineNumberRef) = @_;

    my $tokens = $self->Tokens();

    We can ignore the scope stack because we're just skipping everything without

    parsing, and we need recursion anyway.

    if ($tokens-> eq '{')

    {

    $$indexRef++;

    $self->GenericSkipUntilAfter($indexRef, $lineNumberRef, '}');

    }

    elsif ($tokens-> eq '(')

    {

    $$indexRef++;

    $self->GenericSkipUntilAfter($indexRef, $lineNumberRef, ')');

    }

    elsif ($tokens-> eq '');

    }

    elsif ($self->TryToSkipWhitespace($indexRef, $lineNumberRef) ||

    $self->TryToSkipString($indexRef, $lineNumberRef))

    {

    }

    else

    { $$indexRef++; };

    };

    Function: GenericSkipUntilAfter

    Advances the position via <GenericSkip()> until a specific token is

    reached and passed.

    sub GenericSkipUntilAfter #(indexRef, lineNumberRef, token)

    {

    my ($self, $indexRef, $lineNumberRef, $token) = @_;

    my $tokens = $self->Tokens();

    while ($$indexRef < scalar @$tokens && $tokens-> ne $token)

    { $self->GenericSkip($indexRef, $lineNumberRef); };

    if ($tokens-> eq "\n")

    { $$lineNumberRef++; };

    $$indexRef++;

    };

    Function: SkipRestOfStatement

    Advances the position via <GenericSkip()> until after the end of the

    current statement, which is defined as a semicolon or

    a brace group. Of course, either of those appearing inside parenthesis, a

    nested brace group, etc. don't count.

    sub SkipRestOfStatement #(indexRef, lineNumberRef)

    {

    my ($self, $indexRef, $lineNumberRef) = @_;

    my $tokens = $self->Tokens();

    while ($$indexRef < scalar @$tokens &&

    $tokens-> ne ';' &&

    $tokens-> ne '{')

    {

    $self->GenericSkip($indexRef, $lineNumberRef);

    };

    if ($tokens-> eq ';')

    { $$indexRef++; }

    elsif ($tokens-> eq '{')

    { $self->GenericSkip($indexRef, $lineNumberRef); };

    };

    Function: TryToSkipString

    If the current position is on a string delimiter, skip past the string and

    return true.

    Parameters:

    indexRef - A reference to the index of the position to start at.

    lineNumberRef - A reference to the line number of the position.

    Returns:

    Whether the position was at a string.

    Syntax Support:

    - Supports quotes, apostrophes, and at-quotes.

    sub TryToSkipString #(indexRef, lineNumberRef)

    {

    my ($self, $indexRef, $lineNumberRef) = @_;

    my $tokens = $self->Tokens();

    The three string delimiters. All three are Perl variables when preceded by a

    dollar sign.

    if ($self->SUPER::TryToSkipString($indexRef, $lineNumberRef, '\'') ||

    $self->SUPER::TryToSkipString($indexRef, $lineNumberRef, '"') )

    {

    return 1;

    }

    elsif ($tokens-> eq '@' && $tokens-> eq '"')

    {

    $$indexRef += 2;

    We need to do at-strings manually because backslash characters are accepted

    as regular characters, and two consecutive

    quotes are accepted as well.

    while ($$indexRef < scalar @$tokens && !($tokens-> eq '"'
    && $tokens-> ne '"') )

    {

    if ($tokens-> eq '"')

    {

    This is safe because the while condition will only let through quote pairs.

    $$indexRef += 2;

    }

    elsif ($tokens-> eq "\n")

    {

    $$indexRef++;

    $$lineNumberRef++;

    }

    else

    {

    $$indexRef++;

    };

    };

    Skip the closing quote.

    if ($$indexRef < scalar @$tokens)

    { $$indexRef++; };

    return 1;

    }

    else

    { return undef; };

    };

    Function: TryToSkipAttributes

    If the current position is on an attribute section, skip it and return true.

    Skips multiple attribute sections if they appear

    consecutively.

    fixme: needed ?

    sub TryToSkipAttributes #(indexRef, lineNumberRef)

    {

    my ($self, $indexRef, $lineNumberRef) = @_;

    my $tokens = $self->Tokens();

    my $success;

    while ($tokens-> eq '');

    $self->TryToSkipWhitespace($indexRef, $lineNumberRef);

    };

    return $success;

    };

    Function: TryToSkipWhitespace

    If the current position is on a whitespace token, a line break token, a

    comment, or a preprocessing directive, it skips them

    and returns true. If there are a number of these in a row, it skips them

    all.

    sub TryToSkipWhitespace #(indexRef, lineNumberRef)

    {

    my ($self, $indexRef, $lineNumberRef) = @_;

    my $tokens = $self->Tokens();

    my $result;

    while ($$indexRef < scalar @$tokens)

    {

    if ($tokens-> =~ /^/)

    {

    $$indexRef++;

    $result = 1;

    }

    elsif ($tokens-> eq "\n")

    {

    $$indexRef++;

    $$lineNumberRef++;

    $result = 1;

    }

    elsif ($self->TryToSkipComment($indexRef, $lineNumberRef) ||

    $self->TryToSkipPreprocessingDirective($indexRef, $lineNumberRef))

    {

    $result = 1;

    }

    else

    { last; };

    };

    return $result;

    };

    Function: TryToSkipComment

    If the current position is on a comment, skip past it and return true.

    sub TryToSkipComment #(indexRef, lineNumberRef)

    {

    my ($self, $indexRef, $lineNumberRef) = @_;

    return ( $self->TryToSkipLineComment($indexRef, $lineNumberRef) ||

    $self->TryToSkipMultilineComment($indexRef, $lineNumberRef) );

    };

    Function: TryToSkipLineComment

    If the current position is on a line comment symbol, skip past it and return

    true.

    sub TryToSkipLineComment #(indexRef, lineNumberRef)

    {

    my ($self, $indexRef, $lineNumberRef) = @_;

    my $tokens = $self->Tokens();

    if ($tokens-> eq '/' && $tokens-> eq '/')

    {

    $self->SkipRestOfLine($indexRef, $lineNumberRef);

    return 1;

    }

    else

    { return undef; };

    };

    Function: TryToSkipMultilineComment

    If the current position is on an opening comment symbol, skip past it and

    return true.

    sub TryToSkipMultilineComment #(indexRef, lineNumberRef)

    {

    my ($self, $indexRef, $lineNumberRef) = @_;

    my $tokens = $self->Tokens();

    if ($tokens-> eq '/' && $tokens-> eq '*')

    {

    $self->SkipUntilAfter($indexRef, $lineNumberRef, '*', '/');

    return 1;

    }

    else

    { return undef; };

    };

    Function: TryToSkipPreprocessingDirective

    If the current position is on a preprocessing directive, skip past it and

    return true.

    sub TryToSkipPreprocessingDirective #(indexRef, lineNumberRef)

    {

    my ($self, $indexRef, $lineNumberRef) = @_;

    my $tokens = $self->Tokens();

    if ($tokens-> eq '#' && $self->IsFirstLineToken($$indexRef))

    {

    $self->SkipRestOfLine($indexRef, $lineNumberRef);

    return 1;

    }

    else

    { return undef; };

    };

    1;

     

Log in to post a comment.