1
0
mirror of https://github.com/lxsang/antd-lua-plugin synced 2025-07-15 21:39:47 +02:00

mimgrating from another repo

This commit is contained in:
Xuan Sang LE
2018-09-19 15:08:49 +02:00
parent 91320521e8
commit 38bd13b46b
600 changed files with 362490 additions and 1 deletions

View File

@ -0,0 +1,337 @@
###############################################################################
#
# Package: NaturalDocs::BinaryFile
#
###############################################################################
#
# A package to manage Natural Docs' binary data files.
#
# Usage:
#
# - Only one data file can be managed with this package at a time. You must close the file before opening another
# one.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::BinaryFile;
use vars qw(@EXPORT @ISA);
require Exporter;
@ISA = qw(Exporter);
@EXPORT = ('BINARY_FORMAT');
use Encode qw(encode_utf8 decode_utf8);
###############################################################################
# Group: Format
#
# Topic: Standard Header
#
# > [UInt8: BINARY_FORMAT]
# > [VersionInt: app version]
#
# The first byte is <BINARY_FORMAT>, which distinguishes binary configuration files from text ones, since Natural Docs
# used to use text data files with the same name.
#
# The next section is the version of Natural Docs that wrote the file, as defined by <NaturalDocs::Settings->AppVersion>
# and written by <NaturalDocs::Version->ToBinaryFile()>.
#
#
# Topic: Data Types
#
# All the integer data types are written most significant byte first, aka big endian.
#
# An AString16 is a UInt16 followed by that many 8-bit ASCII characters. It doesn't include a null character at the end. Undef
# strings are represented by a zero for the UInt16 and nothing following it.
#
# A UString16 is a UInt16 followed by that many UTF-8 encoded bytes. It doesn't include a null character at the end. Undef
# strings are represented by a zero for the UInt16 and nothing following it.
#
#
# Constant: BINARY_FORMAT
#
# An 8-bit constant that's used as the first byte of binary data files. This is used so that you can easily distinguish between
# binary and old-style text data files. It's not a character that would appear in plain text files.
#
use constant BINARY_FORMAT => pack('C', 0x06);
# Which is ACK or acknowledge in ASCII. Is the cool spade character in DOS displays.
###############################################################################
# Group: Variables
#
# handle: FH_BINARYDATAFILE
#
# The file handle used for the data file.
#
#
# string: currentFile
#
# The <FileName> for the current configuration file being parsed.
#
my $currentFile;
###############################################################################
# Group: File Functions
#
# Function: OpenForReading
#
# Opens a binary file for reading.
#
# Parameters:
#
# minimumVersion - The minimum version of the file format that is acceptible. May be undef.
#
# Returns:
#
# The format <VersionInt> or undef if it failed. It could fail for any of the following reasons.
#
# - The file doesn't exist.
# - The file couldn't be opened.
# - The file didn't have the proper header.
# - Either the application or the file was from a development release, and they're not the exact same development release.
# - The file's format was less than the minimum version, if one was defined.
# - The file was from a later application version than the current.
#
sub OpenForReading #(FileName file, optional VersionInt minimumVersion) => VersionInt
{
my ($self, $file, $minimumVersion) = @_;
if (defined $currentFile)
{ die "Tried to open binary file " . $file . " for reading when " . $currentFile . " was already open."; };
$currentFile = $file;
if (open(FH_BINARYDATAFILE, '<' . $currentFile))
{
# See if it's binary.
binmode(FH_BINARYDATAFILE);
my $firstChar;
read(FH_BINARYDATAFILE, $firstChar, 1);
if ($firstChar == ::BINARY_FORMAT())
{
my $version = NaturalDocs::Version->FromBinaryFile(\*FH_BINARYDATAFILE);
if (NaturalDocs::Version->CheckFileFormat($version, $minimumVersion))
{ return $version; };
};
close(FH_BINARYDATAFILE);
};
$currentFile = undef;
return undef;
};
#
# Function: OpenForWriting
#
# Opens a binary file for writing and writes the standard header. Dies if the file cannot be opened.
#
sub OpenForWriting #(FileName file)
{
my ($self, $file) = @_;
if (defined $currentFile)
{ die "Tried to open binary file " . $file . " for writing when " . $currentFile . " was already open."; };
$currentFile = $file;
open (FH_BINARYDATAFILE, '>' . $currentFile)
or die "Couldn't save " . $file . ".\n";
binmode(FH_BINARYDATAFILE);
print FH_BINARYDATAFILE '' . ::BINARY_FORMAT();
NaturalDocs::Version->ToBinaryFile(\*FH_BINARYDATAFILE, NaturalDocs::Settings->AppVersion());
};
#
# Function: Close
#
# Closes the current configuration file.
#
sub Close
{
my $self = shift;
if (!$currentFile)
{ die "Tried to close a binary file when one wasn't open."; };
close(FH_BINARYDATAFILE);
$currentFile = undef;
};
###############################################################################
# Group: Reading Functions
#
# Function: GetUInt8
# Reads and returns a UInt8 from the open file.
#
sub GetUInt8 # => UInt8
{
my $raw;
read(FH_BINARYDATAFILE, $raw, 1);
return unpack('C', $raw);
};
#
# Function: GetUInt16
# Reads and returns a UInt16 from the open file.
#
sub GetUInt16 # => UInt16
{
my $raw;
read(FH_BINARYDATAFILE, $raw, 2);
return unpack('n', $raw);
};
#
# Function: GetUInt32
# Reads and returns a UInt32 from the open file.
#
sub GetUInt32 # => UInt32
{
my $raw;
read(FH_BINARYDATAFILE, $raw, 4);
return unpack('N', $raw);
};
#
# Function: GetAString16
# Reads and returns an AString16 from the open file. Supports undef strings.
#
sub GetAString16 # => string
{
my $rawLength;
read(FH_BINARYDATAFILE, $rawLength, 2);
my $length = unpack('n', $rawLength);
if (!$length)
{ return undef; };
my $string;
read(FH_BINARYDATAFILE, $string, $length);
return $string;
};
#
# Function: GetUString16
# Reads and returns a UString16 from the open file. Supports undef strings.
#
sub GetUString16 # => string
{
my $rawLength;
read(FH_BINARYDATAFILE, $rawLength, 2);
my $length = unpack('n', $rawLength);
if (!$length)
{ return undef; };
my $string;
read(FH_BINARYDATAFILE, $string, $length);
$string = decode_utf8($string);
return $string;
};
###############################################################################
# Group: Writing Functions
#
# Function: WriteUInt8
# Writes a UInt8 to the open file.
#
sub WriteUInt8 #(UInt8 value)
{
my ($self, $value) = @_;
print FH_BINARYDATAFILE pack('C', $value);
};
#
# Function: WriteUInt16
# Writes a UInt32 to the open file.
#
sub WriteUInt16 #(UInt16 value)
{
my ($self, $value) = @_;
print FH_BINARYDATAFILE pack('n', $value);
};
#
# Function: WriteUInt32
# Writes a UInt32 to the open file.
#
sub WriteUInt32 #(UInt32 value)
{
my ($self, $value) = @_;
print FH_BINARYDATAFILE pack('N', $value);
};
#
# Function: WriteAString16
# Writes an AString16 to the open file. Supports undef strings.
#
sub WriteAString16 #(string value)
{
my ($self, $string) = @_;
if (length($string))
{ print FH_BINARYDATAFILE pack('nA*', length($string), $string); }
else
{ print FH_BINARYDATAFILE pack('n', 0); };
};
#
# Function: WriteUString16
# Writes an UString16 to the open file. Supports undef strings.
#
sub WriteUString16 #(string value)
{
my ($self, $string) = @_;
if (length($string))
{
$string = encode_utf8($string);
print FH_BINARYDATAFILE pack('na*', length($string), $string);
}
else
{ print FH_BINARYDATAFILE pack('n', 0); };
};
1;

View File

@ -0,0 +1,281 @@
###############################################################################
#
# Package: NaturalDocs::Builder
#
###############################################################################
#
# A package that takes parsed source file and builds the output for it.
#
# Usage and Dependencies:
#
# - <Add()> can be called immediately.
# - <OutputPackages()> and <OutputPackageOf()> can be called once all sub-packages have been registered via <Add()>.
# Since this is normally done in their INIT functions, they should be available to all normal functions immediately.
#
# - Prior to calling <Run()>, <NaturalDocs::Settings>, <NaturalDocs::Project>, <NaturalDocs::Menu>, and
# <NaturalDocs::Parser> must be initialized. <NaturalDocs::Settings->GenerateDirectoryNames()> must be called.
# <NaturalDocs::SymbolTable> and <NaturalDocs::ClassHierarchy> must be initialized and fully resolved.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
use NaturalDocs::Builder::Base;
use NaturalDocs::Builder::HTML;
use NaturalDocs::Builder::FramedHTML;
package NaturalDocs::Builder;
###############################################################################
# Group: Variables
#
# Array: outputPackages
#
# An array of the output packages available for use.
#
my @outputPackages;
###############################################################################
# Group: Functions
#
# Function: OutputPackages
#
# Returns an arrayref of the output packages available for use. The arrayref is not a copy of the data, so don't change it.
#
# Add output packages to this list with the <Add()> function.
#
sub OutputPackages
{ return \@outputPackages; };
#
# Function: OutputPackageOf
#
# Returns the output package corresponding to the passed command line option, or undef if none.
#
sub OutputPackageOf #(commandLineOption)
{
my ($self, $commandLineOption) = @_;
$commandLineOption = lc($commandLineOption);
foreach my $package (@outputPackages)
{
if (lc($package->CommandLineOption()) eq $commandLineOption)
{ return $package; };
};
return undef;
};
#
# Function: Add
#
# Adds an output package to those available for use. All output packages must call this function in order to be recognized.
#
# Parameters:
#
# package - The package name.
#
sub Add #(package)
{
my ($self, $package) = @_;
# Output packages shouldn't register themselves more than once, so we don't need to check for it.
push @outputPackages, $package;
};
#
# Function: Run
#
# Runs the build process. This must be called *every time* Natural Docs is run, regardless of whether any source files changed
# or not. Some output packages have dependencies on files outside of the source tree that need to be checked.
#
# Since there are multiple stages to the build process, this function will handle its own status messages. There's no need to print
# "Building files..." or something similar beforehand.
#
sub Run
{
my ($self) = @_;
# Determine what we're doing.
my $buildTargets = NaturalDocs::Settings->BuildTargets();
my $filesToBuild = NaturalDocs::Project->FilesToBuild();
my $numberOfFilesToBuild = (scalar keys %$filesToBuild) * (scalar @$buildTargets);
my $filesToPurge = NaturalDocs::Project->FilesToPurge();
my $numberOfFilesToPurge = (scalar keys %$filesToPurge) * (scalar @$buildTargets);
my $imagesToUpdate = NaturalDocs::Project->ImageFilesToUpdate();
my $numberOfImagesToUpdate = (scalar keys %$imagesToUpdate) * (scalar @$buildTargets);
my $imagesToPurge = NaturalDocs::Project->ImageFilesToPurge();
my $numberOfImagesToPurge = (scalar keys %$imagesToPurge) * (scalar @$buildTargets);
my %indexesToBuild;
my %indexesToPurge;
my $currentIndexes = NaturalDocs::Menu->Indexes();
my $previousIndexes = NaturalDocs::Menu->PreviousIndexes();
foreach my $index (keys %$currentIndexes)
{
if (NaturalDocs::SymbolTable->IndexChanged($index) || !exists $previousIndexes->{$index})
{
$indexesToBuild{$index} = 1;
};
};
# All indexes that still exist should have been deleted.
foreach my $index (keys %$previousIndexes)
{
if (!exists $currentIndexes->{$index})
{
$indexesToPurge{$index} = 1;
};
};
my $numberOfIndexesToBuild = (scalar keys %indexesToBuild) * (scalar @$buildTargets);
my $numberOfIndexesToPurge = (scalar keys %indexesToPurge) * (scalar @$buildTargets);
# Start the build process
foreach my $buildTarget (@$buildTargets)
{
$buildTarget->Builder()->BeginBuild( $numberOfFilesToBuild || $numberOfFilesToPurge ||
$numberOfImagesToUpdate || $numberOfImagesToPurge ||
$numberOfIndexesToBuild || $numberOfIndexesToPurge ||
NaturalDocs::Menu->HasChanged() );
};
if ($numberOfFilesToPurge)
{
NaturalDocs::StatusMessage->Start('Purging ' . $numberOfFilesToPurge
. ' file' . ($numberOfFilesToPurge > 1 ? 's' : '') . '...',
scalar @$buildTargets);
foreach my $buildTarget (@$buildTargets)
{
$buildTarget->Builder()->PurgeFiles($filesToPurge);
NaturalDocs::StatusMessage->CompletedItem();
};
};
if ($numberOfIndexesToPurge)
{
NaturalDocs::StatusMessage->Start('Purging ' . $numberOfIndexesToPurge
. ' index' . ($numberOfIndexesToPurge > 1 ? 'es' : '') . '...',
scalar @$buildTargets);
foreach my $buildTarget (@$buildTargets)
{
$buildTarget->Builder()->PurgeIndexes(\%indexesToPurge);
NaturalDocs::StatusMessage->CompletedItem();
};
};
if ($numberOfImagesToPurge)
{
NaturalDocs::StatusMessage->Start('Purging ' . $numberOfImagesToPurge
. ' image' . ($numberOfImagesToPurge > 1 ? 's' : '') . '...',
scalar @$buildTargets);
foreach my $buildTarget (@$buildTargets)
{
$buildTarget->Builder()->PurgeImages($imagesToPurge);
NaturalDocs::StatusMessage->CompletedItem();
};
};
if ($numberOfFilesToBuild)
{
NaturalDocs::StatusMessage->Start('Building ' . $numberOfFilesToBuild
. ' file' . ($numberOfFilesToBuild > 1 ? 's' : '') . '...',
$numberOfFilesToBuild);
foreach my $file (keys %$filesToBuild)
{
my $parsedFile = NaturalDocs::Parser->ParseForBuild($file);
NaturalDocs::Error->OnStartBuilding($file);
foreach my $buildTarget (@$buildTargets)
{
$buildTarget->Builder()->BuildFile($file, $parsedFile);
NaturalDocs::StatusMessage->CompletedItem();
};
NaturalDocs::Error->OnEndBuilding($file);
};
};
if ($numberOfIndexesToBuild)
{
NaturalDocs::StatusMessage->Start('Building ' . $numberOfIndexesToBuild
. ' index' . ($numberOfIndexesToBuild > 1 ? 'es' : '') . '...',
$numberOfIndexesToBuild);
foreach my $index (keys %indexesToBuild)
{
foreach my $buildTarget (@$buildTargets)
{
$buildTarget->Builder()->BuildIndex($index);
NaturalDocs::StatusMessage->CompletedItem();
};
};
};
if ($numberOfImagesToUpdate)
{
NaturalDocs::StatusMessage->Start('Updating ' . $numberOfImagesToUpdate
. ' image' . ($numberOfImagesToUpdate > 1 ? 's' : '') . '...',
$numberOfImagesToUpdate);
foreach my $image (keys %$imagesToUpdate)
{
foreach my $buildTarget (@$buildTargets)
{
$buildTarget->Builder()->UpdateImage($image);
NaturalDocs::StatusMessage->CompletedItem();
};
};
};
if (NaturalDocs::Menu->HasChanged())
{
if (!NaturalDocs::Settings->IsQuiet())
{ print "Updating menu...\n"; };
foreach my $buildTarget (@$buildTargets)
{ $buildTarget->Builder()->UpdateMenu(); };
};
foreach my $buildTarget (@$buildTargets)
{
$buildTarget->Builder()->EndBuild($numberOfFilesToBuild || $numberOfFilesToPurge ||
$numberOfIndexesToBuild || $numberOfIndexesToPurge ||
$numberOfImagesToUpdate || $numberOfImagesToPurge ||
NaturalDocs::Menu->HasChanged());
};
};
1;

View File

@ -0,0 +1,349 @@
###############################################################################
#
# Class: NaturalDocs::Builder::Base
#
###############################################################################
#
# A base class for all Builder output formats.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::Builder::Base;
###############################################################################
# Group: Notes
#
# Topic: Implementation
#
# Builder packages are implemented as blessed arrayrefs, not hashrefs. This is done for all objects in Natural Docs for
# efficiency reasons. You create members by defining constants via <NaturalDocs::DefineMembers> and using them as
# indexes into the array.
#
#
# Topic: Function Order
#
# The functions in the build process will always be called in the following order.
#
# - <BeginBuild()> will always be called.
# - <PurgeFiles()> will be called next only if there's files that need to be purged.
# - <PurgeIndexes()> will be called next only if there's indexes that need to be purged.
# - <PurgeImages()> will e called next only if there's images that need to be purged.
# - <BuildFile()> will be called once for each file that needs to be built, if any.
# - <BuildIndex()> will be called once for each index that changed and is part of the menu, if any.
# - <UpdateImage()> will be called once for each image that needs to be updated, if any.
# - <UpdateMenu()> will be called next only if the menu changed.
# - <EndBuild()> will always be called.
#
#
# Topic: How to Approach
#
# Here's an idea of how to approach making packages for different output types.
#
#
# Multiple Output Files, Embedded Menu:
#
# This example is for when you want to build one output file per source file, each with its own copy of the menu within it.
# This is how <NaturalDocs::Builder::HTML> works.
#
# Make sure you create a function that generates just the menu for a particular source file. We'll need to generate menus for
# both building a file from scratch and for updating the menu on an existing output file, so it's better to give it its own function.
# You may want to surround it with something that can be easily detected in the output file to make replacing easier.
#
# <BeginBuild()> isn't important. You don't need to implement it.
#
# Implement <PurgeFiles()> to delete the output files associated with the purged files.
#
# Implement <PurgeIndexes()> to delete the output files associated with the purged indexes.
#
# Implement <BuildFile()> to create an output file for the parsed source file. Use the menu function described earlier.
#
# Implement <BuildIndex()> to create an output file for each index. Use the menu function described earlier for each page.
#
# Implement <UpdateMenu()> to go through the list of unbuilt files and update their menus. You can get the list from
# <NaturalDocs::Project->UnbuiltFilesWithContent()>. You need to open their output files, replace the menu, and save it back
# to disk. Yes, it would be simpler from a programmer's point of view to just rebuild the file completely, but that would be
# _very_ inefficient since there could potentially be a _lot_ of files in this group.
#
# Also make sure <UpdateMenu()> goes through the unchanged indexes and updates them as well.
#
# <EndBuild()> isn't important. You don't need to implement it.
#
#
# Multiple Output Files, Menu in File:
#
# This example is for when you want to build one output file per source file, but keep the menu in its own separate file. This
# is how <NaturalDocs::Builder::FramedHTML> works.
#
# <BeginBuild()> isn't important. You don't need to implement it.
#
# Implement <PurgeFiles()> to delete the output files associated with the purged files.
#
# Implement <PurgeIndexes()> to delete the output files associated with the purged indexes.
#
# Implement <BuildFile()> to generate an output file from the parsed source file.
#
# Implement <BuildIndex()> to generate an output file for each index.
#
# Implement <UpdateMenu()> to rebuild the menu file.
#
# <EndBuild()> isn't important. You don't need to implement it.
#
#
# Single Output File using Intermediate Files:
#
# This example is for when you want to build one output file, such as a PDF file, but use intermediate files to handle differential
# building. This would be much like how a compiler compiles each source file into a object file, and then a linker stitches them
# all together into the final executable file.
#
# <BeginBuild()> isn't important. You don't need to implement it.
#
# Implement <PurgeFiles()> to delete the intermediate files associated with the purged files.
#
# Implement <PurgeIndexes()> to delete the intermediate files associated with the purged indexes.
#
# Implement <BuildFile()> to generate an intermediate file from the parsed source file.
#
# Implement <BuildIndex()> to generate an intermediate file for the specified index.
#
# Implement <UpdateMenu()> to generate the intermediate file for the menu.
#
# Implement <EndBuild()> so that if the project changed, it stitches the intermediate files together into the final
# output file. Make sure you check the parameter because the function will be called when nothing changes too.
#
#
# Single Output File using Direct Changes:
#
# This example is for when you want to build one output file, such as a PDF file, but engineering it in such a way that you don't
# need to use intermediate files. In other words, you're able to add, delete, and modify entries directly in the output file.
#
# Implement <BeginBuild()> so that if the project changed, it opens the output file and does anything it needs to do
# to get ready for editing.
#
# Implement <PurgeFiles()> to remove the entries associated with the purged files.
#
# Implement <PurgeIndexes()> to remove the entries associated with the purged indexes.
#
# Implement <BuildFile()> to add or replace a section of the output file with a new one generated from the parsed file.
#
# Implement <BuildIndex()> to add or replace an index in the output file with a new one generated from the specified index.
#
# Implement <EndBuild()> so that if the project changed, it saves the output file to disk.
#
# How you handle the menu depends on how the output file references other sections of itself. If it can do so by name, then
# you can implement <UpdateMenu()> to update the menu section of the file and you're done. If it has to reference itself
# by address or offset, it gets trickier. You should skip <UpdateMenu()> and instead rebuild the menu in <EndBuild()> if
# the parameter is true. This lets you do it whenever anything changes in a file, rather than just when the menu
# visibly changes. How you keep track of the locations and how they change is your problem.
#
###############################################################################
#
# Group: Required Interface Functions
#
# All Builder classes *must* define these functions.
#
#
# Function: INIT
#
# Define this function to call <NaturalDocs::Builder->Add()> so that <NaturalDocs::Builder> knows about this package.
# Packages are defined this way so that new ones can be added without messing around in other code.
#
#
# Function: CommandLineOption
#
# Define this function to return the text that should be put in the command line after -o to use this package. It cannot have
# spaces and is not case sensitive.
#
# For example, <NaturalDocs::Builder::HTML> returns 'html' so someone could use -o html [directory] to use that package.
#
sub CommandLineOption
{
NaturalDocs::Error->SoftDeath($_[0] . " didn't define CommandLineOption().");
};
#
# Function: BuildFile
#
# Define this function to convert a parsed file to this package's output format. This function will be called once for every source
# file that needs to be rebuilt. However, if a file hasn't changed since the last time Natural Docs was run, it will not be sent to
# this function. All packages must support differential build.
#
# Parameters:
#
# sourceFile - The name of the source file.
# parsedFile - The parsed source file, as an arrayref of <NaturalDocs::Parser::ParsedTopic> objects.
#
sub BuildFile #(sourceFile, parsedFile)
{
NaturalDocs::Error->SoftDeath($_[0] . " didn't define BuildFile().");
};
###############################################################################
#
# Group: Optional Interface Functions
#
# These functions can be implemented but packages are not required to do so.
#
#
# Function: New
#
# Creates and returns a new object.
#
# Note that this is the only function where the first parameter will be the package name, not the object itself.
#
sub New
{
my $package = shift;
my $object = [ ];
bless $object, $package;
return $object;
};
#
# Function: BeginBuild
#
# Define this function if the package needs to do anything at the beginning of the build process. This function will be called
# every time Natural Docs is run, even if the project hasn't changed. This allows you to manage dependencies specific
# to the output format that may change independently from the source tree and menu. For example,
# <NaturalDocs::Builder::HTML> needs to keep the CSS files in sync regardless of whether the source tree changed or not.
#
# Parameters:
#
# hasChanged - Whether the project has changed, such as source files or the menu file. If false, nothing else is going to be
# called except <EndBuild()>.
#
sub BeginBuild #(hasChanged)
{
};
#
# Function: EndBuild
#
# Define this function if the package needs to do anything at the end of the build process. This function will be called every time
# Natural Docs is run, even if the project hasn't changed. This allows you to manage dependencies specific to the output
# format that may change independently from the source tree. For example, <NaturalDocs::Builder::HTML> needs to keep the
# CSS files in sync regardless of whether the source tree changed or not.
#
# Parameters:
#
# hasChanged - Whether the project has changed, such as source files or the menu file. If false, the only other function that
# was called was <BeginBuild()>.
#
sub EndBuild #(hasChanged)
{
};
#
# Function: BuildIndex
#
# Define this function to create an index for the passed topic. You can get the index from
# <NaturalDocs::SymbolTable->Index()>.
#
# The reason it's not passed directly to this function is because indexes may be time-consuming to create. As such, they're
# generated on demand because some output packages may choose not to implement them.
#
# Parameters:
#
# topic - The <TopicType> to limit the index by.
#
sub BuildIndex #(topic)
{
};
#
# Function: UpdateImage
#
# Define this function to add or update the passed image in the output.
#
# Parameters:
#
# file - The image <FileName>
#
sub UpdateImage #(file)
{
};
#
# Function: PurgeFiles
#
# Define this function to make the package remove all output related to the passed files. These files no longer have Natural Docs
# content.
#
# Parameters:
#
# files - An existence hashref of the files to purge.
#
sub PurgeFiles #(files)
{
};
#
# Function: PurgeIndexes
#
# Define this function to make the package remove all output related to the passed indexes. These indexes are no longer part
# of the menu.
#
# Parameters:
#
# indexes - An existence hashref of the <TopicTypes> of the indexes to purge.
#
sub PurgeIndexes #(indexes)
{
};
#
# Function: PurgeImages
#
# Define this function to make the package remove all output related to the passed image files. These files are no longer used
# by the documentation.
#
# Parameters:
#
# files - An existence hashref of the image <FileNames> to purge.
#
sub PurgeImages #(files)
{
};
#
# Function: UpdateMenu
#
# Define this function to make the package update the menu. It will only be called if the menu changed.
#
sub UpdateMenu
{
};
1;

View File

@ -0,0 +1,354 @@
###############################################################################
#
# Package: NaturalDocs::Builder::FramedHTML
#
###############################################################################
#
# A package that generates output in HTML with frames.
#
# All functions are called with Package->Function() notation.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::Builder::FramedHTML;
use base 'NaturalDocs::Builder::HTMLBase';
###############################################################################
# Group: Implemented Interface Functions
#
# Function: INIT
#
# Registers the package with <NaturalDocs::Builder>.
#
sub INIT
{
NaturalDocs::Builder->Add(__PACKAGE__);
};
#
# Function: CommandLineOption
#
# Returns the option to follow -o to use this package. In this case, "html".
#
sub CommandLineOption
{
return 'FramedHTML';
};
#
# Function: BuildFile
#
# Builds the output file from the parsed source file.
#
# Parameters:
#
# sourcefile - The <FileName> of the source file.
# parsedFile - An arrayref of the source file as <NaturalDocs::Parser::ParsedTopic> objects.
#
sub BuildFile #(sourceFile, parsedFile)
{
my ($self, $sourceFile, $parsedFile) = @_;
my $outputFile = $self->OutputFileOf($sourceFile);
# 99.99% of the time the output directory will already exist, so this will actually be more efficient. It only won't exist
# if a new file was added in a new subdirectory and this is the first time that file was ever parsed.
if (!open(OUTPUTFILEHANDLE, '>' . $outputFile))
{
NaturalDocs::File->CreatePath( NaturalDocs::File->NoFileName($outputFile) );
open(OUTPUTFILEHANDLE, '>' . $outputFile)
or die "Couldn't create output file " . $outputFile . "\n";
};
binmode(OUTPUTFILEHANDLE, ':encoding(UTF-8)');
my $usePrettify = (NaturalDocs::Settings->HighlightCode() || NaturalDocs::Settings->HighlightAnonymous());
print OUTPUTFILEHANDLE
# IE 6 doesn't like any doctype here at all. Add one (strict or transitional doesn't matter) and it makes the page slightly too
# wide for the frame. Mozilla and Opera handle it like champs either way because they Don't Suck(tm).
# '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" '
# . '"http://www.w3.org/TR/REC-html40/loose.dtd">' . "\n\n"
'<html><head>'
. '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">'
. '<title>'
. $self->BuildTitle($sourceFile)
. '</title>'
. '<link rel="stylesheet" type="text/css" href="' . $self->MakeRelativeURL($outputFile, $self->MainCSSFile(), 1) . '">'
. '<script language=JavaScript src="' . $self->MakeRelativeURL($outputFile, $self->MainJavaScriptFile(), 1) . '"></script>';
if ($usePrettify)
{
print OUTPUTFILEHANDLE
'<script language=JavaScript src="' . $self->MakeRelativeURL($outputFile, $self->PrettifyJavaScriptFile(), 1) . '">'
. '</script>';
}
print OUTPUTFILEHANDLE
'</head><body class="FramedContentPage" onLoad="NDOnLoad();' . ($usePrettify ? 'prettyPrint();' : '') . '">'
. $self->OpeningBrowserStyles()
. $self->StandardComments()
. "\n\n\n"
. $self->BuildContent($sourceFile, $parsedFile)
. "\n\n\n"
. $self->BuildToolTips()
. $self->ClosingBrowserStyles()
. '</body></html>';
close(OUTPUTFILEHANDLE);
};
#
# Function: BuildIndex
#
# Builds an index for the passed type.
#
# Parameters:
#
# type - The <TopicType> to limit the index to, or undef if none.
#
sub BuildIndex #(type)
{
my ($self, $type) = @_;
my $indexTitle = $self->IndexTitleOf($type);
my $indexFile = $self->IndexFileOf($type);
my $startIndexPage =
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" '
. '"http://www.w3.org/TR/REC-html40/loose.dtd">' . "\n\n"
. '<html><head>'
. '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">'
. '<title>';
if (defined NaturalDocs::Menu->Title())
{ $startIndexPage .= $self->StringToHTML(NaturalDocs::Menu->Title()) . ' - '; };
$startIndexPage .=
$indexTitle
. '</title>'
. '<link rel="stylesheet" type="text/css" href="' . $self->MakeRelativeURL($indexFile, $self->MainCSSFile(), 1) . '">'
. '<script language=JavaScript src="' . $self->MakeRelativeURL($indexFile, $self->MainJavaScriptFile(), 1) . '"></script>'
. '</head><body class="FramedIndexPage" onLoad="NDOnLoad()">'
. $self->OpeningBrowserStyles()
. "\n\n\n"
. $self->StandardComments()
. "\n\n\n"
. '<div id=Index>'
. '<div class=IPageTitle>'
. $indexTitle
. '</div>';
my $endIndexPage =
'</div><!--Index-->'
. "\n\n\n"
. $self->ClosingBrowserStyles()
. '</body></html>';
my $startSearchResultsPage =
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" '
. '"http://www.w3.org/TR/REC-html40/loose.dtd">' . "\n\n"
. '<html><head>'
. '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">'
. '<link rel="stylesheet" type="text/css" href="' . $self->MakeRelativeURL($indexFile, $self->MainCSSFile(), 1) . '">'
. '<script language=JavaScript src="' . $self->MakeRelativeURL($indexFile, $self->MainJavaScriptFile(), 1) . '"></script>'
. '<script language=JavaScript src="' . $self->MakeRelativeURL($indexFile, $self->SearchDataJavaScriptFile(), 1) . '">'
. '</script>'
. '</head><body class="FramedSearchResultsPage" onLoad="NDOnLoad()">'
. $self->OpeningBrowserStyles()
. "\n\n\n"
. $self->StandardComments()
. "\n\n\n"
. '<div id=Index>'
. '<div class=IPageTitle>'
. 'Search Results'
. '</div>';
my $endSearchResultsPage =
'</div><!--Index-->'
. "\n\n\n"
. $self->ClosingBrowserStyles()
. '</body></html>';
my $indexContent = NaturalDocs::SymbolTable->Index($type);
my $pageCount = $self->BuildIndexPages($type, $indexContent, $startIndexPage, $endIndexPage,
$startSearchResultsPage, $endSearchResultsPage);
$self->PurgeIndexFiles($type, $indexContent, $pageCount + 1);
};
#
# Function: UpdateMenu
#
# Builds the menu file. Also generates index.html.
#
sub UpdateMenu
{
my $self = shift;
my $outputDirectory = NaturalDocs::Settings->OutputDirectoryOf($self);
my $outputFile = NaturalDocs::File->JoinPaths($outputDirectory, 'menu.html');
open(OUTPUTFILEHANDLE, '>' . $outputFile)
or die "Couldn't create output file " . $outputFile . "\n";
binmode(OUTPUTFILEHANDLE, ':encoding(UTF-8)');
my $title = 'Menu';
if (defined $title)
{ $title .= ' - ' . NaturalDocs::Menu->Title(); };
$title = $self->StringToHTML($title);
print OUTPUTFILEHANDLE
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" '
. '"http://www.w3.org/TR/REC-html40/loose.dtd">' . "\n\n"
. '<html><head>'
. '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">'
. '<title>'
. $title
. '</title>'
. '<base target="Content">'
. '<link rel="stylesheet" type="text/css" href="' . $self->MakeRelativeURL($outputFile, $self->MainCSSFile(), 1) . '">'
. '<script language=JavaScript src="' . $self->MakeRelativeURL($outputFile, $self->MainJavaScriptFile(), 1) . '"></script>'
. '<script language=JavaScript src="' . $self->MakeRelativeURL($outputFile, $self->SearchDataJavaScriptFile(), 1) . '">'
. '</script>'
. '</head><body class="FramedMenuPage" onLoad="NDOnLoad()">'
. $self->OpeningBrowserStyles()
. $self->StandardComments()
. "\n\n\n"
. $self->BuildMenu(undef, undef)
. "\n\n\n"
. $self->BuildFooter(1)
. "\n\n\n"
. $self->ClosingBrowserStyles()
. '</body></html>';
close(OUTPUTFILEHANDLE);
# Update index.html
my $firstMenuEntry = $self->FindFirstFile();
my $indexFile = NaturalDocs::File->JoinPaths( NaturalDocs::Settings->OutputDirectoryOf($self), 'index.html' );
# We have to check because it's possible that there may be no files with Natural Docs content and thus no files on the menu.
if (defined $firstMenuEntry)
{
open(INDEXFILEHANDLE, '>' . $indexFile)
or die "Couldn't create output file " . $indexFile . ".\n";
binmode(OUTPUTFILEHANDLE, ':encoding(UTF-8)');
print INDEXFILEHANDLE
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN" '
. '"http://www.w3.org/TR/REC-html40/frameset.dtd">'
. '<html>'
. '<head>'
. '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">'
. '<title>'
. $self->StringToHTML(NaturalDocs::Menu->Title())
. '</title>'
. '</head>'
. $self->StandardComments()
. '<frameset cols="185,*">'
. '<frame name=Menu src="menu.html">'
. '<frame name=Content src="'
. $self->MakeRelativeURL($indexFile, $self->OutputFileOf($firstMenuEntry->Target()), 1) . '">'
. '</frameset>'
. '<noframes>'
. 'This documentation was designed for use with frames. However, you can still use it by '
. '<a href="menu.html">starting from the menu page</a>.'
. "<script language=JavaScript><!--\n"
. 'location.href="menu.html";'
. "\n// --></script>"
. '</noframes>'
. '</html>';
close INDEXFILEHANDLE;
}
elsif (-e $indexFile)
{
unlink($indexFile);
};
};
1;

View File

@ -0,0 +1,414 @@
###############################################################################
#
# Package: NaturalDocs::Builder::HTML
#
###############################################################################
#
# A package that generates output in HTML.
#
# All functions are called with Package->Function() notation.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::Builder::HTML;
use base 'NaturalDocs::Builder::HTMLBase';
###############################################################################
# Group: Implemented Interface Functions
#
# Function: INIT
#
# Registers the package with <NaturalDocs::Builder>.
#
sub INIT
{
NaturalDocs::Builder->Add(__PACKAGE__);
};
#
# Function: CommandLineOption
#
# Returns the option to follow -o to use this package. In this case, "html".
#
sub CommandLineOption
{
return 'HTML';
};
#
# Function: BuildFile
#
# Builds the output file from the parsed source file.
#
# Parameters:
#
# sourcefile - The <FileName> of the source file.
# parsedFile - An arrayref of the source file as <NaturalDocs::Parser::ParsedTopic> objects.
#
sub BuildFile #(sourceFile, parsedFile)
{
my ($self, $sourceFile, $parsedFile) = @_;
my $outputFile = $self->OutputFileOf($sourceFile);
# 99.99% of the time the output directory will already exist, so this will actually be more efficient. It only won't exist
# if a new file was added in a new subdirectory and this is the first time that file was ever parsed.
if (!open(OUTPUTFILEHANDLE, '>' . $outputFile))
{
NaturalDocs::File->CreatePath( NaturalDocs::File->NoFileName($outputFile) );
open(OUTPUTFILEHANDLE, '>' . $outputFile)
or die "Couldn't create output file " . $outputFile . "\n";
};
binmode(OUTPUTFILEHANDLE, ':encoding(UTF-8)');
my $usePrettify = (NaturalDocs::Settings->HighlightCode() || NaturalDocs::Settings->HighlightAnonymous());
print OUTPUTFILEHANDLE
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" '
. '"http://www.w3.org/TR/REC-html40/strict.dtd">' . "\n\n"
. '<html><head>'
. '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">'
. '<title>'
. $self->BuildTitle($sourceFile)
. '</title>'
. '<link rel="stylesheet" type="text/css" href="' . $self->MakeRelativeURL($outputFile, $self->MainCSSFile(), 1) . '">'
. '<script language=JavaScript src="' . $self->MakeRelativeURL($outputFile, $self->MainJavaScriptFile(), 1) . '">'
. '</script>';
if ($usePrettify)
{
print OUTPUTFILEHANDLE
'<script language=JavaScript src="' . $self->MakeRelativeURL($outputFile, $self->PrettifyJavaScriptFile(), 1) . '">'
. '</script>';
}
print OUTPUTFILEHANDLE
'<script language=JavaScript src="' . $self->MakeRelativeURL($outputFile, $self->SearchDataJavaScriptFile(), 1) . '">'
. '</script>'
. '</head><body class="ContentPage" onLoad="NDOnLoad();' . ($usePrettify ? 'prettyPrint();' : '') . '">'
. $self->OpeningBrowserStyles()
. $self->StandardComments()
. "\n\n\n"
. $self->BuildContent($sourceFile, $parsedFile)
. "\n\n\n"
. $self->BuildFooter()
. "\n\n\n"
. $self->BuildMenu($sourceFile, undef)
. "\n\n\n"
. $self->BuildToolTips()
. "\n\n\n"
. '<div id=MSearchResultsWindow>'
. '<iframe src="" frameborder=0 name=MSearchResults id=MSearchResults></iframe>'
. '<a href="javascript:searchPanel.CloseResultsWindow()" id=MSearchResultsWindowClose>Close</a>'
. '</div>'
. "\n\n\n"
. $self->ClosingBrowserStyles()
. '</body></html>';
close(OUTPUTFILEHANDLE);
};
#
# Function: BuildIndex
#
# Builds an index for the passed type.
#
# Parameters:
#
# type - The <TopicType> to limit the index to, or undef if none.
#
sub BuildIndex #(type)
{
my ($self, $type) = @_;
my $indexTitle = $self->IndexTitleOf($type);
my $startIndexPage =
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" '
. '"http://www.w3.org/TR/REC-html40/strict.dtd">' . "\n\n"
. '<html><head>'
. '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">'
. '<title>'
. $indexTitle;
if (defined NaturalDocs::Menu->Title())
{ $startIndexPage .= ' - ' . $self->StringToHTML(NaturalDocs::Menu->Title()); };
$startIndexPage .=
'</title>'
. '<link rel="stylesheet" type="text/css" href="' . $self->MakeRelativeURL($self->IndexDirectory(),
$self->MainCSSFile()) . '">'
. '<script language=JavaScript src="' . $self->MakeRelativeURL($self->IndexDirectory(),
$self->MainJavaScriptFile()) . '"></script>'
. '<script language=JavaScript src="' . $self->MakeRelativeURL($self->IndexDirectory(),
$self->SearchDataJavaScriptFile()) . '">'
. '</script>'
. '</head><body class="IndexPage" onLoad="NDOnLoad()">'
. $self->OpeningBrowserStyles()
. $self->StandardComments()
. "\n\n\n"
. '<div id=Index>'
. '<div class=IPageTitle>'
. $indexTitle
. '</div>';
my $endIndexPage =
'</div><!--Index-->'
. "\n\n\n"
. $self->BuildFooter()
. "\n\n\n"
. $self->BuildMenu(undef, $type)
. "\n\n\n"
. '<div id=MSearchResultsWindow>'
. '<iframe src="" frameborder=0 name=MSearchResults id=MSearchResults></iframe>'
. '<a href="javascript:searchPanel.CloseResultsWindow()" id=MSearchResultsWindowClose>Close</a>'
. '</div>'
. "\n\n\n"
. $self->ClosingBrowserStyles()
. '</body></html>';
my $startSearchResultsPage =
'<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" '
. '"http://www.w3.org/TR/REC-html40/strict.dtd">' . "\n\n"
. '<html><head>'
. '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">'
. '<link rel="stylesheet" type="text/css" href="' . $self->MakeRelativeURL($self->SearchResultsDirectory(),
$self->MainCSSFile()) . '">'
. '<script language=JavaScript src="' . $self->MakeRelativeURL($self->SearchResultsDirectory(),
$self->MainJavaScriptFile()) . '"></script>'
. '</head><body class="PopupSearchResultsPage" onLoad="NDOnLoad()">'
. $self->OpeningBrowserStyles()
. $self->StandardComments()
. "\n\n\n"
. '<div id=Index>';
my $endSearchResultsPage =
'</div>'
. $self->ClosingBrowserStyles()
. '</body></html>';
my $indexContent = NaturalDocs::SymbolTable->Index($type);
my $pageCount = $self->BuildIndexPages($type, $indexContent, $startIndexPage, $endIndexPage,
$startSearchResultsPage, $endSearchResultsPage);
$self->PurgeIndexFiles($type, $indexContent, $pageCount + 1);
};
#
# Function: UpdateMenu
#
# Updates the menu in all the output files that weren't rebuilt. Also generates index.html.
#
sub UpdateMenu
{
my $self = shift;
# Update the menu on unbuilt files.
my $filesToUpdate = NaturalDocs::Project->UnbuiltFilesWithContent();
foreach my $sourceFile (keys %$filesToUpdate)
{
$self->UpdateFile($sourceFile);
};
# Update the menu on unchanged index files.
my $indexes = NaturalDocs::Menu->Indexes();
foreach my $index (keys %$indexes)
{
if (!NaturalDocs::SymbolTable->IndexChanged($index))
{
$self->UpdateIndex($index);
};
};
# Update index.html
my $firstMenuEntry = $self->FindFirstFile();
my $indexFile = NaturalDocs::File->JoinPaths( NaturalDocs::Settings->OutputDirectoryOf($self), 'index.html' );
# We have to check because it's possible that there may be no files with Natural Docs content and thus no files on the menu.
if (defined $firstMenuEntry)
{
open(INDEXFILEHANDLE, '>' . $indexFile)
or die "Couldn't create output file " . $indexFile . ".\n";
binmode(INDEXFILEHANDLE, ':encoding(UTF-8)');
print INDEXFILEHANDLE
'<html><head>'
. '<meta http-equiv="Refresh" CONTENT="0; URL='
. $self->MakeRelativeURL( NaturalDocs::File->JoinPaths( NaturalDocs::Settings->OutputDirectoryOf($self), 'index.html'),
$self->OutputFileOf($firstMenuEntry->Target()), 1 ) . '">'
. '</head></html>';
close INDEXFILEHANDLE;
}
elsif (-e $indexFile)
{
unlink($indexFile);
};
};
###############################################################################
# Group: Support Functions
#
# Function: UpdateFile
#
# Updates an output file. Replaces the menu, HTML title, and footer. It opens the output file, makes the changes, and saves it
# back to disk, which is much quicker than rebuilding the file from scratch if these were the only things that changed.
#
# Parameters:
#
# sourceFile - The source <FileName>.
#
# Dependencies:
#
# - Requires <Builder::BuildMenu()> to surround its content with the exact strings "<div id=Menu>" and "</div><!--Menu-->".
# - Requires <Builder::BuildFooter()> to surround its content with the exact strings "<div id=Footer>" and
# "</div><!--Footer-->".
#
sub UpdateFile #(sourceFile)
{
my ($self, $sourceFile) = @_;
my $outputFile = $self->OutputFileOf($sourceFile);
if (open(OUTPUTFILEHANDLE, '<' . $outputFile))
{
my $content;
binmode(OUTPUTFILEHANDLE, ':encoding(UTF-8)');
read(OUTPUTFILEHANDLE, $content, -s OUTPUTFILEHANDLE);
close(OUTPUTFILEHANDLE);
$content =~ s{<title>[^<]*<\/title>}{'<title>' . $self->BuildTitle($sourceFile) . '</title>'}e;
$content =~ s/<div id=Menu>.*?<\/div><!--Menu-->/$self->BuildMenu($sourceFile, undef)/es;
$content =~ s/<div id=Footer>.*?<\/div><!--Footer-->/$self->BuildFooter()/e;
open(OUTPUTFILEHANDLE, '>' . $outputFile);
binmode(OUTPUTFILEHANDLE, ':encoding(UTF-8)');
print OUTPUTFILEHANDLE $content;
close(OUTPUTFILEHANDLE);
};
};
#
# Function: UpdateIndex
#
# Updates an index's output file. Replaces the menu and footer. It opens the output file, makes the changes, and saves it
# back to disk, which is much quicker than rebuilding the file from scratch if these were the only things that changed.
#
# Parameters:
#
# type - The index <TopicType>, or undef if none.
#
sub UpdateIndex #(type)
{
my ($self, $type) = @_;
my $page = 1;
my $outputFile = $self->IndexFileOf($type, $page);
my $newMenu = $self->BuildMenu(undef, $type);
my $newFooter = $self->BuildFooter();
while (-e $outputFile)
{
open(OUTPUTFILEHANDLE, '<' . $outputFile)
or die "Couldn't open output file " . $outputFile . ".\n";
my $content;
binmode(OUTPUTFILEHANDLE, ':encoding(UTF-8)');
read(OUTPUTFILEHANDLE, $content, -s OUTPUTFILEHANDLE);
close(OUTPUTFILEHANDLE);
$content =~ s/<div id=Menu>.*?<\/div><!--Menu-->/$newMenu/es;
$content =~ s/<div id=Footer>.*<\/div><!--Footer-->/$newFooter/e;
open(OUTPUTFILEHANDLE, '>' . $outputFile)
or die "Couldn't save output file " . $outputFile . ".\n";
binmode(OUTPUTFILEHANDLE, ':encoding(UTF-8)');
print OUTPUTFILEHANDLE $content;
close(OUTPUTFILEHANDLE);
$page++;
$outputFile = $self->IndexFileOf($type, $page);
};
};
1;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,869 @@
###############################################################################
#
# Package: NaturalDocs::ClassHierarchy
#
###############################################################################
#
# A package that handles all the gory details of managing the class hierarchy. It handles the hierarchy itself, which files define
# them, rebuilding the files that are affected by changes, and loading and saving them to a file.
#
# Usage and Dependencies:
#
# - <NaturalDocs::Settings> and <NaturalDocs::Project> must be initialized before use.
#
# - <NaturalDocs::SymbolTable> must be initialized before <Load()> is called. It must reflect the state as of the last time
# Natural Docs was run.
#
# - <Load()> must be called to initialize the package. At this point, the <Information Functions> will return the state as
# of the last time Natural Docs was run. You are free to resolve <NaturalDocs::SymbolTable()> afterwards.
#
# - <Purge()> must be called, and then <NaturalDocs::Parser->ParseForInformation()> must be called on all files that
# have changed so it can fully resolve the hierarchy via the <Modification Functions()>. Afterwards the
# <Information Functions> will reflect the current state of the code.
#
# - <Save()> must be called to commit any changes to the symbol table back to disk.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
use NaturalDocs::ClassHierarchy::Class;
use NaturalDocs::ClassHierarchy::File;
package NaturalDocs::ClassHierarchy;
use Encode qw(encode_utf8 decode_utf8);
###############################################################################
# Group: Variables
#
# handle: CLASS_HIERARCHY_FILEHANDLE
# The file handle used with <ClassHierarchy.nd>.
#
#
# hash: classes
#
# A hash of all the classes. The keys are the class <SymbolStrings> and the values are <NaturalDocs::ClassHierarchy::Classes>.
#
my %classes;
#
# hash: files
#
# A hash of the hierarchy information referenced by file. The keys are the <FileNames>, and the values are
# <NaturalDocs::ClassHierarchy::File>s.
#
my %files;
#
# hash: parentReferences
#
# A hash of all the parent reference strings and what they resolve to. The keys are the <ReferenceStrings> and the values are
# the class <SymbolStrings> that they resolve to.
#
my %parentReferences;
#
# object: watchedFile
#
# A <NaturalDocs::ClassHierarchy::File> object of the file being watched for changes. This is compared to the version in <files>
# to see if anything was changed since the last parse.
#
my $watchedFile;
#
# string: watchedFileName
#
# The <FileName> of the watched file, if any. If there is no watched file, this will be undef.
#
my $watchedFileName;
#
# bool: dontRebuildFiles
#
# A bool to set if you don't want changes in the hierarchy to cause files to be rebuilt.
#
my $dontRebuildFiles;
###############################################################################
# Group: Files
#
# File: ClassHierarchy.nd
#
# Stores the class hierarchy on disk.
#
# Format:
#
# > [BINARY_FORMAT]
# > [VersionInt: app version]
#
# The standard <BINARY_FORMAT> and <VersionInt> header.
#
# > [SymbolString: class or undef to end]
#
# Next we begin a class segment with its <SymbolString>. These continue until the end of the file. Only defined classes are
# included.
#
# > [UInt32: number of files]
# > [UString16: file] [UString16: file] ...
#
# Next there is the number of files that define that class. It's a UInt32, which seems like overkill, but I could imagine every
# file in a huge C++ project being under the same namespace, and thus contributing its own definition. It's theoretically
# possible.
#
# Following the number is that many file names. You must remember the index of each file, as they will be important later.
# Indexes start at one because zero has a special meaning.
#
# > [UInt8: number of parents]
# > ( [ReferenceString (no type): parent]
# > [UInt32: file index] [UInt32: file index] ... [UInt32: 0] ) ...
#
# Next there is the number of parents defined for this class. For each one, we define a parent segment, which consists of
# its <ReferenceString>, and then a zero-terminated string of indexes of the files that define that parent as part of that class.
# The indexes start at one, and are into the list of files we saw previously.
#
# Note that we do store class segments for classes without parents, but not for undefined classes.
#
# This concludes a class segment. These segments continue until an undef <SymbolString>.
#
# See Also:
#
# <File Format Conventions>
#
# Revisions:
#
# 1.52:
#
# - Changed AString16s to UString16s.
#
# 1.22:
#
# - Classes and parents switched from AString16s to <SymbolStrings> and <ReferenceStrings>.
# - A ending undef <SymbolString> was added to the end. Previously it stopped when the file ran out.
#
# 1.2:
#
# - This file was introduced in 1.2.
#
###############################################################################
# Group: File Functions
#
# Function: Load
#
# Loads the class hierarchy from disk.
#
sub Load
{
my ($self) = @_;
$dontRebuildFiles = 1;
my $fileIsOkay;
my $fileName = NaturalDocs::Project->DataFile('ClassHierarchy.nd');
if (!NaturalDocs::Settings->RebuildData() && open(CLASS_HIERARCHY_FILEHANDLE, '<' . $fileName))
{
# See if it's binary.
binmode(CLASS_HIERARCHY_FILEHANDLE);
my $firstChar;
read(CLASS_HIERARCHY_FILEHANDLE, $firstChar, 1);
if ($firstChar != ::BINARY_FORMAT())
{
close(CLASS_HIERARCHY_FILEHANDLE);
}
else
{
my $version = NaturalDocs::Version->FromBinaryFile(\*CLASS_HIERARCHY_FILEHANDLE);
# Last file format change was 1.52
if (NaturalDocs::Version->CheckFileFormat( $version, NaturalDocs::Version->FromString('1.52') ))
{ $fileIsOkay = 1; }
else
{ close(CLASS_HIERARCHY_FILEHANDLE); };
};
};
if (!$fileIsOkay)
{
NaturalDocs::Project->ReparseEverything();
}
else
{
my $raw;
for (;;)
{
# [SymbolString: class or undef to end]
my $class = NaturalDocs::SymbolString->FromBinaryFile(\*CLASS_HIERARCHY_FILEHANDLE);
if (!defined $class)
{ last; };
# [UInt32: number of files]
read(CLASS_HIERARCHY_FILEHANDLE, $raw, 4);
my $numberOfFiles = unpack('N', $raw);
my @files;
while ($numberOfFiles)
{
# [UString16: file]
read(CLASS_HIERARCHY_FILEHANDLE, $raw, 2);
my $fileLength = unpack('n', $raw);
my $file;
read(CLASS_HIERARCHY_FILEHANDLE, $file, $fileLength);
$file = decode_utf8($file);
push @files, $file;
$self->AddClass($file, $class, NaturalDocs::Languages->LanguageOf($file)->Name());
$numberOfFiles--;
};
# [UInt8: number of parents]
read(CLASS_HIERARCHY_FILEHANDLE, $raw, 1);
my $numberOfParents = unpack('C', $raw);
while ($numberOfParents)
{
# [ReferenceString (no type): parent]
my $parent = NaturalDocs::ReferenceString->FromBinaryFile(\*CLASS_HIERARCHY_FILEHANDLE,
::BINARYREF_NOTYPE(),
::REFERENCE_CH_PARENT());
for (;;)
{
# [UInt32: file index or 0]
read(CLASS_HIERARCHY_FILEHANDLE, $raw, 4);
my $fileIndex = unpack('N', $raw);
if ($fileIndex == 0)
{ last; }
$self->AddParentReference( $files[$fileIndex - 1], $class, $parent );
};
$numberOfParents--;
};
};
close(CLASS_HIERARCHY_FILEHANDLE);
};
$dontRebuildFiles = undef;
};
#
# Function: Save
#
# Saves the class hierarchy to disk.
#
sub Save
{
my ($self) = @_;
open (CLASS_HIERARCHY_FILEHANDLE, '>' . NaturalDocs::Project->DataFile('ClassHierarchy.nd'))
or die "Couldn't save " . NaturalDocs::Project->DataFile('ClassHierarchy.nd') . ".\n";
binmode(CLASS_HIERARCHY_FILEHANDLE);
print CLASS_HIERARCHY_FILEHANDLE '' . ::BINARY_FORMAT();
NaturalDocs::Version->ToBinaryFile(\*CLASS_HIERARCHY_FILEHANDLE, NaturalDocs::Settings->AppVersion());
while (my ($class, $classObject) = each %classes)
{
if ($classObject->IsDefined())
{
# [SymbolString: class or undef to end]
NaturalDocs::SymbolString->ToBinaryFile(\*CLASS_HIERARCHY_FILEHANDLE, $class);
# [UInt32: number of files]
my @definitions = $classObject->Definitions();
my %definitionIndexes;
print CLASS_HIERARCHY_FILEHANDLE pack('N', scalar @definitions);
for (my $i = 0; $i < scalar @definitions; $i++)
{
# [UString16: file]
my $uDefinition = encode_utf8($definitions[$i]);
print CLASS_HIERARCHY_FILEHANDLE pack('na*', length($uDefinition), $uDefinition);
$definitionIndexes{$definitions[$i]} = $i + 1;
};
# [UInt8: number of parents]
my @parents = $classObject->ParentReferences();
print CLASS_HIERARCHY_FILEHANDLE pack('C', scalar @parents);
foreach my $parent (@parents)
{
# [ReferenceString (no type): parent]
NaturalDocs::ReferenceString->ToBinaryFile(\*CLASS_HIERARCHY_FILEHANDLE, $parent, ::BINARYREF_NOTYPE());
# [UInt32: file index]
my @parentDefinitions = $classObject->ParentReferenceDefinitions($parent);
foreach my $parentDefinition (@parentDefinitions)
{
print CLASS_HIERARCHY_FILEHANDLE pack('N', $definitionIndexes{$parentDefinition});
};
# [UInt32: 0]
print CLASS_HIERARCHY_FILEHANDLE pack('N', 0);
};
};
};
# [SymbolString: class or undef to end]
NaturalDocs::SymbolString->ToBinaryFile(\*CLASS_HIERARCHY_FILEHANDLE, undef);
close(CLASS_HIERARCHY_FILEHANDLE);
};
#
# Function: Purge
#
# Purges the hierarchy of files that no longer have Natural Docs content.
#
sub Purge
{
my ($self) = @_;
my $filesToPurge = NaturalDocs::Project->FilesToPurge();
foreach my $file (keys %$filesToPurge)
{
$self->DeleteFile($file);
};
};
###############################################################################
# Group: Interface Functions
#
# Function: OnInterpretationChange
#
# Called by <NaturalDocs::SymbolTable> whenever a class hierarchy reference's intepretation changes, meaning it switched
# from one symbol to another.
#
# reference - The <ReferenceString> whose current interpretation changed.
#
sub OnInterpretationChange #(reference)
{
my ($self, $reference) = @_;
if (NaturalDocs::ReferenceString->TypeOf($reference) == ::REFERENCE_CH_PARENT())
{
# The approach here is simply to completely delete the reference and readd it. This is less than optimal efficiency, since it's
# being removed and added from %files too, even though that isn't required. However, the simpler code is worth it
# considering this will only happen when a parent reference becomes defined or undefined, or on the rare languages (like C#)
# that allow relative parent references.
my $oldTargetSymbol = $parentReferences{$reference};
my $oldTargetObject = $classes{$oldTargetSymbol};
my @classesWithReferenceParent = $oldTargetObject->Children();
# Each entry is an arrayref of file names. Indexes are the same as classesWithReferenceParent's.
my @filesDefiningReferenceParent;
foreach my $classWithReferenceParent (@classesWithReferenceParent)
{
my $fileList = [ $classes{$classWithReferenceParent}->ParentReferenceDefinitions($reference) ];
push @filesDefiningReferenceParent, $fileList;
foreach my $fileDefiningReferenceParent (@$fileList)
{
$self->DeleteParentReference($fileDefiningReferenceParent, $classWithReferenceParent, $reference);
};
};
# This will force the reference to be reinterpreted on the next add.
delete $parentReferences{$reference};
# Now we can just readd it.
for (my $i = 0; $i < scalar @classesWithReferenceParent; $i++)
{
foreach my $file (@{$filesDefiningReferenceParent[$i]})
{
$self->AddParentReference($file, $classesWithReferenceParent[$i], $reference);
};
};
};
# The only way for a REFERENCE_CH_CLASS reference to change is if the symbol is deleted. That will be handled by
# <AnalyzeChanges()>, so we don't need to do anything here.
};
#
# Function: OnTargetSymbolChange
#
# Called by <NaturalDocs::SymbolTable> whenever a class hierarchy reference's target symbol changes, but the reference
# still resolves to the same symbol.
#
# Parameters:
#
# reference - The <ReferenceString> that was affected by the change.
#
sub OnTargetSymbolChange #(reference)
{
my ($self, $reference) = @_;
my $type = NaturalDocs::ReferenceString->TypeOf($reference);
my $class;
if ($type == ::REFERENCE_CH_PARENT())
{ $class = $parentReferences{$reference}; }
else # ($type == ::REFERENCE_CH_CLASS())
{
# Class references are global absolute, so we can just yank the symbol.
(undef, $class, undef, undef, undef, undef) = NaturalDocs::ReferenceString->InformationOf($reference);
};
$self->RebuildFilesFor($class, 1, 0, 1);
};
###############################################################################
# Group: Modification Functions
#
# Function: AddClass
#
# Adds a class to the hierarchy.
#
# Parameters:
#
# file - The <FileName> the class was defined in.
# class - The class <SymbolString>.
# languageName - The name of the language this applies to.
#
# Note:
#
# The file parameter must be defined when using this function externally. It may be undef for internal use only.
#
sub AddClass #(file, class, languageName)
{
my ($self, $file, $class, $languageName) = @_;
if (!exists $classes{$class})
{
$classes{$class} = NaturalDocs::ClassHierarchy::Class->New();
NaturalDocs::SymbolTable->AddReference($self->ClassReferenceOf($class, $languageName), $file)
};
if (defined $file)
{
# If this was the first definition for this class...
if ($classes{$class}->AddDefinition($file))
{ $self->RebuildFilesFor($class, 1, 1, 1); };
if (!exists $files{$file})
{ $files{$file} = NaturalDocs::ClassHierarchy::File->New(); };
$files{$file}->AddClass($class);
if (defined $watchedFileName)
{ $watchedFile->AddClass($class); };
};
};
#
# Function: AddParentReference
#
# Adds a class-parent relationship to the hierarchy. The classes will be created if they don't already exist.
#
# Parameters:
#
# file - The <FileName> the reference was defined in.
# class - The class <SymbolString>.
# symbol - The parent class <SymbolString>.
# 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.
#
# Alternate Parameters:
#
# file - The <FileName> the reference was defined in.
# class - The class <SymbolString>.
# reference - The parent <ReferenceString>.
#
sub AddParentReference #(file, class, symbol, scope, using, resolvingFlags) or (file, class, reference)
{
my ($self, $file, $class, $symbol, $parentReference);
if (scalar @_ == 7)
{
my ($scope, $using, $resolvingFlags);
($self, $file, $class, $symbol, $scope, $using, $resolvingFlags) = @_;
$parentReference = NaturalDocs::ReferenceString->MakeFrom(::REFERENCE_CH_PARENT(), $symbol,
NaturalDocs::Languages->LanguageOf($file)->Name(),
$scope, $using, $resolvingFlags);
}
else
{
($self, $file, $class, $parentReference) = @_;
$symbol = (NaturalDocs::ReferenceString->InformationOf($parentReference))[1];
};
# In case it doesn't already exist.
$self->AddClass($file, $class);
my $parent;
if (exists $parentReferences{$parentReference})
{
$parent = $parentReferences{$parentReference};
}
else
{
NaturalDocs::SymbolTable->AddReference($parentReference, $file);
my $parentTarget = NaturalDocs::SymbolTable->References($parentReference);
if (defined $parentTarget)
{ $parent = $parentTarget->Symbol(); }
else
{ $parent = $symbol; };
# In case it doesn't already exist.
$self->AddClass(undef, $parent);
$parentReferences{$parentReference} = $parent;
};
# If this defined a new parent...
if ($classes{$class}->AddParentReference($parentReference, $file, \%parentReferences))
{
$classes{$parent}->AddChild($class);
$self->RebuildFilesFor($class, 0, 1, 0);
$self->RebuildFilesFor($parent, 0, 1, 0);
};
$files{$file}->AddParentReference($class, $parentReference);
if (defined $watchedFileName)
{ $watchedFile->AddParentReference($class, $parentReference); };
};
#
# Function: WatchFileForChanges
#
# Watches a file for changes, which can then be applied by <AnalyzeChanges()>. Definitions are not deleted via a DeleteClass()
# function. Instead, a file is watched for changes, reparsed, and then a comparison is made to look for definitions that
# disappeared and any other relevant changes.
#
# Parameters:
#
# file - The <FileName> to watch.
#
sub WatchFileForChanges #(file)
{
my ($self, $file) = @_;
$watchedFile = NaturalDocs::ClassHierarchy::File->New();
$watchedFileName = $file;
};
#
# Function: AnalyzeChanges
#
# Checks the watched file for any changes that occured since the last time is was parsed, and updates the hierarchy as
# necessary. Also sends any files that are affected to <NaturalDocs::Project->RebuildFile()>.
#
sub AnalyzeChanges
{
my ($self) = @_;
# If the file didn't have any classes before, and it still doesn't, it wont be in %files.
if (exists $files{$watchedFileName})
{
my @originalClasses = $files{$watchedFileName}->Classes();
foreach my $originalClass (@originalClasses)
{
# If the class isn't there the second time around...
if (!$watchedFile->HasClass($originalClass))
{ $self->DeleteClass($watchedFileName, $originalClass); }
else
{
my @originalParents = $files{$watchedFileName}->ParentReferencesOf($originalClass);
foreach my $originalParent (@originalParents)
{
# If the parent reference wasn't there the second time around...
if (!$watchedFile->HasParentReference($originalClass, $originalParent))
{ $self->DeleteParentReference($watchedFileName, $originalClass, $originalParent); };
};
};
};
};
$watchedFile = undef;
$watchedFileName = undef;
};
###############################################################################
# Group: Information Functions
#
# Function: ParentsOf
# Returns a <SymbolString> array of the passed class' parents, or an empty array if none. Note that not all of them may be
# defined.
#
sub ParentsOf #(class)
{
my ($self, $class) = @_;
if (exists $classes{$class})
{ return $classes{$class}->Parents(); }
else
{ return ( ); };
};
#
# Function: ChildrenOf
# Returns a <SymbolString> array of the passed class' children, or an empty array if none. Note that not all of them may be
# defined.
#
sub ChildrenOf #(class)
{
my ($self, $class) = @_;
if (exists $classes{$class})
{ return $classes{$class}->Children(); }
else
{ return ( ); };
};
###############################################################################
# Group: Support Functions
#
# Function: DeleteFile
#
# Deletes a file and everything defined in it.
#
# Parameters:
#
# file - The <FileName>.
#
sub DeleteFile #(file)
{
my ($self, $file) = @_;
if (!exists $files{$file})
{ return; };
my @classes = $files{$file}->Classes();
foreach my $class (@classes)
{
$self->DeleteClass($file, $class);
};
delete $files{$file};
};
#
# Function: DeleteClass
#
# Deletes a class definition from a file. Will also delete any parent references from this class and file. Will rebuild any file
# affected unless <dontRebuildFiles> is set.
#
# Parameters:
#
# file - The <FileName> that defines the class.
# class - The class <SymbolString>.
#
sub DeleteClass #(file, class)
{
my ($self, $file, $class) = @_;
my @parents = $files{$file}->ParentReferencesOf($class);
foreach my $parent (@parents)
{
$self->DeleteParentReference($file, $class, $parent);
};
$files{$file}->DeleteClass($class);
# If we're deleting the last definition of this class.
if ($classes{$class}->DeleteDefinition($file))
{
if (!$classes{$class}->HasChildren())
{
delete $classes{$class};
if (!$dontRebuildFiles)
{ NaturalDocs::Project->RebuildFile($file); };
}
else
{ $self->RebuildFilesFor($class, 0, 1, 1); };
};
};
#
# Function: DeleteParentReference
#
# Deletes a class' parent reference and returns whether it resulted in the loss of a parent class. Will rebuild any file affected
# unless <dontRebuildFiles> is set.
#
# Parameters:
#
# file - The <FileName> that defines the reference.
# class - The class <SymbolString>.
# reference - The parent <ReferenceString>.
#
# Returns:
#
# If the class lost a parent as a result of this, it will return its <SymbolString>. It will return undef otherwise.
#
sub DeleteParentReference #(file, class, reference)
{
my ($self, $file, $class, $reference) = @_;
if (!exists $classes{$class})
{ return; };
$files{$file}->DeleteParentReference($class, $reference);
my $deletedParent = $classes{$class}->DeleteParentReference($reference, $file, \%parentReferences);
if (defined $deletedParent)
{
my $deletedParentObject = $classes{$deletedParent};
$deletedParentObject->DeleteChild($class);
$self->RebuildFilesFor($deletedParent, 0, 1, 0);
$self->RebuildFilesFor($class, 0, 1, 0);
if (!$deletedParentObject->HasChildren() && !$deletedParentObject->IsDefined())
{
delete $classes{$deletedParent};
NaturalDocs::SymbolTable->DeleteReference(
$self->ClassReferenceOf($class, NaturalDocs::Languages->LanguageOf($file)->Name()) );
};
return $deletedParent;
};
return undef;
};
#
# Function: ClassReferenceOf
#
# Returns the <REFERENCE_CH_CLASS> <ReferenceString> of the passed class <SymbolString>.
#
sub ClassReferenceOf #(class, languageName)
{
my ($self, $class, $languageName) = @_;
return NaturalDocs::ReferenceString->MakeFrom(::REFERENCE_CH_CLASS(), $class, $languageName, undef, undef,
::RESOLVE_ABSOLUTE() | ::RESOLVE_NOPLURAL());
};
#
# Function: RebuildFilesFor
#
# Calls <NaturalDocs::Project->RebuildFile()> for every file defining the passed class, its parents, and/or its children.
# Returns without doing anything if <dontRebuildFiles> is set.
#
# Parameters:
#
# class - The class <SymbolString>.
# rebuildParents - Whether to rebuild the class' parents.
# rebuildSelf - Whether to rebuild the class.
# rebuildChildren - Whether to rebuild the class' children.
#
sub RebuildFilesFor #(class, rebuildParents, rebuildSelf, rebuildChildren)
{
my ($self, $class, $rebuildParents, $rebuildSelf, $rebuildChildren) = @_;
if ($dontRebuildFiles)
{ return; };
my @classesToBuild;
if ($rebuildParents)
{ @classesToBuild = $classes{$class}->Parents(); };
if ($rebuildSelf)
{ push @classesToBuild, $class; };
if ($rebuildChildren)
{ push @classesToBuild, $classes{$class}->Children(); };
foreach my $classToBuild (@classesToBuild)
{
my @definitions = $classes{$classToBuild}->Definitions();
foreach my $definition (@definitions)
{ NaturalDocs::Project->RebuildFile($definition); };
};
};
1;

View File

@ -0,0 +1,413 @@
###############################################################################
#
# Class: NaturalDocs::ClassHierarchy::Class
#
###############################################################################
#
# An object that stores information about a class in the hierarchy. It does not store its <SymbolString>; it assumes that it will
# be stored in a hashref where the key is the <SymbolString>.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::ClassHierarchy::Class;
#
# Constants: Members
#
# The class is implemented as a blessed arrayref. The keys are the constants below.
#
# DEFINITIONS - An existence hashref of all the <FileNames> which define this class. Undef if none.
# PARENTS - An existence hashref of the <SymbolStrings> of all the parents this class has.
# CHILDREN - An existence hashref of the <SymbolStrings> of all the children this class has.
# PARENT_REFERENCES - A hashref of the parent <ReferenceStrings> this class has. The keys are the <ReferenceStrings>,
# and the values are existence hashrefs of all the <FileNames> that define them. Undef if none.
#
use NaturalDocs::DefineMembers 'DEFINITIONS', 'PARENTS', 'CHILDREN', 'PARENT_REFERENCES';
# Dependency: New() depends on the order of these constants, as well as the class not being derived from any other.
###############################################################################
# Group: Modification Functions
#
# Function: New
#
# Creates and returns a new class.
#
sub New
{
# Dependency: This function depends on the order of the constants, as well as the class not being derived from any other.
my ($package, $definitionFile) = @_;
my $object = [ undef, undef, undef, undef ];
bless $object, $package;
return $object;
};
#
# Function: AddDefinition
#
# Adds a rew definition of this class and returns if that was the first definition.
#
# Parameters:
#
# file - The <FileName> the definition appears in.
#
# Returns:
#
# Whether this was the first definition of this class.
#
sub AddDefinition #(file)
{
my ($self, $file) = @_;
my $wasFirst;
if (!defined $self->[DEFINITIONS])
{
$self->[DEFINITIONS] = { };
$wasFirst = 1;
};
$self->[DEFINITIONS]->{$file} = 1;
return $wasFirst;
};
#
# Function: DeleteDefinition
#
# Removes the definition of this class and returns if there are no more definitions. Note that if there are no more
# definitions, you may still want to keep the object around if <HasChildren()> returns true.
#
# Parameters:
#
# file - The <FileName> the definition appears in.
#
# Returns:
#
# Whether this deleted the last definition of this class.
#
sub DeleteDefinition #(file)
{
my ($self, $file) = @_;
if (defined $self->[DEFINITIONS])
{
delete $self->[DEFINITIONS]->{$file};
if (!scalar keys %{$self->[DEFINITIONS]})
{
$self->[DEFINITIONS] = undef;
return 1;
};
};
return undef;
};
#
# Function: AddParentReference
#
# Adds a parent reference to the class and return whether it resulted in a new parent class.
#
# Parameters:
#
# reference - The <ReferenceString> used to determine the parent.
# file - The <FileName> the parent reference is in.
# referenceTranslations - A hashref of what each reference currently resolves to. The keys are the
# <ReferenceStrings> and the values are class <SymbolStrings>. It should include an entry for
# the reference parameter above.
#
# Returns:
#
# If the reference adds a new parent, it will return that parent's <SymbolString>. Otherwise it will return undef.
#
sub AddParentReference #(reference, file, referenceTranslations)
{
my ($self, $reference, $file, $referenceTranslations) = @_;
if (!defined $self->[PARENT_REFERENCES])
{ $self->[PARENT_REFERENCES] = { }; };
if (!defined $self->[PARENTS])
{ $self->[PARENTS] = { }; };
if (!exists $self->[PARENT_REFERENCES]->{$reference})
{
$self->[PARENT_REFERENCES]->{$reference} = { $file => 1 };
my $symbol = $referenceTranslations->{$reference};
if (!exists $self->[PARENTS]->{$symbol})
{
$self->[PARENTS]->{$symbol} = 1;
return $symbol;
}
else
{ return undef; };
}
else
{
$self->[PARENT_REFERENCES]->{$reference}->{$file} = 1;
return undef;
};
};
#
# Function: DeleteParentReference
#
# Deletes a parent reference from the class and return whether it resulted in a loss of a parent class.
#
# Parameters:
#
# reference - The <ReferenceString> used to determine the parent.
# file - The <FileName> the parent declaration is in.
# referenceTranslations - A hashref of what each reference currently resolves to. The keys are the
# <ReferenceStrings> and the values are class <SymbolStrings>. It should include an entry for
# the reference parameter above.
#
# Returns:
#
# If this causes a parent class to be lost, it will return that parent's <SymbolString>. Otherwise it will return undef.
#
sub DeleteParentReference #(reference, file, referenceTranslations)
{
my ($self, $reference, $file, $referenceTranslations) = @_;
if (defined $self->[PARENT_REFERENCES] && exists $self->[PARENT_REFERENCES]->{$reference} &&
exists $self->[PARENT_REFERENCES]->{$reference}->{$file})
{
delete $self->[PARENT_REFERENCES]->{$reference}->{$file};
# Quit if there are other definitions of this reference.
if (scalar keys %{$self->[PARENT_REFERENCES]->{$reference}})
{ return undef; };
delete $self->[PARENT_REFERENCES]->{$reference};
if (!scalar keys %{$self->[PARENT_REFERENCES]})
{ $self->[PARENT_REFERENCES] = undef; };
my $parent = $referenceTranslations->{$reference};
# Check if any other references resolve to the same parent.
if (defined $self->[PARENT_REFERENCES])
{
foreach my $parentReference (keys %{$self->[PARENT_REFERENCES]})
{
if ($referenceTranslations->{$parentReference} eq $parent)
{ return undef; };
};
};
# If we got this far, no other parent references resolve to this symbol.
delete $self->[PARENTS]->{$parent};
if (!scalar keys %{$self->[PARENTS]})
{ $self->[PARENTS] = undef; };
return $parent;
}
else
{ return undef; };
};
#
# Function: AddChild
# Adds a child <SymbolString> to the class. Unlike <AddParentReference()>, this does not keep track of anything other than
# whether it has it or not.
#
# Parameters:
#
# child - The <SymbolString> to add.
#
sub AddChild #(child)
{
my ($self, $child) = @_;
if (!defined $self->[CHILDREN])
{ $self->[CHILDREN] = { }; };
$self->[CHILDREN]->{$child} = 1;
};
#
# Function: DeleteChild
# Deletes a child <SymbolString> from the class. Unlike <DeleteParentReference()>, this does not keep track of anything other
# than whether it has it or not.
#
# Parameters:
#
# child - The <SymbolString> to delete.
#
sub DeleteChild #(child)
{
my ($self, $child) = @_;
if (defined $self->[CHILDREN])
{
delete $self->[CHILDREN]->{$child};
if (!scalar keys %{$self->[CHILDREN]})
{ $self->[CHILDREN] = undef; };
};
};
###############################################################################
# Group: Information Functions
#
# Function: Definitions
# Returns an array of the <FileNames> that define this class, or an empty array if none.
#
sub Definitions
{
my ($self) = @_;
if (defined $self->[DEFINITIONS])
{ return keys %{$self->[DEFINITIONS]}; }
else
{ return ( ); };
};
#
# Function: IsDefinedIn
# Returns whether the class is defined in the passed <FileName>.
#
sub IsDefinedIn #(file)
{
my ($self, $file) = @_;
if (defined $self->[DEFINITIONS])
{ return exists $self->[DEFINITIONS]->{$file}; }
else
{ return 0; };
};
#
# Function: IsDefined
# Returns whether the class is defined in any files.
#
sub IsDefined
{
my ($self) = @_;
return defined $self->[DEFINITIONS];
};
#
# Function: ParentReferences
# Returns an array of the parent <ReferenceStrings>, or an empty array if none.
#
sub ParentReferences
{
my ($self) = @_;
if (defined $self->[PARENT_REFERENCES])
{ return keys %{$self->[PARENT_REFERENCES]}; }
else
{ return ( ); };
};
#
# Function: HasParentReference
# Returns whether the class has the passed parent <ReferenceString>.
#
sub HasParentReference #(reference)
{
my ($self, $reference) = @_;
return (defined $self->[PARENT_REFERENCES] && exists $self->[PARENT_REFERENCES]->{$reference});
};
#
# Function: HasParentReferences
# Returns whether the class has any parent <ReferenceStrings>.
#
sub HasParentReferences
{
my ($self) = @_;
return defined $self->[PARENT_REFERENCES];
};
#
# Function: Parents
# Returns an array of the parent <SymbolStrings>, or an empty array if none.
#
sub Parents
{
my ($self) = @_;
if (defined $self->[PARENTS])
{ return keys %{$self->[PARENTS]}; }
else
{ return ( ); };
};
#
# Function: HasParents
# Returns whether the class has any parent <SymbolStrings> defined.
#
sub HasParents
{
my ($self) = @_;
return defined $self->[PARENTS];
};
#
# Function: Children
# Returns an array of the child <SymbolStrings>, or an empty array if none.
#
sub Children
{
my ($self) = @_;
if (defined $self->[CHILDREN])
{ return keys %{$self->[CHILDREN]}; }
else
{ return ( ); };
};
#
# Function: HasChildren
# Returns whether any child <SymbolStrings> are defined.
#
sub HasChildren
{
my ($self) = @_;
return defined $self->[CHILDREN];
};
#
# Function: ParentReferenceDefinitions
# Returns an array of the <FileNames> which define the passed parent <ReferenceString>, or an empty array if none.
#
sub ParentReferenceDefinitions #(reference)
{
my ($self, $reference) = @_;
if (defined $self->[PARENT_REFERENCES] && exists $self->[PARENT_REFERENCES]->{$reference})
{ return keys %{$self->[PARENT_REFERENCES]->{$reference}}; }
else
{ return ( ); };
};
1;

View File

@ -0,0 +1,158 @@
###############################################################################
#
# Class: NaturalDocs::ClassHierarchy::File
#
###############################################################################
#
# An object that stores information about what hierarchy information is present in a file. It does not store its <FileName>; it
# assumes that it will be stored in a hashref where the key is the <FileName>.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::ClassHierarchy::File;
#
# Topic: Implementation
#
# Since there's only one member in the class, and it's a hashref, the class is simply the hashref itself blessed as a class.
# The keys are the class <SymbolStrings> that are defined in the file, and the values are existence hashrefs of each class'
# parent <ReferenceStrings>, or undef if none.
#
###############################################################################
# Group: Modification Functions
#
# Function: New
#
# Creates and returns a new class.
#
sub New
{
my ($package) = @_;
my $object = { };
bless $object, $package;
return $object;
};
#
# Function: AddClass
# Adds a rew class <SymbolString> to the file.
#
sub AddClass #(class)
{
my ($self, $class) = @_;
if (!exists $self->{$class})
{ $self->{$class} = undef; };
};
#
# Function: DeleteClass
# Deletes a class <SymbolString> from the file.
#
sub DeleteClass #(class)
{
my ($self, $class) = @_;
delete $self->{$class};
};
#
# Function: AddParentReference
# Adds a parent <ReferenceString> to a class <SymbolString>.
#
sub AddParentReference #(class, parentReference)
{
my ($self, $class, $parent) = @_;
if (!exists $self->{$class} || !defined $self->{$class})
{ $self->{$class} = { }; };
$self->{$class}->{$parent} = 1;
};
#
# Function: DeleteParentReference
# Deletes a parent <ReferenceString> from a class <SymbolString>.
#
sub DeleteParentReference #(class, parent)
{
my ($self, $class, $parent) = @_;
if (exists $self->{$class})
{
delete $self->{$class}->{$parent};
if (!scalar keys %{$self->{$class}})
{ $self->{$class} = undef; };
};
};
###############################################################################
# Group: Information Functions
#
# Function: Classes
# Returns an array of the class <SymbolStrings> that are defined by this file, or an empty array if none.
#
sub Classes
{
my ($self) = @_;
return keys %{$self};
};
#
# Function: HasClass
# Returns whether the file defines the passed class <SymbolString>.
#
sub HasClass #(class)
{
my ($self, $class) = @_;
return exists $self->{$class};
};
#
# Function: ParentReferencesOf
# Returns an array of the parent <ReferenceStrings> that are defined by the class, or an empty array if none.
#
sub ParentReferencesOf #(class)
{
my ($self, $class) = @_;
if (!exists $self->{$class} || !defined $self->{$class})
{ return ( ); }
else
{ return keys %{$self->{$class}}; };
};
#
# Function: HasParentReference
# Returns whether the file defines the passed class <SymbolString> and parent <ReferenceString>.
#
sub HasParentReference #(class, parent)
{
my ($self, $class, $parent) = @_;
if (!$self->HasClass($class))
{ return undef; };
return exists $self->{$class}->{$parent};
};
1;

View File

@ -0,0 +1,508 @@
###############################################################################
#
# Package: NaturalDocs::ConfigFile
#
###############################################################################
#
# A package to manage Natural Docs' configuration files.
#
# Usage:
#
# - Only one configuration file can be managed with this package at a time. You must close the file before opening another
# one.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::ConfigFile;
#
# Topic: Format
#
# All configuration files are text files.
#
# > # [comment]
#
# Comments start with the # character.
#
# > Format: [version]
#
# All configuration files *must* have a format line as its first line containing content. Whitespace and comments are permitted
# ahead of it.
#
# > [keyword]: [value]
#
# Keywords can only contain <CFChars>. Keywords are not case sensitive. Values can be anything and run until the end of
# the line or a comment.
#
# > [value]
#
# Lines that don't start with a valid keyword format are considered to be all value.
#
# > [line] { [line] } [line]
#
# Files supporting brace groups (specified in <Open()>) may also have braces that can appear anywhere. It allows more than
# one thing to appear per line, which isn't supported otherwise. Consequently, values may not have braces.
#
#
# Type: CFChars
#
# The characters that can appear in configuration file keywords and user-defined element names: letters, numbers, spaces,
# dashes, slashes, apostrophes, and periods.
#
# Although the list above is exhaustive, it should be noted that you especially can *not* use colons (messes up keyword: value
# sequences) commas (messes up item, item, item list sequences) and hashes (messes up comment detection.)
#
# You can search the source code for [CFChars] to find all the instances where this definition is used.
#
###############################################################################
# Group: Variables
#
# handle: CONFIG_FILEHANDLE
#
# The file handle used for the configuration file.
#
#
# string: file
#
# The <FileName> for the current configuration file being parsed.
#
my $file;
#
# var: lineReader
#
# The <LineReader> used to read the configuration file.
#
my $lineReader;
#
# array: errors
#
# An array of errors added by <AddError()>. Every odd entry is the line number, and every even entry following is the
# error message.
#
my @errors;
#
# var: lineNumber
#
# The current line number for the configuration file.
#
my $lineNumber;
#
# bool: hasBraceGroups
#
# Whether the file has brace groups or not.
#
my $hasBraceGroups;
#
# array: virtualLines
#
# An array of virtual lines if a line from the file contained more than one.
#
# Files with brace groups may have more than one virtual line per actual file line, such as "Group: A { Group: B". When that
# happens, any extra virtual lines are put into here so they can be returned on the next call.
#
my @virtualLines;
###############################################################################
# Group: Functions
#
# Function: Open
#
# Opens a configuration file for parsing and returns the format <VersionInt>.
#
# Parameters:
#
# file - The <FileName> to parse.
# hasBraceGroups - Whether the file supports brace groups or not. If so, lines with braces will be split apart behind the
# scenes.
#
# Returns:
#
# The <VersionInt> of the file, or undef if the file doesn't exist.
#
sub Open #(file, hasBraceGroups)
{
my $self;
($self, $file, $hasBraceGroups) = @_;
@errors = ( );
# It will be incremented to one when the first line is read from the file.
$lineNumber = 0;
open(CONFIG_FILEHANDLE, '<' . $file) or return undef;
$lineReader = NaturalDocs::LineReader->New(\*CONFIG_FILEHANDLE);
# Get the format line.
my ($keyword, $value, $comment) = $self->GetLine();
if ($keyword eq 'format')
{ return NaturalDocs::Version->FromString($value); }
else
{ die "The first content line in " . $file . " must be the Format: line.\n"; };
};
#
# Function: Close
#
# Closes the current configuration file.
#
sub Close
{
my $self = shift;
close(CONFIG_FILEHANDLE);
};
#
# Function: GetLine
#
# Returns the next line containing content, or an empty array if none.
#
# Returns:
#
# Returns the array ( keyword, value, comment ), or an empty array if none. All tabs will be converted to spaces, and all
# whitespace will be condensed into a single space.
#
# keyword - The keyword part of the line, if any. Is converted to lowercase and doesn't include the colon. If the file supports
# brace groups, opening and closing braces will be returned as keywords.
# value - The value part of the line, minus any whitespace. Keeps its original case.
# comment - The comment following the line, if any. This includes the # symbol and a leading space if there was
# any whitespace, since it may be significant. Otherwise undef. Used for lines where the # character needs to be
# accepted as part of the value.
#
sub GetLine
{
my $self = shift;
my ($line, $comment);
# Get the next line with content.
do
{
# Get the next line.
my $isFileLine;
if (scalar @virtualLines)
{
$line = shift @virtualLines;
$isFileLine = 0;
}
else
{
$line = $lineReader->Get();
$lineNumber++;
if (!defined $line)
{ return ( ); };
# Condense spaces and tabs into a single space.
$line =~ tr/\t / /s;
$isFileLine = 1;
};
# Split off the comment.
if ($line =~ /^(.*?)( ?#.*)$/)
{ ($line, $comment) = ($1, $2); }
else
{ $comment = undef; };
# Split any brace groups.
if ($isFileLine && $hasBraceGroups && $line =~ /[\{\}]/)
{
($line, @virtualLines) = split(/([\{\}])/, $line);
$virtualLines[-1] .= $comment;
$comment = undef;
};
# Remove whitespace.
$line =~ s/^ //;
$line =~ s/ $//;
$comment =~ s/ $//;
# We want to keep the leading space on a comment.
}
while (!$line);
# Process the line.
if ($hasBraceGroups && ($line eq '{' || $line eq '}'))
{
return ($line, undef, undef);
};
if ($line =~ /^([a-z0-9\ \'\/\.\-]+?) ?: ?(.*)$/i) # [CFChars]
{
my ($keyword, $value) = ($1, $2);
return (lc($keyword), $value, $comment);
}
else
{
return (undef, $line, $comment);
};
};
#
# Function: LineNumber
#
# Returns the line number for the line last returned by <GetLine()>.
#
sub LineNumber
{ return $lineNumber; };
###############################################################################
# Group: Error Functions
#
# Function: AddError
#
# Stores an error for the current configuration file. Will be attached to the last line read by <GetLine()>.
#
# Parameters:
#
# message - The error message.
# lineNumber - The line number to use. If not specified, it will use the line number from the last call to <GetLine()>.
#
sub AddError #(message, lineNumber)
{
my ($self, $message, $messageLineNumber) = @_;
if (!defined $messageLineNumber)
{ $messageLineNumber = $lineNumber; };
push @errors, $messageLineNumber, $message;
};
#
# Function: ErrorCount
#
# Returns how many errors the configuration file has.
#
sub ErrorCount
{
return (scalar @errors) / 2;
};
#
# Function: PrintErrorsAndAnnotateFile
#
# Prints the errors to STDERR in the standard GNU format and annotates the configuration file with them. It does *not* end
# execution. <Close()> *must* be called before this function.
#
sub PrintErrorsAndAnnotateFile
{
my ($self) = @_;
if (scalar @errors)
{
open(CONFIG_FILEHANDLE, '<' . $file);
my $lineReader = NaturalDocs::LineReader->New(\*CONFIG_FILEHANDLE);
my @lines = $lineReader->GetAll();
close(CONFIG_FILEHANDLE);
# We need to keep track of both the real and the original line numbers. The original line numbers are for matching errors in
# the errors array, and don't include any comment lines added or deleted. Line number is the current line number including
# those comment lines for sending to the display.
my $lineNumber = 1;
my $originalLineNumber = 1;
open(CONFIG_FILEHANDLE, '>' . $file);
# We don't want to keep the old error header, if present.
if ($lines[0] =~ /^\# There (?:is an error|are \d+ errors) in this file\./)
{
shift @lines;
$originalLineNumber++;
# We want to drop the blank line after it as well.
if ($lines[0] eq "\n")
{
shift @lines;
$originalLineNumber++;
};
};
if ($self->ErrorCount() == 1)
{
print CONFIG_FILEHANDLE
"# There is an error in this file. Search for ERROR to find it.\n\n";
}
else
{
print CONFIG_FILEHANDLE
"# There are " . $self->ErrorCount() . " errors in this file. Search for ERROR to find them.\n\n";
};
$lineNumber += 2;
foreach my $line (@lines)
{
while (scalar @errors && $originalLineNumber == $errors[0])
{
my $errorLine = shift @errors;
my $errorMessage = shift @errors;
print CONFIG_FILEHANDLE "# ERROR: " . $errorMessage . "\n";
# Use the GNU error format, which should make it easier to handle errors when Natural Docs is part of a build process.
# See http://www.gnu.org/prep/standards_15.html
$errorMessage = lcfirst($errorMessage);
$errorMessage =~ s/\.$//;
print STDERR 'NaturalDocs:' . $file . ':' . $lineNumber . ': ' . $errorMessage . "\n";
$lineNumber++;
};
# We want to remove error lines from previous runs.
if (substr($line, 0, 9) ne '# ERROR: ')
{
print CONFIG_FILEHANDLE $line;
$lineNumber++;
};
$originalLineNumber++;
};
# Clean up any remaining errors.
while (scalar @errors)
{
my $errorLine = shift @errors;
my $errorMessage = shift @errors;
print CONFIG_FILEHANDLE "# ERROR: " . $errorMessage . "\n";
# Use the GNU error format, which should make it easier to handle errors when Natural Docs is part of a build process.
# See http://www.gnu.org/prep/standards_15.html
$errorMessage = lcfirst($errorMessage);
$errorMessage =~ s/\.$//;
print STDERR 'NaturalDocs:' . $file . ':' . $lineNumber . ': ' . $errorMessage . "\n";
};
close(CONFIG_FILEHANDLE);
};
};
###############################################################################
# Group: Misc Functions
#
# Function: HasOnlyCFChars
#
# Returns whether the passed string contains only <CFChars>.
#
sub HasOnlyCFChars #(string)
{
my ($self, $string) = @_;
return ($string =~ /^[a-z0-9\ \.\-\/\']*$/i); # [CFChars]
};
#
# Function: CFCharNames
#
# Returns a plain-english list of <CFChars> which can be embedded in a sentence. For example, "You can only use
# [CFCharsList()] in the name.
#
sub CFCharNames
{
# [CFChars]
return 'letters, numbers, spaces, periods, dashes, slashes, and apostrophes';
};
#
# Function: Obscure
#
# Obscures the passed text so that it is not user editable and returns it. The encoding method is not secure; it is just designed
# to be fast and to discourage user editing.
#
sub Obscure #(text)
{
my ($self, $text) = @_;
# ` is specifically chosen to encode to space because of its rarity. We don't want a trailing one to get cut off before decoding.
$text =~ tr{a-zA-Z0-9\ \\\/\.\:\_\-\`}
{pY9fGc\`R8lAoE\\uIdH6tN\/7sQjKx0B5mW\.vZ41PyFg\:CrLaO\_eUi2DhT\-nSqJkXb3MwVz\ };
return $text;
};
#
# Function: Unobscure
#
# Restores text encoded with <Obscure()> and returns it.
#
sub Unobscure #(text)
{
my ($self, $text) = @_;
$text =~ tr{pY9fGc\`R8lAoE\\uIdH6tN\/7sQjKx0B5mW\.vZ41PyFg\:CrLaO\_eUi2DhT\-nSqJkXb3MwVz\ }
{a-zA-Z0-9\ \\\/\.\:\_\-\`};
return $text;
};
1;

View File

@ -0,0 +1,166 @@
###############################################################################
#
# Package: NaturalDocs::Constants
#
###############################################################################
#
# Constants that are used throughout the script. All are exported by default.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::Constants;
use vars qw(@EXPORT @ISA);
require Exporter;
@ISA = qw(Exporter);
@EXPORT = ('MENU_TITLE', 'MENU_SUBTITLE', 'MENU_FILE', 'MENU_GROUP', 'MENU_TEXT', 'MENU_LINK', 'MENU_FOOTER',
'MENU_INDEX', 'MENU_FORMAT', 'MENU_ENDOFORIGINAL', 'MENU_DATA',
'MENU_FILE_NOAUTOTITLE', 'MENU_GROUP_UPDATETITLES', 'MENU_GROUP_UPDATESTRUCTURE',
'MENU_GROUP_UPDATEORDER', 'MENU_GROUP_HASENDOFORIGINAL',
'MENU_GROUP_UNSORTED', 'MENU_GROUP_FILESSORTED',
'MENU_GROUP_FILESANDGROUPSSORTED', 'MENU_GROUP_EVERYTHINGSORTED',
'MENU_GROUP_ISINDEXGROUP',
'FILE_NEW', 'FILE_CHANGED', 'FILE_SAME', 'FILE_DOESNTEXIST');
#
# Topic: Assumptions
#
# - No constant here will ever be zero.
# - All constants are exported by default.
#
###############################################################################
# Group: Virtual Types
# These are only groups of constants, but should be treated like typedefs or enums. Each one represents a distinct type and
# their values should only be one of their constants or undef.
#
# Constants: MenuEntryType
#
# The types of entries that can appear in the menu.
#
# MENU_TITLE - The title of the menu.
# MENU_SUBTITLE - The sub-title of the menu.
# MENU_FILE - A source file, relative to the source directory.
# MENU_GROUP - A group.
# MENU_TEXT - Arbitrary text.
# MENU_LINK - A web link.
# MENU_FOOTER - Footer text.
# MENU_INDEX - An index.
# MENU_FORMAT - The version of Natural Docs the menu file was generated with.
# MENU_ENDOFORIGINAL - A dummy entry that marks where the original group content ends. This is used when automatically
# changing the groups so that the alphabetization or lack thereof can be detected without being
# affected by new entries tacked on to the end.
# MENU_DATA - Data not meant for user editing.
#
# Dependency:
#
# <PreviousMenuState.nd> depends on these values all being able to fit into a UInt8, i.e. <= 255.
#
use constant MENU_TITLE => 1;
use constant MENU_SUBTITLE => 2;
use constant MENU_FILE => 3;
use constant MENU_GROUP => 4;
use constant MENU_TEXT => 5;
use constant MENU_LINK => 6;
use constant MENU_FOOTER => 7;
use constant MENU_INDEX => 8;
use constant MENU_FORMAT => 9;
use constant MENU_ENDOFORIGINAL => 10;
use constant MENU_DATA => 11;
#
# Constants: FileStatus
#
# What happened to a file since Natural Docs' last execution.
#
# FILE_NEW - The file has been added since the last run.
# FILE_CHANGED - The file has been modified since the last run.
# FILE_SAME - The file hasn't been modified since the last run.
# FILE_DOESNTEXIST - The file doesn't exist, or was deleted.
#
use constant FILE_NEW => 1;
use constant FILE_CHANGED => 2;
use constant FILE_SAME => 3;
use constant FILE_DOESNTEXIST => 4;
###############################################################################
# Group: Flags
# These constants can be combined with each other.
#
# Constants: Menu Entry Flags
#
# The various flags that can apply to a menu entry. You cannot mix flags of different types, since they may overlap.
#
# File Flags:
#
# MENU_FILE_NOAUTOTITLE - Whether the file is auto-titled or not.
#
# Group Flags:
#
# MENU_GROUP_UPDATETITLES - The group should have its auto-titles regenerated.
# MENU_GROUP_UPDATESTRUCTURE - The group should be checked for structural changes, such as being removed or being
# split into subgroups.
# MENU_GROUP_UPDATEORDER - The group should be resorted.
#
# MENU_GROUP_HASENDOFORIGINAL - Whether the group contains a dummy <MENU_ENDOFORIGINAL> entry.
# MENU_GROUP_ISINDEXGROUP - Whether the group is used primarily for <MENU_INDEX> entries. <MENU_TEXT> entries
# are tolerated.
#
# MENU_GROUP_UNSORTED - The group's contents are not sorted.
# MENU_GROUP_FILESSORTED - The group's files are sorted alphabetically.
# MENU_GROUP_FILESANDGROUPSSORTED - The group's files and sub-groups are sorted alphabetically.
# MENU_GROUP_EVERYTHINGSORTED - All entries in the group are sorted alphabetically.
#
use constant MENU_FILE_NOAUTOTITLE => 0x0001;
use constant MENU_GROUP_UPDATETITLES => 0x0001;
use constant MENU_GROUP_UPDATESTRUCTURE => 0x0002;
use constant MENU_GROUP_UPDATEORDER => 0x0004;
use constant MENU_GROUP_HASENDOFORIGINAL => 0x0008;
# This could really be a two-bit field instead of four flags, but it's not worth the effort since it's only used internally.
use constant MENU_GROUP_UNSORTED => 0x0010;
use constant MENU_GROUP_FILESSORTED => 0x0020;
use constant MENU_GROUP_FILESANDGROUPSSORTED => 0x0040;
use constant MENU_GROUP_EVERYTHINGSORTED => 0x0080;
use constant MENU_GROUP_ISINDEXGROUP => 0x0100;
###############################################################################
# Group: Support Functions
#
# Function: IsClassHierarchyReference
# Returns whether the passed <ReferenceType> belongs to <NaturalDocs::ClassHierarchy>.
#
sub IsClassHierarchyReference #(reference)
{
my ($self, $reference) = @_;
return ($reference == ::REFERENCE_CH_CLASS() || $reference == ::REFERENCE_CH_PARENT());
};
1;

View File

@ -0,0 +1,101 @@
###############################################################################
#
# Package: NaturalDocs::DefineMembers
#
###############################################################################
#
# A custom Perl pragma to define member constants and accessors for use in Natural Docs objects while supporting inheritance.
#
# Each member will be defined as a numeric constant which should be used as that variable's index into the object arrayref.
# They will be assigned sequentially from zero, and take into account any members defined this way in parent classes. Note
# that you can *not* use multiple inheritance with this method.
#
# If a parameter ends in parenthesis, it will be generated as an accessor for the previous member. If it also starts with "Set",
# the accessor will accept a single parameter to replace the value with. If it's followed with "duparrayref", it will assume the
# parameter is either an arrayref or undef, and if the former, will duplicate it to set the value.
#
# Example:
#
# > package MyPackage;
# >
# > use NaturalDocs::DefineMembers 'VAR_A', 'VarA()', 'SetVarA()',
# > 'VAR_B', 'VarB()',
# > 'VAR_C',
# > 'VAR_D', 'VarD()', 'SetVarD() duparrayref';
# >
# > sub SetC #(C)
# > {
# > my ($self, $c) = @_;
# > $self->[VAR_C] = $c;
# > };
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
package NaturalDocs::DefineMembers;
sub import #(member, member, ...)
{
my ($self, @parameters) = @_;
my $package = caller();
no strict 'refs';
my $parent = ${$package . '::ISA'}[0];
use strict 'refs';
my $memberConstant = 0;
my $lastMemberName;
if (defined $parent && $parent->can('END_OF_MEMBERS'))
{ $memberConstant = $parent->END_OF_MEMBERS(); };
my $code = '{ package ' . $package . ";\n";
foreach my $parameter (@parameters)
{
if ($parameter =~ /^(.+)\(\) *(duparrayref)?$/i)
{
my ($functionName, $pragma) = ($1, lc($2));
if ($functionName =~ /^Set/)
{
if ($pragma eq 'duparrayref')
{
$code .=
'sub ' . $functionName . '
{
if (defined $_[1])
{ $_[0]->[' . $lastMemberName . '] = [ @{$_[1]} ]; }
else
{ $_[0]->[' . $lastMemberName . '] = undef; };
};' . "\n";
}
else
{
$code .= 'sub ' . $functionName . ' { $_[0]->[' . $lastMemberName . '] = $_[1]; };' . "\n";
};
}
else
{
$code .= 'sub ' . $functionName . ' { return $_[0]->[' . $lastMemberName . ']; };' . "\n";
};
}
else
{
$code .= 'use constant ' . $parameter . ' => ' . $memberConstant . ";\n";
$memberConstant++;
$lastMemberName = $parameter;
};
};
$code .= 'use constant END_OF_MEMBERS => ' . $memberConstant . ";\n";
$code .= '};';
eval $code;
};
1;

View File

@ -0,0 +1,306 @@
###############################################################################
#
# Package: NaturalDocs::Error
#
###############################################################################
#
# Manages all aspects of error handling in Natural Docs.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
$SIG{'__DIE__'} = \&NaturalDocs::Error::CatchDeath;
package NaturalDocs::Error;
###############################################################################
# Group: Variables
#
# handle: FH_CRASHREPORT
# The filehandle used for generating crash reports.
#
#
# var: stackTrace
# The stack trace generated by <CatchDeath()>.
#
my $stackTrace;
#
# var: softDeath
# Whether the program exited using <SoftDeath()>.
#
my $softDeath;
#
# var: currentAction
# What Natural Docs was doing when it crashed. This stores strings generated by functions like <OnStartParsing()>.
#
my $currentAction;
###############################################################################
# Group: Functions
#
# Function: SoftDeath
#
# Generates a "soft" death, which means the program exits like with Perl's die(), but no crash report will be generated.
#
# Parameter:
#
# message - The error message to die with.
#
sub SoftDeath #(message)
{
my ($self, $message) = @_;
$softDeath = 1;
if ($message !~ /\n$/)
{ $message .= "\n"; };
die $message;
};
#
# Function: OnStartParsing
#
# Called whenever <NaturalDocs::Parser> starts parsing a source file.
#
sub OnStartParsing #(FileName file)
{
my ($self, $file) = @_;
$currentAction = 'Parsing ' . $file;
};
#
# Function: OnEndParsing
#
# Called whenever <NaturalDocs::Parser> is done parsing a source file.
#
sub OnEndParsing #(FileName file)
{
my ($self, $file) = @_;
$currentAction = undef;
};
#
# Function: OnStartBuilding
#
# Called whenever <NaturalDocs::Builder> starts building a source file.
#
sub OnStartBuilding #(FileName file)
{
my ($self, $file) = @_;
$currentAction = 'Building ' . $file;
};
#
# Function: OnEndBuilding
#
# Called whenever <NaturalDocs::Builder> is done building a source file.
#
sub OnEndBuilding #(FileName file)
{
my ($self, $file) = @_;
$currentAction = undef;
};
#
# Function: HandleDeath
#
# Should be called whenever Natural Docs dies out of execution.
#
sub HandleDeath
{
my $self = shift;
my $reason = $::EVAL_ERROR;
$reason =~ s/[\n\r]+$//;
my $errorMessage =
"\n"
. "Natural Docs encountered the following error and was stopped:\n"
. "\n"
. " " . $reason . "\n"
. "\n"
. "You can get help at the following web site:\n"
. "\n"
. " " . NaturalDocs::Settings->AppURL() . "\n"
. "\n";
if (!$softDeath)
{
my $crashReport = $self->GenerateCrashReport();
if ($crashReport)
{
$errorMessage .=
"If sending an error report, please include the information found in the\n"
. "following file:\n"
. "\n"
. " " . $crashReport . "\n"
. "\n";
}
else
{
$errorMessage .=
"If sending an error report, please include the following information:\n"
. "\n"
. " Natural Docs version: " . NaturalDocs::Settings->TextAppVersion() . "\n"
. " Perl version: " . $self->PerlVersion() . " on " . $::OSNAME . "\n"
. "\n";
};
};
die $errorMessage;
};
###############################################################################
# Group: Support Functions
#
# Function: PerlVersion
# Returns the current Perl version as a string.
#
sub PerlVersion
{
my $self = shift;
my $perlVersion;
if ($^V)
{ $perlVersion = sprintf('%vd', $^V); }
if (!$perlVersion || substr($perlVersion, 0, 1) eq '%')
{ $perlVersion = $]; };
return $perlVersion;
};
#
# Function: GenerateCrashReport
#
# Generates a report and returns the <FileName> it's located at. Returns undef if it could not generate one.
#
sub GenerateCrashReport
{
my $self = shift;
my $errorMessage = $::EVAL_ERROR;
$errorMessage =~ s/[\r\n]+$//;
my $reportDirectory = NaturalDocs::Settings->ProjectDirectory();
if (!$reportDirectory || !-d $reportDirectory)
{ return undef; };
my $file = NaturalDocs::File->JoinPaths($reportDirectory, 'LastCrash.txt');
open(FH_CRASHREPORT, '>' . $file) or return undef;
print FH_CRASHREPORT
'Crash Message:' . "\n\n"
. ' ' . $errorMessage . "\n\n";
if ($currentAction)
{
print FH_CRASHREPORT
'Current Action:' . "\n\n"
. ' ' . $currentAction . "\n\n";
};
print FH_CRASHREPORT
'Natural Docs version ' . NaturalDocs::Settings->TextAppVersion() . "\n"
. 'Perl version ' . $self->PerlVersion . ' on ' . $::OSNAME . "\n\n"
. 'Command Line:' . "\n\n"
. ' ' . join(' ', @ARGV) . "\n\n";
if ($stackTrace)
{
print FH_CRASHREPORT
'Stack Trace:' . "\n\n"
. $stackTrace;
}
else
{
print FH_CRASHREPORT
'Stack Trace not available.' . "\n\n";
};
close(FH_CRASHREPORT);
return $file;
};
###############################################################################
# Group: Signal Handlers
#
# Function: CatchDeath
#
# Catches Perl die calls.
#
# *IMPORTANT:* This function is a signal handler and should not be called manually. Also, because of this, it does not have
# a $self parameter.
#
# Parameters:
#
# message - The error message to die with.
#
sub CatchDeath #(message)
{
# No $self because it's a signal handler.
my $message = shift;
if (!$NaturalDocs::Error::softDeath)
{
my $i = 0;
my ($lastPackage, $lastFile, $lastLine, $lastFunction);
while (my ($package, $file, $line, $function) = caller($i))
{
if ($i != 0)
{ $stackTrace .= ', called from' . "\n"; };
$stackTrace .= ' ' . $function;
if (defined $lastLine)
{
$stackTrace .= ', line ' . $lastLine;
if ($function !~ /^NaturalDocs::/)
{ $stackTrace .= ' of ' . $lastFile; };
};
($lastPackage, $lastFile, $lastLine, $lastFunction) = ($package, $file, $line, $function);
$i++;
};
};
};
1;

View File

@ -0,0 +1,541 @@
###############################################################################
#
# Package: NaturalDocs::File
#
###############################################################################
#
# A package to manage file access across platforms. Incorporates functions from various standard File:: packages, but more
# importantly, works around the glorious suckage present in File::Spec, at least in version 0.82 and earlier. Read the "Why oh
# why?" sections for why this package was necessary.
#
# Usage and Dependencies:
#
# - The package doesn't depend on any other Natural Docs packages and is ready to use immediately.
#
# - All functions except <CanonizePath()> assume that all parameters are canonized.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use File::Spec ();
use File::Path ();
use File::Copy ();
use strict;
use integer;
package NaturalDocs::File;
#
# Function: CheckCompatibility
#
# Checks if the standard packages required by this one are up to snuff and dies if they aren't. This is done because I can't
# tell which versions of File::Spec have splitpath just by the version numbers.
#
sub CheckCompatibility
{
my ($self) = @_;
eval {
File::Spec->splitpath('');
};
if ($@)
{
NaturalDocs::Error->SoftDeath("Natural Docs requires a newer version of File::Spec than you have. "
. "You must either upgrade it or upgrade Perl.");
};
};
###############################################################################
# Group: Path String Functions
#
# Function: CanonizePath
#
# Takes a path and returns a logically simplified version of it.
#
# Why oh why?:
#
# Because File::Spec->canonpath doesn't strip quotes on Windows. So if you pass in "a b\c" or "a b"\c, they still end up as
# different strings even though they're logically the same.
#
# It also doesn't remove things like "..", so "a/b/../c" doesn't simplify to "a/c" like it should.
#
sub CanonizePath #(path)
{
my ($self, $path) = @_;
if ($::OSNAME eq 'MSWin32')
{
# We don't have to use a smarter algorithm for dropping quotes because they're invalid characters for actual file and
# directory names.
$path =~ s/\"//g;
};
$path = File::Spec->canonpath($path);
# Condense a/b/../c into a/c.
my $upDir = File::Spec->updir();
if (index($path, $upDir) != -1)
{
my ($volume, $directoryString, $file) = $self->SplitPath($path);
my @directories = $self->SplitDirectories($directoryString);
my $i = 1;
while ($i < scalar @directories)
{
if ($i > 0 && $directories[$i] eq $upDir && $directories[$i - 1] ne $upDir)
{
splice(@directories, $i - 1, 2);
$i--;
}
else
{ $i++; };
};
$directoryString = $self->JoinDirectories(@directories);
$path = $self->JoinPath($volume, $directoryString, $file);
};
return $path;
};
#
# Function: PathIsAbsolute
#
# Returns whether the passed path is absolute.
#
sub PathIsAbsolute #(path)
{
my ($self, $path) = @_;
return File::Spec->file_name_is_absolute($path);
};
#
# Function: JoinPath
#
# Creates a path from its elements.
#
# Parameters:
#
# volume - The volume, such as the drive letter on Windows. Undef if none.
# dirString - The directory string. Create with <JoinDirectories()> if necessary.
# file - The file name, or undef if none.
#
# Returns:
#
# The joined path.
#
sub JoinPath #(volume, dirString, $file)
{
my ($self, $volume, $dirString, $file) = @_;
return File::Spec->catpath($volume, $dirString, $file);
};
#
# Function: JoinPaths
#
# Joins two paths.
#
# Parameters:
#
# basePath - May be a relative path, an absolute path, or undef.
# extraPath - May be a relative path, a file, a relative path and file together, or undef.
# noFileInExtra - Set this to true if extraPath is a relative path only, and doesn't have a file.
#
# Returns:
#
# The joined path.
#
# Why oh why?:
#
# Because nothing in File::Spec will simply slap two paths together. They have to be split up for catpath/file, and rel2abs
# requires the base to be absolute.
#
sub JoinPaths #(basePath, extraPath, noFileInExtra)
{
my ($self, $basePath, $extraPath, $noFileInExtra) = @_;
# If both are undef, it will return undef, which is what we want.
if (!defined $basePath)
{ return $extraPath; }
elsif (!defined $extraPath)
{ return $basePath; };
my ($baseVolume, $baseDirString, $baseFile) = File::Spec->splitpath($basePath, 1);
my ($extraVolume, $extraDirString, $extraFile) = File::Spec->splitpath($extraPath, $noFileInExtra);
my @baseDirectories = $self->SplitDirectories($baseDirString);
my @extraDirectories = $self->SplitDirectories($extraDirString);
my $fullDirString = $self->JoinDirectories(@baseDirectories, @extraDirectories);
my $fullPath = File::Spec->catpath($baseVolume, $fullDirString, $extraFile);
return $self->CanonizePath($fullPath);
};
#
# Function: SplitPath
#
# Takes a path and returns its elements.
#
# Parameters:
#
# path - The path to split.
# noFile - Set to true if the path doesn't have a file at the end.
#
# Returns:
#
# The array ( volume, directoryString, file ). If any don't apply, they will be undef. Use <SplitDirectories()> to split the
# directory string if desired.
#
# Why oh Why?:
#
# Because File::Spec->splitpath may leave a trailing slash/backslash/whatever on the directory string, which makes
# it a bit hard to match it with results from File::Spec->catdir.
#
sub SplitPath #(path, noFile)
{
my ($self, $path, $noFile) = @_;
my @segments = File::Spec->splitpath($path, $noFile);
if (!length $segments[0])
{ $segments[0] = undef; };
if (!length $segments[2])
{ $segments[2] = undef; };
$segments[1] = File::Spec->catdir( File::Spec->splitdir($segments[1]) );
return @segments;
};
#
# Function: JoinDirectories
#
# Creates a directory string from an array of directory names.
#
# Parameters:
#
# directory - A directory name. There may be as many of these as desired.
#
sub JoinDirectories #(directory, directory, ...)
{
my ($self, @directories) = @_;
return File::Spec->catdir(@directories);
};
#
# Function: SplitDirectories
#
# Takes a string of directories and returns an array of its elements.
#
# Why oh why?:
#
# Because File::Spec->splitdir might leave an empty element at the end of the array, which screws up both joining in
# <ConvertToURL> and navigation in <MakeRelativePath>.
#
sub SplitDirectories #(directoryString)
{
my ($self, $directoryString) = @_;
my @directories = File::Spec->splitdir($directoryString);
if (!length $directories[-1])
{ pop @directories; };
return @directories;
};
#
# Function: MakeRelativePath
#
# Takes two paths and returns a relative path between them.
#
# Parameters:
#
# basePath - The starting path. May be relative or absolute, so long as the target path is as well.
# targetPath - The target path. May be relative or absolute, so long as the base path is as well.
#
# If both paths are relative, they are assumed to be relative to the same base.
#
# Returns:
#
# The target path relative to base.
#
# Why oh why?:
#
# First, there's nothing that gives a relative path between two relative paths.
#
# Second, if target and base are absolute but on different volumes, File::Spec->abs2rel creates a totally non-functional
# relative path. It should return the target as is, since there is no relative path.
#
# Third, File::Spec->abs2rel between absolute paths on the same volume, at least on Windows, leaves the drive letter
# on. So abs2rel('a:\b\c\d', 'a:\b') returns 'a:c\d' instead of the expected 'c\d'. That makes no sense whatsoever. It's
# not like it was designed to handle only directory names, either; the documentation says 'path' and the code seems to
# explicitly handle it. There's just an 'unless' in there that tacks on the volume, defeating the purpose of a *relative* path
# and making the function worthless.
#
sub MakeRelativePath #(basePath, targetPath)
{
my ($self, $basePath, $targetPath) = @_;
my ($baseVolume, $baseDirString, $baseFile) = $self->SplitPath($basePath, 1);
my ($targetVolume, $targetDirString, $targetFile) = $self->SplitPath($targetPath);
# If the volumes are different, there is no possible relative path.
if ($targetVolume ne $baseVolume)
{ return $targetPath; };
my @baseDirectories = $self->SplitDirectories($baseDirString);
my @targetDirectories = $self->SplitDirectories($targetDirString);
# Skip the parts of the path that are the same.
while (scalar @baseDirectories && @targetDirectories && $baseDirectories[0] eq $targetDirectories[0])
{
shift @baseDirectories;
shift @targetDirectories;
};
# Back out of the base path until it reaches where they were similar.
for (my $i = 0; $i < scalar @baseDirectories; $i++)
{
unshift @targetDirectories, File::Spec->updir();
};
$targetDirString = $self->JoinDirectories(@targetDirectories);
return File::Spec->catpath(undef, $targetDirString, $targetFile);
};
#
# Function: IsSubPathOf
#
# Returns whether the path is a descendant of another path.
#
# Parameters:
#
# base - The base path to test against.
# path - The possible subpath to test.
#
# Returns:
#
# Whether path is a descendant of base.
#
sub IsSubPathOf #(base, path)
{
my ($self, $base, $path) = @_;
# This is a quick test that should find a false quickly.
if ($base eq substr($path, 0, length($base)))
{
# This doesn't guarantee true, because it could be "C:\A B" and "C:\A B C\File". So we test for it by seeing if the last
# directory in base is the same as the equivalent directory in path.
my ($baseVolume, $baseDirString, $baseFile) = NaturalDocs::File->SplitPath($base, 1);
my @baseDirectories = NaturalDocs::File->SplitDirectories($baseDirString);
my ($pathVolume, $pathDirString, $pathFile) = NaturalDocs::File->SplitPath($path);
my @pathDirectories = NaturalDocs::File->SplitDirectories($pathDirString);
return ( $baseDirectories[-1] eq $pathDirectories[ scalar @baseDirectories - 1 ] );
}
else
{ return undef; };
};
#
# Function: ConvertToURL
#
# Takes a relative path and converts it from the native format to a relative URL. Note that it _doesn't_ convert special characters
# to amp chars.
#
sub ConvertToURL #(path)
{
my ($self, $path) = @_;
my ($pathVolume, $pathDirString, $pathFile) = $self->SplitPath($path);
my @pathDirectories = $self->SplitDirectories($pathDirString);
my $i = 0;
while ($i < scalar @pathDirectories && $pathDirectories[$i] eq File::Spec->updir())
{
$pathDirectories[$i] = '..';
$i++;
};
return join('/', @pathDirectories, $pathFile);
};
#
# Function: NoUpwards
#
# Takes an array of directory entries and returns one without all the entries that refer to the parent directory, such as '.' and '..'.
#
sub NoUpwards #(array)
{
my ($self, @array) = @_;
return File::Spec->no_upwards(@array);
};
#
# Function: NoFileName
#
# Takes a path and returns a version without the file name. Useful for sending paths to <CreatePath()>.
#
sub NoFileName #(path)
{
my ($self, $path) = @_;
my ($pathVolume, $pathDirString, $pathFile) = File::Spec->splitpath($path);
return File::Spec->catpath($pathVolume, $pathDirString, undef);
};
#
# Function: NoExtension
#
# Returns the path without an extension.
#
sub NoExtension #(path)
{
my ($self, $path) = @_;
my $extension = $self->ExtensionOf($path);
if ($extension)
{ $path = substr($path, 0, length($path) - length($extension) - 1); };
return $path;
};
#
# Function: ExtensionOf
#
# Returns the extension of the passed path, or undef if none.
#
sub ExtensionOf #(path)
{
my ($self, $path) = @_;
my ($pathVolume, $pathDirString, $pathFile) = File::Spec->splitpath($path);
# We need the leading dot in the regex so files that start with a dot but don't have an extension count as extensionless files.
if ($pathFile =~ /.\.([^\.]+)$/)
{ return $1; }
else
{ return undef; };
};
#
# Function: IsCaseSensitive
#
# Returns whether the current platform has case-sensitive paths.
#
sub IsCaseSensitive
{
return !(File::Spec->case_tolerant());
};
###############################################################################
# Group: Disk Functions
#
# Function: CreatePath
#
# Creates a directory tree corresponding to the passed path, regardless of how many directories do or do not already exist.
# Do _not_ include a file name in the path. Use <NoFileName()> first if you need to.
#
sub CreatePath #(path)
{
my ($self, $path) = @_;
File::Path::mkpath($path);
};
#
# Function: RemoveEmptyTree
#
# Removes an empty directory tree. The passed directory will be removed if it's empty, and it will keep removing its parents
# until it reaches one that's not empty or a set limit.
#
# Parameters:
#
# path - The path to start from. It will try to remove this directory and work it's way down.
# limit - The path to stop at if it doesn't find any non-empty directories first. This path will *not* be removed.
#
sub RemoveEmptyTree #(path, limit)
{
my ($self, $path, $limit) = @_;
my ($volume, $directoryString) = $self->SplitPath($path, 1);
my @directories = $self->SplitDirectories($directoryString);
my $directory = $path;
while (-d $directory && $directory ne $limit)
{
opendir FH_ND_FILE, $directory;
my @entries = readdir FH_ND_FILE;
closedir FH_ND_FILE;
@entries = $self->NoUpwards(@entries);
if (scalar @entries || !rmdir($directory))
{ last; };
pop @directories;
$directoryString = $self->JoinDirectories(@directories);
$directory = $self->JoinPath($volume, $directoryString);
};
};
#
# Function: Copy
#
# Copies a file from one path to another. If the destination file exists, it is overwritten.
#
# Parameters:
#
# source - The file to copy.
# destination - The destination to copy to.
#
# Returns:
#
# Whether it succeeded
#
sub Copy #(source, destination) => bool
{
my ($self, $source, $destination) = @_;
return File::Copy::copy($source, $destination);
};
1;

View File

@ -0,0 +1,398 @@
###############################################################################
#
# Package: NaturalDocs::ImageReferenceTable
#
###############################################################################
#
# A <NaturalDocs::SourceDB>-based package that manages all the image references appearing in source files.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
use NaturalDocs::ImageReferenceTable::String;
use NaturalDocs::ImageReferenceTable::Reference;
package NaturalDocs::ImageReferenceTable;
use base 'NaturalDocs::SourceDB::Extension';
###############################################################################
# Group: Information
#
# Topic: Usage
#
# - <NaturalDocs::Project> and <NaturalDocs::SourceDB> must be initialized before this package can be used.
#
# - Call <Register()> before using.
#
#
# Topic: Programming Notes
#
# When working on this code, remember that there are three things it has to juggle.
#
# - The information in <NaturalDocs::SourceDB>.
# - Image file references in <NaturalDocs::Project>.
# - Source file rebuilding on changes.
#
# Managing the actual image files will be handled between <NaturalDocs::Project> and the <NaturalDocs::Builder>
# sub-packages.
#
#
# Topic: Implementation
#
# Managing image references is simpler than managing the references in <NaturalDocs::SymbolTable>. In SymbolTable,
# you have to worry about reference targets popping into and out of existence. A link may go to a file that hasn't been
# reparsed yet and the target may no longer exist. We have to deal with that when we know it, which may be after the
# reference's file was parsed. Also, a new definition may appear that serves as a better interpretation of a link than its
# current target, and again we may only know that after the reference's file has been parsed already. So we have to deal
# with scores and potential symbols and each symbol knowing exactly what links to it and so forth.
#
# Not so with image references. All possible targets (all possible image files) are known by <NaturalDocs::Project> early
# on and will remain consistent throughout execution. So because of that, we can get away with only storing reference
# counts with each image and determining exactly where a reference points to as we find them.
#
# Reference counts are stored with the image file information in <NaturalDocs::Project>. However, it is not loaded and
# saved to disk by it. Rather, it is regenerated by this package when it loads <ImageReferenceTable.nd>.
# NaturalDocs::Project only stores the last modification time (so it can add files to the build list if they've changed) and
# whether it had any references at all on the last run (so it knows whether it should care if they've changed.)
# ImageReferenceTable.nd stores each reference's target, width, and height. Whether their interpretations have changed is
# dealt with in the <Load()> function, again since the list of targets (image files) is constant.
#
# The package is based on <NaturalDocs::SourceDB>, so read it's documentation for more information on how it works.
#
###############################################################################
# Group: Variables
#
# var: extensionID
# The <ExtensionID> granted by <NaturalDocs::SourceDB>.
#
my $extensionID;
###############################################################################
# Group: Files
#
# File: ImageReferenceTable.nd
#
# The data file which stores all the image references from the last run of Natural Docs.
#
# Format:
#
# > [Standard Binary Header]
#
# It starts with the standard binary header from <NaturalDocs::BinaryFile>.
#
# > [Image Reference String or undef]
# > [UString16: target file]
# > [UInt16: target width or 0]
# > [UInt16: target height or 0]
#
# For each <ImageReferenceString>, it's target, width, and height are stored. The target is needed so we can tell if it
# changed from the last run, and the dimensions are needed because if the target hasn't changed but the file's dimensions
# have, the source files need to be rebuilt.
#
# <ImageReferenceStrings> are encoded by <NaturalDocs::ImageReferenceTable::String>.
#
# > [UString16: definition file or undef] ...
#
# Then comes a series of UString16s for all the files that define the reference until it hits an undef.
#
# This whole series is repeated for each <ImageReferenceString> until it hits an undef.
#
# Revisions:
#
# 1.52:
#
# - AString16s were changed to UString16s.
#
# 1.4:
#
# - The file was added to Natural Docs.
#
###############################################################################
# Group: Functions
#
# Function: Register
# Registers the package with <NaturalDocs::SourceDB>.
#
sub Register
{
my $self = shift;
$extensionID = NaturalDocs::SourceDB->RegisterExtension($self, 0);
};
#
# Function: Load
#
# Loads the data from <ImageReferenceTable.nd>. Returns whether it was successful.
#
sub Load # => bool
{
my $self = shift;
if (NaturalDocs::Settings->RebuildData())
{ return 0; };
my $version = NaturalDocs::BinaryFile->OpenForReading( NaturalDocs::Project->DataFile('ImageReferenceTable.nd') );
my $fileIsOkay;
if (defined $version)
{
if (NaturalDocs::Version->CheckFileFormat($version, NaturalDocs::Version->FromString('1.52')))
{ $fileIsOkay = 1; }
else
{ NaturalDocs::BinaryFile->Close(); };
};
if (!$fileIsOkay)
{ return 0; };
# [Image Reference String or undef]
while (my $referenceString = NaturalDocs::ImageReferenceTable::String->FromBinaryFile())
{
NaturalDocs::SourceDB->AddItem($extensionID, $referenceString,
NaturalDocs::ImageReferenceTable::Reference->New());
# [UString16: target file]
# [UInt16: target width or 0]
# [UInt16: target height or 0]
my $targetFile = NaturalDocs::BinaryFile->GetUString16();
my $width = NaturalDocs::BinaryFile->GetUInt16();
my $height = NaturalDocs::BinaryFile->GetUInt16();
my $newTargetFile = $self->SetReferenceTarget($referenceString);
my $newWidth;
my $newHeight;
if ($newTargetFile)
{
NaturalDocs::Project->AddImageFileReference($newTargetFile);
($newWidth, $newHeight) = NaturalDocs::Project->ImageFileDimensions($newTargetFile);
};
my $rebuildDefinitions = ($newTargetFile ne $targetFile || $newWidth != $width || $newHeight != $height);
# [UString16: definition file or undef] ...
while (my $definitionFile = NaturalDocs::BinaryFile->GetUString16())
{
NaturalDocs::SourceDB->AddDefinition($extensionID, $referenceString, $definitionFile);
if ($rebuildDefinitions)
{ NaturalDocs::Project->RebuildFile($definitionFile); };
};
};
NaturalDocs::BinaryFile->Close();
return 1;
};
#
# Function: Save
#
# Saves the data to <ImageReferenceTable.nd>.
#
sub Save
{
my $self = shift;
my $references = NaturalDocs::SourceDB->GetAllItemsHashRef($extensionID);
NaturalDocs::BinaryFile->OpenForWriting( NaturalDocs::Project->DataFile('ImageReferenceTable.nd') );
while (my ($referenceString, $referenceObject) = each %$references)
{
# [Image Reference String or undef]
# [UString16: target file]
# [UInt16: target width or 0]
# [UInt16: target height or 0]
NaturalDocs::ImageReferenceTable::String->ToBinaryFile($referenceString);
my $target = $referenceObject->Target();
my ($width, $height);
if ($target)
{ ($width, $height) = NaturalDocs::Project->ImageFileDimensions($target); };
NaturalDocs::BinaryFile->WriteUString16( $referenceObject->Target() );
NaturalDocs::BinaryFile->WriteUInt16( ($width || 0) );
NaturalDocs::BinaryFile->WriteUInt16( ($height || 0) );
# [UString16: definition file or undef] ...
my $definitions = $referenceObject->GetAllDefinitionsHashRef();
foreach my $definition (keys %$definitions)
{ NaturalDocs::BinaryFile->WriteUString16($definition); };
NaturalDocs::BinaryFile->WriteUString16(undef);
};
NaturalDocs::ImageReferenceTable::String->ToBinaryFile(undef);
NaturalDocs::BinaryFile->Close();
};
#
# Function: AddReference
#
# Adds a new image reference.
#
sub AddReference #(FileName file, string referenceText)
{
my ($self, $file, $referenceText) = @_;
my $referenceString = NaturalDocs::ImageReferenceTable::String->Make($file, $referenceText);
if (!NaturalDocs::SourceDB->HasItem($extensionID, $referenceString))
{
my $referenceObject = NaturalDocs::ImageReferenceTable::Reference->New();
NaturalDocs::SourceDB->AddItem($extensionID, $referenceString, $referenceObject);
my $target = $self->SetReferenceTarget($referenceString);
if ($target)
{ NaturalDocs::Project->AddImageFileReference($target); };
};
NaturalDocs::SourceDB->AddDefinition($extensionID, $referenceString, $file);
};
#
# Function: OnDeletedDefinition
#
# Called for each definition deleted by <NaturalDocs::SourceDB>. This is called *after* the definition has been deleted from
# the database, so don't expect to be able to read it.
#
sub OnDeletedDefinition #(ImageReferenceString referenceString, FileName file, bool wasLastDefinition)
{
my ($self, $referenceString, $file, $wasLastDefinition) = @_;
if ($wasLastDefinition)
{
my $referenceObject = NaturalDocs::SourceDB->GetItem($extensionID, $referenceString);
my $target = $referenceObject->Target();
if ($target)
{ NaturalDocs::Project->DeleteImageFileReference($target); };
NaturalDocs::SourceDB->DeleteItem($extensionID, $referenceString);
};
};
#
# Function: GetReferenceTarget
#
# Returns the image file the reference resolves to, or undef if none.
#
# Parameters:
#
# sourceFile - The source <FileName> the reference appears in.
# text - The reference text.
#
sub GetReferenceTarget #(FileName sourceFile, string text) => FileName
{
my ($self, $sourceFile, $text) = @_;
my $referenceString = NaturalDocs::ImageReferenceTable::String->Make($sourceFile, $text);
my $reference = NaturalDocs::SourceDB->GetItem($extensionID, $referenceString);
if (!defined $reference)
{ return undef; }
else
{ return $reference->Target(); };
};
#
# Function: SetReferenceTarget
#
# Determines the best target for the passed <ImageReferenceString> and sets it on the
# <NaturalDocs::ImageReferenceTable::Reference> object. Returns the new target <FileName>. Does *not* add any source
# files to the bulid list.
#
sub SetReferenceTarget #(ImageReferenceString referenceString) => FileName
{
my ($self, $referenceString) = @_;
my $referenceObject = NaturalDocs::SourceDB->GetItem($extensionID, $referenceString);
my ($sourcePath, $text) = NaturalDocs::ImageReferenceTable::String->InformationOf($referenceString);
# Try the path relative to the source file first.
my $target;
my $imageFile = NaturalDocs::File->JoinPaths($sourcePath, $text);
my $exists = NaturalDocs::Project->ImageFileExists($imageFile);
# Then try relative image directories.
if (!$exists)
{
my $relativeImageDirectories = NaturalDocs::Settings->RelativeImageDirectories();
for (my $i = 0; $i < scalar @$relativeImageDirectories && !$exists; $i++)
{
$imageFile = NaturalDocs::File->JoinPaths($sourcePath, $relativeImageDirectories->[$i], 1);
$imageFile = NaturalDocs::File->JoinPaths($imageFile, $text);
$exists = NaturalDocs::Project->ImageFileExists($imageFile);
};
};
# Then try absolute image directories.
if (!$exists)
{
my $imageDirectories = NaturalDocs::Settings->ImageDirectories();
for (my $i = 0; $i < scalar @$imageDirectories && !$exists; $i++)
{
$imageFile = NaturalDocs::File->JoinPaths($imageDirectories->[$i], $text);
$exists = NaturalDocs::Project->ImageFileExists($imageFile);
};
};
if ($exists)
{ $target = NaturalDocs::Project->ImageFileCapitalization($imageFile); };
#else leave it as undef.
$referenceObject->SetTarget($target);
return $target;
};
1;

View File

@ -0,0 +1,45 @@
###############################################################################
#
# Package: NaturalDocs::ImageReferenceTable::Reference
#
###############################################################################
#
# A class for references being tracked in <NaturalDocs::SourceDB>.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::ImageReferenceTable::Reference;
use base 'NaturalDocs::SourceDB::Item';
use NaturalDocs::DefineMembers 'TARGET', 'Target()', 'SetTarget()',
'NEEDS_REBUILD', 'NeedsRebuild()', 'SetNeedsRebuild()';
#
# Variables: Members
#
# The following constants are indexes into the object array.
#
# TARGET - The image <FileName> this reference resolves to, or undef if none.
#
#
# Functions: Member Functions
#
# Target - Returns the image <FileName> this reference resolves to, or undef if none.
# SetTarget - Replaces the image <FileName> this reference resolves to, or undef if none.
#
1;

View File

@ -0,0 +1,111 @@
###############################################################################
#
# Package: NaturalDocs::ImageReferenceTable::String
#
###############################################################################
#
# A package for creating and managing <ImageReferenceStrings>.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::ImageReferenceTable::String;
#
# Type: ImageReferenceString
#
# A string representing a unique image reference. It's composed of the reference text and the directory of the source file.
# The source file name itself isn't included because two files in the same directory with the same reference text will always go
# to the same targets.
#
#
# Function: Make
#
# Converts a source <FileName> and the reference text to an <ImageReferenceString>.
#
sub Make #(FileName sourceFile, string text) => ImageReferenceString
{
my ($self, $sourceFile, $text) = @_;
my $path = NaturalDocs::File->NoFileName($sourceFile);
# Condense whitespace and remove any separator characters.
$path =~ tr/ \t\r\n\x1C/ /s;
$text =~ tr/ \t\r\n\x1C/ /s;
return $path . "\x1C" . $text;
};
#
# Function: InformationOf
#
# Returns the information contained in the <ImageReferenceString> as the array ( path, text ).
#
sub InformationOf #(ImageReferenceString referenceString)
{
my ($self, $referenceString) = @_;
return split(/\x1C/, $referenceString);
};
#
# Function: ToBinaryFile
#
# Writes an <ImageReferenceString> to <NaturalDocs::BinaryFile>. Can also encode an undef.
#
# Format:
#
# > [UString16: path] [UString16: reference text] ...
#
# Undef is represented by the first UString16 being undef.
#
sub ToBinaryFile #(ImageReferenceString referenceString)
{
my ($self, $referenceString) = @_;
if (defined $referenceString)
{
my ($path, $text) = split(/\x1C/, $referenceString);
NaturalDocs::BinaryFile->WriteUString16($path);
NaturalDocs::BinaryFile->WriteUString16($text);
}
else
{
NaturalDocs::BinaryFile->WriteUString16(undef);
};
};
#
# Function: FromBinaryFile
#
# Loads an <ImageReferenceString> or undef from <NaturalDocs::BinaryFile> and returns it.
#
sub FromBinaryFile
{
my $self = shift;
my $path = NaturalDocs::BinaryFile->GetUString16();
if (!defined $path)
{ return undef; };
my $text = NaturalDocs::BinaryFile->GetUString16();
return $path . "\x1C" . $text;
};
1;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,39 @@
###############################################################################
#
# Class: NaturalDocs::Languages::Ada
#
###############################################################################
#
# A subclass to handle the language variations of Ada
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::Languages::Ada;
use base 'NaturalDocs::Languages::Simple';
#
# Function: ParseParameterLine
# Overridden because Ada uses Pascal-style parameters
#
sub ParseParameterLine #(...)
{
my ($self, @params) = @_;
return $self->SUPER::ParsePascalParameterLine(@params);
};
sub TypeBeforeParameter
{
return 0;
};
1;

View File

@ -0,0 +1,817 @@
###############################################################################
#
# Class: NaturalDocs::Languages::Advanced
#
###############################################################################
#
# The base class for all languages that have full support in Natural Docs. Each one will have a custom parser capable
# of documenting undocumented aspects of the code.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
use NaturalDocs::Languages::Advanced::Scope;
use NaturalDocs::Languages::Advanced::ScopeChange;
package NaturalDocs::Languages::Advanced;
use base 'NaturalDocs::Languages::Base';
#############################################################################
# Group: Implementation
#
# Constants: Members
#
# The class is implemented as a blessed arrayref. The following constants are used as indexes.
#
# TOKENS - An arrayref of tokens used in all the <Parsing Functions>.
# SCOPE_STACK - An arrayref of <NaturalDocs::Languages::Advanced::Scope> objects serving as a scope stack for parsing.
# There will always be one available, with a symbol of undef, for the top level.
# SCOPE_RECORD - An arrayref of <NaturalDocs::Languages::Advanced::ScopeChange> objects, as generated by the scope
# stack. If there is more than one change per line, only the last is stored.
# AUTO_TOPICS - An arrayref of <NaturalDocs::Parser::ParsedTopics> generated automatically from the code.
#
use NaturalDocs::DefineMembers 'TOKENS', 'SCOPE_STACK', 'SCOPE_RECORD', 'AUTO_TOPICS';
#############################################################################
# Group: Functions
#
# Function: New
#
# Creates and returns a new object.
#
# Parameters:
#
# name - The name of the language.
#
sub New #(name)
{
my ($package, @parameters) = @_;
my $object = $package->SUPER::New(@parameters);
$object->[TOKENS] = undef;
$object->[SCOPE_STACK] = undef;
$object->[SCOPE_RECORD] = undef;
return $object;
};
# Function: Tokens
# Returns the tokens found by <ParseForCommentsAndTokens()>.
sub Tokens
{ return $_[0]->[TOKENS]; };
# Function: SetTokens
# Replaces the tokens.
sub SetTokens #(tokens)
{ $_[0]->[TOKENS] = $_[1]; };
# Function: ClearTokens
# Resets the token list. You may want to do this after parsing is over to save memory.
sub ClearTokens
{ $_[0]->[TOKENS] = undef; };
# Function: AutoTopics
# Returns the arrayref of automatically generated topics, or undef if none.
sub AutoTopics
{ return $_[0]->[AUTO_TOPICS]; };
# Function: AddAutoTopic
# Adds a <NaturalDocs::Parser::ParsedTopic> to <AutoTopics()>.
sub AddAutoTopic #(topic)
{
my ($self, $topic) = @_;
if (!defined $self->[AUTO_TOPICS])
{ $self->[AUTO_TOPICS] = [ ]; };
push @{$self->[AUTO_TOPICS]}, $topic;
};
# Function: ClearAutoTopics
# Resets the automatic topic list. Not necessary if you call <ParseForCommentsAndTokens()>.
sub ClearAutoTopics
{ $_[0]->[AUTO_TOPICS] = undef; };
# Function: ScopeRecord
# Returns an arrayref of <NaturalDocs::Languages::Advanced::ScopeChange> objects describing how and when the scope
# changed thoughout the file. There will always be at least one entry, which will be for line 1 and undef as the scope.
sub ScopeRecord
{ return $_[0]->[SCOPE_RECORD]; };
###############################################################################
#
# Group: Parsing Functions
#
# These functions are good general language building blocks. Use them to create your language-specific parser.
#
# All functions work on <Tokens()> and assume it is set by <ParseForCommentsAndTokens()>.
#
#
# Function: ParseForCommentsAndTokens
#
# Loads the passed file, sends all appropriate comments to <NaturalDocs::Parser->OnComment()>, and breaks the rest into
# an arrayref of tokens. Tokens are defined as
#
# - All consecutive alphanumeric and underscore characters.
# - All consecutive whitespace.
# - A single line break. It will always be "\n"; you don't have to worry about platform differences.
# - A single character not included above, which is usually a symbol. Multiple consecutive ones each get their own token.
#
# The result will be placed in <Tokens()>.
#
# Parameters:
#
# sourceFile - The source <FileName> to load and parse.
# lineCommentSymbols - An arrayref of symbols that designate line comments, or undef if none.
# blockCommentSymbols - An arrayref of symbol pairs that designate multiline comments, or undef if none. Symbol pairs are
# designated as two consecutive array entries, the opening symbol appearing first.
# javadocLineCommentSymbols - An arrayref of symbols that designate the start of a JavaDoc comment, or undef if none.
# javadocBlockCommentSymbols - An arrayref of symbol pairs that designate multiline JavaDoc comments, or undef if none.
#
# Notes:
#
# - This function automatically calls <ClearAutoTopics()> and <ClearScopeStack()>. You only need to call those functions
# manually if you override this one.
# - To save parsing time, all comment lines sent to <NaturalDocs::Parser->OnComment()> will be replaced with blank lines
# in <Tokens()>. It's all the same to most languages.
#
sub ParseForCommentsAndTokens #(FileName sourceFile, string[] lineCommentSymbols, string[] blockCommentSymbols, string[] javadocLineCommentSymbols, string[] javadocBlockCommentSymbols)
{
my ($self, $sourceFile, $lineCommentSymbols, $blockCommentSymbols,
$javadocLineCommentSymbols, $javadocBlockCommentSymbols) = @_;
open(SOURCEFILEHANDLE, '<' . $sourceFile)
or die "Couldn't open input file " . $sourceFile . "\n";
my $lineReader = NaturalDocs::LineReader->New(\*SOURCEFILEHANDLE);
my $tokens = [ ];
$self->SetTokens($tokens);
# For convenience.
$self->ClearAutoTopics();
$self->ClearScopeStack();
# Load and preprocess the file
my @lines = $lineReader->GetAll();
close(SOURCEFILEHANDLE);
$self->PreprocessFile(\@lines);
# Go through the file
my $lineIndex = 0;
while ($lineIndex < scalar @lines)
{
my $line = $lines[$lineIndex];
my @commentLines;
my $commentLineNumber;
my $isJavaDoc;
my $closingSymbol;
# Retrieve single line comments. This leaves $lineIndex at the next line.
if ( ($isJavaDoc = $self->StripOpeningJavaDocSymbols(\$line, $javadocLineCommentSymbols)) ||
$self->StripOpeningSymbols(\$line, $lineCommentSymbols))
{
$commentLineNumber = $lineIndex + 1;
do
{
push @commentLines, $line;
push @$tokens, "\n";
$lineIndex++;
if ($lineIndex >= scalar @lines)
{ goto EndDo; };
$line = $lines[$lineIndex];
}
while ($self->StripOpeningSymbols(\$line, $lineCommentSymbols));
EndDo: # I hate Perl sometimes.
}
# Retrieve multiline comments. This leaves $lineIndex at the next line.
elsif ( ($isJavaDoc = $self->StripOpeningJavaDocBlockSymbols(\$line, $javadocBlockCommentSymbols)) ||
($closingSymbol = $self->StripOpeningBlockSymbols(\$line, $blockCommentSymbols)) )
{
$commentLineNumber = $lineIndex + 1;
if ($isJavaDoc)
{ $closingSymbol = $isJavaDoc; };
# Note that it is possible for a multiline comment to start correctly but not end so. We want those comments to stay in
# the code. For example, look at this prototype with this splint annotation:
#
# int get_array(integer_t id,
# /*@out@*/ array_t array);
#
# The annotation starts correctly but doesn't end so because it is followed by code on the same line.
my ($lineRemainder, $isMultiLine);
for (;;)
{
$lineRemainder = $self->StripClosingSymbol(\$line, $closingSymbol);
push @commentLines, $line;
# If we found an end comment symbol...
if (defined $lineRemainder)
{ last; };
push @$tokens, "\n";
$lineIndex++;
$isMultiLine = 1;
if ($lineIndex >= scalar @lines)
{ last; };
$line = $lines[$lineIndex];
};
if ($lineRemainder !~ /^[ \t]*$/)
{
# If there was something past the closing symbol this wasn't an acceptable comment.
if ($isMultiLine)
{ $self->TokenizeLine($lineRemainder); }
else
{
# We go back to the original line if it wasn't a multiline comment because we want the comment to stay in the
# code. Otherwise the /*@out@*/ from the example would be removed.
$self->TokenizeLine($lines[$lineIndex]);
};
@commentLines = ( );
}
else
{
push @$tokens, "\n";
};
$lineIndex++;
}
# Otherwise just add it to the code.
else
{
$self->TokenizeLine($line);
$lineIndex++;
};
# If there were comments, send them to Parser->OnComment().
if (scalar @commentLines)
{
NaturalDocs::Parser->OnComment(\@commentLines, $commentLineNumber, $isJavaDoc);
@commentLines = ( );
$isJavaDoc = undef;
};
# $lineIndex was incremented by the individual code paths above.
}; # while ($lineIndex < scalar @lines)
};
#
# Function: PreprocessFile
#
# An overridable function if you'd like to preprocess the file before it goes into <ParseForCommentsAndTokens()>.
#
# Parameters:
#
# lines - An arrayref to the file's lines. Each line has its line break stripped off, but is otherwise untouched.
#
sub PreprocessFile #(lines)
{
};
#
# Function: TokenizeLine
#
# Converts the passed line to tokens as described in <ParseForCommentsAndTokens> and adds them to <Tokens()>. Also
# adds a line break token after it.
#
sub TokenizeLine #(line)
{
my ($self, $line) = @_;
push @{$self->Tokens()}, $line =~ /(\w+|[ \t]+|.)/g, "\n";
};
#
# Function: TryToSkipString
#
# If the position is on a string delimiter, moves the position to the token following the closing delimiter, or past the end of the
# tokens if there is none. Assumes all other characters are allowed in the string, the delimiter itself is allowed if it's preceded by
# a backslash, and line breaks are allowed in the string.
#
# Parameters:
#
# indexRef - A reference to the position's index into <Tokens()>.
# lineNumberRef - A reference to the position's line number.
# openingDelimiter - The opening string delimiter, such as a quote or an apostrophe.
# closingDelimiter - The closing string delimiter, if different. If not defined, assumes the same as openingDelimiter.
# startContentIndexRef - A reference to a variable in which to store the index of the first token of the string's content.
# May be undef.
# endContentIndexRef - A reference to a variable in which to store the index of the end of the string's content, which is one
# past the last index of content. May be undef.
#
# Returns:
#
# Whether the position was on the passed delimiter or not. The index, line number, and content index ref variables will be
# updated only if true.
#
sub TryToSkipString #(indexRef, lineNumberRef, openingDelimiter, closingDelimiter, startContentIndexRef, endContentIndexRef)
{
my ($self, $index, $lineNumber, $openingDelimiter, $closingDelimiter, $startContentIndexRef, $endContentIndexRef) = @_;
my $tokens = $self->Tokens();
if (!defined $closingDelimiter)
{ $closingDelimiter = $openingDelimiter; };
if ($tokens->[$$index] ne $openingDelimiter)
{ return undef; };
$$index++;
if (defined $startContentIndexRef)
{ $$startContentIndexRef = $$index; };
while ($$index < scalar @$tokens)
{
if ($tokens->[$$index] eq "\\")
{
# Skip the token after it.
$$index += 2;
}
elsif ($tokens->[$$index] eq "\n")
{
$$lineNumber++;
$$index++;
}
elsif ($tokens->[$$index] eq $closingDelimiter)
{
if (defined $endContentIndexRef)
{ $$endContentIndexRef = $$index; };
$$index++;
last;
}
else
{
$$index++;
};
};
if ($$index >= scalar @$tokens && defined $endContentIndexRef)
{ $$endContentIndexRef = scalar @$tokens; };
return 1;
};
#
# Function: SkipRestOfLine
#
# Moves the position to the token following the next line break, or past the end of the tokens array if there is none. Useful for
# line comments.
#
# Note that it skips blindly. It assumes there cannot be anything of interest, such as a string delimiter, between the position
# and the end of the line.
#
# Parameters:
#
# indexRef - A reference to the position's index into <Tokens()>.
# lineNumberRef - A reference to the position's line number.
sub SkipRestOfLine #(indexRef, lineNumberRef)
{
my ($self, $index, $lineNumber) = @_;
my $tokens = $self->Tokens();
while ($$index < scalar @$tokens)
{
if ($tokens->[$$index] eq "\n")
{
$$lineNumber++;
$$index++;
last;
}
else
{
$$index++;
};
};
};
#
# Function: SkipUntilAfter
#
# Moves the position to the token following the next occurance of a particular token sequence, or past the end of the tokens
# array if it never occurs. Useful for multiline comments.
#
# Note that it skips blindly. It assumes there cannot be anything of interest, such as a string delimiter, between the position
# and the end of the line.
#
# Parameters:
#
# indexRef - A reference to the position's index.
# lineNumberRef - A reference to the position's line number.
# token - A token that must be matched. Can be specified multiple times to match a sequence of tokens.
#
sub SkipUntilAfter #(indexRef, lineNumberRef, token, token, ...)
{
my ($self, $index, $lineNumber, @target) = @_;
my $tokens = $self->Tokens();
while ($$index < scalar @$tokens)
{
if ($tokens->[$$index] eq $target[0] && ($$index + scalar @target) <= scalar @$tokens)
{
my $match = 1;
for (my $i = 1; $i < scalar @target; $i++)
{
if ($tokens->[$$index+$i] ne $target[$i])
{
$match = 0;
last;
};
};
if ($match)
{
$$index += scalar @target;
return;
};
};
if ($tokens->[$$index] eq "\n")
{
$$lineNumber++;
$$index++;
}
else
{
$$index++;
};
};
};
#
# Function: IsFirstLineToken
#
# Returns whether the position is at the first token of a line, not including whitespace.
#
# Parameters:
#
# index - The index of the position.
#
sub IsFirstLineToken #(index)
{
my ($self, $index) = @_;
my $tokens = $self->Tokens();
if ($index == 0)
{ return 1; };
$index--;
if ($tokens->[$index] =~ /^[ \t]/)
{ $index--; };
if ($index <= 0 || $tokens->[$index] eq "\n")
{ return 1; }
else
{ return undef; };
};
#
# Function: IsLastLineToken
#
# Returns whether the position is at the last token of a line, not including whitespace.
#
# Parameters:
#
# index - The index of the position.
#
sub IsLastLineToken #(index)
{
my ($self, $index) = @_;
my $tokens = $self->Tokens();
do
{ $index++; }
while ($index < scalar @$tokens && $tokens->[$index] =~ /^[ \t]/);
if ($index >= scalar @$tokens || $tokens->[$index] eq "\n")
{ return 1; }
else
{ return undef; };
};
#
# Function: IsAtSequence
#
# Returns whether the position is at a sequence of tokens.
#
# Parameters:
#
# index - The index of the position.
# token - A token to match. Specify multiple times to specify the sequence.
#
sub IsAtSequence #(index, token, token, token ...)
{
my ($self, $index, @target) = @_;
my $tokens = $self->Tokens();
if ($index + scalar @target > scalar @$tokens)
{ return undef; };
for (my $i = 0; $i < scalar @target; $i++)
{
if ($tokens->[$index + $i] ne $target[$i])
{ return undef; };
};
return 1;
};
#
# Function: IsBackslashed
#
# Returns whether the position is after a backslash.
#
# Parameters:
#
# index - The index of the postition.
#
sub IsBackslashed #(index)
{
my ($self, $index) = @_;
my $tokens = $self->Tokens();
if ($index > 0 && $tokens->[$index - 1] eq "\\")
{ return 1; }
else
{ return undef; };
};
###############################################################################
#
# Group: Scope Functions
#
# These functions provide a nice scope stack implementation for language-specific parsers to use. The default implementation
# makes the following assumptions.
#
# - Packages completely replace one another, rather than concatenating. You need to concatenate manually if that's the
# behavior.
#
# - Packages inherit, so if a scope level doesn't set its own, the package is the same as the parent scope's.
#
#
# Function: ClearScopeStack
#
# Clears the scope stack for a new file. Not necessary if you call <ParseForCommentsAndTokens()>.
#
sub ClearScopeStack
{
my ($self) = @_;
$self->[SCOPE_STACK] = [ NaturalDocs::Languages::Advanced::Scope->New(undef, undef) ];
$self->[SCOPE_RECORD] = [ NaturalDocs::Languages::Advanced::ScopeChange->New(undef, 1) ];
};
#
# Function: StartScope
#
# Records a new scope level.
#
# Parameters:
#
# closingSymbol - The closing symbol of the scope.
# lineNumber - The line number where the scope begins.
# package - The package <SymbolString> of the scope. Undef means no change.
#
sub StartScope #(closingSymbol, lineNumber, package)
{
my ($self, $closingSymbol, $lineNumber, $package) = @_;
push @{$self->[SCOPE_STACK]},
NaturalDocs::Languages::Advanced::Scope->New($closingSymbol, $package, $self->CurrentUsing());
$self->AddToScopeRecord($self->CurrentScope(), $lineNumber);
};
#
# Function: EndScope
#
# Records the end of the current scope level. Note that this is blind; you need to manually check <ClosingScopeSymbol()> if
# you need to determine if it is correct to do so.
#
# Parameters:
#
# lineNumber - The line number where the scope ends.
#
sub EndScope #(lineNumber)
{
my ($self, $lineNumber) = @_;
if (scalar @{$self->[SCOPE_STACK]} > 1)
{ pop @{$self->[SCOPE_STACK]}; };
$self->AddToScopeRecord($self->CurrentScope(), $lineNumber);
};
#
# Function: ClosingScopeSymbol
#
# Returns the symbol that ends the current scope level, or undef if we are at the top level.
#
sub ClosingScopeSymbol
{
my ($self) = @_;
return $self->[SCOPE_STACK]->[-1]->ClosingSymbol();
};
#
# Function: CurrentScope
#
# Returns the current calculated scope, or undef if global. The default implementation just returns <CurrentPackage()>. This
# is a separate function because C++ may need to track namespaces and classes separately, and so the current scope would
# be a concatenation of them.
#
sub CurrentScope
{
return $_[0]->CurrentPackage();
};
#
# Function: CurrentPackage
#
# Returns the current calculated package or class, or undef if none.
#
sub CurrentPackage
{
my ($self) = @_;
my $package;
for (my $index = scalar @{$self->[SCOPE_STACK]} - 1; $index >= 0 && !defined $package; $index--)
{
$package = $self->[SCOPE_STACK]->[$index]->Package();
};
return $package;
};
#
# Function: SetPackage
#
# Sets the package for the current scope level.
#
# Parameters:
#
# package - The new package <SymbolString>.
# lineNumber - The line number the new package starts on.
#
sub SetPackage #(package, lineNumber)
{
my ($self, $package, $lineNumber) = @_;
$self->[SCOPE_STACK]->[-1]->SetPackage($package);
$self->AddToScopeRecord($self->CurrentScope(), $lineNumber);
};
#
# Function: CurrentUsing
#
# Returns the current calculated arrayref of <SymbolStrings> from Using statements, or undef if none.
#
sub CurrentUsing
{
my ($self) = @_;
return $self->[SCOPE_STACK]->[-1]->Using();
};
#
# Function: AddUsing
#
# Adds a Using <SymbolString> to the current scope.
#
sub AddUsing #(using)
{
my ($self, $using) = @_;
$self->[SCOPE_STACK]->[-1]->AddUsing($using);
};
###############################################################################
# Group: Support Functions
#
# Function: AddToScopeRecord
#
# Adds a change to the scope record, condensing unnecessary entries.
#
# Parameters:
#
# newScope - What the scope <SymbolString> changed to.
# lineNumber - Where the scope changed.
#
sub AddToScopeRecord #(newScope, lineNumber)
{
my ($self, $scope, $lineNumber) = @_;
my $scopeRecord = $self->ScopeRecord();
if ($scope ne $scopeRecord->[-1]->Scope())
{
if ($scopeRecord->[-1]->LineNumber() == $lineNumber)
{ $scopeRecord->[-1]->SetScope($scope); }
else
{ push @$scopeRecord, NaturalDocs::Languages::Advanced::ScopeChange->New($scope, $lineNumber); };
};
};
#
# Function: CreateString
#
# Converts the specified tokens into a string and returns it.
#
# Parameters:
#
# startIndex - The starting index to convert.
# endIndex - The ending index, which is *not inclusive*.
#
# Returns:
#
# The string.
#
sub CreateString #(startIndex, endIndex)
{
my ($self, $startIndex, $endIndex) = @_;
my $tokens = $self->Tokens();
my $string;
while ($startIndex < $endIndex && $startIndex < scalar @$tokens)
{
$string .= $tokens->[$startIndex];
$startIndex++;
};
return $string;
};
1;

View File

@ -0,0 +1,96 @@
###############################################################################
#
# Class: NaturalDocs::Languages::Advanced::Scope
#
###############################################################################
#
# A class used to store a scope level.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::Languages::Advanced::Scope;
#
# Constants: Implementation
#
# The object is implemented as a blessed arrayref. The constants below are used as indexes.
#
# CLOSING_SYMBOL - The closing symbol character of the scope.
# PACKAGE - The package <SymbolString> of the scope.
# USING - An arrayref of <SymbolStrings> for using statements, or undef if none.
#
use NaturalDocs::DefineMembers 'CLOSING_SYMBOL', 'PACKAGE', 'USING';
# Dependency: New() depends on the order of these constants as well as that there is no inherited members.
#
# Function: New
#
# Creates and returns a new object.
#
# Parameters:
#
# closingSymbol - The closing symbol character of the scope.
# package - The package <SymbolString> of the scope.
# using - An arrayref of using <SymbolStrings>, or undef if none. The contents of the array will be duplicated.
#
# If package is set to undef, it is assumed that it inherits the value of the previous scope on the stack.
#
sub New #(closingSymbol, package, using)
{
# Dependency: This depends on the order of the parameters matching the constants, and that there are no inherited
# members.
my $package = shift;
my $object = [ @_ ];
bless $object, $package;
if (defined $object->[USING])
{ $object->[USING] = [ @{$object->[USING]} ]; };
return $object;
};
# Function: ClosingSymbol
# Returns the closing symbol character of the scope.
sub ClosingSymbol
{ return $_[0]->[CLOSING_SYMBOL]; };
# Function: Package
# Returns the package <SymbolString> of the scope, or undef if none.
sub Package
{ return $_[0]->[PACKAGE]; };
# Function: SetPackage
# Sets the package <SymbolString> of the scope.
sub SetPackage #(package)
{ $_[0]->[PACKAGE] = $_[1]; };
# Function: Using
# Returns an arrayref of <SymbolStrings> for using statements, or undef if none
sub Using
{ return $_[0]->[USING]; };
# Function: AddUsing
# Adds a <SymbolString> to the <Using()> array.
sub AddUsing #(using)
{
my ($self, $using) = @_;
if (!defined $self->[USING])
{ $self->[USING] = [ ]; };
push @{$self->[USING]}, $using;
};
1;

View File

@ -0,0 +1,71 @@
###############################################################################
#
# Class: NaturalDocs::Languages::Advanced::ScopeChange
#
###############################################################################
#
# A class used to store a scope change.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::Languages::Advanced::ScopeChange;
#
# Constants: Implementation
#
# The object is implemented as a blessed arrayref. The constants below are used as indexes.
#
# SCOPE - The new scope <SymbolString>.
# LINE_NUMBER - The line number of the change.
#
use NaturalDocs::DefineMembers 'SCOPE', 'LINE_NUMBER';
# Dependency: New() depends on the order of these constants as well as that there is no inherited members.
#
# Function: New
#
# Creates and returns a new object.
#
# Parameters:
#
# scope - The <SymbolString> the scope was changed to.
# lineNumber - What line it occurred on.
#
sub New #(scope, lineNumber)
{
# Dependency: This depends on the order of the parameters matching the constants, and that there are no inherited
# members.
my $self = shift;
my $object = [ @_ ];
bless $object, $self;
return $object;
};
# Function: Scope
# Returns the <SymbolString> the scope was changed to.
sub Scope
{ return $_[0]->[SCOPE]; };
# Function: SetScope
# Replaces the <SymbolString> the scope was changed to.
sub SetScope #(scope)
{ $_[0]->[SCOPE] = $_[1]; };
# Function: LineNumber
# Returns the line number of the change.
sub LineNumber
{ return $_[0]->[LINE_NUMBER]; };
1;

View File

@ -0,0 +1,833 @@
###############################################################################
#
# Class: NaturalDocs::Languages::Base
#
###############################################################################
#
# A base class for all programming language parsers.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::Languages::Base;
use NaturalDocs::DefineMembers 'NAME', 'Name()',
'EXTENSIONS', 'Extensions()', 'SetExtensions() duparrayref',
'SHEBANG_STRINGS', 'ShebangStrings()', 'SetShebangStrings() duparrayref',
'IGNORED_PREFIXES',
'ENUM_VALUES';
use base 'Exporter';
our @EXPORT = ('ENUM_GLOBAL', 'ENUM_UNDER_TYPE', 'ENUM_UNDER_PARENT');
#
# Constants: EnumValuesType
#
# How enum values are handled in the language.
#
# ENUM_GLOBAL - Values are always global and thus 'value'.
# ENUM_UNDER_TYPE - Values are under the type in the hierarchy, and thus 'package.enum.value'.
# ENUM_UNDER_PARENT - Values are under the parent in the hierarchy, putting them on the same level as the enum itself. Thus
# 'package.value'.
#
use constant ENUM_GLOBAL => 1;
use constant ENUM_UNDER_TYPE => 2;
use constant ENUM_UNDER_PARENT => 3;
#
# Handle: SOURCEFILEHANDLE
#
# The handle of the source file currently being parsed.
#
#
# Function: New
#
# Creates and returns a new object.
#
# Parameters:
#
# name - The name of the language.
#
sub New #(name)
{
my ($selfPackage, $name) = @_;
my $object = [ ];
$object->[NAME] = $name;
bless $object, $selfPackage;
return $object;
};
#
# Functions: Members
#
# Name - Returns the language's name.
# Extensions - Returns an arrayref of the language's file extensions, or undef if none.
# SetExtensions - Replaces the arrayref of the language's file extensions.
# ShebangStrings - Returns an arrayref of the language's shebang strings, or undef if none.
# SetShebangStrings - Replaces the arrayref of the language's shebang strings.
#
#
# Function: PackageSeparator
# Returns the language's package separator string.
#
sub PackageSeparator
{ return '.'; };
#
# Function: PackageSeparatorWasSet
# Returns whether the language's package separator string was ever changed from the default.
#
sub PackageSeparatorWasSet
{ return 0; };
#
# Function: EnumValues
# Returns the <EnumValuesType> that describes how the language handles enums.
#
sub EnumValues
{ return ENUM_GLOBAL; };
#
# Function: IgnoredPrefixesFor
#
# Returns an arrayref of ignored prefixes for the passed <TopicType>, or undef if none. The array is sorted so that the longest
# prefixes are first.
#
sub IgnoredPrefixesFor #(type)
{
my ($self, $type) = @_;
if (defined $self->[IGNORED_PREFIXES])
{ return $self->[IGNORED_PREFIXES]->{$type}; }
else
{ return undef; };
};
#
# Function: SetIgnoredPrefixesFor
#
# Replaces the arrayref of ignored prefixes for the passed <TopicType>.
#
sub SetIgnoredPrefixesFor #(type, prefixes)
{
my ($self, $type, $prefixesRef) = @_;
if (!defined $self->[IGNORED_PREFIXES])
{ $self->[IGNORED_PREFIXES] = { }; };
if (!defined $prefixesRef)
{ delete $self->[IGNORED_PREFIXES]->{$type}; }
else
{
my $prefixes = [ @$prefixesRef ];
# Sort prefixes to be longest to shortest.
@$prefixes = sort { length $b <=> length $a } @$prefixes;
$self->[IGNORED_PREFIXES]->{$type} = $prefixes;
};
};
#
# Function: HasIgnoredPrefixes
#
# Returns whether the language has any ignored prefixes at all.
#
sub HasIgnoredPrefixes
{ return defined $_[0]->[IGNORED_PREFIXES]; };
#
# Function: CopyIgnoredPrefixesOf
#
# Copies all the ignored prefix settings of the passed <NaturalDocs::Languages::Base> object.
#
sub CopyIgnoredPrefixesOf #(language)
{
my ($self, $language) = @_;
if ($language->HasIgnoredPrefixes())
{
$self->[IGNORED_PREFIXES] = { };
while (my ($topicType, $prefixes) = each %{$language->[IGNORED_PREFIXES]})
{
$self->[IGNORED_PREFIXES]->{$topicType} = [ @$prefixes ];
};
};
};
###############################################################################
# Group: Parsing Functions
#
# Function: ParseFile
#
# Parses the passed source file, sending comments acceptable for documentation to <NaturalDocs::Parser->OnComment()>.
# This *must* be defined by a subclass.
#
# Parameters:
#
# sourceFile - The <FileName> of the source file 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 <NaturalDocs::Parser::ParsedTopics> from the file, or undef if none.
# scopeRecord - An arrayref of <NaturalDocs::Languages::Advanced::ScopeChanges>, or undef if none.
#
#
# Function: ParsePrototype
#
# Parses the prototype and returns it as a <NaturalDocs::Languages::Prototype> object.
#
# Parameters:
#
# type - The <TopicType>.
# prototype - The text prototype.
#
# Returns:
#
# A <NaturalDocs::Languages::Prototype> object.
#
sub ParsePrototype #(type, prototype)
{
my ($self, $type, $prototype) = @_;
my $isClass = NaturalDocs::Topics->TypeInfo($type)->ClassHierarchy();
if ($prototype !~ /\(.*[^ ].*\)/ && (!$isClass || $prototype !~ /\{.*[^ ].*\}/))
{
my $object = NaturalDocs::Languages::Prototype->New($prototype);
return $object;
};
# Parse the parameters out of the prototype.
my @tokens = $prototype =~ /([^\(\)\[\]\{\}\<\>\'\"\,\;]+|.)/g;
my $parameter;
my @parameterLines;
my @symbolStack;
my $finishedParameters;
my ($beforeParameters, $afterParameters);
foreach my $token (@tokens)
{
if ($finishedParameters)
{ $afterParameters .= $token; }
elsif ($symbolStack[-1] eq '\'' || $symbolStack[-1] eq '"')
{
if ($symbolStack[0] eq '(' || ($isClass && $symbolStack[0] eq '{'))
{ $parameter .= $token; }
else
{ $beforeParameters .= $token; };
if ($token eq $symbolStack[-1])
{ pop @symbolStack; };
}
elsif ($token =~ /^[\(\[\{\<\'\"]$/)
{
if ($symbolStack[0] eq '(' || ($isClass && $symbolStack[0] eq '{'))
{ $parameter .= $token; }
else
{ $beforeParameters .= $token; };
push @symbolStack, $token;
}
elsif ( ($token eq ')' && $symbolStack[-1] eq '(') ||
($token eq ']' && $symbolStack[-1] eq '[') ||
($token eq '}' && $symbolStack[-1] eq '{') ||
($token eq '>' && $symbolStack[-1] eq '<') )
{
if ($symbolStack[0] eq '(')
{
if ($token eq ')' && scalar @symbolStack == 1)
{
if ($parameter ne ' ')
{ push @parameterLines, $parameter; };
$finishedParameters = 1;
$afterParameters .= $token;
}
else
{ $parameter .= $token; };
}
elsif ($isClass && $symbolStack[0] eq '{')
{
if ($token eq '}' && scalar @symbolStack == 1)
{
if ($parameter ne ' ')
{ push @parameterLines, $parameter; };
$finishedParameters = 1;
$afterParameters .= $token;
}
else
{ $parameter .= $token; };
}
else
{
$beforeParameters .= $token;
};
pop @symbolStack;
}
elsif ($token eq ',' || $token eq ';')
{
if ($symbolStack[0] eq '(' || ($isClass && $symbolStack[0] eq '{'))
{
if (scalar @symbolStack == 1)
{
push @parameterLines, $parameter . $token;
$parameter = undef;
}
else
{
$parameter .= $token;
};
}
else
{
$beforeParameters .= $token;
};
}
else
{
if ($symbolStack[0] eq '(' || ($isClass && $symbolStack[0] eq '{'))
{ $parameter .= $token; }
else
{ $beforeParameters .= $token; };
};
};
foreach my $part (\$beforeParameters, \$afterParameters)
{
$$part =~ s/^ //;
$$part =~ s/ $//;
};
my $prototypeObject = NaturalDocs::Languages::Prototype->New($beforeParameters, $afterParameters);
# Parse the actual parameters.
foreach my $parameterLine (@parameterLines)
{
$prototypeObject->AddParameter( $self->ParseParameterLine($parameterLine) );
};
return $prototypeObject;
};
#
# Function: ParseParameterLine
#
# Parses a prototype parameter line and returns it as a <NaturalDocs::Languages::Prototype::Parameter> object.
#
# This vesion assumes a C++ style line. If you need a Pascal style line, override this function to forward to
# <ParsePascalParameterLine()>.
#
# > Function(parameter, type parameter, type parameter = value);
#
sub ParseParameterLine #(line)
{
my ($self, $line) = @_;
$line =~ s/^ //;
$line =~ s/ $//;
my @tokens = $line =~ /([^ \(\)\{\}\[\]\<\>\'\"\=]+|.)/g;
my @symbolStack;
my @parameterWords = ( undef );
my ($defaultValue, $defaultValuePrefix, $inDefaultValue);
foreach my $token (@tokens)
{
if ($inDefaultValue)
{ $defaultValue .= $token; }
elsif ($symbolStack[-1] eq '\'' || $symbolStack[-1] eq '"')
{
$parameterWords[-1] .= $token;
if ($token eq $symbolStack[-1])
{ pop @symbolStack; };
}
elsif ($token =~ /^[\(\[\{\<\'\"]$/)
{
push @symbolStack, $token;
$parameterWords[-1] .= $token;
}
elsif ( ($token eq ')' && $symbolStack[-1] eq '(') ||
($token eq ']' && $symbolStack[-1] eq '[') ||
($token eq '}' && $symbolStack[-1] eq '{') ||
($token eq '>' && $symbolStack[-1] eq '<') )
{
pop @symbolStack;
$parameterWords[-1] .= $token;
}
elsif ($token eq ' ')
{
if (!scalar @symbolStack)
{ push @parameterWords, undef; }
else
{ $parameterWords[-1] .= $token; };
}
elsif ($token eq '=')
{
if (!scalar @symbolStack)
{
$defaultValuePrefix = $token;
$inDefaultValue = 1;
}
else
{ $parameterWords[-1] .= $token; };
}
else
{
$parameterWords[-1] .= $token;
};
};
my ($name, $namePrefix, $type, $typePrefix);
if (!$parameterWords[-1])
{ pop @parameterWords; };
$name = pop @parameterWords;
if ($parameterWords[-1]=~ /([\*\&]+)$/)
{
$namePrefix = $1;
$parameterWords[-1] = substr($parameterWords[-1], 0, 0 - length($namePrefix));
$parameterWords[-1] =~ s/ $//;
if (!$parameterWords[-1])
{ pop @parameterWords; };
}
elsif ($name =~ /^([\*\&]+)/)
{
$namePrefix = $1;
$name = substr($name, length($namePrefix));
$name =~ s/^ //;
};
$type = pop @parameterWords;
$typePrefix = join(' ', @parameterWords);
if ($typePrefix)
{ $typePrefix .= ' '; };
if ($type =~ /^([a-z0-9_\:\.]+(?:\.|\:\:))[a-z0-9_]/i)
{
my $attachedTypePrefix = $1;
$typePrefix .= $attachedTypePrefix;
$type = substr($type, length($attachedTypePrefix));
};
$defaultValue =~ s/ $//;
return NaturalDocs::Languages::Prototype::Parameter->New($type, $typePrefix, $name, $namePrefix,
$defaultValue, $defaultValuePrefix);
};
#
# Function: ParsePascalParameterLine
#
# Parses a Pascal-like prototype parameter line and returns it as a <NaturalDocs::Languages::Prototype::Parameter> object.
# Pascal lines are as follows:
#
# > Function (name: type; name, name: type := value)
#
# Also supports ActionScript lines
#
# > Function (name: type, name, name: type = value)
#
sub ParsePascalParameterLine #(line)
{
my ($self, $line) = @_;
$line =~ s/^ //;
$line =~ s/ $//;
my @tokens = $line =~ /([^\(\)\{\}\[\]\<\>\'\"\=\:]+|\:\=|.)/g;
my ($type, $name, $defaultValue, $defaultValuePrefix, $afterName, $afterDefaultValue);
my @symbolStack;
foreach my $token (@tokens)
{
if ($afterDefaultValue)
{ $defaultValue .= $token; }
elsif ($symbolStack[-1] eq '\'' || $symbolStack[-1] eq '"')
{
if ($afterName)
{ $type .= $token; }
else
{ $name .= $token; };
if ($token eq $symbolStack[-1])
{ pop @symbolStack; };
}
elsif ($token =~ /^[\(\[\{\<\'\"]$/)
{
push @symbolStack, $token;
if ($afterName)
{ $type .= $token; }
else
{ $name .= $token; };
}
elsif ( ($token eq ')' && $symbolStack[-1] eq '(') ||
($token eq ']' && $symbolStack[-1] eq '[') ||
($token eq '}' && $symbolStack[-1] eq '{') ||
($token eq '>' && $symbolStack[-1] eq '<') )
{
pop @symbolStack;
if ($afterName)
{ $type .= $token; }
else
{ $name .= $token; };
}
elsif ($afterName)
{
if (($token eq ':=' || $token eq '=') && !scalar @symbolStack)
{
$defaultValuePrefix = $token;
$afterDefaultValue = 1;
}
else
{ $type .= $token; };
}
elsif ($token eq ':' && !scalar @symbolStack)
{
$name .= $token;
$afterName = 1;
}
else
{ $name .= $token; };
};
foreach my $part (\$type, \$name, \$defaultValue)
{
$$part =~ s/^ //;
$$part =~ s/ $//;
};
return NaturalDocs::Languages::Prototype::Parameter->New($type, undef, $name, undef, $defaultValue, $defaultValuePrefix);
};
#
# Function: TypeBeforeParameter
#
# Returns whether the type appears before the parameter in prototypes.
#
# For example, it does in C++
# > void Function (int a, int b)
#
# but does not in Pascal
# > function Function (a: int; b, c: int)
#
sub TypeBeforeParameter
{
return 1;
};
#
# Function: IgnoredPrefixLength
#
# Returns the length of the prefix that should be ignored in the index, or zero if none.
#
# Parameters:
#
# name - The name of the symbol.
# type - The symbol's <TopicType>.
#
# Returns:
#
# The length of the prefix to ignore, or zero if none.
#
sub IgnoredPrefixLength #(name, type)
{
my ($self, $name, $type) = @_;
foreach my $prefixes ($self->IgnoredPrefixesFor($type), $self->IgnoredPrefixesFor(::TOPIC_GENERAL()))
{
if (defined $prefixes)
{
foreach my $prefix (@$prefixes)
{
if (substr($name, 0, length($prefix)) eq $prefix)
{ return length($prefix); };
};
};
};
return 0;
};
###############################################################################
# Group: Support Functions
#
# Function: StripOpeningSymbols
#
# Determines if the line starts with any of the passed symbols, and if so, replaces it with spaces. This only happens
# if the only thing before it on the line is whitespace.
#
# Parameters:
#
# lineRef - A reference to the line to check.
# symbols - An arrayref of the symbols to check for.
#
# Returns:
#
# If the line starts with any of the passed comment symbols, it will replace it in the line with spaces and return the symbol.
# If the line doesn't, it will leave the line alone and return undef.
#
sub StripOpeningSymbols #(lineRef, symbols)
{
my ($self, $lineRef, $symbols) = @_;
if (!defined $symbols)
{ return undef; };
my ($index, $symbol) = ::FindFirstSymbol($$lineRef, $symbols);
if ($index != -1 && substr($$lineRef, 0, $index) =~ /^[ \t]*$/)
{
return substr($$lineRef, $index, length($symbol), ' ' x length($symbol));
};
return undef;
};
#
# Function: StripOpeningJavaDocSymbols
#
# Determines if the line starts with any of the passed symbols, and if so, replaces it with spaces. This only happens
# if the only thing before it on the line is whitespace and the next character after it is whitespace or the end of the line.
#
# Parameters:
#
# lineRef - A reference to the line to check.
# symbols - An arrayref of the symbols to check for.
#
# Returns:
#
# If the line starts with any of the passed comment symbols, it will replace it in the line with spaces and return the symbol.
# If the line doesn't, it will leave the line alone and return undef.
#
sub StripOpeningJavaDocSymbols #(lineRef, symbols)
{
my ($self, $lineRef, $symbols) = @_;
if (!defined $symbols)
{ return undef; };
my ($index, $symbol) = ::FindFirstSymbol($$lineRef, $symbols);
if ($index != -1 && substr($$lineRef, 0, $index) =~ /^[ \t]*$/ && substr($$lineRef, $index + length($symbol), 1) =~ /^[ \t]?$/)
{
return substr($$lineRef, $index, length($symbol), ' ' x length($symbol));
};
return undef;
};
#
# Function: StripOpeningBlockSymbols
#
# Determines if the line starts with any of the opening symbols in the passed symbol pairs, and if so, replaces it with spaces.
# This only happens if the only thing before it on the line is whitespace.
#
# Parameters:
#
# lineRef - A reference to the line to check.
# symbolPairs - An arrayref of the symbol pairs to check for. Pairs are specified as two consecutive array entries, with the
# opening symbol first.
#
# Returns:
#
# If the line starts with any of the opening symbols, it will replace it in the line with spaces and return the closing symbol.
# If the line doesn't, it will leave the line alone and return undef.
#
sub StripOpeningBlockSymbols #(lineRef, symbolPairs)
{
my ($self, $lineRef, $symbolPairs) = @_;
if (!defined $symbolPairs)
{ return undef; };
for (my $i = 0; $i < scalar @$symbolPairs; $i += 2)
{
my $index = index($$lineRef, $symbolPairs->[$i]);
if ($index != -1 && substr($$lineRef, 0, $index) =~ /^[ \t]*$/)
{
substr($$lineRef, $index, length($symbolPairs->[$i]), ' ' x length($symbolPairs->[$i]));
return $symbolPairs->[$i + 1];
};
};
return undef;
};
#
# Function: StripOpeningJavaDocBlockSymbols
#
# Determines if the line starts with any of the opening symbols in the passed symbol pairs, and if so, replaces it with spaces.
# This only happens if the only thing before it on the line is whitespace and the next character is whitespace or the end of the line.
#
# Parameters:
#
# lineRef - A reference to the line to check.
# symbolPairs - An arrayref of the symbol pairs to check for. Pairs are specified as two consecutive array entries, with the
# opening symbol first.
#
# Returns:
#
# If the line starts with any of the opening symbols, it will replace it in the line with spaces and return the closing symbol.
# If the line doesn't, it will leave the line alone and return undef.
#
sub StripOpeningJavaDocBlockSymbols #(lineRef, symbolPairs)
{
my ($self, $lineRef, $symbolPairs) = @_;
if (!defined $symbolPairs)
{ return undef; };
for (my $i = 0; $i < scalar @$symbolPairs; $i += 2)
{
my $index = index($$lineRef, $symbolPairs->[$i]);
if ($index != -1 && substr($$lineRef, 0, $index) =~ /^[ \t]*$/ &&
substr($$lineRef, $index + length($symbolPairs->[$i]), 1) =~ /^[ \t]?$/)
{
substr($$lineRef, $index, length($symbolPairs->[$i]), ' ' x length($symbolPairs->[$i]));
return $symbolPairs->[$i + 1];
};
};
return undef;
};
#
# Function: StripClosingSymbol
#
# Determines if the line contains a symbol, and if so, truncates it just before the symbol.
#
# Parameters:
#
# lineRef - A reference to the line to check.
# symbol - The symbol to check for.
#
# Returns:
#
# The remainder of the line, or undef if the symbol was not found.
#
sub StripClosingSymbol #(lineRef, symbol)
{
my ($self, $lineRef, $symbol) = @_;
my $index = index($$lineRef, $symbol);
if ($index != -1)
{
my $lineRemainder = substr($$lineRef, $index + length($symbol));
$$lineRef = substr($$lineRef, 0, $index);
return $lineRemainder;
}
else
{ return undef; };
};
#
# Function: NormalizePrototype
#
# Normalizes a prototype. Specifically, condenses spaces, tabs, and line breaks into single spaces and removes leading and
# trailing ones.
#
# Parameters:
#
# prototype - The original prototype string.
#
# Returns:
#
# The normalized prototype.
#
sub NormalizePrototype #(prototype)
{
my ($self, $prototype) = @_;
$prototype =~ tr/ \t\r\n/ /s;
$prototype =~ s/^ //;
$prototype =~ s/ $//;
return $prototype;
};
1;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,320 @@
###############################################################################
#
# Class: NaturalDocs::Languages::PLSQL
#
###############################################################################
#
# A subclass to handle the language variations of PL/SQL.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::Languages::PLSQL;
use base 'NaturalDocs::Languages::Simple';
#
# Function: OnPrototypeEnd
#
# Microsoft's SQL specifies parameters as shown below.
#
# > CREATE PROCEDURE Test @as int, @foo int AS ...
#
# Having a parameter @is or @as is perfectly valid even though those words are also used to end the prototype. We need to
# ignore text-based enders preceded by an at sign. Also note that it does not have parenthesis for parameter lists. We need to
# skip all commas if the prototype doesn't have parenthesis but does have @ characters.
#
# Identifiers such as function names may contain the characters $, #, and _, so if "as" or "is" appears directly after one of them
# we need to ignore the ender there as well.
#
# > FUNCTION Something_is_something ...
#
# Parameters:
#
# type - The <TopicType> of the prototype.
# prototypeRef - A reference to the prototype so far, minus the ender in dispute.
# ender - The ender symbol.
#
# Returns:
#
# ENDER_ACCEPT - The ender is accepted and the prototype is finished.
# ENDER_IGNORE - The ender is rejected and parsing should continue. Note that the prototype will be rejected as a whole
# if all enders are ignored before reaching the end of the code.
# ENDER_ACCEPT_AND_CONTINUE - The ender is accepted so the prototype may stand as is. However, the prototype might
# also continue on so continue parsing. If there is no accepted ender between here and
# the end of the code this version will be accepted instead.
# ENDER_REVERT_TO_ACCEPTED - The expedition from ENDER_ACCEPT_AND_CONTINUE failed. Use the last accepted
# version and end parsing.
#
sub OnPrototypeEnd #(type, prototypeRef, ender)
{
my ($self, $type, $prototypeRef, $ender) = @_;
# _ should be handled already.
if ($ender =~ /^[a-z]+$/i && substr($$prototypeRef, -1) =~ /^[\@\$\#]$/)
{ return ::ENDER_IGNORE(); }
elsif ($type eq ::TOPIC_FUNCTION() && $ender eq ',')
{
if ($$prototypeRef =~ /^[^\(]*\@/)
{ return ::ENDER_IGNORE(); }
else
{ return ::ENDER_ACCEPT(); };
}
else
{ return ::ENDER_ACCEPT(); };
};
#
# Function: ParsePrototype
#
# Overridden to handle Microsoft's parenthesisless version. Otherwise just throws to the parent.
#
# Parameters:
#
# type - The <TopicType>.
# prototype - The text prototype.
#
# Returns:
#
# A <NaturalDocs::Languages::Prototype> object.
#
sub ParsePrototype #(type, prototype)
{
my ($self, $type, $prototype) = @_;
my $noParenthesisParameters = ($type eq ::TOPIC_FUNCTION() && $prototype =~ /^[^\(]*\@/);
if ($prototype !~ /\(.*[^ ].*\)/ && !$noParenthesisParameters)
{ return $self->SUPER::ParsePrototype($type, $prototype); };
my ($beforeParameters, $afterParameters, $isAfterParameters);
if ($noParenthesisParameters)
{
($beforeParameters, $prototype) = split(/\@/, $prototype, 2);
$prototype = '@' . $prototype;
};
my @tokens = $prototype =~ /([^\(\)\[\]\{\}\<\>\'\"\,]+|.)/g;
my $parameter;
my @parameterLines;
my @symbolStack;
foreach my $token (@tokens)
{
if ($isAfterParameters)
{ $afterParameters .= $token; }
elsif ($symbolStack[-1] eq '\'' || $symbolStack[-1] eq '"')
{
if ($noParenthesisParameters || $symbolStack[0] eq '(')
{ $parameter .= $token; }
else
{ $beforeParameters .= $token; };
if ($token eq $symbolStack[-1])
{ pop @symbolStack; };
}
elsif ($token =~ /^[\(\[\{\<\'\"]$/)
{
if ($noParenthesisParameters || $symbolStack[0] eq '(')
{ $parameter .= $token; }
else
{ $beforeParameters .= $token; };
push @symbolStack, $token;
}
elsif ( ($token eq ')' && $symbolStack[-1] eq '(') ||
($token eq ']' && $symbolStack[-1] eq '[') ||
($token eq '}' && $symbolStack[-1] eq '{') ||
($token eq '>' && $symbolStack[-1] eq '<') )
{
if (!$noParenthesisParameters && $token eq ')' && scalar @symbolStack == 1 && $symbolStack[0] eq '(')
{
$afterParameters .= $token;
$isAfterParameters = 1;
}
else
{ $parameter .= $token; };
pop @symbolStack;
}
elsif ($token eq ',')
{
if (!scalar @symbolStack)
{
if ($noParenthesisParameters)
{
push @parameterLines, $parameter . $token;
$parameter = undef;
}
else
{
$beforeParameters .= $token;
};
}
else
{
if (scalar @symbolStack == 1 && $symbolStack[0] eq '(' && !$noParenthesisParameters)
{
push @parameterLines, $parameter . $token;
$parameter = undef;
}
else
{
$parameter .= $token;
};
};
}
else
{
if ($noParenthesisParameters || $symbolStack[0] eq '(')
{ $parameter .= $token; }
else
{ $beforeParameters .= $token; };
};
};
push @parameterLines, $parameter;
foreach my $item (\$beforeParameters, \$afterParameters)
{
$$item =~ s/^ //;
$$item =~ s/ $//;
}
my $prototypeObject = NaturalDocs::Languages::Prototype->New($beforeParameters, $afterParameters);
# Parse the actual parameters.
foreach my $parameterLine (@parameterLines)
{
$prototypeObject->AddParameter( $self->ParseParameterLine($parameterLine) );
};
return $prototypeObject;
};
#
# Function: ParseParameterLine
#
# Parses a prototype parameter line and returns it as a <NaturalDocs::Languages::Prototype::Parameter> object.
#
sub ParseParameterLine #(line)
{
my ($self, $line) = @_;
$line =~ s/^ //;
$line =~ s/ $//;
my @tokens = $line =~ /([^\(\)\[\]\{\}\<\>\'\"\:\=\ ]+|\:\=|.)/g;
my ($name, $type, $defaultValue, $defaultValuePrefix, $inType, $inDefaultValue);
my @symbolStack;
foreach my $token (@tokens)
{
if ($inDefaultValue)
{ $defaultValue .= $token; }
elsif ($symbolStack[-1] eq '\'' || $symbolStack[-1] eq '"')
{
if ($inType)
{ $type .= $token; }
else
{ $name .= $token; };
if ($token eq $symbolStack[-1])
{ pop @symbolStack; };
}
elsif ($token =~ /^[\(\[\{\<\'\"]$/)
{
if ($inType)
{ $type .= $token; }
else
{ $name .= $token; };
push @symbolStack, $token;
}
elsif ( ($token eq ')' && $symbolStack[-1] eq '(') ||
($token eq ']' && $symbolStack[-1] eq '[') ||
($token eq '}' && $symbolStack[-1] eq '{') ||
($token eq '>' && $symbolStack[-1] eq '<') )
{
if ($inType)
{ $type .= $token; }
else
{ $name .= $token; };
pop @symbolStack;
}
elsif ($token eq ' ')
{
if ($inType)
{ $type .= $token; }
elsif (!scalar @symbolStack)
{ $inType = 1; }
else
{ $name .= $token; };
}
elsif ($token eq ':=' || $token eq '=')
{
if (!scalar @symbolStack)
{
$defaultValuePrefix = $token;
$inDefaultValue = 1;
}
elsif ($inType)
{ $type .= $token; }
else
{ $name .= $token; };
}
else
{
if ($inType)
{ $type .= $token; }
else
{ $name .= $token; };
};
};
foreach my $part (\$type, \$defaultValue)
{
$$part =~ s/ $//;
};
return NaturalDocs::Languages::Prototype::Parameter->New($type, undef, $name, undef, $defaultValue, $defaultValuePrefix);
};
sub TypeBeforeParameter
{ return 0; };
1;

View File

@ -0,0 +1,144 @@
###############################################################################
#
# Class: NaturalDocs::Languages::Pascal
#
###############################################################################
#
# A subclass to handle the language variations of Pascal and Delphi.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::Languages::Pascal;
use base 'NaturalDocs::Languages::Simple';
#
# hash: prototypeDirectives
#
# An existence hash of all the directives that can appear after a function prototype and will be included. The keys are the all
# lowercase keywords.
#
my %prototypeDirectives = ( 'overload' => 1,
'override' => 1,
'virtual' => 1,
'abstract' => 1,
'reintroduce' => 1,
'export' => 1,
'public' => 1,
'interrupt' => 1,
'register' => 1,
'pascal' => 1,
'cdecl' => 1,
'stdcall' => 1,
'popstack' => 1,
'saveregisters' => 1,
'inline' => 1,
'safecall' => 1 );
#
# hash: longPrototypeDirectives
#
# An existence hash of all the directives with parameters that can appear after a function prototype and will be included. The
# keys are the all lowercase keywords.
#
my %longPrototypeDirectives = ( 'alias' => 1,
'external' => 1 );
#
# bool: checkingForDirectives
#
# Set after the first function semicolon, which means we're in directives mode.
#
my $checkingForDirectives;
#
# Function: OnCode
#
# Just overridden to reset <checkingForDirectives>.
#
sub OnCode #(...)
{
my ($self, @parameters) = @_;
$checkingForDirectives = 0;
return $self->SUPER::OnCode(@parameters);
};
#
# Function: OnPrototypeEnd
#
# Pascal's syntax has directives after the prototype that should be included.
#
# > function MyFunction ( param1: type ); virtual; abstract;
#
# Parameters:
#
# type - The <TopicType> of the prototype.
# prototypeRef - A reference to the prototype so far, minus the ender in dispute.
# ender - The ender symbol.
#
# Returns:
#
# ENDER_ACCEPT - The ender is accepted and the prototype is finished.
# ENDER_IGNORE - The ender is rejected and parsing should continue. Note that the prototype will be rejected as a whole
# if all enders are ignored before reaching the end of the code.
# ENDER_ACCEPT_AND_CONTINUE - The ender is accepted so the prototype may stand as is. However, the prototype might
# also continue on so continue parsing. If there is no accepted ender between here and
# the end of the code this version will be accepted instead.
# ENDER_REVERT_TO_ACCEPTED - The expedition from ENDER_ACCEPT_AND_CONTINUE failed. Use the last accepted
# version and end parsing.
#
sub OnPrototypeEnd #(type, prototypeRef, ender)
{
my ($self, $type, $prototypeRef, $ender) = @_;
if ($type eq ::TOPIC_FUNCTION() && $ender eq ';')
{
if (!$checkingForDirectives)
{
$checkingForDirectives = 1;
return ::ENDER_ACCEPT_AND_CONTINUE();
}
elsif ($$prototypeRef =~ /;[ \t]*([a-z]+)([^;]*)$/i)
{
my ($lastDirective, $extra) = (lc($1), $2);
if (exists $prototypeDirectives{$lastDirective} && $extra =~ /^[ \t]*$/)
{ return ::ENDER_ACCEPT_AND_CONTINUE(); }
elsif (exists $longPrototypeDirectives{$lastDirective})
{ return ::ENDER_ACCEPT_AND_CONTINUE(); }
else
{ return ::ENDER_REVERT_TO_ACCEPTED(); };
}
else
{ return ::ENDER_REVERT_TO_ACCEPTED(); };
}
else
{ return ::ENDER_ACCEPT(); };
};
sub ParseParameterLine #(...)
{
my ($self, @params) = @_;
return $self->SUPER::ParsePascalParameterLine(@params);
};
sub TypeBeforeParameter
{
return 0;
};
1;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,93 @@
###############################################################################
#
# Class: NaturalDocs::Languages::Prototype
#
###############################################################################
#
# A data class for storing parsed prototypes.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
use NaturalDocs::Languages::Prototype::Parameter;
package NaturalDocs::Languages::Prototype;
use NaturalDocs::DefineMembers 'BEFORE_PARAMETERS', 'BeforeParameters()', 'SetBeforeParameters()',
'AFTER_PARAMETERS', 'AfterParameters()', 'SetAfterParameters()',
'PARAMETERS', 'Parameters()';
# Dependency: New(), constant order, no parents.
#
# Function: New
#
# Creates and returns a new prototype object.
#
# Parameters:
#
# beforeParameters - The part of the prototype before the parameter list.
# afterParameters - The part of the prototype after the parameter list.
#
# You cannot set the parameters from here. Use <AddParameter()>.
#
sub New #(beforeParameters, afterParameters)
{
my ($package, @params) = @_;
# Dependency: Constant order, no parents.
my $object = [ @params ];
bless $object, $package;
return $object;
};
#
# Functions: Members
#
# BeforeParameters - Returns the part of the prototype before the parameter list. If there is no parameter list, this will be the
# only thing that returns content.
# SetBeforeParameters - Replaces the part of the prototype before the parameter list.
# AfterParameters - Returns the part of the prototype after the parameter list, if any.
# SetAfterParameters - Replaces the part of the prototype after the parameter list.
# Parameters - Returns the parameter list as an arrayref of <NaturalDocs::Languages::Prototype::Parameters>, or undef if none.
#
#
# Function: AddParameter
#
# Adds a <NaturalDocs::Languages::Prototype::Parameter> to the list.
#
sub AddParameter #(parameter)
{
my ($self, $parameter) = @_;
if (!defined $self->[PARAMETERS])
{ $self->[PARAMETERS] = [ ]; };
push @{$self->[PARAMETERS]}, $parameter;
};
#
# Function: OnlyBeforeParameters
#
# Returns whether <BeforeParameters()> is the only thing set.
#
sub OnlyBeforeParameters
{
my $self = shift;
return (!defined $self->[PARAMETERS] && !defined $self->[AFTER_PARAMETERS]);
};
1;

View File

@ -0,0 +1,88 @@
###############################################################################
#
# Class: NaturalDocs::Languages::Prototype::Parameter
#
###############################################################################
#
# A data class for storing parsed prototype parameters.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::Languages::Prototype::Parameter;
use NaturalDocs::DefineMembers 'TYPE', 'Type()', 'SetType()',
'TYPE_PREFIX', 'TypePrefix()', 'SetTypePrefix()',
'NAME', 'Name()', 'SetName()',
'NAME_PREFIX', 'NamePrefix()', 'SetNamePrefix()',
'DEFAULT_VALUE', 'DefaultValue()', 'SetDefaultValue()',
'DEFAULT_VALUE_PREFIX', 'DefaultValuePrefix()', 'SetDefaultValuePrefix()';
# Dependency: New() depends on the order of these constants and that they don't inherit from another class.
#
# Constants: Members
#
# The object is implemented as a blessed arrayref, with the following constants as its indexes.
#
# TYPE - The parameter type, if any.
# TYPE_PREFIX - The parameter type prefix which should be aligned separately, if any.
# NAME - The parameter name.
# NAME_PREFIX - The parameter name prefix which should be aligned separately, if any.
# DEFAULT_VALUE - The default value expression, if any.
# DEFAULT_VALUE_PREFIX - The default value prefix which should be aligned separately, if any.
#
#
# Function: New
#
# Creates and returns a new prototype object.
#
# Parameters:
#
# type - The parameter type, if any.
# typePrefix - The parameter type prefix which should be aligned separately, if any.
# name - The parameter name.
# namePrefix - The parameter name prefix which should be aligned separately, if any.
# defaultValue - The default value expression, if any.
# defaultValuePrefix - The default value prefix which should be aligned separately, if any.
#
sub New #(type, typePrefix, name, namePrefix, defaultValue, defaultValuePrefix)
{
my ($package, @params) = @_;
# Dependency: This depends on the order of the parameters being the same as the order of the constants, and that the
# constants don't inherit from another class.
my $object = [ @params ];
bless $object, $package;
return $object;
};
#
# Functions: Members
#
# Type - The parameter type, if any.
# SetType - Replaces the parameter type.
# TypePrefix - The parameter type prefix, which should be aligned separately, if any.
# SetTypePrefix - Replaces the parameter type prefix.
# Name - The parameter name.
# SetName - Replaces the parameter name.
# NamePrefix - The parameter name prefix, which should be aligned separately, if any.
# SetNamePrefix - Replaces the parameter name prefix.
# DefaultValue - The default value expression, if any.
# SetDefaultValue - Replaces the default value expression.
# DefaultValuePrefix - The default value prefix, which should be aligned separately, if any.
# SetDefaultValuePrefix - Replaces the default value prefix.
#
1;

View File

@ -0,0 +1,487 @@
###############################################################################
#
# Class: NaturalDocs::Languages::Simple
#
###############################################################################
#
# A class containing the characteristics of a particular programming language for basic support within Natural Docs.
# Also serves as a base class for languages that break from general conventions, such as not having parameter lists use
# parenthesis and commas.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::Languages::Simple;
use base 'NaturalDocs::Languages::Base';
use base 'Exporter';
our @EXPORT = ( 'ENDER_ACCEPT', 'ENDER_IGNORE', 'ENDER_ACCEPT_AND_CONTINUE', 'ENDER_REVERT_TO_ACCEPTED' );
use NaturalDocs::DefineMembers 'LINE_COMMENT_SYMBOLS', 'LineCommentSymbols()', 'SetLineCommentSymbols() duparrayref',
'BLOCK_COMMENT_SYMBOLS', 'BlockCommentSymbols()',
'SetBlockCommentSymbols() duparrayref',
'PROTOTYPE_ENDERS',
'LINE_EXTENDER', 'LineExtender()', 'SetLineExtender()',
'PACKAGE_SEPARATOR', 'PackageSeparator()',
'PACKAGE_SEPARATOR_WAS_SET', 'PackageSeparatorWasSet()',
'ENUM_VALUES', 'EnumValues()',
'ENUM_VALUES_WAS_SET', 'EnumValuesWasSet()';
#
# Function: New
#
# Creates and returns a new object.
#
# Parameters:
#
# name - The name of the language.
#
sub New #(name)
{
my ($selfPackage, $name) = @_;
my $object = $selfPackage->SUPER::New($name);
$object->[ENUM_VALUES] = ::ENUM_GLOBAL();
$object->[PACKAGE_SEPARATOR] = '.';
return $object;
};
#
# Functions: Members
#
# LineCommentSymbols - Returns an arrayref of symbols that start a line comment, or undef if none.
# SetLineCommentSymbols - Replaces the arrayref of symbols that start a line comment.
# BlockCommentSymbols - Returns an arrayref of start/end symbol pairs that specify a block comment, or undef if none. Pairs
# are specified with two consecutive array entries.
# SetBlockCommentSymbols - Replaces the arrayref of start/end symbol pairs that specify a block comment. Pairs are
# specified with two consecutive array entries.
# LineExtender - Returns the symbol to ignore a line break in languages where line breaks are significant.
# SetLineExtender - Replaces the symbol to ignore a line break in languages where line breaks are significant.
# PackageSeparator - Returns the package separator symbol.
# PackageSeparatorWasSet - Returns whether the package separator symbol was ever changed from the default.
#
#
# Function: SetPackageSeparator
# Replaces the language's package separator string.
#
sub SetPackageSeparator #(separator)
{
my ($self, $separator) = @_;
$self->[PACKAGE_SEPARATOR] = $separator;
$self->[PACKAGE_SEPARATOR_WAS_SET] = 1;
};
#
# Functions: Members
#
# EnumValues - Returns the <EnumValuesType> that describes how the language handles enums.
# EnumValuesWasSet - Returns whether <EnumValues> was ever changed from the default.
#
# Function: SetEnumValues
# Replaces the <EnumValuesType> that describes how the language handles enums.
#
sub SetEnumValues #(EnumValuesType newBehavior)
{
my ($self, $behavior) = @_;
$self->[ENUM_VALUES] = $behavior;
$self->[ENUM_VALUES_WAS_SET] = 1;
};
#
# Function: PrototypeEndersFor
#
# Returns an arrayref of prototype ender symbols for the passed <TopicType>, or undef if none.
#
sub PrototypeEndersFor #(type)
{
my ($self, $type) = @_;
if (defined $self->[PROTOTYPE_ENDERS])
{ return $self->[PROTOTYPE_ENDERS]->{$type}; }
else
{ return undef; };
};
#
# Function: SetPrototypeEndersFor
#
# Replaces the arrayref of prototype ender symbols for the passed <TopicType>.
#
sub SetPrototypeEndersFor #(type, enders)
{
my ($self, $type, $enders) = @_;
if (!defined $self->[PROTOTYPE_ENDERS])
{ $self->[PROTOTYPE_ENDERS] = { }; };
if (!defined $enders)
{ delete $self->[PROTOTYPE_ENDERS]->{$type}; }
else
{
$self->[PROTOTYPE_ENDERS]->{$type} = [ @$enders ];
};
};
###############################################################################
# Group: Parsing Functions
#
# Function: ParseFile
#
# Parses the passed source file, sending comments acceptable for documentation to <NaturalDocs::Parser->OnComment()>
# and all other sections to <OnCode()>.
#
# Parameters:
#
# sourceFile - The <FileName> of the source file to parse.
# topicList - A reference to the list of <NaturalDocs::Parser::ParsedTopics> being built by the file.
#
# Returns:
#
# Since this class cannot automatically document the code or generate a scope record, it always returns ( undef, undef ).
#
sub ParseFile #(sourceFile, topicsList)
{
my ($self, $sourceFile, $topicsList) = @_;
open(SOURCEFILEHANDLE, '<' . $sourceFile)
or die "Couldn't open input file " . $sourceFile . "\n";
my $lineReader = NaturalDocs::LineReader->New(\*SOURCEFILEHANDLE);
my @commentLines;
my @codeLines;
my $lastCommentTopicCount = 0;
if ($self->Name() eq 'Text File')
{
@commentLines = $lineReader->GetAll();
NaturalDocs::Parser->OnComment(\@commentLines, 1);
}
else
{
my $line = $lineReader->Get();
my $lineNumber = 1;
while (defined $line)
{
my $originalLine = $line;
# Retrieve multiline comments. This leaves $line at the next line.
# We check for multiline comments before single line comments because in Lua the symbols are --[[ and --.
if (my $closingSymbol = $self->StripOpeningBlockSymbols(\$line, $self->BlockCommentSymbols()))
{
# Note that it is possible for a multiline comment to start correctly but not end so. We want those comments to stay in
# the code. For example, look at this prototype with this splint annotation:
#
# int get_array(integer_t id,
# /*@out@*/ array_t array);
#
# The annotation starts correctly but doesn't end so because it is followed by code on the same line.
my $lineRemainder;
for (;;)
{
$lineRemainder = $self->StripClosingSymbol(\$line, $closingSymbol);
push @commentLines, $line;
# If we found an end comment symbol...
if (defined $lineRemainder)
{ last; };
$line = $lineReader->Get();
if (!defined $line)
{ last; };
};
if ($lineRemainder !~ /^[ \t]*$/)
{
# If there was something past the closing symbol this wasn't an acceptable comment, so move the lines to code.
push @codeLines, @commentLines;
@commentLines = ( );
};
$line = $lineReader->Get();
}
# Retrieve single line comments. This leaves $line at the next line.
elsif ($self->StripOpeningSymbols(\$line, $self->LineCommentSymbols()))
{
do
{
push @commentLines, $line;
$line = $lineReader->Get();
if (!defined $line)
{ goto EndDo; };
}
while ($self->StripOpeningSymbols(\$line, $self->LineCommentSymbols()));
EndDo: # I hate Perl sometimes.
}
# Otherwise just add it to the code.
else
{
push @codeLines, $line;
$line = $lineReader->Get();
};
# If there were comments, send them to Parser->OnComment().
if (scalar @commentLines)
{
# First process any code lines before the comment.
if (scalar @codeLines)
{
$self->OnCode(\@codeLines, $lineNumber, $topicsList, $lastCommentTopicCount);
$lineNumber += scalar @codeLines;
@codeLines = ( );
};
$lastCommentTopicCount = NaturalDocs::Parser->OnComment(\@commentLines, $lineNumber);
$lineNumber += scalar @commentLines;
@commentLines = ( );
};
}; # while (defined $line)
# Clean up any remaining code.
if (scalar @codeLines)
{
$self->OnCode(\@codeLines, $lineNumber, $topicsList, $lastCommentTopicCount);
@codeLines = ( );
};
};
close(SOURCEFILEHANDLE);
return ( undef, undef );
};
#
# Function: OnCode
#
# Called whenever a section of code is encountered by the parser. Is used to find the prototype of the last topic created.
#
# Parameters:
#
# codeLines - The source code as an arrayref of lines.
# codeLineNumber - The line number of the first line of code.
# topicList - A reference to the list of <NaturalDocs::Parser::ParsedTopics> being built by the file.
# lastCommentTopicCount - The number of Natural Docs topics that were created by the last comment.
#
sub OnCode #(codeLines, codeLineNumber, topicList, lastCommentTopicCount)
{
my ($self, $codeLines, $codeLineNumber, $topicList, $lastCommentTopicCount) = @_;
if ($lastCommentTopicCount && defined $self->PrototypeEndersFor($topicList->[-1]->Type()))
{
my $lineIndex = 0;
my $prototype;
# Skip all blank lines before a prototype.
while ($lineIndex < scalar @$codeLines && $codeLines->[$lineIndex] =~ /^[ \t]*$/)
{ $lineIndex++; };
my @tokens;
my $tokenIndex = 0;
my @brackets;
my $enders = $self->PrototypeEndersFor($topicList->[-1]->Type());
# Add prototype lines until we reach the end of the prototype or the end of the code lines.
while ($lineIndex < scalar @$codeLines)
{
my $line = $self->RemoveLineExtender($codeLines->[$lineIndex] . "\n");
push @tokens, $line =~ /([^\(\)\[\]\{\}\<\>]+|.)/g;
while ($tokenIndex < scalar @tokens)
{
# If we're not inside brackets, check for ender symbols.
if (!scalar @brackets)
{
my $startingIndex = 0;
my $testPrototype;
for (;;)
{
my ($enderIndex, $ender) = ::FindFirstSymbol($tokens[$tokenIndex], $enders, $startingIndex);
if ($enderIndex == -1)
{ last; }
else
{
# We do this here so we don't duplicate prototype for every single token. Just the first time an ender symbol
# is found in one.
if (!defined $testPrototype)
{ $testPrototype = $prototype; };
$testPrototype .= substr($tokens[$tokenIndex], $startingIndex, $enderIndex - $startingIndex);
my $enderResult;
# If the ender is all text and the character preceding or following it is as well, ignore it.
if ($ender =~ /^[a-z0-9]+$/i &&
( ($enderIndex > 0 && substr($tokens[$tokenIndex], $enderIndex - 1, 1) =~ /^[a-z0-9_]$/i) ||
substr($tokens[$tokenIndex], $enderIndex + length($ender), 1) =~ /^[a-z0-9_]$/i ) )
{ $enderResult = ENDER_IGNORE(); }
else
{ $enderResult = $self->OnPrototypeEnd($topicList->[-1]->Type(), \$testPrototype, $ender); }
if ($enderResult == ENDER_IGNORE())
{
$testPrototype .= $ender;
$startingIndex = $enderIndex + length($ender);
}
elsif ($enderResult == ENDER_REVERT_TO_ACCEPTED())
{
return;
}
else # ENDER_ACCEPT || ENDER_ACCEPT_AND_CONTINUE
{
my $titleInPrototype = $topicList->[-1]->Title();
# Strip parenthesis so Function(2) and Function(int, int) will still match Function(anything).
$titleInPrototype =~ s/[\t ]*\([^\(]*$//;
if (index($testPrototype, $titleInPrototype) != -1)
{
$topicList->[-1]->SetPrototype( $self->NormalizePrototype($testPrototype) );
};
if ($enderResult == ENDER_ACCEPT())
{ return; }
else # ENDER_ACCEPT_AND_CONTINUE
{
$testPrototype .= $ender;
$startingIndex = $enderIndex + length($ender);
};
};
};
};
}
# If we are inside brackets, check for closing symbols.
elsif ( ($tokens[$tokenIndex] eq ')' && $brackets[-1] eq '(') ||
($tokens[$tokenIndex] eq ']' && $brackets[-1] eq '[') ||
($tokens[$tokenIndex] eq '}' && $brackets[-1] eq '{') ||
($tokens[$tokenIndex] eq '>' && $brackets[-1] eq '<') )
{
pop @brackets;
};
# Check for opening brackets.
if ($tokens[$tokenIndex] =~ /^[\(\[\{]$/ ||
($tokens[$tokenIndex] eq "<" && $tokens[$tokenIndex-1] !~ /operator[ \t]*$/) )
{
push @brackets, $tokens[$tokenIndex];
};
$prototype .= $tokens[$tokenIndex];
$tokenIndex++;
};
$lineIndex++;
};
# If we got out of that while loop by running out of lines, there was no prototype.
};
};
use constant ENDER_ACCEPT => 1;
use constant ENDER_IGNORE => 2;
use constant ENDER_ACCEPT_AND_CONTINUE => 3;
use constant ENDER_REVERT_TO_ACCEPTED => 4;
#
# Function: OnPrototypeEnd
#
# Called whenever the end of a prototype is found so that there's a chance for derived classes to mark false positives.
#
# Parameters:
#
# type - The <TopicType> of the prototype.
# prototypeRef - A reference to the prototype so far, minus the ender in dispute.
# ender - The ender symbol.
#
# Returns:
#
# ENDER_ACCEPT - The ender is accepted and the prototype is finished.
# ENDER_IGNORE - The ender is rejected and parsing should continue. Note that the prototype will be rejected as a whole
# if all enders are ignored before reaching the end of the code.
# ENDER_ACCEPT_AND_CONTINUE - The ender is accepted so the prototype may stand as is. However, the prototype might
# also continue on so continue parsing. If there is no accepted ender between here and
# the end of the code this version will be accepted instead.
# ENDER_REVERT_TO_ACCEPTED - The expedition from ENDER_ACCEPT_AND_CONTINUE failed. Use the last accepted
# version and end parsing.
#
sub OnPrototypeEnd #(type, prototypeRef, ender)
{
return ENDER_ACCEPT();
};
#
# Function: RemoveLineExtender
#
# If the passed line has a line extender, returns it without the extender or the line break that follows. If it doesn't, or there are
# no line extenders defined, returns the passed line unchanged.
#
sub RemoveLineExtender #(line)
{
my ($self, $line) = @_;
if (defined $self->LineExtender())
{
my $lineExtenderIndex = rindex($line, $self->LineExtender());
if ($lineExtenderIndex != -1 &&
substr($line, $lineExtenderIndex + length($self->LineExtender())) =~ /^[ \t]*\n$/)
{
$line = substr($line, 0, $lineExtenderIndex) . ' ';
};
};
return $line;
};
1;

View File

@ -0,0 +1,220 @@
###############################################################################
#
# Class: NaturalDocs::Languages::Tcl
#
###############################################################################
#
# A subclass to handle the language variations of Tcl.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::Languages::Tcl;
use base 'NaturalDocs::Languages::Simple';
#
# bool: pastFirstBrace
#
# Whether we've past the first brace in a function prototype or not.
#
my $pastFirstBrace;
#
# Function: OnCode
#
# This is just overridden to reset <pastFirstBrace>.
#
sub OnCode #(...)
{
my ($self, @params) = @_;
$pastFirstBrace = 0;
return $self->SUPER::OnCode(@params);
};
#
# Function: OnPrototypeEnd
#
# Tcl's function syntax is shown below.
#
# > proc [name] { [params] } { [code] }
#
# The opening brace is one of the prototype enders. We need to allow the first opening brace because it contains the
# parameters.
#
# Also, the parameters may have braces within them. I've seen one that used { seconds 20 } as a parameter.
#
# Parameters:
#
# type - The <TopicType> of the prototype.
# prototypeRef - A reference to the prototype so far, minus the ender in dispute.
# ender - The ender symbol.
#
# Returns:
#
# ENDER_ACCEPT - The ender is accepted and the prototype is finished.
# ENDER_IGNORE - The ender is rejected and parsing should continue. Note that the prototype will be rejected as a whole
# if all enders are ignored before reaching the end of the code.
# ENDER_ACCEPT_AND_CONTINUE - The ender is accepted so the prototype may stand as is. However, the prototype might
# also continue on so continue parsing. If there is no accepted ender between here and
# the end of the code this version will be accepted instead.
# ENDER_REVERT_TO_ACCEPTED - The expedition from ENDER_ACCEPT_AND_CONTINUE failed. Use the last accepted
# version and end parsing.
#
sub OnPrototypeEnd #(type, prototypeRef, ender)
{
my ($self, $type, $prototypeRef, $ender) = @_;
if ($type eq ::TOPIC_FUNCTION() && $ender eq '{' && !$pastFirstBrace)
{
$pastFirstBrace = 1;
return ::ENDER_IGNORE();
}
else
{ return ::ENDER_ACCEPT(); };
};
#
# Function: ParsePrototype
#
# Parses the prototype and returns it as a <NaturalDocs::Languages::Prototype> object.
#
# Parameters:
#
# type - The <TopicType>.
# prototype - The text prototype.
#
# Returns:
#
# A <NaturalDocs::Languages::Prototype> object.
#
sub ParsePrototype #(type, prototype)
{
my ($self, $type, $prototype) = @_;
if ($type ne ::TOPIC_FUNCTION())
{
my $object = NaturalDocs::Languages::Prototype->New($prototype);
return $object;
};
# Parse the parameters out of the prototype.
my @tokens = $prototype =~ /([^\{\}\ ]+|.)/g;
my $parameter;
my @parameterLines;
my $braceLevel = 0;
my ($beforeParameters, $afterParameters, $finishedParameters);
foreach my $token (@tokens)
{
if ($finishedParameters)
{ $afterParameters .= $token; }
elsif ($token eq '{')
{
if ($braceLevel == 0)
{ $beforeParameters .= $token; }
else # braceLevel > 0
{ $parameter .= $token; };
$braceLevel++;
}
elsif ($token eq '}')
{
if ($braceLevel == 1)
{
if ($parameter && $parameter ne ' ')
{ push @parameterLines, $parameter; };
$finishedParameters = 1;
$afterParameters .= $token;
$braceLevel--;
}
elsif ($braceLevel > 1)
{
$parameter .= $token;
$braceLevel--;
};
}
elsif ($token eq ' ')
{
if ($braceLevel == 1)
{
if ($parameter)
{ push @parameterLines, $parameter; };
$parameter = undef;
}
elsif ($braceLevel > 1)
{
$parameter .= $token;
}
else
{
$beforeParameters .= $token;
};
}
else
{
if ($braceLevel > 0)
{ $parameter .= $token; }
else
{ $beforeParameters .= $token; };
};
};
foreach my $part (\$beforeParameters, \$afterParameters)
{
$$part =~ s/^ //;
$$part =~ s/ $//;
};
my $prototypeObject = NaturalDocs::Languages::Prototype->New($beforeParameters, $afterParameters);
# Parse the actual parameters.
foreach my $parameterLine (@parameterLines)
{
$prototypeObject->AddParameter( $self->ParseParameterLine($parameterLine) );
};
return $prototypeObject;
};
#
# Function: ParseParameterLine
#
# Parses a prototype parameter line and returns it as a <NaturalDocs::Languages::Prototype::Parameter> object.
#
sub ParseParameterLine #(line)
{
my ($self, $line) = @_;
return NaturalDocs::Languages::Prototype::Parameter->New(undef, undef, $line, undef, undef, undef);
};
1;

View File

@ -0,0 +1,192 @@
###############################################################################
#
# Class: NaturalDocs::LineReader
#
###############################################################################
#
# An object to handle reading text files line by line in a cross platform manner. Using this class instead of the standard
# angle brackets approach has the following benefits:
#
# - It strips all three types of line breaks automatically: CR/LF (Windows) LF (Unix) and CR (Classic Mac). You do not need to
# call chomp(). Perl's chomp() fails when parsing Windows-format line breaks on a Unix platform anyway. It leaves the /r on,
# which screws everything up.
# - It reads Classic Mac files line by line correctly, whereas the Perl version returns it all as one line.
# - It abstracts away ignoring the Unicode BOM on the first line, if present.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
use Encode;
package NaturalDocs::LineReader;
#
# Constants: Members
#
# LINEREADER_FILEHANDLE - The file handle being used to read the file. Has the LINEREADER_ prefix to make sure it doesn't
# conflict with any actual filehandles named FILEHANDLE in the program.
# CACHED_LINES - An arrayref of lines already read into memory.
#
use NaturalDocs::DefineMembers 'LINEREADER_FILEHANDLE',
'CACHED_LINES';
#
# Function: New
#
# Creates and returns a new object.
#
# Parameters:
#
# filehandle - The file handle being used to read the file.
#
sub New #(filehandle)
{
my ($selfPackage, $filehandle) = @_;
my $object = [ ];
$object->[LINEREADER_FILEHANDLE] = $filehandle;
$object->[CACHED_LINES] = [ ];
binmode($filehandle, ':raw');
my $hasBOM = 0;
my $possibleBOM = undef;
read($filehandle, $possibleBOM, 2);
if ($possibleBOM eq "\xEF\xBB")
{
read($filehandle, $possibleBOM, 1);
if ($possibleBOM eq "\xBF")
{
binmode($filehandle, ':crlf:encoding(UTF-8)'); # Strict UTF-8, not Perl's lax version.
$hasBOM = 1;
}
}
elsif ($possibleBOM eq "\xFE\xFF")
{
binmode($filehandle, ':crlf:encoding(UTF-16BE)');
$hasBOM = 1;
}
elsif ($possibleBOM eq "\xFF\xFE")
{
binmode($filehandle, ':crlf:encoding(UTF-16LE)');
$hasBOM = 1;
}
if (!$hasBOM)
{
seek($filehandle, 0, 0);
my $rawData = undef;
my $readLength = -s $filehandle;
# Since we're only reading the data to determine if it's UTF-8, sanity check the file length. We may run
# across a huge extensionless system file and we don't want to load the whole thing. Half a meg should
# be good enough to encompass giant source files while not bogging things down on system files.
if ($readLength > 512 * 1024)
{ $readLength = 512 * 1024; }
read($filehandle, $rawData, $readLength);
eval
{ $rawData = Encode::decode("UTF-8", $rawData, Encode::FB_CROAK); };
if ($::EVAL_ERROR)
{ binmode($filehandle, ':crlf'); }
else
{
# Theoretically, since this is valid UTF-8 data we should be able to split it on line breaks and feed them into
# CACHED_LINES instead of setting the encoding to UTF-8 and seeking back to zero just to read it all again.
# Alas, this doesn't work for an easily identifiable reason. I'm sure there is one, but I couldn't figure it out
# before my patience ran out so I'm just letting the file cache absorb the hit instead. If we were ever to do
# this in the future you'd have to handle the file length capping code above too.
binmode($filehandle, ':crlf:encoding(UTF-8)');
}
seek($filehandle, 0, 0);
}
bless $object, $selfPackage;
return $object;
};
#
# Function: Chomp
#
# Removes any line breaks from the end of a value. It does not remove any that are in the middle of it.
#
# Parameters:
#
# lineRef - A *reference* to the line to chomp.
#
sub Chomp #(lineRef)
{
my ($self, $lineRef) = @_;
$$lineRef =~ s/(?:\r\n|\r|\n)$//;
};
#
# Function: Get
#
# Returns the next line of text from the file, or undef if there are no more. The line break will be removed automatically. If
# the first line contains a Unicode BOM, that will also be removed automatically.
#
sub Get
{
my $self = shift;
my $line = undef;
if (scalar @{$self->[CACHED_LINES]} == 0)
{
my $filehandle = $self->[LINEREADER_FILEHANDLE];
my $rawLine = <$filehandle>;
if (!defined $rawLine)
{ return undef; }
$self->Chomp(\$rawLine);
if ($rawLine =~ /\r/)
{
push @{$self->[CACHED_LINES]}, split(/\r/, $rawLine); # Split for Classic Mac
$line = shift @{$self->[CACHED_LINES]};
}
else
{ $line = $rawLine; }
}
else
{ $line = shift @{$self->[CACHED_LINES]}; }
return $line;
}
#
# Function: GetAll
#
# Returns an array of all the lines from the file. The line breaks will be removed automatically. If the first line contains a
# Unicode BOM, that will also be removed automatically.
#
sub GetAll
{
my $self = shift;
my $filehandle = $self->[LINEREADER_FILEHANDLE];
my $rawContent;
read($filehandle, $rawContent, -s $filehandle);
return split(/\r\n|\n|\r/, $rawContent);
}
1;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,202 @@
###############################################################################
#
# Package: NaturalDocs::Menu::Entry
#
###############################################################################
#
# A class representing an entry in the menu.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::Menu::Entry;
###############################################################################
# Group: Implementation
#
# Constants: Members
#
# The object is implemented as a blessed arrayref with the indexes below.
#
# TYPE - The <MenuEntryType>
# TITLE - The title of the entry.
# TARGET - The target of the entry. If the type is <MENU_FILE>, it will be the source <FileName>. If the type is
# <MENU_LINK>, it will be the URL. If the type is <MENU_GROUP>, it will be an arrayref of
# <NaturalDocs::Menu::Entry> objects representing the group's content. If the type is <MENU_INDEX>, it will be
# a <TopicType>.
# FLAGS - Any <Menu Entry Flags> that apply.
#
use constant TYPE => 0;
use constant TITLE => 1;
use constant TARGET => 2;
use constant FLAGS => 3;
# DEPENDENCY: New() depends on the order of these constants.
###############################################################################
# Group: Functions
#
# Function: New
#
# Creates and returns a new object.
#
# Parameters:
#
# type - The <MenuEntryType>.
# title - The title of the entry.
# target - The target of the entry, if applicable. If the type is <MENU_FILE>, use the source <FileName>. If the type is
# <MENU_LINK>, use the URL. If the type is <MENU_INDEX>, use the <TopicType>. Otherwise set it to undef.
# flags - Any <Menu Entry Flags> that apply.
#
sub New #(type, title, target, flags)
{
# DEPENDENCY: This gode depends on the order of the constants.
my $package = shift;
my $object = [ @_ ];
bless $object, $package;
if ($object->[TYPE] == ::MENU_GROUP())
{ $object->[TARGET] = [ ]; };
if (!defined $object->[FLAGS])
{ $object->[FLAGS] = 0; };
return $object;
};
# Function: Type
# Returns the <MenuEntryType>.
sub Type
{ return $_[0]->[TYPE]; };
# Function: Title
# Returns the title of the entry.
sub Title
{ return $_[0]->[TITLE]; };
# Function: SetTitle
# Replaces the entry's title.
sub SetTitle #(title)
{ $_[0]->[TITLE] = $_[1]; };
#
# Function: Target
#
# Returns the target of the entry, if applicable. If the type is <MENU_FILE>, it returns the source <FileName>. If the type is
# <MENU_LINK>, it returns the URL. If the type is <MENU_INDEX>, it returns the <TopicType>. Otherwise it returns undef.
#
sub Target
{
my $self = shift;
# Group entries are the only time when target won't be undef when it should be.
if ($self->Type() == ::MENU_GROUP())
{ return undef; }
else
{ return $self->[TARGET]; };
};
# Function: SetTarget
# Replaces the entry's target.
sub SetTarget #(target)
{ $_[0]->[TARGET] = $_[1]; };
# Function: Flags
# Returns the <Menu Entry Flags>.
sub Flags
{ return $_[0]->[FLAGS]; };
# Function: SetFlags
# Replaces the <Menu Entry Flags>.
sub SetFlags #(flags)
{ $_[0]->[FLAGS] = $_[1]; };
###############################################################################
# Group: Group Functions
#
# All of these functions assume the type is <MENU_GROUP>. Do *not* call any of these without checking <Type()> first.
#
# Function: GroupContent
#
# Returns an arrayref of <NaturalDocs::Menu::Entry> objects representing the contents of the
# group, or undef otherwise. This arrayref will always exist for <MENU_GROUP>'s and can be changed.
#
sub GroupContent
{
return $_[0]->[TARGET];
};
#
# Function: GroupIsEmpty
#
# If the type is <MENU_GROUP>, returns whether the group is empty.
#
sub GroupIsEmpty
{
my $self = shift;
return (scalar @{$self->GroupContent()} > 0);
};
#
# Function: PushToGroup
#
# Pushes the entry to the end of the group content.
#
sub PushToGroup #(entry)
{
my ($self, $entry) = @_;
push @{$self->GroupContent()}, $entry;
};
#
# Function: DeleteFromGroup
#
# Deletes an entry from the group content by index.
#
sub DeleteFromGroup #(index)
{
my ($self, $index) = @_;
my $groupContent = $self->GroupContent();
splice( @$groupContent, $index, 1 );
};
#
# Function: MarkEndOfOriginal
#
# If the group doesn't already have one, adds a <MENU_ENDOFORIGINAL> entry to the end and sets the
# <MENU_GROUP_HASENDOFORIGINAL> flag.
#
sub MarkEndOfOriginal
{
my $self = shift;
if (($self->Flags() & ::MENU_GROUP_HASENDOFORIGINAL()) == 0)
{
$self->PushToGroup( NaturalDocs::Menu::Entry->New(::MENU_ENDOFORIGINAL(), undef, undef, undef) );
$self->SetFlags( $self->Flags() | ::MENU_GROUP_HASENDOFORIGINAL() );
};
};
1;

View File

@ -0,0 +1,77 @@
###############################################################################
#
# Package: NaturalDocs::NDMarkup
#
###############################################################################
#
# A package of support functions for dealing with <NDMarkup>.
#
# Usage and Dependencies:
#
# The package doesn't depend on any Natural Docs packages and is ready to use right away.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::NDMarkup;
#
# Function: ConvertAmpChars
#
# Substitutes certain characters with their <NDMarkup> amp chars.
#
# Parameters:
#
# text - The block of text to convert.
#
# Returns:
#
# The converted text block.
#
sub ConvertAmpChars #(text)
{
my ($self, $text) = @_;
$text =~ s/&/&amp;/g;
$text =~ s/</&lt;/g;
$text =~ s/>/&gt;/g;
$text =~ s/\"/&quot;/g;
return $text;
};
#
# Function: RestoreAmpChars
#
# Replaces <NDMarkup> amp chars with their original symbols.
#
# Parameters:
#
# text - The text to restore.
#
# Returns:
#
# The restored text.
#
sub RestoreAmpChars #(text)
{
my ($self, $text) = @_;
$text =~ s/&quot;/\"/g;
$text =~ s/&gt;/>/g;
$text =~ s/&lt;/</g;
$text =~ s/&amp;/&/g;
return $text;
};
1;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,465 @@
###############################################################################
#
# Package: NaturalDocs::Parser::JavaDoc
#
###############################################################################
#
# A package for translating JavaDoc topics into Natural Docs.
#
# Supported tags:
#
# - @param
# - @author
# - @deprecated
# - @code, @literal (doesn't change font)
# - @exception, @throws (doesn't link to class)
# - @link, @linkplain (doesn't change font)
# - @return, @returns
# - @see
# - @since
# - @value (shown as link instead of replacement)
# - @version
#
# Stripped tags:
#
# - @inheritDoc
# - @serial, @serialField, @serialData
# - All other block level tags.
#
# Unsupported tags:
#
# These will appear literally in the output because I cannot handle them easily.
#
# - @docRoot
# - Any other tags not mentioned
#
# Supported HTML:
#
# - p
# - b, i, u
# - pre
# - a href
# - ol, ul, li (ol gets converted to ul)
# - gt, lt, amp, quot, nbsp entities
#
# Stripped HTML:
#
# - code
# - HTML comments
#
# Unsupported HTML:
#
# These will appear literally in the output because I cannot handle them easily.
#
# - Any tags with additional properties other than a href. (ex. <p class=Something>)
# - Any other tags not mentioned
#
# Reference:
#
# http://java.sun.com/j2se/1.5.0/docs/tooldocs/windows/javadoc.html
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::Parser::JavaDoc;
#
# hash: blockTags
# An existence hash of the all-lowercase JavaDoc block tags, not including the @.
#
my %blockTags = ( 'param' => 1, 'author' => 1, 'deprecated' => 1, 'exception' => 1, 'return' => 1, 'see' => 1,
'serial' => 1, 'serialfield' => 1, 'serialdata' => 1, 'since' => 1, 'throws' => 1, 'version' => 1,
'returns' => 1 );
#
# hash: inlineTags
# An existence hash of the all-lowercase JavaDoc inline tags, not including the @.
#
my %inlineTags = ( 'inheritdoc' => 1, 'docroot' => 1, 'code' => 1, 'literal' => 1, 'link' => 1, 'linkplain' => 1, 'value' => 1 );
##
# Examines the comment and returns whether it is *definitely* JavaDoc content, i.e. is owned by this package.
#
# Parameters:
#
# commentLines - An arrayref of the comment lines. Must have been run through <NaturalDocs::Parser->CleanComment()>.
# isJavaDoc - Whether the comment is JavaDoc styled. This doesn't necessarily mean it has JavaDoc content.
#
# Returns:
#
# Whether the comment is *definitely* JavaDoc content.
#
sub IsMine #(string[] commentLines, bool isJavaDoc)
{
my ($self, $commentLines, $isJavaDoc) = @_;
if (!$isJavaDoc)
{ return undef; };
for (my $line = 0; $line < scalar @$commentLines; $line++)
{
if ($commentLines->[$line] =~ /^ *@([a-z]+) /i && exists $blockTags{$1} ||
$commentLines->[$line] =~ /\{@([a-z]+) /i && exists $inlineTags{$1})
{
return 1;
};
};
return 0;
};
##
# Parses the JavaDoc-syntax comment and adds it to the parsed topic list.
#
# Parameters:
#
# commentLines - An arrayref of the comment lines. Must have been run through <NaturalDocs::Parser->CleanComment()>.
# *The original memory will be changed.*
# isJavaDoc - Whether the comment is JavaDoc styled. This doesn't necessarily mean it has JavaDoc content.
# lineNumber - The line number of the first of the comment lines.
# parsedTopics - A reference to the array where any new <NaturalDocs::Parser::ParsedTopics> should be placed.
#
# Returns:
#
# The number of parsed topics added to the array, which in this case will always be one.
#
sub ParseComment #(string[] commentLines, bool isJavaDoc, int lineNumber, ParsedTopics[]* parsedTopics)
{
my ($self, $commentLines, $isJavaDoc, $lineNumber, $parsedTopics) = @_;
# Stage one: Before block level tags.
my $i = 0;
my $output;
my $unformattedText;
my $inCode;
my $sharedCodeIndent;
while ($i < scalar @$commentLines &&
!($commentLines->[$i] =~ /^ *@([a-z]+) /i && exists $blockTags{$1}) )
{
my $line = $self->ConvertAmpChars($commentLines->[$i]);
my @tokens = split(/(&lt;\/?pre&gt;)/, $line);
foreach my $token (@tokens)
{
if ($token =~ /^&lt;pre&gt;$/i)
{
if (!$inCode && $unformattedText)
{
$output .= '<p>' . $self->FormatText($unformattedText, 1) . '</p>';
};
$inCode = 1;
$unformattedText = undef;
}
elsif ($token =~ /^&lt;\/pre&gt;$/i)
{
if ($inCode && $unformattedText)
{
$unformattedText =~ s/^ {$sharedCodeIndent}//mg;
$unformattedText =~ s/\n{3,}/\n\n/g;
$unformattedText =~ s/\n+$//;
$output .= '<code type="anonymous">' . $unformattedText . '</code>';
$sharedCodeIndent = undef;
};
$inCode = 0;
$unformattedText = undef;
}
elsif (length($token))
{
if (!$inCode)
{
$token =~ s/^ +//;
if ($unformattedText)
{ $unformattedText .= ' '; };
}
else
{
$token =~ /^( *)/;
my $indent = length($1);
if (!defined $sharedCodeIndent || $indent < $sharedCodeIndent)
{ $sharedCodeIndent = $indent; };
};
$unformattedText .= $token;
};
};
if ($inCode && $unformattedText)
{ $unformattedText .= "\n"; };
$i++;
};
if ($unformattedText)
{
if ($inCode)
{
$unformattedText =~ s/^ {$sharedCodeIndent}//mg;
$unformattedText =~ s/\n{3,}/\n\n/g;
$unformattedText =~ s/\n+$//;
$output .= '<code type="anonymous">' . $unformattedText . '</code>';
}
else
{ $output .= '<p>' . $self->FormatText($unformattedText, 1) . '</p>'; };
$unformattedText = undef;
};
# Stage two: Block level tags.
my ($keyword, $value, $unformattedTextPtr, $unformattedTextCloser);
my ($params, $authors, $deprecation, $throws, $returns, $seeAlso, $since, $version);
while ($i < scalar @$commentLines)
{
my $line = $self->ConvertAmpChars($commentLines->[$i]);
$line =~ s/^ +//;
if ($line =~ /^@([a-z]+) ?(.*)$/i)
{
($keyword, $value) = (lc($1), $2);
# Process the previous one, if any.
if ($unformattedText)
{
$$unformattedTextPtr .= $self->FormatText($unformattedText) . $unformattedTextCloser;
$unformattedText = undef;
};
if ($keyword eq 'param')
{
$value =~ /^([a-z0-9_]+) *(.*)$/i;
$params .= '<de>' . $1 . '</de><dd>';
$unformattedText = $2;
$unformattedTextPtr = \$params;
$unformattedTextCloser = '</dd>';
}
elsif ($keyword eq 'exception' || $keyword eq 'throws')
{
$value =~ /^([a-z0-9_]+) *(.*)$/i;
$throws .= '<de>' . $1 . '</de><dd>';
$unformattedText = $2;
$unformattedTextPtr = \$throws;
$unformattedTextCloser = '</dd>';
}
elsif ($keyword eq 'return' || $keyword eq 'returns')
{
if ($returns)
{ $returns .= ' '; };
$unformattedText = $value;
$unformattedTextPtr = \$returns;
$unformattedTextCloser = undef;
}
elsif ($keyword eq 'author')
{
if ($authors)
{ $authors .= ', '; };
$unformattedText = $value;
$unformattedTextPtr = \$authors;
$unformattedTextCloser = undef;
}
elsif ($keyword eq 'deprecated')
{
if ($deprecation)
{ $deprecation .= ' '; };
$unformattedText = $value;
$unformattedTextPtr = \$deprecation;
$unformattedTextCloser = undef;
}
elsif ($keyword eq 'since')
{
if ($since)
{ $since .= ', '; };
$unformattedText = $value;
$unformattedTextPtr = \$since;
$unformattedTextCloser = undef;
}
elsif ($keyword eq 'version')
{
if ($version)
{ $version .= ', '; };
$unformattedText = $value;
$unformattedTextPtr = \$version;
$unformattedTextCloser = undef;
}
elsif ($keyword eq 'see')
{
if ($seeAlso)
{ $seeAlso .= ', '; };
$unformattedText = undef;
if ($value =~ /^&(?:quot|lt);/i)
{ $seeAlso .= $self->FormatText($value); }
else
{ $seeAlso .= $self->ConvertLink($value); };
};
# Everything else will be skipped.
}
elsif ($unformattedText)
{
$unformattedText .= ' ' . $line;
};
$i++;
};
if ($unformattedText)
{
$$unformattedTextPtr .= $self->FormatText($unformattedText) . $unformattedTextCloser;
$unformattedText = undef;
};
if ($params)
{ $output .= '<h>Parameters</h><dl>' . $params . '</dl>'; };
if ($returns)
{ $output .= '<h>Returns</h><p>' . $returns . '</p>'; };
if ($throws)
{ $output .= '<h>Throws</h><dl>' . $throws . '</dl>'; };
if ($since)
{ $output .= '<h>Since</h><p>' . $since . '</p>'; };
if ($version)
{ $output .= '<h>Version</h><p>' . $version . '</p>'; };
if ($deprecation)
{ $output .= '<h>Deprecated</h><p>' . $deprecation . '</p>'; };
if ($authors)
{ $output .= '<h>Author</h><p>' . $authors . '</p>'; };
if ($seeAlso)
{ $output .= '<h>See Also</h><p>' . $seeAlso . '</p>'; };
# Stage three: Build the parsed topic.
my $summary = NaturalDocs::Parser->GetSummaryFromBody($output);
push @$parsedTopics, NaturalDocs::Parser::ParsedTopic->New(undef, undef, undef, undef, undef, $summary,
$output, $lineNumber, undef);
return 1;
};
##
# Translates any inline tags or HTML codes to <NDMarkup> and returns it.
#
sub FormatText #(string text, bool inParagraph)
{
my ($self, $text, $inParagraph) = @_;
# JavaDoc Literal
$text =~ s/\{\@(?:code|literal) ([^\}]*)\}/$self->ConvertAmpChars($1)/gie;
# HTML
$text =~ s/&lt;b&gt;(.*?)&lt;\/b&gt;/<b>$1<\/b>/gi;
$text =~ s/&lt;i&gt;(.*?)&lt;\/i&gt;/<i>$1<\/i>/gi;
$text =~ s/&lt;u&gt;(.*?)&lt;\/u&gt;/<u>$1<\/u>/gi;
$text =~ s/&lt;code&gt;(.*?)&lt;\/code&gt;/$1/gi;
$text =~ s/&lt;ul.*?&gt;(.*?)&lt;\/ul&gt;/<ul>$1<\/ul>/gi;
$text =~ s/&lt;ol.*?&gt;(.*?)&lt;\/ol&gt;/<ul>$1<\/ul>/gi;
$text =~ s/&lt;li.*?&gt;(.*?)&lt;\/li&gt;/<li>$1<\/li>/gi;
$text =~ s/&lt;!--.*?--&gt;//gi;
$text =~ s/&lt;\/p&gt;//gi;
$text =~ s/^&lt;p&gt;//i;
if ($inParagraph)
{ $text =~ s/&lt;p&gt;/<\/p><p>/gi; }
else
{ $text =~ s/&lt;p&gt;//gi; };
$text =~ s/&lt;a href=&quot;mailto:(.*?)&quot;.*?&gt;(.*?)&lt;\/a&gt;/$self->MakeEMailLink($1, $2)/gie;
$text =~ s/&lt;a href=&quot;(.*?)&quot;.*?&gt;(.*?)&lt;\/a&gt;/$self->MakeURLLink($1, $2)/gie;
$text =~ s/&amp;nbsp;/ /gi;
$text =~ s/&amp;amp;/&amp;/gi;
$text =~ s/&amp;gt;/&gt;/gi;
$text =~ s/&amp;lt;/&lt;/gi;
$text =~ s/&amp;quot;/&quot;/gi;
# JavaDoc
$text =~ s/\{\@inheritdoc\}//gi;
$text =~ s/\{\@(?:linkplain|link|value) ([^\}]*)\}/$self->ConvertLink($1)/gie;
return $text;
};
sub ConvertAmpChars #(text)
{
my ($self, $text) = @_;
$text =~ s/&/&amp;/g;
$text =~ s/</&lt;/g;
$text =~ s/>/&gt;/g;
$text =~ s/"/&quot;/g;
return $text;
};
sub ConvertLink #(text)
{
my ($self, $text) = @_;
$text =~ /^ *([a-z0-9\_\.\:\#]+(?:\([^\)]*\))?) *(.*)$/i;
my ($target, $label) = ($1, $2);
# Convert the anchor to part of the link, but remove it altogether if it's the beginning of the link.
$target =~ s/^\#//;
$target =~ s/\#/\./;
$label =~ s/ +$//;
if (!length $label)
{ return '<link target="' . $target . '" name="' . $target . '" original="' . $target . '">'; }
else
{ return '<link target="' . $target . '" name="' . $label . '" original="' . $label . ' (' . $target . ')">'; };
};
sub MakeURLLink #(target, text)
{
my ($self, $target, $text) = @_;
return '<url target="' . $target . '" name="' . $text . '">';
};
sub MakeEMailLink #(target, text)
{
my ($self, $target, $text) = @_;
return '<email target="' . $target . '" name="' . $text . '">';
};
1;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,254 @@
###############################################################################
#
# Package: NaturalDocs::Parser::ParsedTopic
#
###############################################################################
#
# A class for parsed topics of source files. Also encompasses some of the <TopicType>-specific behavior.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::Parser::ParsedTopic;
###############################################################################
# Group: Implementation
#
# Constants: Members
#
# The object is a blessed arrayref with the following indexes.
#
# TYPE - The <TopicType>.
# TITLE - The title of the topic.
# PACKAGE - The package <SymbolString> the topic appears in, or undef if none.
# USING - An arrayref of additional package <SymbolStrings> available to the topic via "using" statements, or undef if
# none.
# PROTOTYPE - The prototype, if it exists and is applicable.
# SUMMARY - The summary, if it exists.
# BODY - The body of the topic, formatted in <NDMarkup>. Some topics may not have bodies, and if not, this
# will be undef.
# LINE_NUMBER - The line number the topic appears at in the file.
# IS_LIST - Whether the topic is a list.
#
use NaturalDocs::DefineMembers 'TYPE', 'TITLE', 'PACKAGE', 'USING', 'PROTOTYPE', 'SUMMARY', 'BODY',
'LINE_NUMBER', 'IS_LIST';
# DEPENDENCY: New() depends on the order of these constants, and that this class is not inheriting any members.
#
# Architecture: Title, Package, and Symbol Behavior
#
# Title, package, and symbol behavior is a little awkward so it deserves some explanation. Basically you set them according to
# certain rules, but you get computed values that try to hide all the different scoping situations.
#
# Normal Topics:
#
# Set them to the title and package as they appear. "Function" and "PkgA.PkgB" will return "Function" for the title,
# "PkgA.PkgB" for the package, and "PkgA.PkgB.Function" for the symbol.
#
# In the rare case that a title has a separator symbol it's treated as inadvertant, so "A vs. B" in "PkgA.PkgB" still returns just
# "PkgA.PkgB" for the package even though if you got it from the symbol it can be seen as "PkgA.PkgB.A vs".
#
# Scope Topics:
#
# Set the title normally and leave the package undef. So "PkgA.PkgB" and undef will return "PkgA.PkgB" for the title as well
# as for the package and symbol.
#
# The only time you should set the package is when you have full language support and they only documented the class with
# a partial title. So if you documented "PkgA.PkgB" with just "PkgB", you want to set the package to "PkgA". This
# will return "PkgB" as the title for presentation and will return "PkgA.PkgB" for the package and symbol, which is correct.
#
# Always Global Topics:
#
# Set the title and package normally, do not set the package to undef. So "Global" and "PkgA.PkgB" will return "Global" as
# the title, "PkgA.PkgB" as the package, and "Global" as the symbol.
#
# Um, yeah...:
#
# So does this suck? Yes, yes it does. But the suckiness is centralized here instead of having to be handled everywhere these
# issues come into play. Just realize there are a certain set of rules to follow when you *set* these variables, and the results
# you see when you *get* them are computed rather than literal.
#
###############################################################################
# Group: Functions
#
# Function: New
#
# Creates a new object.
#
# Parameters:
#
# type - The <TopicType>.
# title - The title of the topic.
# package - The package <SymbolString> the topic appears in, or undef if none.
# using - An arrayref of additional package <SymbolStrings> available to the topic via "using" statements, or undef if
# none.
# prototype - The prototype, if it exists and is applicable. Otherwise set to undef.
# summary - The summary of the topic, if any.
# body - The body of the topic, formatted in <NDMarkup>. May be undef, as some topics may not have bodies.
# lineNumber - The line number the topic appears at in the file.
# isList - Whether the topic is a list topic or not.
#
# Returns:
#
# The new object.
#
sub New #(type, title, package, using, prototype, summary, body, lineNumber, isList)
{
# DEPENDENCY: This depends on the order of the parameter list being the same as the constants, and that there are no
# members inherited from a base class.
my $package = shift;
my $object = [ @_ ];
bless $object, $package;
if (defined $object->[USING])
{ $object->[USING] = [ @{$object->[USING]} ]; };
return $object;
};
# Function: Type
# Returns the <TopicType>.
sub Type
{ return $_[0]->[TYPE]; };
# Function: SetType
# Replaces the <TopicType>.
sub SetType #(type)
{ $_[0]->[TYPE] = $_[1]; };
# Function: IsList
# Returns whether the topic is a list.
sub IsList
{ return $_[0]->[IS_LIST]; };
# Function: SetIsList
# Sets whether the topic is a list.
sub SetIsList
{ $_[0]->[IS_LIST] = $_[1]; };
# Function: Title
# Returns the title of the topic.
sub Title
{ return $_[0]->[TITLE]; };
# Function: SetTitle
# Replaces the topic title.
sub SetTitle #(title)
{ $_[0]->[TITLE] = $_[1]; };
#
# Function: Symbol
#
# Returns the <SymbolString> defined by the topic. It is fully resolved and does _not_ need to be joined with <Package()>.
#
# Type-Specific Behavior:
#
# - If the <TopicType> is always global, the symbol will be generated from the title only.
# - Everything else's symbols will be generated from the title and the package passed to <New()>.
#
sub Symbol
{
my ($self) = @_;
my $titleSymbol = NaturalDocs::SymbolString->FromText($self->[TITLE]);
if (NaturalDocs::Topics->TypeInfo($self->Type())->Scope() == ::SCOPE_ALWAYS_GLOBAL())
{ return $titleSymbol; }
else
{
return NaturalDocs::SymbolString->Join( $self->[PACKAGE], $titleSymbol );
};
};
#
# Function: Package
#
# Returns the package <SymbolString> that the topic appears in.
#
# Type-Specific Behavior:
#
# - If the <TopicType> has scope, the package will be generated from both the title and the package passed to <New()>, not
# just the package.
# - If the <TopicType> is always global, the package will be the one passed to <New()>, even though it isn't part of it's
# <Symbol()>.
# - Everything else's package will be what was passed to <New()>, even if the title has separator symbols in it.
#
sub Package
{
my ($self) = @_;
# Headerless topics may not have a type yet.
if ($self->Type() && NaturalDocs::Topics->TypeInfo($self->Type())->Scope() == ::SCOPE_START())
{ return $self->Symbol(); }
else
{ return $self->[PACKAGE]; };
};
# Function: SetPackage
# Replaces the package the topic appears in. This will behave the same way as the package parameter in <New()>. Later calls
# to <Package()> will still be generated according to its type-specific behavior.
sub SetPackage #(package)
{ $_[0]->[PACKAGE] = $_[1]; };
# Function: Using
# Returns an arrayref of additional scope <SymbolStrings> available to the topic via "using" statements, or undef if none.
sub Using
{ return $_[0]->[USING]; };
# Function: SetUsing
# Replaces the using arrayref of sope <SymbolStrings>.
sub SetUsing #(using)
{ $_[0]->[USING] = $_[1]; };
# Function: Prototype
# Returns the prototype if one is defined. Will be undef otherwise.
sub Prototype
{ return $_[0]->[PROTOTYPE]; };
# Function: SetPrototype
# Replaces the function or variable prototype.
sub SetPrototype #(prototype)
{ $_[0]->[PROTOTYPE] = $_[1]; };
# Function: Summary
# Returns the topic summary, if it exists, formatted in <NDMarkup>.
sub Summary
{ return $_[0]->[SUMMARY]; };
# Function: Body
# Returns the topic's body, formatted in <NDMarkup>. May be undef.
sub Body
{ return $_[0]->[BODY]; };
# Function: SetBody
# Replaces the topic's body, formatted in <NDMarkup>. May be undef.
sub SetBody #(body)
{
my ($self, $body) = @_;
$self->[BODY] = $body;
};
# Function: LineNumber
# Returns the line the topic appears at in the file.
sub LineNumber
{ return $_[0]->[LINE_NUMBER]; };
1;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,161 @@
###############################################################################
#
# Class: NaturalDocs::Project::ImageFile
#
###############################################################################
#
# A simple information class about project image files.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::Project::ImageFile;
###############################################################################
# Group: Implementation
#
# Constants: Members
#
# The class is implemented as a blessed arrayref. The following constants are used as indexes.
#
# LAST_MODIFIED - The integer timestamp of when the file was last modified.
# STATUS - <FileStatus> since the last build.
# REFERENCE_COUNT - The number of references to the image from the source files.
# WAS_USED - Whether the image was used the last time Natural Docs was run.
# WIDTH - The image width. Undef if can't be determined, -1 if haven't attempted to determine yet.
# HEIGHT - The image height. Undef if can't be determined, -1 if haven't attempted to determine yet.
#
use NaturalDocs::DefineMembers 'LAST_MODIFIED', 'LastModified()', 'SetLastModified()',
'STATUS', 'Status()', 'SetStatus()',
'REFERENCE_COUNT', 'ReferenceCount()',
'WAS_USED', 'WasUsed()', 'SetWasUsed()',
'WIDTH', 'Width()',
'HEIGHT', 'Height()';
#
# Topic: WasUsed versus References
#
# <WasUsed()> is a simple true/false that notes whether this image file was used the last time Natural Docs was run.
# <ReferenceCount()> is a counter for the number of times it's used *this* run. As such, it starts at zero regardless of whether
# <WasUsed()> is set or not.
#
###############################################################################
# Group: Functions
#
# Function: New
#
# Creates and returns a new file object.
#
# Parameters:
#
# lastModified - The image file's last modification timestamp
# status - The <FileStatus>.
# wasUsed - Whether this image file was used the *last* time Natural Docs was run.
#
sub New #(timestamp lastModified, FileStatus status, bool wasUsed)
{
my ($package, $lastModified, $status, $width, $height, $wasUsed) = @_;
my $object = [ ];
$object->[LAST_MODIFIED] = $lastModified;
$object->[STATUS] = $status;
$object->[REFERENCE_COUNT] = 0;
$object->[WAS_USED] = $wasUsed;
$object->[WIDTH] = -1;
$object->[HEIGHT] = -1;
bless $object, $package;
return $object;
};
#
# Functions: Member Functions
#
# LastModified - Returns the integer timestamp of when the file was last modified.
# SetLastModified - Sets the file's last modification timestamp.
# Status - Returns the <FileStatus> since the last build.
# SetStatus - Sets the <FileStatus> since the last build.
#
#
# Function: ReferenceCount
# Returns the current number of references to this image file during *this* Natural Docs execution.
#
#
# Function: AddReference
# Increases the number of references to this image file by one. Returns the new reference count.
#
sub AddReference
{
my $self = shift;
$self->[REFERENCE_COUNT]++;
return $self->[REFERENCE_COUNT];
};
#
# Function: DeleteReference
# Decreases the number of references to this image file by one. Returns the new reference count.
#
sub DeleteReference
{
my $self = shift;
$self->[REFERENCE_COUNT]--;
if ($self->[REFERENCE_COUNT] < 0)
{ die "Deleted more references to an image file than existed."; };
return $self->[REFERENCE_COUNT];
};
#
# Functions: Member Functions
#
# WasUsed - Returns whether this image file was used during the *last* Natural Docs execution.
# SetWasUsed - Sets whether this image file was used during the *last* Natural Docs execution.
# Width - Returns the width in pixels, undef if it can't be determined, and -1 if determination hasn't been attempted yet.
# Height - Returns the width in pixels, undef if it can't be determined, and -1 if determination hasn't been attempted yet.
#
#
# Function: SetDimensions
# Sets the width and height of the image. Set to undef if they can't be determined.
#
sub SetDimensions #(int width, int height)
{
my ($self, $width, $height) = @_;
# If either are undef, both should be undef. This will also convert zeroes to undef.
if (!$width || !$height)
{
$self->[WIDTH] = undef;
$self->[HEIGHT] = undef;
}
else
{
$self->[WIDTH] = $width;
$self->[HEIGHT] = $height;
};
};
1;

View File

@ -0,0 +1,114 @@
###############################################################################
#
# Class: NaturalDocs::Project::SourceFile
#
###############################################################################
#
# A simple information class about project files.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::Project::SourceFile;
###############################################################################
# Group: Implementation
#
# Constants: Members
#
# The class is implemented as a blessed arrayref. The following constants are used as indexes.
#
# HAS_CONTENT - Whether the file contains Natural Docs content or not.
# LAST_MODIFIED - The integer timestamp of when the file was last modified.
# STATUS - <FileStatus> since the last build.
# DEFAULT_MENU_TITLE - The file's default title in the menu.
#
# DEPENDENCY: New() depends on its parameter list being in the same order as these constants. If the order changes, New()
# needs to be changed.
use NaturalDocs::DefineMembers 'HAS_CONTENT', 'LAST_MODIFIED', 'STATUS', 'DEFAULT_MENU_TITLE';
###############################################################################
# Group: Functions
#
# Function: New
#
# Creates and returns a new file object.
#
# Parameters:
#
# hasContent - Whether the file contains Natural Docs content or not.
# lastModified - The integer timestamp of when the file was last modified.
# status - The <FileStatus> since the last build.
# defaultMenuTitle - The file's title in the menu.
#
# Returns:
#
# A reference to the new object.
#
sub New #(hasContent, lastModified, status, defaultMenuTitle)
{
# DEPENDENCY: This function depends on its parameter list being in the same order as the member constants. If either order
# changes, this function needs to be changed.
my $package = shift;
my $object = [ @_ ];
bless $object, $package;
return $object;
};
# Function: HasContent
# Returns whether the file contains Natural Docs content or not.
sub HasContent
{ return $_[0]->[HAS_CONTENT]; };
# Function: SetHasContent
# Sets whether the file contains Natural Docs content or not.
sub SetHasContent #(hasContent)
{ $_[0]->[HAS_CONTENT] = $_[1]; };
# Function: LastModified
# Returns the integer timestamp of when the file was last modified.
sub LastModified
{ return $_[0]->[LAST_MODIFIED]; };
# Function: SetLastModified
# Sets the file's last modification timestamp.
sub SetLastModified #(lastModified)
{ $_[0]->[LAST_MODIFIED] = $_[1]; };
# Function: Status
# Returns the <FileStatus> since the last build.
sub Status
{ return $_[0]->[STATUS]; };
# Function: SetStatus
# Sets the <FileStatus> since the last build.
sub SetStatus #(status)
{ $_[0]->[STATUS] = $_[1]; };
# Function: DefaultMenuTitle
# Returns the file's default title on the menu.
sub DefaultMenuTitle
{ return $_[0]->[DEFAULT_MENU_TITLE]; };
# Function: SetDefaultMenuTitle
# Sets the file's default title on the menu.
sub SetDefaultMenuTitle #(menuTitle)
{ $_[0]->[DEFAULT_MENU_TITLE] = $_[1]; };
1;

View File

@ -0,0 +1,345 @@
###############################################################################
#
# Package: NaturalDocs::ReferenceString
#
###############################################################################
#
# A package to manage <ReferenceString> handling throughout the program.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::ReferenceString;
use vars '@ISA', '@EXPORT';
@ISA = 'Exporter';
@EXPORT = ( 'BINARYREF_NOTYPE', 'BINARYREF_NORESOLVINGFLAGS',
'REFERENCE_TEXT', 'REFERENCE_CH_CLASS', 'REFERENCE_CH_PARENT',
'RESOLVE_RELATIVE', 'RESOLVE_ABSOLUTE', 'RESOLVE_NOPLURAL', 'RESOLVE_NOUSING' );
use Encode qw(encode_utf8 decode_utf8);
#
# Constants: Binary Format Flags
#
# These flags can be combined to specify the format when using <ToBinaryFile()> and <FromBinaryFile()>. All are exported
# by default.
#
# BINARYREF_NOTYPE - Do not include the <ReferenceType>.
# BINARYREF_NORESOLVEFLAGS - Do not include the <Resolving Flags>.
#
use constant BINARYREF_NOTYPE => 0x01;
use constant BINARYREF_NORESOLVINGFLAGS => 0x02;
#
# Constants: ReferenceType
#
# The type of a reference.
#
# REFERENCE_TEXT - The reference appears in the text of the documentation.
# REFERENCE_CH_CLASS - A class reference handled by <NaturalDocs::ClassHierarchy>.
# REFERENCE_CH_PARENT - A parent class reference handled by <NaturalDocs::ClassHierarchy>.
#
# Dependencies:
#
# - <ToBinaryFile()> and <FromBinaryFile()> require that these values fit into a UInt8, i.e. are <= 255.
#
use constant REFERENCE_TEXT => 1;
use constant REFERENCE_CH_CLASS => 2;
use constant REFERENCE_CH_PARENT => 3;
#
# Constants: Resolving Flags
#
# Used to influence the method of resolving references in <NaturalDocs::SymbolTable>.
#
# RESOLVE_RELATIVE - The reference text is truly relative, rather than Natural Docs' semi-relative.
# RESOLVE_ABSOLUTE - The reference text is always absolute. No local or relative references.
# RESOLVE_NOPLURAL - The reference text may not be interpreted as a plural, and thus match singular forms as well.
# RESOLVE_NOUSING - The reference text may not include "using" statements when being resolved.
#
# If neither <RESOLVE_RELATIVE> or <RESOLVE_ABSOLUTE> is specified, Natural Docs' semi-relative kicks in instead,
# which is where links are interpreted as local, then global, then relative. <RESOLVE_RELATIVE> states that links are
# local, then relative, then global.
#
# Dependencies:
#
# - <ToBinaryFile()> and <FromBinaryFile()> require that these values fit into a UInt8, i.e. are <= 255.
#
use constant RESOLVE_RELATIVE => 0x01;
use constant RESOLVE_ABSOLUTE => 0x02;
use constant RESOLVE_NOPLURAL => 0x04;
use constant RESOLVE_NOUSING => 0x08;
#
#
# Function: MakeFrom
#
# Encodes the passed information as a <ReferenceString>. The format of the string should be treated as opaque. However, the
# characteristic you can rely on is that the same string will always be made from the same parameters, and thus it's suitable
# for comparison and use as hash keys.
#
# Parameters:
#
# type - The <ReferenceType>.
# symbol - The <SymbolString> of the reference.
# language - The name of the language that defines the file this reference appears in.
# scope - The scope <SymbolString> the reference appears in, or undef if none.
# using - An arrayref of scope <SymbolStrings> that are also available for checking due to the equivalent a "using" statement,
# or undef if none.
# resolvingFlags - The <Resolving Flags> to use with this reference. They are ignored if the type is <REFERENCE_TEXT>.
#
# Returns:
#
# The encoded <ReferenceString>.
#
sub MakeFrom #(ReferenceType type, SymbolString symbol, string language, SymbolString scope, SymbolString[]* using, flags resolvingFlags)
{
my ($self, $type, $symbol, $language, $scope, $using, $resolvingFlags) = @_;
if ($type == ::REFERENCE_TEXT() || $resolvingFlags == 0)
{ $resolvingFlags = undef; };
# The format is [type] 0x1E [resolving flags] 0x1E [symbol] 0x1E [scope] ( 0x1E [using] )*
# If there is no scope and/or using, the separator characters still remain.
# DEPENDENCY: SymbolString->FromText() removed all 0x1E characters.
# DEPENDENCY: SymbolString->FromText() doesn't use 0x1E characters in its encoding.
my $string = $type . "\x1E" . $symbol . "\x1E" . $language . "\x1E" . $resolvingFlags . "\x1E";
if (defined $scope)
{
$string .= $scope;
};
$string .= "\x1E";
if (defined $using)
{
$string .= join("\x1E", @$using);
};
return $string;
};
#
# Function: ToBinaryFile
#
# Writes a <ReferenceString> to the passed filehandle. Can also encode an undef.
#
# Parameters:
#
# fileHandle - The filehandle to write to.
# referenceString - The <ReferenceString> to write, or undef.
# binaryFormatFlags - Any <Binary Format Flags> you want to use to influence encoding.
#
# Format:
#
# > [SymbolString: Symbol or undef for an undef reference]
# > [UString16: language]
# > [SymbolString: Scope or undef for none]
# >
# > [SymbolString: Using or undef for none]
# > [SymbolString: Using or undef for no more]
# > ...
# >
# > [UInt8: Type unless BINARYREF_NOTYPE is set]
# > [UInt8: Resolving Flags unless BINARYREF_NORESOLVINGFLAGS is set]
#
# Dependencies:
#
# - <ReferenceTypes> must fit into a UInt8. All values must be <= 255.
# - All <Resolving Flags> must fit into a UInt8. All values must be <= 255.
#
sub ToBinaryFile #(FileHandle fileHandle, ReferenceString referenceString, flags binaryFormatFlags)
{
my ($self, $fileHandle, $referenceString, $binaryFormatFlags) = @_;
my ($type, $symbol, $language, $scope, $using, $resolvingFlags) = $self->InformationOf($referenceString);
# [SymbolString: Symbol or undef for an undef reference]
NaturalDocs::SymbolString->ToBinaryFile($fileHandle, $symbol);
# [UString16: language]
# $language may be undefined because $referenceString may be undefined to end a list of them.
if (defined $language)
{
my $uLanguage = encode_utf8($language);
print $fileHandle pack('na*', length $uLanguage, $uLanguage);
}
else
{ print $fileHandle pack('n', 0); }
# [SymbolString: scope or undef if none]
NaturalDocs::SymbolString->ToBinaryFile($fileHandle, $scope);
# [SymbolString: using or undef if none/no more] ...
if (defined $using)
{
foreach my $usingScope (@$using)
{ NaturalDocs::SymbolString->ToBinaryFile($fileHandle, $usingScope); };
};
NaturalDocs::SymbolString->ToBinaryFile($fileHandle, undef);
# [UInt8: Type unless BINARYREF_NOTYPE is set]
if (!($binaryFormatFlags & BINARYREF_NOTYPE))
{ print $fileHandle pack('C', $type); };
# [UInt8: Resolving Flags unless BINARYREF_NORESOLVINGFLAGS is set]
if (!($binaryFormatFlags & BINARYREF_NORESOLVINGFLAGS))
{ print $fileHandle pack('C', $type); };
};
#
# Function: FromBinaryFile
#
# Reads a <ReferenceString> or undef from the passed filehandle.
#
# Parameters:
#
# fileHandle - The filehandle to read from.
# binaryFormatFlags - Any <Binary Format Flags> you want to use to influence decoding.
# type - The <ReferenceType> to use if <BINARYREF_NOTYPE> is set.
# resolvingFlags - The <Resolving Flags> to use if <BINARYREF_NORESOLVINGFLAGS> is set.
#
# Returns:
#
# The <ReferenceString> or undef.
#
# See Also:
#
# See <ToBinaryFile()> for format and dependencies.
#
sub FromBinaryFile #(FileHandle fileHandle, flags binaryFormatFlags, ReferenceType type, flags resolvingFlags)
{
my ($self, $fileHandle, $binaryFormatFlags, $type, $resolvingFlags) = @_;
my $raw;
# [SymbolString: Symbol or undef for an undef reference]
my $symbol = NaturalDocs::SymbolString->FromBinaryFile($fileHandle);
if (!defined $symbol)
{ return undef; };
# [UString16: language]
read($fileHandle, $raw, 2);
my $languageLength = unpack('n', $raw);
my $language;
read($fileHandle, $language, $languageLength);
$language = decode_utf8($language);
# [SymbolString: scope or undef if none]
my $scope = NaturalDocs::SymbolString->FromBinaryFile($fileHandle);
# [SymbolString: using or undef if none/no more] ...
my $usingSymbol;
my @using;
while ($usingSymbol = NaturalDocs::SymbolString->FromBinaryFile($fileHandle))
{ push @using, $usingSymbol; };
if (scalar @using)
{ $usingSymbol = \@using; }
else
{ $usingSymbol = undef; };
# [UInt8: Type unless BINARYREF_NOTYPE is set]
if (!($binaryFormatFlags & BINARYREF_NOTYPE))
{
my $raw;
read($fileHandle, $raw, 1);
$type = unpack('C', $raw);
};
# [UInt8: Resolving Flags unless BINARYREF_NORESOLVINGFLAGS is set]
if (!($binaryFormatFlags & BINARYREF_NORESOLVINGFLAGS))
{
my $raw;
read($fileHandle, $raw, 1);
$resolvingFlags = unpack('C', $raw);
};
return $self->MakeFrom($type, $symbol, $language, $scope, $usingSymbol, $resolvingFlags);
};
#
# Function: InformationOf
#
# Returns the information encoded in a <ReferenceString>.
#
# Parameters:
#
# referenceString - The <ReferenceString> to decode.
#
# Returns:
#
# The array ( type, symbol, language, scope, using, resolvingFlags ).
#
# type - The <ReferenceType>.
# symbol - The <SymbolString>.
# language - The name of the language that defined the file the reference was defined in.
# scope - The scope <SymbolString>, or undef if none.
# using - An arrayref of scope <SymbolStrings> that the reference also has access to via "using" statements, or undef if none.
# resolvingFlags - The <Resolving Flags> of the reference.
#
sub InformationOf #(ReferenceString referenceString)
{
my ($self, $referenceString) = @_;
my ($type, $symbolString, $language, $resolvingFlags, $scopeString, @usingStrings) = split(/\x1E/, $referenceString);
if (!length $resolvingFlags)
{ $resolvingFlags = undef; };
return ( $type, $symbolString, $language, $scopeString, [ @usingStrings ], $resolvingFlags );
};
#
# Function: TypeOf
#
# Returns the <ReferenceType> encoded in the reference string. This is faster than <InformationOf()> if this is
# the only information you need.
#
sub TypeOf #(ReferenceString referenceString)
{
my ($self, $referenceString) = @_;
$referenceString =~ /^([^\x1E]+)/;
return $1;
};
1;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,67 @@
###############################################################################
#
# Class: NaturalDocs::Settings::BuildTarget
#
###############################################################################
#
# A class that stores information about a build target.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::Settings::BuildTarget;
use NaturalDocs::DefineMembers 'BUILDER', 'Builder()', 'SetBuilder()',
'DIRECTORY', 'Directory()', 'SetDirectory()';
#
# Constants: Members
#
# The class is implemented as a blessed arrayref with the members below.
#
# BUILDER - The <NaturalDocs::Builder::Base>-derived object for the target's output format.
# DIRECTORY - The output directory of the target.
#
#
# Function: New
#
# Creates and returns a new object.
#
# Parameters:
#
# builder - The <NaturalDocs::Builder::Base>-derived object for the target's output format.
# directory - The directory to place the output files in.
#
sub New #(builder, directory)
{
my ($package, $builder, $directory) = @_;
my $object = [ ];
bless $object, $package;
$object->SetBuilder($builder);
$object->SetDirectory($directory);
return $object;
};
#
# Functions: Member Functions
#
# Builder - Returns the <NaturalDocs::Builder::Base>-derived object for the target's output format.
# SetBuilder - Replaces the <NaturalDocs::Builder::Base>-derived object for the target's output format.
# Directory - Returns the directory for the target's output files.
# SetDirectory - Replaces the directory for the target's output files.
#
1;

View File

@ -0,0 +1,679 @@
###############################################################################
#
# Package: NaturalDocs::SourceDB
#
###############################################################################
#
# SourceDB is an experimental package meant to unify the tracking of various elements in the source code.
#
# Requirements:
#
# - All extension packages must call <RegisterExtension()> before they can be used.
#
#
# Architecture: The Idea
#
# For quite a while Natural Docs only needed <SymbolTable>. However, 1.3 introduced the <ClassHierarchy> package
# which duplicated some of its functionality to track classes and parent references. 1.4 now needs <ImageReferenceTable>,
# so this package was an attempt to isolate the common functionality so the wheel doesn't have to keep being rewritten as
# the scope of Natural Docs expands.
#
# SourceDB is designed around <Extensions> and items. The purposefully vague "items" are anything in the source code
# that we need to track the definitions of. Extensions are the packages to track them, only they're derived from
# <NaturalDocs::SourceDB::Extension> and registered with this package instead of being free standing and duplicating
# functionality such as watched files.
#
# The architecture on this package isn't comprehensive yet. As more extensions are added or previously made free standing
# packages are migrated to it it will expand to encompass them. However, it's still experimental so this concept may
# eventually be abandoned for something better instead.
#
#
# Architecture: Assumptions
#
# SourceDB is built around certain assumptions.
#
# One item per file:
#
# SourceDB assumes that only the first item per file with a particular item string is relevant. For example, if two functions
# have the exact same name, there's no way to link to the second one either in HTML or internally so it doesn't matter for
# our purposes. Likewise, if two references are exactly the same they go to the same target, so it doesn't matter whether
# there's one or two or a thousand. All that matters is that at least one reference exists in this file because you only need
# to determine whether the entire file gets rebuilt. If two items are different in some meaningful way, they should generate
# different item strings.
#
# Watched file parsing:
#
# SourceDB assumes the parse method is that the information that was stored from Natural Docs' previous run is loaded, a
# file is watched, that file is reparsed, and then <AnalyzeWatchedFileChanges()> is called. When the file is reparsed all
# items within it are added the same as if the file was never parsed before.
#
# If there's a new item this time around, that's fine no matter what. However, a changed item wouldn't normally be
# recorded because the previous run's definition is seen as the first one and subsequent ones are ignored. Also, deleted
# items would normally not be recorded either because we're only adding.
#
# The watched file method fixes this because everything is also added to a second, clean database specifically for the
# watched file. Because it starts clean, it always gets the first definition from the current parse which can then be
# compared to the original by <AnalyzeWatchedFileChanges()>. Because it starts clean you can also compare it to the
# main database to see if anything was deleted, because it would appear in the main database but not the watched one.
#
# This means that functions like <ChangeDefinition()> and <DeleteDefinition()> should only be called by
# <AnalyzeWatchedFileChanges()>. Externally only <AddDefinition()> should be called. <DeleteItem()> is okay to be
# called externally because entire items aren't managed by the watched file database, only definitions.
#
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
use NaturalDocs::SourceDB::Extension;
use NaturalDocs::SourceDB::Item;
use NaturalDocs::SourceDB::ItemDefinition;
use NaturalDocs::SourceDB::File;
use NaturalDocs::SourceDB::WatchedFileDefinitions;
package NaturalDocs::SourceDB;
###############################################################################
# Group: Types
#
# Type: ExtensionID
#
# A unique identifier for each <NaturalDocs::SourceDB> extension as given out by <RegisterExtension()>.
#
###############################################################################
# Group: Variables
#
# array: extensions
#
# An array of <NaturalDocs::SourceDB::Extension>-derived extensions, as added with <RegisterExtension()>. The indexes
# are the <ExtensionIDs> and the values are package references.
#
my @extensions;
#
# array: extensionUsesDefinitionObjects
#
# An array where the indexes are <ExtensionIDs> and the values are whether that extension uses its own definition class
# derived from <NaturalDocs::SourceDB::ItemDefinition> or it just tracks their existence.
#
my @extensionUsesDefinitionObjects;
#
# array: items
#
# The array of source items. The <ExtensionIDs> are the indexes, and the values are hashrefs mapping the item
# string to <NaturalDocs::SourceDB::Item>-derived objects. Hashrefs may be undef.
#
my @items;
#
# hash: files
#
# A hashref mapping source <FileNames> to <NaturalDocs::SourceDB::Files>.
#
my %files;
#
# object: watchedFile
#
# When a file is being watched for changes, will be a <NaturalDocs::SourceDB::File> for that file. Is undef otherwise.
#
# When the file is parsed, items are added to both this and the version in <files>. Thus afterwards we can compare the two to
# see if any were deleted since the last time Natural Docs was run, because they would be in the <files> version but not this
# one.
#
my $watchedFile;
#
# string: watchedFileName
#
# When a file is being watched for changes, will be the <FileName> of the file being watched. Is undef otherwise.
#
my $watchedFileName;
#
# object: watchedFileDefinitions
#
# When a file is being watched for changes, will be a <NaturalDocs::SourceDB::WatchedFileDefinitions> object. Is undef
# otherwise.
#
# When the file is parsed, items are added to both this and the version in <items>. Since only the first definition is kept, this
# will always have the definition info from the file whereas the version in <items> will have the first definition as of the last time
# Natural Docs was run. Thus they can be compared to see if the definitions of items that existed the last time around have
# changed.
#
my $watchedFileDefinitions;
###############################################################################
# Group: Extension Functions
#
# Function: RegisterExtension
#
# Registers a <NaturalDocs::SourceDB::Extension>-derived package and returns a unique <ExtensionID> for it. All extensions
# must call this before they can be used.
#
# Registration Order:
#
# The order in which extensions register is important. Whenever possible, items are added in the order their extensions
# registered. However, items are changed and deleted in the reverse order. Take advantage of this to minimize
# churn between extensions that are dependent on each other.
#
# For example, when symbols are added or deleted they may cause references to be retargeted and thus their files need to
# be rebuilt. However, adding or deleting references never causes the symbols' files to be rebuilt. So it makes sense that
# symbols should be created before references, and that references should be deleted before symbols.
#
# Parameters:
#
# extension - The package or object of the extension. Must be derived from <NaturalDocs::SourceDB::Extension>.
# usesDefinitionObjects - Whether the extension uses its own class derived from <NaturalDocs::SourceDB::ItemDefinition>
# or simply tracks each definitions existence.
#
# Returns:
#
# An <ExtensionID> unique to the extension. This should be saved because it's required in functions such as <AddItem()>.
#
sub RegisterExtension #(package extension, bool usesDefinitionObjects) => ExtensionID
{
my ($self, $extension, $usesDefinitionObjects) = @_;
push @extensions, $extension;
push @extensionUsesDefinitionObjects, $usesDefinitionObjects;
return scalar @extensions - 1;
};
###############################################################################
# Group: File Functions
#
# Function: Load
#
# Loads the data of the source database and all the extensions. Will call <NaturalDocs::SourceDB::Extension->Load()> for
# all of them, unless there's a situation where all the source files are going to be reparsed anyway in which case it's not needed.
#
sub Load
{
my $self = shift;
# No point loading if RebuildData is set.
if (!NaturalDocs::Settings->RebuildData())
{
# If any load fails, stop loading the rest and just reparse all the source files.
my $success = 1;
for (my $extension = 0; $extension < scalar @extensions && $success; $extension++)
{
$success = $extensions[$extension]->Load();
};
if (!$success)
{ NaturalDocs::Project->ReparseEverything(); };
};
};
#
# Function: Save
#
# Saves the data of the source database and all its extensions. Will call <NaturalDocs::SourceDB::Extension->Save()> for all
# of them.
#
sub Save
{
my $self = shift;
for (my $extension = scalar @extensions - 1; $extension >= 0; $extension--)
{
$extensions[$extension]->Save();
};
};
#
# Function: PurgeDeletedSourceFiles
#
# Removes all data associated with deleted source files.
#
sub PurgeDeletedSourceFiles
{
my $self = shift;
my $filesToPurge = NaturalDocs::Project->FilesToPurge();
# Extension is the outermost loop because we want the extensions added last to have their definitions removed first to cause
# the least amount of churn between interdependent extensions.
for (my $extension = scalar @extensions - 1; $extension >= 0; $extension--)
{
foreach my $file (keys %$filesToPurge)
{
if (exists $files{$file})
{
my @items = $files{$file}->ListItems($extension);
foreach my $item (@items)
{
$self->DeleteDefinition($extension, $item, $file);
};
}; # file exists
}; # each file
}; # each extension
};
###############################################################################
# Group: Item Functions
#
# Function: AddItem
#
# Adds the passed item to the database. This will not work if the item string already exists. The item added should *not*
# already have definitions attached. Only use this to add blank items and then call <AddDefinition()> instead.
#
# Parameters:
#
# extension - An <ExtensionID>.
# itemString - The string serving as the item identifier.
# item - An object derived from <NaturalDocs::SourceDB::Item>.
#
# Returns:
#
# Whether the item was added, that is, whether it was the first time this item was added.
#
sub AddItem #(ExtensionID extension, string itemString, NaturalDocs::SourceDB::Item item) => bool
{
my ($self, $extension, $itemString, $item) = @_;
if (!defined $items[$extension])
{ $items[$extension] = { }; };
if (!exists $items[$extension]->{$itemString})
{
if ($item->HasDefinitions())
{ die "Tried to add an item to SourceDB that already had definitions."; };
$items[$extension]->{$itemString} = $item;
return 1;
};
return 0;
};
#
# Function: GetItem
#
# Returns the <NaturalDocs::SourceDB::Item>-derived object for the passed <ExtensionID> and item string, or undef if there
# is none.
#
sub GetItem #(ExtensionID extension, string itemString) => bool
{
my ($self, $extensionID, $itemString) = @_;
if (defined $items[$extensionID])
{ return $items[$extensionID]->{$itemString}; }
else
{ return undef; };
};
#
# Function: DeleteItem
#
# Deletes the record of the passed <ExtensionID> and item string. Do *not* delete items that still have definitions. Use
# <DeleteDefinition()> first.
#
# Parameters:
#
# extension - The <ExtensionID>.
# itemString - The item's identifying string.
#
# Returns:
#
# Whether it was successful, meaning whether an entry existed for it.
#
sub DeleteItem #(ExtensionID extension, string itemString) => bool
{
my ($self, $extension, $itemString) = @_;
if (defined $items[$extension] && exists $items[$extension]->{$itemString})
{
if ($items[$extension]->{$itemString}->HasDefinitions())
{ die "Tried to delete an item from SourceDB that still has definitions."; };
delete $items[$extension]->{$itemString};
return 1;
}
else
{ return 0; };
};
#
# Function: HasItem
#
# Returns whether there is an item defined for the passed <ExtensionID> and item string.
#
sub HasItem #(ExtensionID extension, string itemString) => bool
{
my ($self, $extension, $itemString) = @_;
if (defined $items[$extension])
{ return (exists $items[$extension]->{$itemString}); }
else
{ return 0; };
};
#
# Function: GetAllItemsHashRef
#
# Returns a hashref of all the items defined for an extension. *Do not change the contents.* The keys are the item strings and
# the values are <NaturalDocs::SourceDB::Items> or derived classes.
#
sub GetAllItemsHashRef #(ExtensionID extension) => hashref
{
my ($self, $extension) = @_;
return $items[$extension];
};
###############################################################################
# Group: Definition Functions
#
# Function: AddDefinition
#
# Adds a definition to an item. Assumes the item was already created with <AddItem()>. If there's already a definition for this
# file in the item, the new definition will be ignored.
#
# Parameters:
#
# extension - The <ExtensionID>.
# itemString - The item string.
# file - The <FileName> the definition is in.
# definition - If you're using a custom <NaturalDocs::SourceDB::ItemDefinition> class, you must include an object for it here.
# Otherwise this parameter is ignored.
#
# Returns:
#
# Whether the definition was added, which is to say, whether this was the first definition for the passed <FileName>.
#
sub AddDefinition #(ExtensionID extension, string itemString, FileName file, optional NaturalDocs::SourceDB::ItemDefinition definition) => bool
{
my ($self, $extension, $itemString, $file, $definition) = @_;
# Items
my $item = $self->GetItem($extension, $itemString);
if (!defined $item)
{ die "Tried to add a definition to an undefined item in SourceDB."; };
if (!$extensionUsesDefinitionObjects[$extension])
{ $definition = 1; };
my $result = $item->AddDefinition($file, $definition);
# Files
if (!exists $files{$file})
{ $files{$file} = NaturalDocs::SourceDB::File->New(); };
$files{$file}->AddItem($extension, $itemString);
# Watched File
if ($self->WatchingFileForChanges())
{
$watchedFile->AddItem($extension, $itemString);
if ($extensionUsesDefinitionObjects[$extension])
{ $watchedFileDefinitions->AddDefinition($extension, $itemString, $definition); };
};
return $result;
};
#
# Function: ChangeDefinition
#
# Changes the definition of an item. This function is only used for extensions that use custom
# <NaturalDocs::SourceDB::ItemDefinition>-derived classes.
#
# Parameters:
#
# extension - The <ExtensionID>.
# itemString - The item string.
# file - The <FileName> the definition is in.
# definition - The definition, which must be an object derived from <NaturalDocs::SourceDB::ItemDefinition>.
#
sub ChangeDefinition #(ExtensionID extension, string itemString, FileName file, NaturalDocs::SourceDB::ItemDefinition definition)
{
my ($self, $extension, $itemString, $file, $definition) = @_;
my $item = $self->GetItem($extension, $itemString);
if (!defined $item)
{ die "Tried to change the definition of an undefined item in SourceDB."; };
if (!$extensionUsesDefinitionObjects[$extension])
{ die "Tried to change the definition of an item in an extension that doesn't use definition objects in SourceDB."; };
if (!$item->HasDefinition($file))
{ die "Tried to change a definition that doesn't exist in SourceDB."; };
$item->ChangeDefinition($file, $definition);
$extensions[$extension]->OnChangedDefinition($itemString, $file);
};
#
# Function: GetDefinition
#
# If the extension uses custom <NaturalDocs::SourceDB::ItemDefinition> classes, returns it for the passed definition or undef
# if it doesn't exist. Otherwise returns whether it exists.
#
sub GetDefinition #(ExtensionID extension, string itemString, FileName file) => NaturalDocs::SourceDB::ItemDefinition or bool
{
my ($self, $extension, $itemString, $file) = @_;
my $item = $self->GetItem($extension, $itemString);
if (!defined $item)
{ return undef; };
return $item->GetDefinition($file);
};
#
# Function: DeleteDefinition
#
# Removes the definition for the passed item. Returns whether it was successful, meaning whether a definition existed for that
# file.
#
sub DeleteDefinition #(ExtensionID extension, string itemString, FileName file) => bool
{
my ($self, $extension, $itemString, $file) = @_;
my $item = $self->GetItem($extension, $itemString);
if (!defined $item)
{ return 0; };
my $result = $item->DeleteDefinition($file);
if ($result)
{
$files{$file}->DeleteItem($extension, $itemString);
$extensions[$extension]->OnDeletedDefinition($itemString, $file, !$item->HasDefinitions());
};
return $result;
};
#
# Function: HasDefinitions
#
# Returns whether there are any definitions for this item.
#
sub HasDefinitions #(ExtensionID extension, string itemString) => bool
{
my ($self, $extension, $itemString) = @_;
my $item = $self->GetItem($extension, $itemString);
if (!defined $item)
{ return 0; };
return $item->HasDefinitions();
};
#
# Function: HasDefinition
#
# Returns whether there is a definition for the passed <FileName>.
#
sub HasDefinition #(ExtensionID extension, string itemString, FileName file) => bool
{
my ($self, $extension, $itemString, $file) = @_;
my $item = $self->GetItem($extension, $itemString);
if (!defined $item)
{ return 0; };
return $item->HasDefinition($file);
};
###############################################################################
# Group: Watched File Functions
#
# Function: WatchFileForChanges
#
# Begins watching a file for changes. Only one file at a time can be watched.
#
# This should be called before a file is parsed so the file info goes both into the main database and the watched file info.
# Afterwards you call <AnalyzeWatchedFileChanges()> so item deletions and definition changes can be detected.
#
# Parameters:
#
# filename - The <FileName> to watch.
#
sub WatchFileForChanges #(FileName filename)
{
my ($self, $filename) = @_;
$watchedFileName = $filename;
$watchedFile = NaturalDocs::SourceDB::File->New();
$watchedFileDefinitions = NaturalDocs::SourceDB::WatchedFileDefinitions->New();
};
#
# Function: WatchingFileForChanges
#
# Returns whether we're currently watching a file for changes or not.
#
sub WatchingFileForChanges # => bool
{
my $self = shift;
return defined $watchedFileName;
};
#
# Function: AnalyzeWatchedFileChanges
#
# Analyzes the watched file for changes. Will delete and change definitions as necessary.
#
sub AnalyzeWatchedFileChanges
{
my $self = shift;
if (!$self->WatchingFileForChanges())
{ die "Tried to analyze watched file for changes in SourceDB when no file was being watched."; };
if (!$files{$watchedFileName})
{ return; };
# Process extensions last registered to first.
for (my $extension = scalar @extensions - 1; $extension >= 0; $extension--)
{
my @items = $files{$watchedFileName}->ListItems($extension);
foreach my $item (@items)
{
if ($watchedFile->HasItem($extension, $item))
{
if ($extensionUsesDefinitionObjects[$extension])
{
my $originalDefinition = $items[$extension]->GetDefinition($watchedFileName);
my $watchedDefinition = $watchedFileDefinitions->GetDefinition($extension, $item);
if (!$originalDefinition->Compare($watchedDefinition))
{ $self->ChangeDefinition($extension, $item, $watchedFileName, $watchedDefinition); };
}
}
else # !$watchedFile->HasItem($item)
{
$self->DeleteDefinition($extension, $item, $watchedFileName);
};
};
};
$watchedFile = undef;
$watchedFileName = undef;
$watchedFileDefinitions = undef;
};
1;

View File

@ -0,0 +1,85 @@
###############################################################################
#
# Package: NaturalDocs::SourceDB::Extension
#
###############################################################################
#
# A base package for all <SourceDB> extensions.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::SourceDB::Extension;
###############################################################################
# Group: Interface Functions
# These functions must be overridden by the derived class.
#
# Function: Register
#
# Override this function to register the package with <NaturalDocs::SourceDB->RegisterExtension()>.
#
sub Register
{
die "Called SourceDB::Extension->Register(). This function should be overridden by every extension.";
};
#
# Function: Load
#
# Called by <NaturalDocs::SourceDB->Load()> to load the extension's data. Returns whether it was successful.
#
# *This function might not be called.* If there's a situation that would cause all the source files to be reparsed anyway,
# <NaturalDocs::SourceDB> may skip calling Load() for the remaining extensions. You should *not* depend on this function
# for any critical initialization that needs to happen every time regardless.
#
sub Load # => bool
{
return 1;
};
#
# Function: Save
#
# Called by <NaturalDocs::SourceDB->Save()> to save the extension's data.
#
sub Save
{
};
#
# Function: OnDeletedDefinition
#
# Called for each definition deleted by <NaturalDocs::SourceDB>. This is called *after* the definition has been deleted from
# the database, so don't expect to be able to read it.
#
sub OnDeletedDefinition #(string itemString, FileName file, bool wasLastDefinition)
{
};
#
# Function: OnChangedDefinition
#
# Called for each definition changed by <NaturalDocs::SourceDB>. This is called *after* the definition has been changed, so
# don't expect to be able to read the original value.
#
sub OnChangedDefinition #(string itemString, FileName file)
{
};
1;

View File

@ -0,0 +1,130 @@
###############################################################################
#
# Package: NaturalDocs::SourceDB::File
#
###############################################################################
#
# A class used to index items by file.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::SourceDB::File;
use NaturalDocs::DefineMembers 'ITEMS';
#
# Variables: Members
#
# These constants serve as indexes into the object array.
#
# ITEMS - An arrayref where an <ExtensionID> is the index and the members are existence hashrefs of the item strigs defined
# in this file. The arrayref will always exist, but the hashrefs may be undef.
#
#
# Function: New
#
# Returns a new object.
#
sub New
{
my $package = shift;
my $object = [ ];
$object->[ITEMS] = [ ];
bless $object, $package;
return $object;
};
#
# Function: AddItem
#
# Adds an item to this file. Returns whether this added a new item.
#
sub AddItem #(ExtensionID extension, string itemString) => bool
{
my ($self, $extension, $itemString) = @_;
if (!defined $self->[ITEMS]->[$extension])
{
$self->[ITEMS]->[$extension] = { $itemString => 1 };
return 1;
}
elsif (!exists $self->[ITEMS]->[$extension]->{$itemString})
{
$self->[ITEMS]->[$extension]->{$itemString} = 1;
return 1;
}
else
{
return 0;
};
};
#
# Function: HasItem
#
# Returns whether the item exists in this file.
#
sub HasItem #(ExtensionID extension, string itemString) => bool
{
my ($self, $extension, $itemString) = @_;
if (defined $self->[ITEMS]->[$extension])
{ return exists $self->[ITEMS]->[$extension]->{$itemString}; }
else
{ return 0; };
};
#
# Function: DeleteItem
#
# Deletes the passed item. Returns whether it existed.
#
sub DeleteItem #(ExtensionID extension, string itemString) => bool
{
my ($self, $extension, $itemString) = @_;
if (!defined $self->[ITEMS]->[$extension])
{ return 0; }
elsif (exists $self->[ITEMS]->[$extension]->{$itemString})
{
delete $self->[ITEMS]->[$extension]->{$itemString};
return 1;
}
else
{ return 0; };
};
#
# Function: ListItems
#
# Returns an array of all the item strings defined for a particular extension, or an empty list if none.
#
sub ListItems #(ExtensionID extension) => string array
{
my ($self, $extension) = @_;
if (defined $self->[ITEMS]->[$extension])
{ return keys %{$self->[ITEMS]->[$extension]}; }
else
{ return ( ); };
};
1;

View File

@ -0,0 +1,202 @@
###############################################################################
#
# Package: NaturalDocs::SourceDB::Item
#
###############################################################################
#
# A base class for something being tracked in <NaturalDocs::SourceDB>.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::SourceDB::Item;
use NaturalDocs::DefineMembers 'DEFINITIONS';
#
# Variables: Members
#
# The following constants are indexes into the object array.
#
# DEFINITIONS - A hashref that maps <FileNames> to either <NaturalDocs::SourceDB::ItemDefinition>-derived objects or
# serves as an existence hashref depending on whether the extension only tracks existence. Will be undef if
# there are none.
#
#
# Function: New
#
# Creates and returns a new object.
#
sub New
{
my $class = shift;
my $object = [ ];
bless $object, $class;
return $object;
};
###############################################################################
#
# Group: Definition Functions
#
# These functions should be called by <NaturalDocs::SourceDB>. You should not be calling them directly. Call functions
# like <NaturalDocs::SourceDB->AddDefinition()> instead.
#
#
# Function: AddDefinition
#
# Adds a definition for the passed <FileName>. If it's already defined, the new definition will be ignored.
#
# Parameters:
#
# file - The <FileName>.
# definition - The definition, which must be an object derived from <NaturalDocs::SourceDB::ItemDefinition> or undef if
# the extension only tracks existence.
#
# Returns:
#
# Whether the definition was added, which is to say, whether this was the first definition for the passed <FileName>.
#
sub AddDefinition #(FileName file, optional NaturalDocs::SourceDB::ItemDefinition definition) => bool
{
my ($self, $file, $definition) = @_;
if (!defined $self->[DEFINITIONS])
{ $self->[DEFINITIONS] = { }; };
if (!exists $self->[DEFINITIONS]->{$file})
{
if (!defined $definition)
{ $definition = 1; };
$self->[DEFINITIONS]->{$file} = $definition;
return 1;
}
else
{ return 0; };
};
#
# Function: ChangeDefinition
#
# Changes the definition for the passed <FileName>.
#
# Parameters:
#
# file - The <FileName>.
# definition - The definition, which must be an object derived from <NaturalDocs::SourceDB::ItemDefinition>.
#
sub ChangeDefinition #(FileName file, NaturalDocs::SourceDB::ItemDefinition definition)
{
my ($self, $file, $definition) = @_;
if (!defined $self->[DEFINITIONS] || !exists $self->[DEFINITIONS]->{$file})
{ die "Tried to change a non-existant definition in SourceD::Item."; };
$self->[DEFINITIONS]->{$file} = $definition;
};
#
# Function: GetDefinition
#
# Returns the <NaturalDocs::SourceDB::ItemDefinition>-derived object for the passed <FileName>, non-zero if it only tracks
# existence, or undef if there is no definition.
#
sub GetDefinition #(FileName file) => NaturalDocs::SourceDB::ItemDefinition or bool
{
my ($self, $file) = @_;
if (defined $self->[DEFINITIONS])
{ return $self->[DEFINITIONS]->{$file}; }
else
{ return undef; };
};
#
# Function: DeleteDefinition
#
# Removes the definition for the passed <FileName>. Returns whether it was successful, meaning whether a definition existed
# for that file.
#
sub DeleteDefinition #(FileName file) => bool
{
my ($self, $file) = @_;
if (defined $self->[DEFINITIONS])
{
if (exists $self->[DEFINITIONS]->{$file})
{
delete $self->[DEFINITIONS]->{$file};
if (!scalar keys %{$self->[DEFINITIONS]})
{ $self->[DEFINITIONS] = undef; };
return 1;
};
};
return 0;
};
#
# Function: HasDefinitions
#
# Returns whether there are any definitions for this item.
#
sub HasDefinitions # => bool
{
my $self = shift;
return (defined $self->[DEFINITIONS]);
};
#
# Function: HasDefinition
#
# Returns whether there is a definition for the passed <FileName>.
#
sub HasDefinition #(FileName file) => bool
{
my ($self, $file) = @_;
if (defined $self->[DEFINITIONS])
{ return (exists $self->[DEFINITIONS]->{$file}); }
else
{ return 0; };
};
#
# Function: GetAllDefinitionsHashRef
#
# Returns a hashref of all the definitions of this item. *Do not change.* The keys are the <FileNames>, and the values are
# either <NaturalDocs::SourceDB::ItemDefinition>-derived objects or it's just an existence hashref if those aren't used.
#
sub GetAllDefinitionsHashRef
{
my $self = shift;
return $self->[DEFINITIONS];
};
1;

View File

@ -0,0 +1,46 @@
###############################################################################
#
# Package: NaturalDocs::SourceDB::ItemDefinition
#
###############################################################################
#
# A base class for all item definitions for extensions that track more than existence.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::SourceDB::ItemDefinition;
#
# Function: Compare
#
# Returns whether the definitions are equal. This version returns true by default, you must override it in your subclasses
# to make the results relevant. This is important for <NaturalDocs::SourceDB->AnalyzeTrackedFileChanges()>.
#
# This will only be called between objects of the same <ExtensionID>. If you use multiple derived classes for the same
# <ExtensionID>, you will have to take that into account yourself.
#
# Parameters:
#
# other - Another <NaturalDocs::SourceDB::ItemDefinition>-derived object to compare this one to. It will always be from
# the same <ExtensionID>.
#
# Returns:
#
# Whether they are equal.
#
sub Compare #(other)
{
return 1;
};
1;

View File

@ -0,0 +1,160 @@
###############################################################################
#
# Package: NaturalDocs::SourceDB::WatchedFileDefinitions
#
###############################################################################
#
# A class to track the definitions appearing in a watched file. This is only used for extensions that track definition info with
# <NaturalDocs::SourceDB::ItemDefinition>-derived objects. Do not use it for extensions that only track existence.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::SourceDB::WatchedFileDefinitions;
#
# Variables: Members
#
# This object would only have one member, which is an array, so the object itself serves as that member.
#
# <ExtensionIDs> are used as indexes into this object. Each entry is a hashref that maps item strings to
# <NaturalDocs::SourceDB::ItemDefinition>-derived objects. This is only done for extensions that use those objects to track
# definitions, it's not needed for extensions that only track existence. If there are no definitions, the entry will be undef.
#
#
# Function: New
#
# Creates and returns a new object.
#
sub New
{
my $class = shift;
my $object = [ ];
bless $object, $class;
return $object;
};
###############################################################################
# Group: Definition Functions
#
#
# Function: AddDefinition
#
# Adds a definition for the passed item string. If it's already defined, the new definition will be ignored.
#
# Parameters:
#
# extension - The <ExtensionID>.
# itemString - The item string.
# definition - The definition, which must be an object derived from <NaturalDocs::SourceDB::ItemDefinition>.
#
# Returns:
#
# Whether the definition was added, which is to say, whether this was the first definition for the passed <FileName>.
#
sub AddDefinition #(ExtensionID extension, string itemString, NaturalDocs::SourceDB::ItemDefinition definition) => bool
{
my ($self, $extension, $itemString, $definition) = @_;
if (!defined $self->[$extension])
{ $self->[$extension] = { }; };
if (!exists $self->[$extension]->{$itemString})
{
$self->[$extension]->{$itemString} = $definition;
return 1;
}
else
{ return 0; };
};
#
# Function: GetDefinition
#
# Returns the <NaturalDocs::SourceDB::ItemDefinition>-derived object for the passed item string or undef if there is none.
#
sub GetDefinition #(ExtensionID extension, string itemString) => NaturalDocs::SourceDB::ItemDefinition
{
my ($self, $extension, $itemString) = @_;
if (defined $self->[$extension])
{ return $self->[$extension]->{$itemString}; }
else
{ return undef; };
};
#
# Function: DeleteDefinition
#
# Removes the definition for the passed item string. Returns whether it was successful, meaning whether a definition existed
# for that item.
#
sub DeleteDefinition #(ExtensionID extension, string itemString) => bool
{
my ($self, $extension, $itemString) = @_;
if (defined $self->[$extension])
{
if (exists $self->[$extension]->{$itemString})
{
delete $self->[$extension]->{$itemString};
if (!scalar keys %{$self->[$extension]})
{ $self->[$extension] = undef; };
return 1;
};
};
return 0;
};
#
# Function: HasDefinitions
#
# Returns whether there are any definitions for this item.
#
sub HasDefinitions #(ExtensionID extension) => bool
{
my ($self, $extension) = @_;
return (defined $self->[$extension]);
};
#
# Function: HasDefinition
#
# Returns whether there is a definition for the passed item string.
#
sub HasDefinition #(ExtensionID extension, string itemString) => bool
{
my ($self, $extension, $itemString) = @_;
if (defined $self->[$extension])
{ return (exists $self->[$extension]->{$itemString}); }
else
{ return 0; };
};
1;

View File

@ -0,0 +1,103 @@
###############################################################################
#
# Package: NaturalDocs::StatusMessage
#
###############################################################################
#
# A package to handle status message updates. Automatically handles <NaturalDocs::Settings->IsQuiet()>.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::StatusMessage;
#
# var: message
# The message to display.
#
my $message;
#
# var: total
# The number of items to work through.
#
my $total;
#
# var: completed
# The number of items completed.
#
my $completed;
#
# var: lastMessageTime
# The time the last message was posted.
#
my $lastMessageTime;
#
# constant: TIME_BETWEEN_UPDATES
# The number of seconds that should occur between updates.
#
use constant TIME_BETWEEN_UPDATES => 10;
#
# Function: Start
#
# Starts the status message.
#
# Parameters:
#
# message - The message to post.
# total - The number of items that are going to be worked through.
#
sub Start #(message, total)
{
my $self = shift;
if (!NaturalDocs::Settings->IsQuiet())
{
($message, $total) = @_;
$completed = 0;
print $message . "\n";
$lastMessageTime = time();
};
};
#
# Function: CompletedItem
#
# Should be called every time an item is completed.
#
sub CompletedItem
{
my $self = shift;
if (!NaturalDocs::Settings->IsQuiet())
{
# We scale completed by 100 since we need to anyway to get the percentage.
$completed += 100;
if (time() >= $lastMessageTime + TIME_BETWEEN_UPDATES && $completed != $total * 100)
{
print $message . ' (' . ($completed / $total) . '%)' . "\n";
$lastMessageTime = time();
};
};
};
1;

View File

@ -0,0 +1,217 @@
###############################################################################
#
# Package: NaturalDocs::SymbolString
#
###############################################################################
#
# A package to manage <SymbolString> handling throughout the program.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::SymbolString;
use Encode qw(encode_utf8 decode_utf8);
#
# Function: FromText
#
# Extracts and returns a <SymbolString> from plain text.
#
# This should be the only way to get a <SymbolString> from plain text, as the splitting and normalization must be consistent
# throughout the application.
#
sub FromText #(string textSymbol)
{
my ($self, $textSymbol) = @_;
# The internal format of a symbol is all the normalized identifiers separated by 0x1F characters.
# Convert whitespace and reserved characters to spaces, and condense multiple consecutive ones.
$textSymbol =~ tr/ \t\r\n\x1C\x1D\x1E\x1F/ /s;
# DEPENDENCY: ReferenceString->MakeFrom() assumes all 0x1E characters were removed.
# DEPENDENCY: ReferenceString->MakeFrom() assumes this encoding doesn't use 0x1E characters.
# Remove spaces unless they're separating two alphanumeric/underscore characters.
$textSymbol =~ s/^ //;
$textSymbol =~ s/ $//;
$textSymbol =~ s/(\W) /$1/g;
$textSymbol =~ s/ (\W)/$1/g;
# Remove trailing empty parenthesis, so Function and Function() are equivalent.
$textSymbol =~ s/\(\)$//;
# Split the string into pieces.
my @pieces = split(/(\.|::|->)/, $textSymbol);
my $symbolString;
my $lastWasSeparator = 1;
foreach my $piece (@pieces)
{
if ($piece =~ /^(?:\.|::|->)$/)
{
if (!$lastWasSeparator)
{
$symbolString .= "\x1F";
$lastWasSeparator = 1;
};
}
elsif (length $piece)
{
$symbolString .= $piece;
$lastWasSeparator = 0;
};
# Ignore empty pieces
};
$symbolString =~ s/\x1F$//;
return $symbolString;
};
#
# Function: ToText
#
# Converts a <SymbolString> to text, using the passed separator.
#
sub ToText #(SymbolString symbolString, string separator)
{
my ($self, $symbolString, $separator) = @_;
my @identifiers = $self->IdentifiersOf($symbolString);
return join($separator, @identifiers);
};
#
# Function: ToBinaryFile
#
# Writes a <SymbolString> to the passed filehandle. Can also encode an undef.
#
# Parameters:
#
# fileHandle - The filehandle to write to.
# symbol - The <SymbolString> to write, or undef.
#
# Format:
#
# > [UInt8: number of identifiers]
# > [UString16: identifier] [UString16: identifier] ...
#
# Undef is represented by a zero for the number of identifiers.
#
sub ToBinaryFile #(FileHandle fileHandle, SymbolString symbol)
{
my ($self, $fileHandle, $symbol) = @_;
my @identifiers;
if (defined $symbol)
{ @identifiers = $self->IdentifiersOf($symbol); };
print $fileHandle pack('C', scalar @identifiers);
foreach my $identifier (@identifiers)
{
my $uIdentifier = encode_utf8($identifier);
print $fileHandle pack('na*', length($uIdentifier), $uIdentifier);
};
};
#
# Function: FromBinaryFile
#
# Loads a <SymbolString> or undef from the filehandle and returns it.
#
# Parameters:
#
# fileHandle - The filehandle to read from.
#
# Returns:
#
# The <SymbolString> or undef.
#
# See also:
#
# See <ToBinaryFile()> for format and dependencies.
#
sub FromBinaryFile #(FileHandle fileHandle)
{
my ($self, $fileHandle) = @_;
my $raw;
# [UInt8: number of identifiers or 0 if none]
read($fileHandle, $raw, 1);
my $identifierCount = unpack('C', $raw);
my @identifiers;
while ($identifierCount)
{
# [UString16: identifier] [UString16: identifier] ...
read($fileHandle, $raw, 2);
my $identifierLength = unpack('n', $raw);
my $identifier;
read($fileHandle, $identifier, $identifierLength);
$identifier = decode_utf8($identifier);
push @identifiers, $identifier;
$identifierCount--;
};
if (scalar @identifiers)
{ return $self->Join(@identifiers); }
else
{ return undef; };
};
#
# Function: IdentifiersOf
#
# Returns the <SymbolString> as an array of identifiers.
#
sub IdentifiersOf #(SymbolString symbol)
{
my ($self, $symbol) = @_;
return split(/\x1F/, $symbol);
};
#
# Function: Join
#
# Takes a list of identifiers and/or <SymbolStrings> and returns it as a new <SymbolString>.
#
sub Join #(string/SymbolString identifier/symbol, string/SymolString identifier/symbol, ...)
{
my ($self, @pieces) = @_;
# Can't have undefs screwing everything up.
while (scalar @pieces && !defined $pieces[0])
{ shift @pieces; };
# We need to test @pieces first because joining on an empty array returns an empty string rather than undef.
if (scalar @pieces)
{ return join("\x1F", @pieces); }
else
{ return undef; };
};
1;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,187 @@
###############################################################################
#
# Package: NaturalDocs::SymbolTable::File
#
###############################################################################
#
# A class representing a file, keeping track of what symbols and references are defined in it.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::SymbolTable::File;
###############################################################################
# Group: Implementation
#
# Constants: Members
#
# The class is implemented as a blessed arrayref. The following constants are its members.
#
# SYMBOLS - An existence hashref of the <SymbolStrings> it defines.
# REFERENCES - An existence hashref of the <ReferenceStrings> in the file.
#
# DEPENDENCY: New() depends on the order of these constants. If they change, New() has to be updated.
use constant SYMBOLS => 0;
use constant REFERENCES => 1;
###############################################################################
# Group: Modification Functions
#
# Function: New
#
# Creates and returns a new object.
#
sub New
{
my $package = shift;
# Let's make it safe, since normally you can pass values to New. Having them just be ignored would be an obscure error.
if (scalar @_)
{ die "You can't pass values to NaturalDocs::SymbolTable::File->New()\n"; };
# DEPENDENCY: This code depends on the order of the member constants.
my $object = [ { }, { } ];
bless $object, $package;
return $object;
};
#
# Function: AddSymbol
#
# Adds a <SymbolString> definition.
#
# Parameters:
#
# symbol - The <SymbolString> being added.
#
sub AddSymbol #(symbol)
{
my ($self, $symbol) = @_;
$self->[SYMBOLS]{$symbol} = 1;
};
#
# Function: DeleteSymbol
#
# Removes a <SymbolString> definition.
#
# Parameters:
#
# symbol - The <SymbolString> to delete.
#
sub DeleteSymbol #(symbol)
{
my ($self, $symbol) = @_;
delete $self->[SYMBOLS]{$symbol};
};
#
# Function: AddReference
#
# Adds a reference definition.
#
# Parameters:
#
# referenceString - The <ReferenceString> being added.
#
sub AddReference #(referenceString)
{
my ($self, $referenceString) = @_;
$self->[REFERENCES]{$referenceString} = 1;
};
#
# Function: DeleteReference
#
# Removes a reference definition.
#
# Parameters:
#
# referenceString - The <ReferenceString> to delete.
#
sub DeleteReference #(referenceString)
{
my ($self, $referenceString) = @_;
delete $self->[REFERENCES]{$referenceString};
};
###############################################################################
# Group: Information Functions
#
# Function: HasAnything
#
# Returns whether the file has any symbol or reference definitions at all.
#
sub HasAnything
{
return (scalar keys %{$_[0]->[SYMBOLS]} || scalar keys %{$_[0]->[REFERENCES]});
};
#
# Function: Symbols
#
# Returns an array of all the <SymbolStrings> defined in this file. If none, returns an empty array.
#
sub Symbols
{
return keys %{$_[0]->[SYMBOLS]};
};
#
# Function: References
#
# Returns an array of all the <ReferenceStrings> defined in this file. If none, returns an empty array.
#
sub References
{
return keys %{$_[0]->[REFERENCES]};
};
#
# Function: DefinesSymbol
#
# Returns whether the file defines the passed <SymbolString> or not.
#
sub DefinesSymbol #(symbol)
{
my ($self, $symbol) = @_;
return exists $self->[SYMBOLS]{$symbol};
};
#
# Function: DefinesReference
#
# Returns whether the file defines the passed <ReferenceString> or not.
#
sub DefinesReference #(referenceString)
{
my ($self, $referenceString) = @_;
return exists $self->[REFERENCES]{$referenceString};
};
1;

View File

@ -0,0 +1,523 @@
###############################################################################
#
# Class: NaturalDocs::SymbolTable::IndexElement
#
###############################################################################
#
# A class representing part of an indexed symbol.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use Tie::RefHash;
use strict;
use integer;
package NaturalDocs::SymbolTable::IndexElement;
#
# Topic: How IndexElements Work
#
# This is a little tricky, so make sure you understand this. Indexes are sorted by symbol, then packages, then file. If there is only
# one package for a symbol, or one file definition for a package/symbol, they are added inline to the entry. However, if there are
# multiple packages or files, the function for it returns an arrayref of IndexElements instead. Which members are defined and
# undefined should follow common sense. For example, if a symbol is defined in multiple packages, the symbol's IndexElement
# will not define <File()>, <Type()>, or <Prototype()>; those will be defined in child elements. Similarly, the child elements will
# not define <Symbol()> since it's redundant.
#
# Diagrams may be clearer. If a member isn't listed for an element, it isn't defined.
#
# A symbol that only has one package and file:
# > [Element]
# > - Symbol
# > - Package
# > - File
# > - Type
# > - Prototype
# > - Summary
#
# A symbol that is defined by multiple packages, each with only one file:
# > [Element]
# > - Symbol
# > - Package
# > [Element]
# > - Package
# > - File
# > - Type
# > - Prototype
# > - Summary
# > [Element]
# > - ...
#
# A symbol that is defined by one package, but has multiple files
# > [Element]
# > - Symbol
# > - Package
# > - File
# > [Element]
# > - File
# > - Type
# > - Protype
# > - Summary
# > [Element]
# > - ...
#
# A symbol that is defined by multiple packages which have multiple files:
# > [Element]
# > - Symbol
# > - Package
# > [Element]
# > - Package
# > - File
# > [Element]
# > - File
# > - Type
# > - Prototype
# > - Summary
# > [Element]
# > - ...
# > [Element]
# > - ...
#
# Why is it done this way?:
#
# Because it makes it easier to generate nice indexes since all the splitting and combining is done for you. If a symbol
# has only one package, you just want to link to it, you don't want to break out a subindex for just one package. However, if
# it has multiple package, you do want the subindex and to link to each one individually. Use <HasMultiplePackages()> and
# <HasMultipleFiles()> to determine whether you need to add a subindex for it.
#
#
# Combining Properties:
#
# All IndexElements also have combining properties set.
#
# CombinedType - The general <TopicType> of the entry. Conflicts combine into <TOPIC_GENERAL>.
# PackageSeparator - The package separator symbol of the entry. Conflicts combine into a dot.
#
# So if an IndexElement only has one definition, <CombinedType()> is the same as the <TopicType> and <PackageSeparator()>
# is that of the definition's language. If other definitions are added and they have the same properties, the combined properties
# will remain the same. However, if they're different, they switch values as noted above.
#
#
# Sortable Symbol:
#
# <SortableSymbol()> is a pseudo-combining property. There were a few options for dealing with multiple languages defining
# the same symbol but stripping different prefixes off it, but ultimately I decided to go with whatever the language does that
# has the most definitions. There's not likely to be many conflicts here in the real world; probably the only thing would be
# defining it in a text file and forgetting to specify the prefixes to strip there too. So this works.
#
# Ties are broken pretty much randomly, except that text files always lose if its one of the options.
#
# It's a pseudo-combining property because it's done after the IndexElements are all filled in and only stored in the top-level
# ones.
#
###############################################################################
# Group: Implementation
#
# Constants: Members
#
# The class is implemented as a blessed arrayref. The following constants are its members.
#
# SYMBOL - The <SymbolString> without the package portion.
# PACKAGE - The package <SymbolString>. Will be a package <SymbolString>, undef for global, or an arrayref of
# <NaturalDocs::SymbolTable::IndexElement> objects if multiple packages define the symbol.
# FILE - The <FileName> the package/symbol is defined in. Will be the file name or an arrayref of
# <NaturalDocs::SymbolTable::IndexElements> if multiple files define the package/symbol.
# TYPE - The package/symbol/file <TopicType>.
# PROTOTYPE - The package/symbol/file prototype, or undef if not applicable.
# SUMMARY - The package/symbol/file summary, or undef if not applicable.
# COMBINED_TYPE - The combined <TopicType> of the element.
# PACKAGE_SEPARATOR - The combined package separator symbol of the element.
# SORTABLE_SYMBOL - The sortable symbol as a text string.
# IGNORED_PREFIX - The part of the symbol that was stripped off to make the sortable symbol.
#
use NaturalDocs::DefineMembers 'SYMBOL', 'Symbol()',
'PACKAGE', 'Package()',
'FILE', 'File()',
'TYPE', 'Type()',
'PROTOTYPE', 'Prototype()',
'SUMMARY', 'Summary()',
'COMBINED_TYPE', 'CombinedType()',
'PACKAGE_SEPARATOR', 'PackageSeparator()',
'SORTABLE_SYMBOL', 'SortableSymbol()',
'IGNORED_PREFIX', 'IgnoredPrefix()';
# DEPENDENCY: New() depends on the order of these constants and that there is no inheritance..
###############################################################################
# Group: Modification Functions
#
# Function: New
#
# Returns a new object.
#
# This should only be used for creating an entirely new symbol. You should *not* pass arrayrefs as package or file parameters
# if you are calling this externally. Use <Merge()> instead.
#
# Parameters:
#
# symbol - The <SymbolString> without the package portion.
# package - The package <SymbolString>, or undef for global.
# file - The symbol's definition file.
# type - The symbol's <TopicType>.
# prototype - The symbol's prototype, if applicable.
# summary - The symbol's summary, if applicable.
#
# Optional Parameters:
#
# These parameters don't need to be specified. You should ignore them when calling this externally.
#
# combinedType - The symbol's combined <TopicType>.
# packageSeparator - The symbol's combined package separator symbol.
#
sub New #(symbol, package, file, type, prototype, summary, combinedType, packageSeparator)
{
# DEPENDENCY: This depends on the parameter list being in the same order as the constants.
my $self = shift;
my $object = [ @_ ];
bless $object, $self;
if (!defined $object->[COMBINED_TYPE])
{ $object->[COMBINED_TYPE] = $object->[TYPE]; };
if (!defined $object->[PACKAGE_SEPARATOR])
{
if ($object->[TYPE] eq ::TOPIC_FILE())
{ $object->[PACKAGE_SEPARATOR] = '.'; }
else
{
$object->[PACKAGE_SEPARATOR] = NaturalDocs::Languages->LanguageOf($object->[FILE])->PackageSeparator();
};
};
return $object;
};
#
# Function: Merge
#
# Adds another definition of the same symbol. Perhaps it has a different package or defining file.
#
# Parameters:
#
# package - The package <SymbolString>, or undef for global.
# file - The symbol's definition file.
# type - The symbol's <TopicType>.
# prototype - The symbol's protoype if applicable.
# summary - The symbol's summary if applicable.
#
sub Merge #(package, file, type, prototype, summary)
{
my ($self, $package, $file, $type, $prototype, $summary) = @_;
# If there's only one package...
if (!$self->HasMultiplePackages())
{
# If there's one package and it's the same as the new one...
if ($package eq $self->Package())
{
$self->MergeFile($file, $type, $prototype, $summary);
}
# If there's one package and the new one is different...
else
{
my $selfDefinition = NaturalDocs::SymbolTable::IndexElement->New(undef, $self->Package(), $self->File(),
$self->Type(), $self->Prototype(),
$self->Summary(), $self->CombinedType(),
$self->PackageSeparator());
my $newDefinition = NaturalDocs::SymbolTable::IndexElement->New(undef, $package, $file, $type, $prototype,
$summary);
$self->[PACKAGE] = [ $selfDefinition, $newDefinition ];
$self->[FILE] = undef;
$self->[TYPE] = undef;
$self->[PROTOTYPE] = undef;
$self->[SUMMARY] = undef;
if ($newDefinition->Type() ne $self->CombinedType())
{ $self->[COMBINED_TYPE] = ::TOPIC_GENERAL(); };
if ($newDefinition->PackageSeparator() ne $self->PackageSeparator())
{ $self->[PACKAGE_SEPARATOR] = '.'; };
};
}
# If there's more than one package...
else
{
# See if the new package is one of them.
my $selfPackages = $self->Package();
my $matchingPackage;
foreach my $testPackage (@$selfPackages)
{
if ($package eq $testPackage->Package())
{
$testPackage->MergeFile($file, $type, $prototype, $summary);;
return;
};
};
my $newDefinition = NaturalDocs::SymbolTable::IndexElement->New(undef, $package, $file, $type, $prototype,
$summary);
push @{$self->[PACKAGE]}, $newDefinition;
if ($newDefinition->Type() ne $self->CombinedType())
{ $self->[COMBINED_TYPE] = ::TOPIC_GENERAL(); };
if ($newDefinition->PackageSeparator() ne $self->PackageSeparator())
{ $self->[PACKAGE_SEPARATOR] = '.'; };
};
};
#
# Function: Sort
#
# Sorts the package and file lists of the symbol.
#
sub Sort
{
my $self = shift;
if ($self->HasMultipleFiles())
{
@{$self->[FILE]} = sort { ::StringCompare($a->File(), $b->File()) } @{$self->File()};
}
elsif ($self->HasMultiplePackages())
{
@{$self->[PACKAGE]} = sort { ::StringCompare( $a->Package(), $b->Package()) } @{$self->[PACKAGE]};
foreach my $packageElement ( @{$self->[PACKAGE]} )
{
if ($packageElement->HasMultipleFiles())
{ $packageElement->Sort(); };
};
};
};
#
# Function: MakeSortableSymbol
#
# Generates <SortableSymbol()> and <IgnoredPrefix()>. Should only be called after everything is merged.
#
sub MakeSortableSymbol
{
my $self = shift;
my $finalLanguage;
if ($self->HasMultiplePackages() || $self->HasMultipleFiles())
{
# Collect all the files that define this symbol.
my @files;
if ($self->HasMultipleFiles())
{
my $fileElements = $self->File();
foreach my $fileElement (@$fileElements)
{ push @files, $fileElement->File(); };
}
else # HasMultiplePackages
{
my $packages = $self->Package();
foreach my $package (@$packages)
{
if ($package->HasMultipleFiles())
{
my $fileElements = $package->File();
foreach my $fileElement (@$fileElements)
{ push @files, $fileElement->File(); };
}
else
{ push @files, $package->File(); };
};
};
# Determine which language defines it the most.
# Keys are language objects, values are counts.
my %languages;
tie %languages, 'Tie::RefHash';
foreach my $file (@files)
{
my $language = NaturalDocs::Languages->LanguageOf($file);
if (exists $languages{$language})
{ $languages{$language}++; }
else
{ $languages{$language} = 1; };
};
my $topCount = 0;
my @topLanguages;
while (my ($language, $count) = each %languages)
{
if ($count > $topCount)
{
$topCount = $count;
@topLanguages = ( $language );
}
elsif ($count == $topCount)
{
push @topLanguages, $language;
};
};
if (scalar @topLanguages == 1)
{ $finalLanguage = $topLanguages[0]; }
else
{
if ($topLanguages[0]->Name() ne 'Text File')
{ $finalLanguage = $topLanguages[0]; }
else
{ $finalLanguage = $topLanguages[1]; };
};
}
else # !hasMultiplePackages && !hasMultipleFiles
{ $finalLanguage = NaturalDocs::Languages->LanguageOf($self->File()); };
my $textSymbol = NaturalDocs::SymbolString->ToText($self->Symbol(), $self->PackageSeparator());
my $ignoredPrefixLength = $finalLanguage->IgnoredPrefixLength($textSymbol, $self->CombinedType());
if ($ignoredPrefixLength)
{
$self->[IGNORED_PREFIX] = substr($textSymbol, 0, $ignoredPrefixLength);
$self->[SORTABLE_SYMBOL] = substr($textSymbol, $ignoredPrefixLength);
}
else
{ $self->[SORTABLE_SYMBOL] = $textSymbol; };
};
###############################################################################
#
# Functions: Information Functions
#
# Symbol - Returns the <SymbolString> without the package portion.
# Package - If <HasMultiplePackages()> is true, returns an arrayref of <NaturalDocs::SymbolTable::IndexElement> objects.
# Otherwise returns the package <SymbolString>, or undef if global.
# File - If <HasMultipleFiles()> is true, returns an arrayref of <NaturalDocs::SymbolTable::IndexElement> objects. Otherwise
# returns the name of the definition file.
# Type - Returns the <TopicType> of the package/symbol/file, if applicable.
# Prototype - Returns the prototype of the package/symbol/file, if applicable.
# Summary - Returns the summary of the package/symbol/file, if applicable.
# CombinedType - Returns the combined <TopicType> of the element.
# PackageSeparator - Returns the combined package separator symbol of the element.
# SortableSymbol - Returns the sortable symbol as a text string. Only available after calling <MakeSortableSymbol()>.
# IgnoredPrefix - Returns the part of the symbol that was stripped off to make the <SortableSymbol()>, or undef if none.
# Only available after calling <MakeSortableSymbol()>.
#
# Function: HasMultiplePackages
# Returns whether <Packages()> is broken out into more elements.
sub HasMultiplePackages
{ return ref($_[0]->[PACKAGE]); };
# Function: HasMultipleFiles
# Returns whether <File()> is broken out into more elements.
sub HasMultipleFiles
{ return ref($_[0]->[FILE]); };
###############################################################################
# Group: Support Functions
#
# Function: MergeFile
#
# Adds another definition of the same package/symbol. Perhaps the file is different.
#
# Parameters:
#
# file - The package/symbol's definition file.
# type - The package/symbol's <TopicType>.
# prototype - The package/symbol's protoype if applicable.
# summary - The package/symbol's summary if applicable.
#
sub MergeFile #(file, type, prototype, summary)
{
my ($self, $file, $type, $prototype, $summary) = @_;
# If there's only one file...
if (!$self->HasMultipleFiles())
{
# If there's one file and it's the different from the new one...
if ($file ne $self->File())
{
my $selfDefinition = NaturalDocs::SymbolTable::IndexElement->New(undef, undef, $self->File(), $self->Type(),
$self->Prototype(), $self->Summary(),
$self->CombinedType(),
$self->PackageSeparator());
my $newDefinition = NaturalDocs::SymbolTable::IndexElement->New(undef, undef, $file, $type, $prototype,
$summary);
$self->[FILE] = [ $selfDefinition, $newDefinition ];
$self->[TYPE] = undef;
$self->[PROTOTYPE] = undef;
$self->[SUMMARY] = undef;
if ($newDefinition->Type() ne $self->CombinedType())
{ $self->[COMBINED_TYPE] = ::TOPIC_GENERAL(); };
if ($newDefinition->PackageSeparator() ne $self->PackageSeparator())
{ $self->[PACKAGE_SEPARATOR] = '.'; };
}
# If the file was the same, just ignore the duplicate in the index.
}
# If there's more than one file...
else
{
# See if the new file is one of them.
my $files = $self->File();
foreach my $testElement (@$files)
{
if ($testElement->File() eq $file)
{
# If the new file's already in the index, ignore the duplicate.
return;
};
};
my $newDefinition = NaturalDocs::SymbolTable::IndexElement->New(undef, undef, $file, $type, $prototype,
$summary);
push @{$self->[FILE]}, $newDefinition;
if ($newDefinition->Type() ne $self->CombinedType())
{ $self->[COMBINED_TYPE] = ::TOPIC_GENERAL(); };
if ($newDefinition->PackageSeparator() ne $self->PackageSeparator())
{ $self->[PACKAGE_SEPARATOR] = '.'; };
};
};
1;

View File

@ -0,0 +1,274 @@
###############################################################################
#
# Package: NaturalDocs::SymbolTable::Reference
#
###############################################################################
#
# A class representing a symbol or a potential symbol.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::SymbolTable::Reference;
###############################################################################
# Group: Implementation
#
# Constants: Members
#
# The class is implemented as a blessed arrayref. The following constants are its members.
#
# DEFINITIONS - An existence hashref of the <FileNames> that define this reference.
# INTERPRETATIONS - A hashref of the possible interpretations of this reference. The keys are the <SymbolStrings>
# and the values are the scores.
# CURRENT_INTERPRETATION - The interpretation currently used as the reference target. It will be the interpretation with
# the highest score that is actually defined. If none are defined, this item will be undef.
#
# DEPENDENCY: New() depends on the order of these constants. If they change, New() has to be updated.
use constant DEFINITIONS => 0;
use constant INTERPRETATIONS => 1;
use constant CURRENT_INTERPRETATION => 2;
###############################################################################
# Group: Modification Functions
#
# Function: New
#
# Creates and returns a new object.
#
sub New
{
my $package = shift;
# Let's make it safe, since normally you can pass values to New. Having them just be ignored would be an obscure error.
if (scalar @_)
{ die "You can't pass values to NaturalDocs::SymbolTable::Reference->New()\n"; };
# DEPENDENCY: This code depends on the order of the member constants.
my $object = [ { }, { }, undef ];
bless $object, $package;
return $object;
};
#
# Function: AddDefinition
#
# Adds a reference definition.
#
# Parameters:
#
# file - The <FileName> that defines the reference.
#
sub AddDefinition #(file)
{
my ($self, $file) = @_;
$self->[DEFINITIONS]{$file} = 1;
};
#
# Function: DeleteDefinition
#
# Removes a reference definition.
#
# Parameters:
#
# file - The <FileName> which has the definition to delete.
#
sub DeleteDefinition #(file)
{
my ($self, $file) = @_;
delete $self->[DEFINITIONS]{$file};
};
#
# Function: AddInterpretation
#
# Adds a symbol that this reference can be interpreted as.
#
# Parameters:
#
# symbol - The <SymbolString>.
# score - The score of this interpretation.
#
sub AddInterpretation #(symbol, score)
{
my ($self, $symbol, $score) = @_;
$self->[INTERPRETATIONS]{$symbol} = $score;
};
#
# Function: DeleteInterpretation
#
# Deletes a symbol that this reference can be interpreted as.
#
# Parameters:
#
# symbol - The <SymbolString> to delete.
#
sub DeleteInterpretation #(symbol)
{
my ($self, $symbol) = @_;
delete $self->[INTERPRETATIONS]{$symbol};
};
#
# Function: DeleteAllInterpretationsButCurrent
#
# Deletes all interpretations except for the current one.
#
sub DeleteAllInterpretationsButCurrent
{
my $self = shift;
if ($self->HasCurrentInterpretation())
{
my $score = $self->CurrentScore();
# Fastest way to clear a hash except for one item? Make a new hash with just that item.
%{$self->[INTERPRETATIONS]} = ( $self->[CURRENT_INTERPRETATION] => $score );
};
};
#
# Function: SetCurrentInterpretation
#
# Changes the current interpretation. The new one must already have been added via <AddInterpretation()>.
#
# Parameters:
#
# symbol - The <SymbolString>l to make the current interpretation. Can be set to undef to clear it.
#
sub SetCurrentInterpretation #(symbol)
{
my ($self, $symbol) = @_;
$self->[CURRENT_INTERPRETATION] = $symbol;
};
###############################################################################
# Group: Information Functions
#
# Function: Definitions
#
# Returns an array of all the <FileNames> that define this reference. If none do, returns an empty array.
#
sub Definitions
{
return keys %{$_[0]->[DEFINITIONS]};
};
#
# Function: IsDefined
#
# Returns whether the reference has any definitions or not.
#
sub IsDefined
{
return scalar keys %{$_[0]->[DEFINITIONS]};
};
#
# Function: IsDefinedIn
#
# Returns whether the reference is defined in the passed <FileName>.
#
sub IsDefinedIn #(file)
{
my ($self, $file) = @_;
return exists $self->[DEFINITIONS]{$file};
};
#
# Function: Interpretations
#
# Returns an array of all the <SymbolStrings> that this reference can be interpreted as. If none, returns an empty array.
#
sub Interpretations
{
return keys %{$_[0]->[INTERPRETATIONS]};
};
#
# Function: InterpretationsAndScores
#
# Returns a hash of all the <SymbolStrings> that this reference can be interpreted as and their scores. The keys are the <SymbolStrings>
# and the values are the scores. If none, returns an empty hash.
#
sub InterpretationsAndScores
{
return %{$_[0]->[INTERPRETATIONS]};
};
#
# Function: HasCurrentInterpretation
#
# Returns whether the reference has a current interpretation or not.
#
sub HasCurrentInterpretation
{
return defined $_[0]->[CURRENT_INTERPRETATION];
};
#
# Function: CurrentInterpretation
#
# Returns the <SymbolString> of the current interpretation, or undef if none.
#
sub CurrentInterpretation
{
return $_[0]->[CURRENT_INTERPRETATION];
};
#
# Function: CurrentScore
#
# Returns the score of the current interpretation, or undef if none.
#
sub CurrentScore
{
my $self = shift;
if (defined $self->[CURRENT_INTERPRETATION])
{
return $self->[INTERPRETATIONS]{ $self->[CURRENT_INTERPRETATION] };
}
else
{ return undef; };
};
1;

View File

@ -0,0 +1,98 @@
###############################################################################
#
# Class: NaturalDocs::SymbolTable::ReferenceTarget
#
###############################################################################
#
# A class for storing information about a reference target.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::SymbolTable::ReferenceTarget;
###############################################################################
# Group: Implementation
#
# Constants: Members
#
# The class is implemented as a blessed arrayref. The following constants are its members.
#
# SYMBOL - The target <SymbolString>.
# FILE - The <FileName> the target is defined in.
# TYPE - The target <TopicType>.
# PROTOTYPE - The target's prototype, or undef if none.
# SUMMARY - The target's summary, or undef if none.
#
# DEPENDENCY: New() depends on the order of these constants. If they change, New() has to be updated.
use constant SYMBOL => 0;
use constant FILE => 1;
use constant TYPE => 2;
use constant PROTOTYPE => 3;
use constant SUMMARY => 4;
###############################################################################
# Group: Functions
#
# Function: New
#
# Creates and returns a new object.
#
# Parameters:
#
# symbol - The target <SymbolString>.
# file - The <FileName> the target is defined in.
# type - The <TopicType> of the target symbol.
# prototype - The target's prototype. Set to undef if not defined or not applicable.
# summary - The target's summary. Set to undef if not defined or not applicable.
#
sub New #(symbol, file, type, prototype, summary)
{
# DEPENDENCY: This code depends on the order of the member constants.
my $package = shift;
my $object = [ @_ ];
bless $object, $package;
return $object;
};
# Function: Symbol
# Returns the target's <SymbolString>.
sub Symbol
{ return $_[0]->[SYMBOL]; };
# Function: File
# Returns the <FileName> the target is defined in.
sub File
{ return $_[0]->[FILE]; };
# Function: Type
# Returns the target's <TopicType>.
sub Type
{ return $_[0]->[TYPE]; };
# Function: Prototype
# Returns the target's prototype, or undef if not defined or not applicable.
sub Prototype
{ return $_[0]->[PROTOTYPE]; };
# Function: Summary
# Returns the target's summary, or undef if not defined or not applicable.
sub Summary
{ return $_[0]->[SUMMARY]; };
1;

View File

@ -0,0 +1,429 @@
###############################################################################
#
# Package: NaturalDocs::SymbolTable::Symbol
#
###############################################################################
#
# A class representing a symbol or a potential symbol.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::SymbolTable::Symbol;
###############################################################################
# Group: Implementation
#
# Constants: Members
#
# The class is implemented as a blessed arrayref. The following constants are its members.
#
# DEFINITIONS - A hashref of all the files which define this symbol. The keys are the <FileNames>, and the values are
# <NaturalDocs::SymbolTable::SymbolDefinition> objects. If no files define this symbol, this item will
# be undef.
# GLOBAL_DEFINITION - The <FileName> which defines the global version of the symbol, which is what is used if
# a file references the symbol but does not have its own definition. If there are no definitions, this
# item will be undef.
# REFERENCES - A hashref of the references that can be interpreted as this symbol. This doesn't mean these
# references necessarily are. The keys are the reference strings, and the values are the scores of
# the interpretations. If no references can be interpreted as this symbol, this item will be undef.
#
use constant DEFINITIONS => 0;
use constant GLOBAL_DEFINITION => 1;
use constant REFERENCES => 2;
###############################################################################
# Group: Modification Functions
#
# Function: New
#
# Creates and returns a new object.
#
sub New
{
my $package = shift;
# Let's make it safe, since normally you can pass values to New. Having them just be ignored would be an obscure error.
if (scalar @_)
{ die "You can't pass values to NaturalDocs::SymbolTable::Symbol->New()\n"; };
my $object = [ undef, undef, undef ];
bless $object, $package;
return $object;
};
#
# Function: AddDefinition
#
# Adds a symbol definition. If this is the first definition for this symbol, it will become the global definition. If the definition
# already exists for the file, it will be ignored.
#
# Parameters:
#
# file - The <FileName> that defines the symbol.
# type - The <TopicType> of the definition.
# prototype - The prototype of the definition, if applicable. Undef otherwise.
# summary - The summary for the definition, if applicable. Undef otherwise.
#
# Returns:
#
# Whether this provided the first definition for this symbol.
#
sub AddDefinition #(file, type, prototype, summary)
{
my ($self, $file, $type, $prototype, $summary) = @_;
my $isFirst;
if (!defined $self->[DEFINITIONS])
{
$self->[DEFINITIONS] = { };
$self->[GLOBAL_DEFINITION] = $file;
$isFirst = 1;
};
if (!exists $self->[DEFINITIONS]{$file})
{
$self->[DEFINITIONS]{$file} = NaturalDocs::SymbolTable::SymbolDefinition->New($type, $prototype, $summary);
};
return $isFirst;
};
#
# Function: ChangeDefinition
#
# Changes the information about an existing definition.
#
# Parameters:
#
# file - The <FileName> that defines the symbol. Must exist.
# type - The new <TopicType> of the definition.
# prototype - The new prototype of the definition, if applicable. Undef otherwise.
# summary - The new summary of the definition, if applicable. Undef otherwise.
#
sub ChangeDefinition #(file, type, prototype, summary)
{
my ($self, $file, $type, $prototype, $summary) = @_;
if (defined $self->[DEFINITIONS] &&
exists $self->[DEFINITIONS]{$file})
{
$self->[DEFINITIONS]{$file}->SetType($type);
$self->[DEFINITIONS]{$file}->SetPrototype($prototype);
$self->[DEFINITIONS]{$file}->SetSummary($summary);
};
};
#
# Function: DeleteDefinition
#
# Removes a symbol definition. If the definition served as the global definition, a new one will be selected.
#
# Parameters:
#
# file - The <FileName> which contains definition to delete.
#
# Returns:
#
# Whether that was the only definition, and the symbol is now undefined.
#
sub DeleteDefinition #(file)
{
my ($self, $file) = @_;
# If there are no definitions...
if (!defined $self->[DEFINITIONS])
{ return undef; };
delete $self->[DEFINITIONS]{$file};
# If there are no more definitions...
if (!scalar keys %{$self->[DEFINITIONS]})
{
$self->[DEFINITIONS] = undef;
# If definitions was previously defined, and now is empty, we can safely assume that the global definition was just deleted
# without checking it against $file.
$self->[GLOBAL_DEFINITION] = undef;
return 1;
}
# If there are more definitions and the global one was just deleted...
elsif ($self->[GLOBAL_DEFINITION] eq $file)
{
# Which one becomes global is pretty much random.
$self->[GLOBAL_DEFINITION] = (keys %{$self->[DEFINITIONS]})[0];
return undef;
};
};
#
# Function: AddReference
#
# Adds a reference that can be interpreted as this symbol. It can be, but not necessarily is.
#
# Parameters:
#
# referenceString - The string of the reference.
# score - The score of this interpretation.
#
sub AddReference #(referenceString, score)
{
my ($self, $referenceString, $score) = @_;
if (!defined $self->[REFERENCES])
{ $self->[REFERENCES] = { }; };
$self->[REFERENCES]{$referenceString} = $score;
};
#
# Function: DeleteReference
#
# Deletes a reference that can be interpreted as this symbol.
#
# Parameters:
#
# referenceString - The string of the reference to delete.
#
sub DeleteReference #(referenceString)
{
my ($self, $referenceString) = @_;
# If there are no definitions...
if (!defined $self->[REFERENCES])
{ return; };
delete $self->[REFERENCES]{$referenceString};
# If there are no more definitions...
if (!scalar keys %{$self->[REFERENCES]})
{
$self->[REFERENCES] = undef;
};
};
#
# Function: DeleteAllReferences
#
# Removes all references that can be interpreted as this symbol.
#
sub DeleteAllReferences
{
$_[0]->[REFERENCES] = undef;
};
###############################################################################
# Group: Information Functions
#
# Function: IsDefined
#
# Returns whether the symbol is defined anywhere or not. If it's not, that means it's just a potential interpretation of a
# reference.
#
sub IsDefined
{
return defined $_[0]->[GLOBAL_DEFINITION];
};
#
# Function: IsDefinedIn
#
# Returns whether the symbol is defined in the passed <FileName>.
#
sub IsDefinedIn #(file)
{
my ($self, $file) = @_;
return ($self->IsDefined() && exists $self->[DEFINITIONS]{$file});
};
#
# Function: Definitions
#
# Returns an array of all the <FileNames> that define this symbol. If none do, will return an empty array.
#
sub Definitions
{
my $self = shift;
if ($self->IsDefined())
{ return keys %{$self->[DEFINITIONS]}; }
else
{ return ( ); };
};
#
# Function: GlobalDefinition
#
# Returns the <FileName> that contains the global definition of this symbol, or undef if the symbol isn't defined.
#
sub GlobalDefinition
{
return $_[0]->[GLOBAL_DEFINITION];
};
#
# Function: TypeDefinedIn
#
# Returns the <TopicType> of the symbol defined in the passed <FileName>, or undef if it's not defined in that file.
#
sub TypeDefinedIn #(file)
{
my ($self, $file) = @_;
if ($self->IsDefined())
{ return $self->[DEFINITIONS]{$file}->Type(); }
else
{ return undef; };
};
#
# Function: GlobalType
#
# Returns the <TopicType> of the global definition, or undef if the symbol isn't defined.
#
sub GlobalType
{
my $self = shift;
my $globalDefinition = $self->GlobalDefinition();
if (!defined $globalDefinition)
{ return undef; }
else
{ return $self->[DEFINITIONS]{$globalDefinition}->Type(); };
};
#
# Function: PrototypeDefinedIn
#
# Returns the prototype of symbol defined in the passed <FileName>, or undef if it doesn't exist or is not defined in that file.
#
sub PrototypeDefinedIn #(file)
{
my ($self, $file) = @_;
if ($self->IsDefined())
{ return $self->[DEFINITIONS]{$file}->Prototype(); }
else
{ return undef; };
};
#
# Function: GlobalPrototype
#
# Returns the prototype of the global definition. Will be undef if it doesn't exist or the symbol isn't defined.
#
sub GlobalPrototype
{
my $self = shift;
my $globalDefinition = $self->GlobalDefinition();
if (!defined $globalDefinition)
{ return undef; }
else
{ return $self->[DEFINITIONS]{$globalDefinition}->Prototype(); };
};
#
# Function: SummaryDefinedIn
#
# Returns the summary of symbol defined in the passed <FileName>, or undef if it doesn't exist or is not defined in that file.
#
sub SummaryDefinedIn #(file)
{
my ($self, $file) = @_;
if ($self->IsDefined())
{ return $self->[DEFINITIONS]{$file}->Summary(); }
else
{ return undef; };
};
#
# Function: GlobalSummary
#
# Returns the summary of the global definition. Will be undef if it doesn't exist or the symbol isn't defined.
#
sub GlobalSummary
{
my $self = shift;
my $globalDefinition = $self->GlobalDefinition();
if (!defined $globalDefinition)
{ return undef; }
else
{ return $self->[DEFINITIONS]{$globalDefinition}->Summary(); };
};
#
# Function: HasReferences
#
# Returns whether the symbol can be interpreted as any references.
#
sub HasReferences
{
return defined $_[0]->[REFERENCES];
};
#
# Function: References
#
# Returns an array of all the reference strings that can be interpreted as this symbol. If none, will return an empty array.
#
sub References
{
if (defined $_[0]->[REFERENCES])
{ return keys %{$_[0]->[REFERENCES]}; }
else
{ return ( ); };
};
#
# Function: ReferencesAndScores
#
# Returns a hash of all the references that can be interpreted as this symbol and their scores. The keys are the reference
# strings, and the values are the scores. If none, will return an empty hash.
#
sub ReferencesAndScores
{
if (defined $_[0]->[REFERENCES])
{ return %{$_[0]->[REFERENCES]}; }
else
{ return ( ); };
};
1;

View File

@ -0,0 +1,97 @@
###############################################################################
#
# Package: NaturalDocs::SymbolTable::SymbolDefinition
#
###############################################################################
#
# A class representing a symbol definition. This does not store the definition symbol, class, or file.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::SymbolTable::SymbolDefinition;
###############################################################################
# Group: Implementation
#
# Constants: Members
#
# The class is implemented as a blessed arrayref. The following constants are its members.
#
# TYPE - The symbol <TopicType>.
# PROTOTYPE - The symbol's prototype, if applicable. Will be undef otherwise.
# SUMMARY - The symbol's summary, if applicable. Will be undef otherwise.
#
use constant TYPE => 0;
use constant PROTOTYPE => 1;
use constant SUMMARY => 2;
# New depends on the order of the constants.
###############################################################################
# Group: Functions
#
# Function: New
#
# Creates and returns a new object.
#
# Parameters:
#
# type - The symbol <TopicType>.
# prototype - The symbol prototype, if applicable. Undef otherwise.
# summary - The symbol's summary, if applicable. Undef otherwise.
#
sub New #(type, prototype, summary)
{
# This depends on the parameter list being the same as the constant order.
my $package = shift;
my $object = [ @_ ];
bless $object, $package;
return $object;
};
# Function: Type
# Returns the definition's <TopicType>.
sub Type
{ return $_[0]->[TYPE]; };
# Function: SetType
# Changes the <TopicType>.
sub SetType #(type)
{ $_[0]->[TYPE] = $_[1]; };
# Function: Prototype
# Returns the definition's prototype, or undef if it doesn't have one.
sub Prototype
{ return $_[0]->[PROTOTYPE]; };
# Function: SetPrototype
# Changes the prototype.
sub SetPrototype #(prototype)
{ $_[0]->[PROTOTYPE] = $_[1]; };
# Function: Summary
# Returns the definition's summary, or undef if it doesn't have one.
sub Summary
{ return $_[0]->[SUMMARY]; };
# Function: SetSummary
# Changes the summary.
sub SetSummary #(summary)
{ $_[0]->[SUMMARY] = $_[1]; };
1;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,152 @@
###############################################################################
#
# Package: NaturalDocs::Topics::Type
#
###############################################################################
#
# A class storing information about a <TopicType>.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::Topics::Type;
use NaturalDocs::DefineMembers 'NAME', 'Name()',
'PLURAL_NAME', 'PluralName()', 'SetPluralName()',
'INDEX', 'Index()', 'SetIndex()',
'SCOPE', 'Scope()', 'SetScope()',
'PAGE_TITLE_IF_FIRST', 'PageTitleIfFirst()', 'SetPageTitleIfFirst()',
'BREAK_LISTS', 'BreakLists()', 'SetBreakLists()',
'CLASS_HIERARCHY', 'ClassHierarchy()', 'SetClassHierarchy()',
'CAN_GROUP_WITH';
# Dependency: New() depends on the order of these and that there are no parent classes.
use base 'Exporter';
our @EXPORT = ('SCOPE_NORMAL', 'SCOPE_START', 'SCOPE_END', 'SCOPE_ALWAYS_GLOBAL');
#
# Constants: Members
#
# The object is implemented as a blessed arrayref, with the following constants as its indexes.
#
# NAME - The topic's name.
# PLURAL_NAME - The topic's plural name.
# INDEX - Whether the topic is indexed.
# SCOPE - The topic's <ScopeType>.
# PAGE_TITLE_IF_FIRST - Whether the topic becomes the page title if it's first in a file.
# BREAK_LISTS - Whether list topics should be broken into individual topics in the output.
# CLASS_HIERARCHY - Whether the topic is part of the class hierarchy.
# CAN_GROUP_WITH - The existence hashref of <TopicTypes> the type can be grouped with.
#
###############################################################################
# Group: Types
#
# Constants: ScopeType
#
# The possible values for <Scope()>.
#
# SCOPE_NORMAL - The topic stays in the current scope without affecting it.
# SCOPE_START - The topic starts a scope.
# SCOPE_END - The topic ends a scope, returning it to global.
# SCOPE_ALWAYS_GLOBAL - The topic is always global, but it doesn't affect the current scope.
#
use constant SCOPE_NORMAL => 1;
use constant SCOPE_START => 2;
use constant SCOPE_END => 3;
use constant SCOPE_ALWAYS_GLOBAL => 4;
###############################################################################
# Group: Functions
#
# Function: New
#
# Creates and returns a new object.
#
# Parameters:
#
# name - The topic name.
# pluralName - The topic's plural name.
# index - Whether the topic is indexed.
# scope - The topic's <ScopeType>.
# pageTitleIfFirst - Whether the topic becomes the page title if it's the first one in a file.
# breakLists - Whether list topics should be broken into individual topics in the output.
#
sub New #(name, pluralName, index, scope, pageTitleIfFirst, breakLists)
{
my ($self, @params) = @_;
# Dependency: Depends on the parameter order matching the member order and that there are no parent classes.
my $object = [ @params ];
bless $object, $self;
return $object;
};
#
# Functions: Accessors
#
# Name - Returns the topic name.
# PluralName - Returns the topic's plural name.
# SetPluralName - Replaces the topic's plural name.
# Index - Whether the topic is indexed.
# SetIndex - Sets whether the topic is indexed.
# Scope - Returns the topic's <ScopeType>.
# SetScope - Replaces the topic's <ScopeType>.
# PageTitleIfFirst - Returns whether the topic becomes the page title if it's first in the file.
# SetPageTitleIfFirst - Sets whether the topic becomes the page title if it's first in the file.
# BreakLists - Returns whether list topics should be broken into individual topics in the output.
# SetBreakLists - Sets whether list topics should be broken into individual topics in the output.
# ClassHierarchy - Returns whether the topic is part of the class hierarchy.
# SetClassHierarchy - Sets whether the topic is part of the class hierarchy.
#
#
# Function: CanGroupWith
#
# Returns whether the type can be grouped with the passed <TopicType>.
#
sub CanGroupWith #(TopicType type) -> bool
{
my ($self, $type) = @_;
return ( defined $self->[CAN_GROUP_WITH] && exists $self->[CAN_GROUP_WITH]->{$type} );
};
#
# Function: SetCanGroupWith
#
# Sets the list of <TopicTypes> the type can be grouped with.
#
sub SetCanGroupWith #(TopicType[] types)
{
my ($self, $types) = @_;
$self->[CAN_GROUP_WITH] = { };
foreach my $type (@$types)
{ $self->[CAN_GROUP_WITH]->{$type} = 1; };
};
1;

View File

@ -0,0 +1,361 @@
###############################################################################
#
# Package: NaturalDocs::Version
#
###############################################################################
#
# A package for handling version information. What? That's right. Although it should be easy and obvious, version numbers
# need to be dealt with in a variety of formats, plus there's compatibility with older releases which handled it differently. I
# wanted to centralize the code after it started getting complicated. So there ya go.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright <20> 2003-2010 Greg Valure
# Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
# Refer to License.txt for the complete details
use strict;
use integer;
package NaturalDocs::Version;
###############################################################################
# Group: Functions
#
# Function: ToString
#
# Converts a <VersionInt> to a string.
#
sub ToString #(VersionInt version) => string
{
my ($self, $version) = @_;
my ($major, $minor, $month, $day, $year) = $self->ToValues($version);
if ($minor % 10 == 0)
{ $minor /= 10; };
if ($day)
{ return sprintf('Development Release %02d-%02d-%d (%d.%d base)', $month, $day, $year, $major, $minor); }
else
{ return $major . '.' . $minor; };
};
#
# Function: FromString
#
# Converts a version string to a <VersionInt>.
#
sub FromString #(string string) => VersionInt
{
my ($self, $string) = @_;
if ($string eq '1')
{
return $self->FromValues(0, 91, 0, 0, 0); # 0.91
}
else
{
my ($major, $minor, $month, $day, $year);
if ($string =~ /^(\d{1,2})\.(\d{1,2})$/)
{
($major, $minor) = ($1, $2);
($month, $day, $year) = (0, 0, 0);
}
elsif ($string =~ /^Development Release (\d{1,2})-(\d{1,2})-(\d\d\d\d) \((\d{1,2})\.(\d{1,2}) base\)$/)
{
($month, $day, $year, $major, $minor) = ($1, $2, $3, $4, $5);
# We have to do sanity checking because these can come from user-editable text files. The version numbers should
# already be constrained simply by being forced to have only two digits.
if ($month > 12 || $month < 1 || $day > 31 || $day < 1 || $year > 2255 || $year < 2000)
{ die 'The version string ' . $string . " doesn't have a valid date.\n"; };
}
else
{
die 'The version string ' . $string . " isn't in a recognized format.\n";
};
if (length $minor == 1)
{ $minor *= 10; };
return $self->FromValues($major, $minor, $month, $day, $year);
};
};
#
# Function: ToTextFile
#
# Writes a <VersionInt> to a text file.
#
# Parameters:
#
# fileHandle - The handle of the file to write it to. It should be at the correct location.
# version - The <VersionInt> to write.
#
sub ToTextFile #(handle fileHandle, VersionInt version)
{
my ($self, $fileHandle, $version) = @_;
print $fileHandle $self->ToString($version) . "\n";
};
#
# Function: ToBinaryFile
#
# Writes a <VersionInt> to a binary file.
#
# Parameters:
#
# fileHandle - The handle of the file to write it to. It should be at the correct location.
# version - The <VersionInt> to write.
#
sub ToBinaryFile #(handle fileHandle, VersionInt version)
{
my ($self, $fileHandle, $version) = @_;
my ($major, $minor, $month, $day, $year) = $self->ToValues($version);
# 1.35 development releases are encoded as 1.36. Everything else is literal.
if ($day && $major == 1 && $minor == 35)
{ $minor = 36; };
print $fileHandle pack('CC', $major, $minor);
# Date fields didn't exist with 1.35 stable and earlier. 1.35 development releases are encoded as 1.36, so this works.
if ($major > 1 || ($major == 1 && $minor > 35))
{
if ($day)
{ $year -= 2000; };
print $fileHandle pack('CCC', $month, $day, $year);
};
};
#
# Function: FromBinaryFile
#
# Retrieves a <VersionInt> from a binary file.
#
# Parameters:
#
# fileHandle - The handle of the file to read it from. It should be at the correct location.
#
# Returns:
#
# The <VersionInt>.
#
sub FromBinaryFile #(handle fileHandle) => VersionInt
{
my ($self, $fileHandle) = @_;
my ($major, $minor, $month, $day, $year);
my $raw;
read($fileHandle, $raw, 2);
($major, $minor) = unpack('CC', $raw);
# 1.35 stable is the last release without the date fields. 1.35 development releases are encoded as 1.36, so this works.
if ($major > 1 || ($major == 1 && $minor > 35))
{
read($fileHandle, $raw, 3);
($month, $day, $year) = unpack('CCC', $raw);
if ($day)
{ $year += 2000; };
}
else
{ ($month, $day, $year) = (0, 0, 0); };
# Fix the 1.35 development release special encoding.
if ($major == 1 && $minor == 36)
{ $minor = 35; };
return $self->FromValues($major, $minor, $month, $day, $year);
};
#
# Function: ToValues
#
# Converts a <VersionInt> to the array ( major, minor, month, day, year ). The minor version will be in two digit form, so x.2
# will return 20. The date fields will be zero for stable releases.
#
sub ToValues #(VersionInt version) => ( int, int, int, int, int )
{
my ($self, $version) = @_;
my $major = ($version & 0x00003F80) >> 7;
my $minor = ($version & 0x0000007F);
my $month = ($version & 0x00780000) >> 19;
my $day = ($version & 0x0007C000) >> 14;
my $year = ($version & 0x7F800000) >> 23;
if ($year)
{ $year += 2000; };
return ( $major, $minor, $month, $day, $year );
};
#
# Function: FromValues
#
# Returns a <VersionInt> created from the passed values.
#
# Parameters:
#
# major - The major version number. For development releases, it should be the stable version it's based off of.
# minor - The minor version number. It should always be two digits, so x.2 should pass 20. For development
# releases, it should be the stable version it's based off of.
# month - The numeric month of the development release. For stable releases it should be zero.
# day - The day of the development release. For stable releases it should be zero.
# year - The year of the development release. For stable releases it should be zero.
#
# Returns:
#
# The <VersionInt>.
#
sub FromValues #(int major, int minor, int month, int day, int year) => VersionInt
{
my ($self, $major, $minor, $month, $day, $year) = @_;
if ($day)
{ $year -= 2000; };
return ($major << 7) + ($minor) + ($month << 19) + ($day << 14) + ($year << 23);
};
#
# Function: CheckFileFormat
#
# Checks if a file's format is compatible with the current release.
#
# - If the application is a development release or the file is from one, this only returns true if they are from the exact same
# development release.
# - If neither of them are development releases, this only returns true if the file is from a release between the minimum specified
# and the current version. If there's no minimum it just checks that it's below the current version.
#
# Parameters:
#
# fileVersion - The <VersionInt> of the file format.
# minimumVersion - The minimum <VersionInt> required of the file format. May be undef.
#
# Returns:
#
# Whether the file's format is compatible per the above rules.
#
sub CheckFileFormat #(VersionInt fileVersion, optional VersionInt minimumVersion) => bool
{
my ($self, $fileVersion, $minimumVersion) = @_;
my $appVersion = NaturalDocs::Settings->AppVersion();
if ($self->IsDevelopmentRelease($appVersion) || $self->IsDevelopmentRelease($fileVersion))
{ return ($appVersion == $fileVersion); }
elsif ($minimumVersion && $fileVersion < $minimumVersion)
{ return 0; }
else
{ return ($fileVersion <= $appVersion); };
};
#
# Function: IsDevelopmentRelease
#
# Returns whether the passed <VersionInt> is for a development release.
#
sub IsDevelopmentRelease #(VersionInt version) => bool
{
my ($self, $version) = @_;
# Return if any of the date fields are set.
return ($version & 0x7FFFC000);
};
###############################################################################
# Group: Implementation
#
# About: String Format
#
# Full Releases:
#
# Full releases are in the common major.minor format. Either part can be up to two digits. The minor version is interpreted
# as decimal places, so 1.3 > 1.22. There are no leading or trailing zeroes.
#
# Development Releases:
#
# Development releases are in the format "Development Release mm-dd-yyyy (vv.vv base)" where vv.vv is the version
# number of the full release it's based off of. The month and day will have leading zeroes where applicable. Example:
# "Development Release 07-09-2006 (1.35 base)".
#
# 0.91 and Earlier:
#
# Text files from releases prior to 0.95 had a separate file format version number that was used instead of the application
# version. These were never changed between 0.85 and 0.91, so they are simply "1". Text version numbers that are "1"
# instead of "1.0" will be interpreted as 0.91.
#
#
# About: Integer Format
#
# <VersionInts> are 32-bit values with the bit distribution below.
#
# > s yyyyyyyy mmmm ddddd vvvvvvv xxxxxxx
# > [syyy|yyyy] [ymmm|mddd] [ddvv|vvvv] [vxxx|xxxx]
#
# s - The sign bit. Always zero, so it's always interpreted as positive.
# y - The year bits if it's a development release, zero otherwise. 2000 is added to the value, so the range is from 2000 to 2255.
# m - The month bits if it's a development release, zero otherwise.
# d - The day bits if it's a development release, zero otherwise.
# v - The major version bits. For development releases, it's the last stable version it was based off of.
# x - The minor version bits. It's always stored as two decimals, so x.2 would store 20 here. For development releases, it's the
# last stable version it was based off of.
#
# It's stored with the development release date at a higher significance than the version because we want a stable release to
# always treat a development release as higher than itself, and thus not attempt to read any of the data files. I'm not tracking
# data file formats at the development release level.
#
#
# About: Binary File Format
#
# Current:
#
# Five 8-bit unsigned values, appearing major, minor, month, day, year. Minor is always stored with two digits, so x.2 would
# store 20. Year is stored minus 2000, so 2006 is stored 6. Stable releases store zero for all the date fields.
#
# 1.35 Development Releases:
#
# 1.35-based development releases are stored the same as current releases, but with 1.36 as the version number. This is
# done so previous versions of Natural Docs that didn't include the date fields would still know it's a higher version. There is
# no actual 1.36 release.
#
# 1.35 and Earlier:
#
# Two 8-bit unsigned values, appearing major then minor. Minor is always stored with two digits, so x.2 would store 20.
#
#
# About: Text File Format
#
# In text files, versions are the <String Format> followed by a native line break.
#
1;