diff options
Diffstat (limited to 'docs/doctool/Modules/NaturalDocs/Parser.pm')
| -rw-r--r-- | docs/doctool/Modules/NaturalDocs/Parser.pm | 1209 |
1 files changed, 0 insertions, 1209 deletions
diff --git a/docs/doctool/Modules/NaturalDocs/Parser.pm b/docs/doctool/Modules/NaturalDocs/Parser.pm deleted file mode 100644 index 87671817..00000000 --- a/docs/doctool/Modules/NaturalDocs/Parser.pm +++ /dev/null @@ -1,1209 +0,0 @@ -############################################################################### -# -# Package: NaturalDocs::Parser -# -############################################################################### -# -# A package that coordinates source file parsing between the <NaturalDocs::Languages::Base>-derived objects and its own -# sub-packages such as <NaturalDocs::Parser::Native>. Also handles sending symbols to <NaturalDocs::SymbolTable> and -# other generic topic processing. -# -# Usage and Dependencies: -# -# - Prior to use, <NaturalDocs::Settings>, <NaturalDocs::Languages>, <NaturalDocs::Project>, <NaturalDocs::SymbolTable>, -# and <NaturalDocs::ClassHierarchy> must be initialized. <NaturalDocs::SymbolTable> and <NaturalDocs::ClassHierarchy> -# do not have to be fully resolved. -# -# - Aside from that, the package is ready to use right away. It does not have its own initialization function. -# -############################################################################### - -# This file is part of Natural Docs, which is Copyright (C) 2003-2005 Greg Valure -# Natural Docs is licensed under the GPL - -use NaturalDocs::Parser::ParsedTopic; -use NaturalDocs::Parser::Native; - -use strict; -use integer; - -package NaturalDocs::Parser; - - - -############################################################################### -# Group: Variables - - -# -# var: sourceFile -# -# The source <FileName> currently being parsed. -# -my $sourceFile; - -# -# var: language -# -# The language object for the file, derived from <NaturalDocs::Languages::Base>. -# -my $language; - -# -# Array: parsedFile -# -# An array of <NaturalDocs::Parser::ParsedTopic> objects. -# -my @parsedFile; - - -# -# bool: parsingForInformation -# Whether <ParseForInformation()> was called. If false, then <ParseForBuild()> was called. -# -my $parsingForInformation; - - - -############################################################################### -# Group: Functions - -# -# Function: ParseForInformation -# -# Parses the input file for information. Will update the information about the file in <NaturalDocs::SymbolTable> and -# <NaturalDocs::Project>. -# -# Parameters: -# -# file - The <FileName> to parse. -# -sub ParseForInformation #(file) - { - my ($self, $file) = @_; - $sourceFile = $file; - - $parsingForInformation = 1; - - # Watch this parse so we detect any changes. - NaturalDocs::SymbolTable->WatchFileForChanges($sourceFile); - NaturalDocs::ClassHierarchy->WatchFileForChanges($sourceFile); - - my $defaultMenuTitle = $self->Parse(); - - foreach my $topic (@parsedFile) - { - # Add a symbol for the topic. - - my $type = $topic->Type(); - if ($type eq ::TOPIC_ENUMERATION()) - { $type = ::TOPIC_TYPE(); }; - - NaturalDocs::SymbolTable->AddSymbol($topic->Symbol(), $sourceFile, $type, - $topic->Prototype(), $topic->Summary()); - - - # You can't put the function call directly in a while with a regex. It has to sit in a variable to work. - my $body = $topic->Body(); - - - # If it's a list or enum topic, add a symbol for each description list entry. - - if ($topic->IsList() || $topic->Type() eq ::TOPIC_ENUMERATION()) - { - # We'll hijack the enum constants to apply to non-enum behavior too. - my $behavior; - - if ($topic->Type() eq ::TOPIC_ENUMERATION()) - { - $type = ::TOPIC_CONSTANT(); - $behavior = $language->EnumValues(); - } - elsif (NaturalDocs::Topics->TypeInfo($topic->Type())->Scope() == ::SCOPE_ALWAYS_GLOBAL()) - { - $behavior = ::ENUM_GLOBAL(); - } - else - { - $behavior = ::ENUM_UNDER_PARENT(); - }; - - while ($body =~ /<ds>([^<]+)<\/ds><dd>(.*?)<\/dd>/g) - { - my ($listTextSymbol, $listSummary) = ($1, $2); - - $listTextSymbol = NaturalDocs::NDMarkup->RestoreAmpChars($listTextSymbol); - my $listSymbol = NaturalDocs::SymbolString->FromText($listTextSymbol); - - if ($behavior == ::ENUM_UNDER_PARENT()) - { $listSymbol = NaturalDocs::SymbolString->Join($topic->Package(), $listSymbol); } - elsif ($behavior == ::ENUM_UNDER_TYPE()) - { $listSymbol = NaturalDocs::SymbolString->Join($topic->Symbol(), $listSymbol); }; - - NaturalDocs::SymbolTable->AddSymbol($listSymbol, $sourceFile, $type, undef, - $self->GetSummaryFromDescriptionList($listSummary)); - }; - }; - - - # Add references in the topic. - - while ($body =~ /<link>([^<]+)<\/link>/g) - { - my $linkText = NaturalDocs::NDMarkup->RestoreAmpChars($1); - my $linkSymbol = NaturalDocs::SymbolString->FromText($linkText); - - NaturalDocs::SymbolTable->AddReference(::REFERENCE_TEXT(), $linkSymbol, - $topic->Package(), $topic->Using(), $sourceFile); - }; - }; - - # Handle any changes to the file. - NaturalDocs::ClassHierarchy->AnalyzeChanges(); - NaturalDocs::SymbolTable->AnalyzeChanges(); - - # Update project on the file's characteristics. - my $hasContent = (scalar @parsedFile > 0); - - NaturalDocs::Project->SetHasContent($sourceFile, $hasContent); - if ($hasContent) - { NaturalDocs::Project->SetDefaultMenuTitle($sourceFile, $defaultMenuTitle); }; - - # We don't need to keep this around. - @parsedFile = ( ); - }; - - -# -# Function: ParseForBuild -# -# Parses the input file for building, returning it as a <NaturalDocs::Parser::ParsedTopic> arrayref. -# -# Note that all new and changed files should be parsed for symbols via <ParseForInformation()> before calling this function on -# *any* file. The reason is that <NaturalDocs::SymbolTable> needs to know about all the symbol definitions and references to -# resolve them properly. -# -# Parameters: -# -# file - The <FileName> to parse for building. -# -# Returns: -# -# An arrayref of the source file as <NaturalDocs::Parser::ParsedTopic> objects. -# -sub ParseForBuild #(file) - { - my ($self, $file) = @_; - $sourceFile = $file; - - $parsingForInformation = undef; - - $self->Parse(); - - return \@parsedFile; - }; - - - -############################################################################### -# Group: Interface Functions - - -# -# Function: OnComment -# -# The function called by <NaturalDocs::Languages::Base>-derived objects when their parsers encounter a comment -# suitable for documentation. -# -# Parameters: -# -# commentLines - An arrayref of the comment's lines. The language's comment symbols should be converted to spaces, -# and there should be no line break characters at the end of each line. *The original memory will be -# changed.* -# lineNumber - The line number of the first of the comment lines. -# -# Returns: -# -# The number of topics created by this comment, or zero if none. -# -sub OnComment #(commentLines, lineNumber) - { - my ($self, $commentLines, $lineNumber) = @_; - - $self->CleanComment($commentLines); - - return NaturalDocs::Parser::Native->ParseComment($commentLines, $lineNumber, \@parsedFile); - }; - - -# -# Function: OnClass -# -# A function called by <NaturalDocs::Languages::Base>-derived objects when their parsers encounter a class declaration. -# -# Parameters: -# -# class - The <SymbolString> of the class encountered. -# -sub OnClass #(class) - { - my ($self, $class) = @_; - - if ($parsingForInformation) - { NaturalDocs::ClassHierarchy->AddClass($sourceFile, $class); }; - }; - - -# -# Function: OnClassParent -# -# A function called by <NaturalDocs::Languages::Base>-derived objects when their parsers encounter a declaration of -# inheritance. -# -# Parameters: -# -# class - The <SymbolString> of the class we're in. -# parent - The <SymbolString> of the class it inherits. -# scope - The package <SymbolString> that the reference appeared in. -# using - An arrayref of package <SymbolStrings> that the reference has access to via "using" statements. -# resolvingFlags - Any <Resolving Flags> to be used when resolving the reference. <RESOLVE_NOPLURAL> is added -# automatically since that would never apply to source code. -# -sub OnClassParent #(class, parent, scope, using, resolvingFlags) - { - my ($self, $class, $parent, $scope, $using, $resolvingFlags) = @_; - - if ($parsingForInformation) - { - NaturalDocs::ClassHierarchy->AddParentReference($sourceFile, $class, $parent, $scope, $using, - $resolvingFlags | ::RESOLVE_NOPLURAL()); - }; - }; - - - -############################################################################### -# Group: Support Functions - - -# Function: Parse -# -# Opens the source file and parses process. Most of the actual parsing is done in <NaturalDocs::Languages::Base->ParseFile()> -# and <OnComment()>, though. -# -# *Do not call externally.* Rather, call <ParseForInformation()> or <ParseForBuild()>. -# -# Returns: -# -# The default menu title of the file. Will be the <FileName> if nothing better is found. -# -sub Parse - { - my ($self) = @_; - - NaturalDocs::Error->OnStartParsing($sourceFile); - - $language = NaturalDocs::Languages->LanguageOf($sourceFile); - NaturalDocs::Parser::Native->Start(); - @parsedFile = ( ); - - my ($autoTopics, $scopeRecord) = $language->ParseFile($sourceFile, \@parsedFile); - - - $self->AddToClassHierarchy(); - - $self->BreakLists(); - - if (defined $autoTopics) - { - if (defined $scopeRecord) - { $self->RepairPackages($autoTopics, $scopeRecord); }; - - $self->MergeAutoTopics($language, $autoTopics); - }; - - # We don't need to do this if there aren't any auto-topics because the only package changes would be implied by the comments. - if (defined $autoTopics) - { $self->AddPackageDelineators(); }; - - if (!NaturalDocs::Settings->NoAutoGroup()) - { $self->MakeAutoGroups($autoTopics); }; - - - # Set the menu title. - - my $defaultMenuTitle = $sourceFile; - - if (scalar @parsedFile) - { - # If there's only one topic, it's title overrides the file name. Certain topic types override the file name as well. - if (scalar @parsedFile == 1 || NaturalDocs::Topics->TypeInfo( $parsedFile[0]->Type() )->PageTitleIfFirst() ) - { - $defaultMenuTitle = $parsedFile[0]->Title(); - } - else - { - # If the title ended up being the file name, add a leading section for it. - my $name; - - my ($inputDirectory, $relativePath) = NaturalDocs::Settings->SplitFromInputDirectory($sourceFile); - - my ($volume, $dirString, $file) = NaturalDocs::File->SplitPath($relativePath); - my @directories = NaturalDocs::File->SplitDirectories($dirString); - - if (scalar @directories > 2) - { - $dirString = NaturalDocs::File->JoinDirectories('...', $directories[-2], $directories[-1]); - $name = NaturalDocs::File->JoinPath(undef, $dirString, $file); - } - else - { - $name = $relativePath; - } - - unshift @parsedFile, - NaturalDocs::Parser::ParsedTopic->New(::TOPIC_FILE(), $name, undef, undef, undef, undef, undef, 1, undef); - }; - }; - - NaturalDocs::Error->OnEndParsing($sourceFile); - - return $defaultMenuTitle; - }; - - -# -# Function: CleanComment -# -# Removes any extraneous formatting and whitespace from the comment. Eliminates comment boxes, horizontal lines, leading -# and trailing line breaks, trailing whitespace from lines, and expands all tab characters. It keeps leading whitespace, though, -# since it may be needed for example code, and multiple blank lines, since the original line numbers are needed. -# -# Parameters: -# -# commentLines - An arrayref of the comment lines to clean. *The original memory will be changed.* Lines should have the -# language's comment symbols replaced by spaces and not have a trailing line break. -# -sub CleanComment #(commentLines) - { - my ($self, $commentLines) = @_; - - use constant DONT_KNOW => 0; - use constant IS_UNIFORM => 1; - use constant IS_UNIFORM_IF_AT_END => 2; - use constant IS_NOT_UNIFORM => 3; - - my $leftSide = DONT_KNOW; - my $rightSide = DONT_KNOW; - my $leftSideChar; - my $rightSideChar; - - my $index = 0; - my $tabLength = NaturalDocs::Settings->TabLength(); - - while ($index < scalar @$commentLines) - { - # Strip trailing whitespace from the original. - - $commentLines->[$index] =~ s/[ \t]+$//; - - - # Expand tabs in the original. This method is almost six times faster than Text::Tabs' method. - - my $tabIndex = index($commentLines->[$index], "\t"); - - while ($tabIndex != -1) - { - substr( $commentLines->[$index], $tabIndex, 1, ' ' x ($tabLength - ($tabIndex % $tabLength)) ); - $tabIndex = index($commentLines->[$index], "\t", $tabIndex); - }; - - - # Make a working copy and strip leading whitespace as well. This has to be done after tabs are expanded because - # stripping indentation could change how far tabs are expanded. - - my $line = $commentLines->[$index]; - $line =~ s/^ +//; - - # If the line is blank... - if (!length $line) - { - # If we have a potential vertical line, this only acceptable if it's at the end of the comment. - if ($leftSide == IS_UNIFORM) - { $leftSide = IS_UNIFORM_IF_AT_END; }; - if ($rightSide == IS_UNIFORM) - { $rightSide = IS_UNIFORM_IF_AT_END; }; - } - - # If there's at least four symbols in a row, it's a horizontal line. The second regex supports differing edge characters. It - # doesn't matter if any of this matches the left and right side symbols. - elsif ($line =~ /^([^a-zA-Z0-9 ])\1{3,}$/ || - $line =~ /^([^a-zA-Z0-9 ])\1*([^a-zA-Z0-9 ])\2{3,}([^a-zA-Z0-9 ])\3*$/) - { - # Convert the original to a blank line. - $commentLines->[$index] = ''; - - # This has no effect on the vertical line detection. - } - - # If the line is not blank or a horizontal line... - else - { - # More content means any previous blank lines are no longer tolerated in vertical line detection. They are only - # acceptable at the end of the comment. - - if ($leftSide == IS_UNIFORM_IF_AT_END) - { $leftSide = IS_NOT_UNIFORM; }; - if ($rightSide == IS_UNIFORM_IF_AT_END) - { $rightSide = IS_NOT_UNIFORM; }; - - - # Detect vertical lines. Lines are only lines if they are followed by whitespace or a connected horizontal line. - # Otherwise we may accidentally detect lines from short comments that just happen to have every first or last - # character the same. - - if ($leftSide != IS_NOT_UNIFORM) - { - if ($line =~ /^([^a-zA-Z0-9])\1*(?: |$)/) - { - if ($leftSide == DONT_KNOW) - { - $leftSide = IS_UNIFORM; - $leftSideChar = $1; - } - else # ($leftSide == IS_UNIFORM) Other choices already ruled out. - { - if ($leftSideChar ne $1) - { $leftSide = IS_NOT_UNIFORM; }; - }; - } - # We'll tolerate the lack of symbols on the left on the first line, because it may be a - # /* Function: Whatever - # * Description. - # */ - # comment which would have the leading /* blanked out. - elsif ($index != 0) - { - $leftSide = IS_NOT_UNIFORM; - }; - }; - - if ($rightSide != IS_NOT_UNIFORM) - { - if ($line =~ / ([^a-zA-Z0-9])\1*$/) - { - if ($rightSide == DONT_KNOW) - { - $rightSide = IS_UNIFORM; - $rightSideChar = $1; - } - else # ($rightSide == IS_UNIFORM) Other choices already ruled out. - { - if ($rightSideChar ne $1) - { $rightSide = IS_NOT_UNIFORM; }; - }; - } - else - { - $rightSide = IS_NOT_UNIFORM; - }; - }; - - # We'll remove vertical lines later if they're uniform throughout the entire comment. - }; - - $index++; - }; - - - if ($leftSide == IS_UNIFORM_IF_AT_END) - { $leftSide = IS_UNIFORM; }; - if ($rightSide == IS_UNIFORM_IF_AT_END) - { $rightSide = IS_UNIFORM; }; - - - $index = 0; - - while ($index < scalar @$commentLines) - { - # Clear vertical lines. - - if ($leftSide == IS_UNIFORM) - { - # This works because every line should either start this way, be blank, or be the first line that doesn't start with a symbol. - $commentLines->[$index] =~ s/^ *([^a-zA-Z0-9 ])\1*//; - }; - - if ($rightSide == IS_UNIFORM) - { - $commentLines->[$index] =~ s/ *([^a-zA-Z0-9 ])\1*$//; - }; - - - # Clear horizontal lines again if there were vertical lines. This catches lines that were separated from the verticals by - # whitespace. We couldn't do this in the first loop because that would make the regexes over-tolerant. - - if ($leftSide == IS_UNIFORM || $rightSide == IS_UNIFORM) - { - $commentLines->[$index] =~ s/^ *([^a-zA-Z0-9 ])\1{3,}$//; - $commentLines->[$index] =~ s/^ *([^a-zA-Z0-9 ])\1*([^a-zA-Z0-9 ])\2{3,}([^a-zA-Z0-9 ])\3*$//; - }; - - - $index++; - }; - - }; - - - -############################################################################### -# Group: Processing Functions - - -# -# Function: RepairPackages -# -# Recalculates the packages for all comment topics using the auto-topics and the scope record. Call this *before* calling -# <MergeAutoTopics()>. -# -# Parameters: -# -# autoTopics - A reference to the list of automatically generated <NaturalDocs::Parser::ParsedTopics>. -# scopeRecord - A reference to an array of <NaturalDocs::Languages::Advanced::ScopeChanges>. -# -sub RepairPackages #(autoTopics, scopeRecord) - { - my ($self, $autoTopics, $scopeRecord) = @_; - - my $topicIndex = 0; - my $autoTopicIndex = 0; - my $scopeIndex = 0; - - my $topic = $parsedFile[0]; - my $autoTopic = $autoTopics->[0]; - my $scopeChange = $scopeRecord->[0]; - - my $currentPackage; - my $inFakePackage; - - while (defined $topic) - { - # First update the scope via the record if its defined and has the lowest line number. - if (defined $scopeChange && - $scopeChange->LineNumber() <= $topic->LineNumber() && - (!defined $autoTopic || $scopeChange->LineNumber() <= $autoTopic->LineNumber()) ) - { - $currentPackage = $scopeChange->Scope(); - $scopeIndex++; - $scopeChange = $scopeRecord->[$scopeIndex]; # Will be undef when past end. - $inFakePackage = undef; - } - - # Next try to end a fake scope with an auto topic if its defined and has the lowest line number. - elsif (defined $autoTopic && - $autoTopic->LineNumber() <= $topic->LineNumber()) - { - if ($inFakePackage) - { - $currentPackage = $autoTopic->Package(); - $inFakePackage = undef; - }; - - $autoTopicIndex++; - $autoTopic = $autoTopics->[$autoTopicIndex]; # Will be undef when past end. - } - - - # Finally try to handle the topic, since it has the lowest line number. - else - { - my $scope = NaturalDocs::Topics->TypeInfo($topic->Type())->Scope(); - - if ($scope == ::SCOPE_START() || $scope == ::SCOPE_END()) - { - # They should already have the correct class and scope. - $currentPackage = $topic->Package(); - $inFakePackage = 1; - } - else - { - # Fix the package of everything else. - - # Note that the first function or variable topic to appear in a fake package will assume that package even if it turns out - # to be incorrect in the actual code, since the topic will come before the auto-topic. This will be corrected in - # MergeAutoTopics(). - - $topic->SetPackage($currentPackage); - }; - - $topicIndex++; - $topic = $parsedFile[$topicIndex]; # Will be undef when past end. - }; - }; - - }; - - -# -# Function: MergeAutoTopics -# -# Merges the automatically generated topics into the file. If an auto-topic matches an existing topic, it will have it's prototype -# and package transferred. If it doesn't, the auto-topic will be inserted into the list unless -# <NaturalDocs::Settings->DocumentedOnly()> is set. -# -# Parameters: -# -# language - The <NaturalDocs::Languages::Base>-derived class for the file. -# autoTopics - A reference to the list of automatically generated topics. -# -sub MergeAutoTopics #(language, autoTopics) - { - my ($self, $language, $autoTopics) = @_; - - my $topicIndex = 0; - my $autoTopicIndex = 0; - - # Keys are topic types, values are existence hashrefs of titles. - my %topicsInLists; - - while ($topicIndex < scalar @parsedFile && $autoTopicIndex < scalar @$autoTopics) - { - my $topic = $parsedFile[$topicIndex]; - my $autoTopic = $autoTopics->[$autoTopicIndex]; - - my $cleanTitle = $topic->Title(); - $cleanTitle =~ s/[\t ]*\([^\(]*$//; - - # Add the auto-topic if it's higher in the file than the current topic. - if ($autoTopic->LineNumber < $topic->LineNumber()) - { - if (exists $topicsInLists{$autoTopic->Type()} && - exists $topicsInLists{$autoTopic->Type()}->{$autoTopic->Title()}) - { - # Remove it from the list so a second one with the same name will be added. - delete $topicsInLists{$autoTopic->Type()}->{$autoTopic->Title()}; - } - elsif (!NaturalDocs::Settings->DocumentedOnly()) - { - splice(@parsedFile, $topicIndex, 0, $autoTopic); - $topicIndex++; - }; - - $autoTopicIndex++; - } - - # Transfer information if we have a match. - elsif ($topic->Type() == $autoTopic->Type() && index($autoTopic->Title(), $cleanTitle) != -1) - { - $topic->SetType($autoTopic->Type()); - $topic->SetPrototype($autoTopic->Prototype()); - - if (NaturalDocs::Topics->TypeInfo($topic->Type())->Scope() != ::SCOPE_START()) - { $topic->SetPackage($autoTopic->Package()); }; - - $topicIndex++; - $autoTopicIndex++; - } - - # Extract topics in lists. - elsif ($topic->IsList()) - { - if (!exists $topicsInLists{$topic->Type()}) - { $topicsInLists{$topic->Type()} = { }; }; - - my $body = $topic->Body(); - - while ($body =~ /<ds>([^<]+)<\/ds>/g) - { $topicsInLists{$topic->Type()}->{NaturalDocs::NDMarkup->RestoreAmpChars($1)} = 1; }; - - $topicIndex++; - } - - # Otherwise there's no match. Skip the topic. The auto-topic will be added later. - else - { - $topicIndex++; - } - }; - - # Add any auto-topics remaining. - if ($autoTopicIndex < scalar @$autoTopics && !NaturalDocs::Settings->DocumentedOnly()) - { - push @parsedFile, @$autoTopics[$autoTopicIndex..scalar @$autoTopics-1]; - }; - }; - - -# -# Function: MakeAutoGroups -# -# Creates group topics for files that do not have them. -# -sub MakeAutoGroups - { - my ($self) = @_; - - # No groups only one topic. - if (scalar @parsedFile < 2) - { return; }; - - my $index = 0; - my $startStretch = 0; - - # Skip the first entry if its the page title. - if (NaturalDocs::Topics->TypeInfo( $parsedFile[0]->Type() )->PageTitleIfFirst()) - { - $index = 1; - $startStretch = 1; - }; - - # Make auto-groups for each stretch between scope-altering topics. - while ($index < scalar @parsedFile) - { - my $scope = NaturalDocs::Topics->TypeInfo($parsedFile[$index]->Type())->Scope(); - - if ($scope == ::SCOPE_START() || $scope == ::SCOPE_END()) - { - if ($index > $startStretch) - { $index += $self->MakeAutoGroupsFor($startStretch, $index); }; - - $startStretch = $index + 1; - }; - - $index++; - }; - - if ($index > $startStretch) - { $self->MakeAutoGroupsFor($startStretch, $index); }; - }; - - -# -# Function: MakeAutoGroupsFor -# -# Creates group topics for sections of files that do not have them. A support function for <MakeAutoGroups()>. -# -# Parameters: -# -# startIndex - The index to start at. -# endIndex - The index to end at. Not inclusive. -# -# Returns: -# -# The number of group topics added. -# -sub MakeAutoGroupsFor #(startIndex, endIndex) - { - my ($self, $startIndex, $endIndex) = @_; - - # No groups if any are defined already. - for (my $i = $startIndex; $i < $endIndex; $i++) - { - if ($parsedFile[$i]->Type() eq ::TOPIC_GROUP()) - { return 0; }; - }; - - - use constant COUNT => 0; - use constant TYPE => 1; - use constant SECOND_TYPE => 2; - use constant SIZE => 3; - - # This is an array of ( count, type, secondType ) triples. Count and Type will always be filled in; count is the number of - # consecutive topics of type. On the second pass, if small groups are combined secondType will be filled in. There will not be - # more than two types per group. - my @groups; - my $groupIndex = 0; - - - # First pass: Determine all the groups. - - my $i = $startIndex; - my $currentType; - - while ($i < $endIndex) - { - if (!defined $currentType || ($parsedFile[$i]->Type() ne $currentType && $parsedFile[$i]->Type() ne ::TOPIC_GENERIC()) ) - { - if (defined $currentType) - { $groupIndex += SIZE; }; - - $currentType = $parsedFile[$i]->Type(); - - $groups[$groupIndex + COUNT] = 1; - $groups[$groupIndex + TYPE] = $currentType; - } - else - { $groups[$groupIndex + COUNT]++; }; - - $i++; - }; - - - # Second pass: Combine groups based on "noise". Noise means types go from A to B to A at least once, and there are at least - # two groups in a row with three or less, and at least one of those groups is two or less. So 3, 3, 3 doesn't count as noise, but - # 3, 2, 3 does. - - $groupIndex = 0; - - # While there are at least three groups left... - while ($groupIndex < scalar @groups - (2 * SIZE)) - { - # If the group two places in front of this one has the same type... - if ($groups[$groupIndex + (2 * SIZE) + TYPE] eq $groups[$groupIndex + TYPE]) - { - # It means we went from A to B to A, which partially qualifies as noise. - - my $firstType = $groups[$groupIndex + TYPE]; - my $secondType = $groups[$groupIndex + SIZE + TYPE]; - - if (NaturalDocs::Topics->TypeInfo($firstType)->CanGroupWith($secondType) || - NaturalDocs::Topics->TypeInfo($secondType)->CanGroupWith($firstType)) - { - my $hasNoise; - - my $hasThrees; - my $hasTwosOrOnes; - - my $endIndex = $groupIndex; - - while ($endIndex < scalar @groups && - ($groups[$endIndex + TYPE] eq $firstType || $groups[$endIndex + TYPE] eq $secondType)) - { - if ($groups[$endIndex + COUNT] > 3) - { - # They must be consecutive to count. - $hasThrees = 0; - $hasTwosOrOnes = 0; - } - elsif ($groups[$endIndex + COUNT] == 3) - { - $hasThrees = 1; - - if ($hasTwosOrOnes) - { $hasNoise = 1; }; - } - else # < 3 - { - if ($hasThrees || $hasTwosOrOnes) - { $hasNoise = 1; }; - - $hasTwosOrOnes = 1; - }; - - $endIndex += SIZE; - }; - - if (!$hasNoise) - { - $groupIndex = $endIndex - SIZE; - } - else # hasNoise - { - $groups[$groupIndex + SECOND_TYPE] = $secondType; - - for (my $noiseIndex = $groupIndex + SIZE; $noiseIndex < $endIndex; $noiseIndex += SIZE) - { - $groups[$groupIndex + COUNT] += $groups[$noiseIndex + COUNT]; - }; - - splice(@groups, $groupIndex + SIZE, $endIndex - $groupIndex - SIZE); - - $groupIndex += SIZE; - }; - } - - else # They can't group together - { - $groupIndex += SIZE; - }; - } - - else - { $groupIndex += SIZE; }; - }; - - - # Finally, create group topics for the parsed file. - - $groupIndex = 0; - $i = $startIndex; - - while ($groupIndex < scalar @groups) - { - if ($groups[$groupIndex + TYPE] ne ::TOPIC_GENERIC()) - { - my $topic = $parsedFile[$i]; - my $title = NaturalDocs::Topics->NameOfType($groups[$groupIndex + TYPE], 1); - - if (defined $groups[$groupIndex + SECOND_TYPE]) - { $title .= ' and ' . NaturalDocs::Topics->NameOfType($groups[$groupIndex + SECOND_TYPE], 1); }; - - splice(@parsedFile, $i, 0, NaturalDocs::Parser::ParsedTopic->New(::TOPIC_GROUP(), - $title, - $topic->Package(), $topic->Using(), - undef, undef, undef, - $topic->LineNumber()) ); - $i++; - }; - - $i += $groups[$groupIndex + COUNT]; - $groupIndex += SIZE; - }; - - return (scalar @groups / SIZE); - }; - - -# -# Function: AddToClassHierarchy -# -# Adds any class topics to the class hierarchy, since they may not have been called with <OnClass()> if they didn't match up to -# an auto-topic. -# -sub AddToClassHierarchy - { - my ($self) = @_; - - foreach my $topic (@parsedFile) - { - if (NaturalDocs::Topics->TypeInfo( $topic->Type() )->ClassHierarchy()) - { - if ($topic->IsList()) - { - my $body = $topic->Body(); - - while ($body =~ /<ds>([^<]+)<\/ds>/g) - { - $self->OnClass( NaturalDocs::SymbolString->FromText( NaturalDocs::NDMarkup->RestoreAmpChars($1) ) ); - }; - } - else - { - $self->OnClass($topic->Package()); - }; - }; - }; - }; - - -# -# Function: AddPackageDelineators -# -# Adds section and class topics to make sure the package is correctly represented in the documentation. Should be called last in -# this process. -# -sub AddPackageDelineators - { - my ($self) = @_; - - my $index = 0; - my $currentPackage; - - # Values are the arrayref [ title, type ]; - my %usedPackages; - - while ($index < scalar @parsedFile) - { - my $topic = $parsedFile[$index]; - - if ($topic->Package() ne $currentPackage) - { - $currentPackage = $topic->Package(); - my $scopeType = NaturalDocs::Topics->TypeInfo($topic->Type())->Scope(); - - if ($scopeType == ::SCOPE_START()) - { - $usedPackages{$currentPackage} = [ $topic->Title(), $topic->Type() ]; - } - elsif ($scopeType == ::SCOPE_END()) - { - my $newTopic; - - if (!defined $currentPackage) - { - $newTopic = NaturalDocs::Parser::ParsedTopic->New(::TOPIC_SECTION(), 'Global', - undef, undef, - undef, undef, undef, - $topic->LineNumber(), undef); - } - else - { - my ($title, $body, $summary, $type); - my @packageIdentifiers = NaturalDocs::SymbolString->IdentifiersOf($currentPackage); - - if (exists $usedPackages{$currentPackage}) - { - $title = $usedPackages{$currentPackage}->[0]; - $type = $usedPackages{$currentPackage}->[1]; - $body = '<p>(continued)</p>'; - $summary = '(continued)'; - } - else - { - $title = join($language->PackageSeparator(), @packageIdentifiers); - $type = ::TOPIC_CLASS(); - - # Body and summary stay undef. - - $usedPackages{$currentPackage} = $title; - }; - - my @titleIdentifiers = NaturalDocs::SymbolString->IdentifiersOf( NaturalDocs::SymbolString->FromText($title) ); - for (my $i = 0; $i < scalar @titleIdentifiers; $i++) - { pop @packageIdentifiers; }; - - $newTopic = NaturalDocs::Parser::ParsedTopic->New($type, $title, - NaturalDocs::SymbolString->Join(@packageIdentifiers), undef, - undef, $summary, $body, - $topic->LineNumber(), undef); - } - - splice(@parsedFile, $index, 0, $newTopic); - $index++; - } - }; - - $index++; - }; - }; - - -# -# Function: BreakLists -# -# Breaks list topics into individual topics. -# -sub BreakLists - { - my $self = shift; - - my $index = 0; - - while ($index < scalar @parsedFile) - { - my $topic = $parsedFile[$index]; - - if ($topic->IsList() && NaturalDocs::Topics->TypeInfo( $topic->Type() )->BreakLists()) - { - my $body = $topic->Body(); - - my @newTopics; - my $newBody; - - my $bodyIndex = 0; - - for (;;) - { - my $startList = index($body, '<dl>', $bodyIndex); - - if ($startList == -1) - { last; }; - - $newBody .= substr($body, $bodyIndex, $startList - $bodyIndex); - - my $endList = index($body, '</dl>', $startList); - my $listBody = substr($body, $startList, $endList - $startList); - - while ($listBody =~ /<ds>([^<]+)<\/ds><dd>(.*?)<\/dd>/g) - { - my ($symbol, $description) = ($1, $2); - - push @newTopics, NaturalDocs::Parser::ParsedTopic->New( $topic->Type(), $symbol, $topic->Package(), - $topic->Using(), undef, - $self->GetSummaryFromDescriptionList($description), - '<p>' . $description . '</p>', $topic->LineNumber(), - undef ); - }; - - $bodyIndex = $endList + 5; - }; - - $newBody .= substr($body, $bodyIndex); - - # Remove trailing headings. - $newBody =~ s/(?:<h>[^<]+<\/h>)+$//; - - # Remove empty headings. - $newBody =~ s/(?:<h>[^<]+<\/h>)+(<h>[^<]+<\/h>)/$1/g; - - if ($newBody) - { - unshift @newTopics, NaturalDocs::Parser::ParsedTopic->New( ::TOPIC_GROUP(), $topic->Title(), $topic->Package(), - $topic->Using(), undef, - $self->GetSummaryFromBody($newBody), $newBody, - $topic->LineNumber(), undef ); - }; - - splice(@parsedFile, $index, 1, @newTopics); - - $index += scalar @newTopics; - } - - else # not a list - { $index++; }; - }; - }; - - -# -# Function: GetSummaryFromBody -# -# Returns the summary text from the topic body. -# -# Parameters: -# -# body - The complete topic body, in <NDMarkup>. -# -# Returns: -# -# The topic summary, or undef if none. -# -sub GetSummaryFromBody #(body) - { - my ($self, $body) = @_; - - my $summary; - - # Extract the first sentence from the leading paragraph, if any. We'll tolerate a single header beforehand, but nothing else. - - if ($body =~ /^(?:<h>[^<]*<\/h>)?<p>(.*?)(<\/p>|[\.\!\?](?:[\)\}\'\ ]|"|>))/x) - { - $summary = $1; - - if ($2 ne '</p>') - { $summary .= $2; }; - }; - - return $summary; - }; - - -# -# Function: GetSummaryFromDescriptionList -# -# Returns the summary text from a description list entry. -# -# Parameters: -# -# description - The description in <NDMarkup>. Should be the content between the <dd></dd> tags only. -# -# Returns: -# -# The description summary, or undef if none. -# -sub GetSummaryFromDescriptionList #(description) - { - my ($self, $description) = @_; - - my $summary; - - if ($description =~ /^(.*?)($|[\.\!\?](?:[\)\}\'\ ]|"|>))/) - { $summary = $1 . $2; }; - - return $summary; - }; - - -1; |