ddnet/docs/tool/Modules/NaturalDocs/Languages.pm
2008-08-02 08:21:29 +00:00

1476 lines
52 KiB
Perl

###############################################################################
#
# Package: NaturalDocs::Languages
#
###############################################################################
#
# A package to manage all the programming languages Natural Docs supports.
#
# Usage and Dependencies:
#
# - Prior to use, <NaturalDocs::Settings> must be initialized and <Load()> must be called.
#
###############################################################################
# This file is part of Natural Docs, which is Copyright (C) 2003-2008 Greg Valure
# Natural Docs is licensed under the GPL
use Text::Wrap();
use NaturalDocs::Languages::Prototype;
use NaturalDocs::Languages::Base;
use NaturalDocs::Languages::Simple;
use NaturalDocs::Languages::Advanced;
use NaturalDocs::Languages::Perl;
use NaturalDocs::Languages::CSharp;
use NaturalDocs::Languages::ActionScript;
use NaturalDocs::Languages::Ada;
use NaturalDocs::Languages::PLSQL;
use NaturalDocs::Languages::Pascal;
use NaturalDocs::Languages::Tcl;
use strict;
use integer;
package NaturalDocs::Languages;
###############################################################################
# Group: Variables
#
# handle: FH_LANGUAGES
#
# The file handle used for writing to <Languages.txt>.
#
#
# hash: languages
#
# A hash of all the defined languages. The keys are the all-lowercase language names, and the values are
# <NaturalDocs::Languages::Base>-derived objects.
#
my %languages;
#
# hash: extensions
#
# A hash of all the defined languages' extensions. The keys are the all-lowercase extensions, and the values are the
# all-lowercase names of the languages that defined them.
#
my %extensions;
#
# hash: shebangStrings
#
# A hash of all the defined languages' strings to search for in the shebang (#!) line. The keys are the all-lowercase strings, and
# the values are the all-lowercase names of the languages that defined them.
#
my %shebangStrings;
#
# hash: shebangFiles
#
# A hash of all the defined languages for files where it needs to be found via shebang strings. The keys are the file names,
# and the values are language names, or undef if the file isn't supported. These values should be filled in the first time
# each file is parsed for a shebang string so that it doesn't have to be done multiple times.
#
my %shebangFiles;
#
# array: mainLanguageNames
#
# An array of the language names that are defined in the main <Languages.txt>.
#
my @mainLanguageNames;
###############################################################################
# Group: Files
#
# File: Languages.txt
#
# The configuration file that defines or overrides the language definitions for Natural Docs. One version sits in Natural Docs'
# configuration directory, and another can be in a project directory to add to or override them.
#
# > # [comments]
#
# Everything after a # symbol is ignored. However, for this particular file, comments can only appear on their own lines.
# They cannot appear after content on the same line.
#
# > Format: [version]
#
# Specifies the file format version of the file.
#
#
# Sections:
#
# > Ignore[d] Extension[s]: [extension] [extension] ...
#
# Causes the listed file extensions to be ignored, even if they were previously defined to be part of a language. The list is
# space-separated. ex. "Ignore Extensions: cvs txt"
#
#
# > Language: [name]
#
# Creates a new language. Everything underneath applies to this language until the next one. Names can use any
# characters.
#
# The languages "Text File" and "Shebang Script" have special meanings. Text files are considered all comment and don't
# have comment symbols. Shebang scripts have their language determined by the shebang string and automatically
# include files with no extension in addition to the extensions defined.
#
# If "Text File" doesn't define ignored prefixes, a package separator, or enum value behavior, those settings will be copied
# from the language with the most files in the source tree.
#
#
# > Alter Language: [name]
#
# Alters an existing language. Everything underneath it overrides the previous settings until the next one. Note that if a
# property has an [Add/Replace] form and that property has already been defined, you have to specify whether you're adding
# to or replacing the defined list.
#
#
# Language Properties:
#
# > Extension[s]: [extension] [extension] ...
# > [Add/Replace] Extension[s]: ...
#
# Defines file extensions for the language's source files. The list is space-separated. ex. "Extensions: c cpp". You can use
# extensions that were previously used by another language to redefine them.
#
#
# > Shebang String[s]: [string] [string] ...
# > [Add/Replace] Shebang String[s]: ...
#
# Defines a list of strings that can appear in the shebang (#!) line to designate that it's part of this language. They can
# appear anywhere in the line, so "php" will work for "#!/user/bin/php4". You can use strings that were previously used by
# another language to redefine them.
#
#
# > Ignore[d] Prefix[es] in Index: [prefix] [prefix] ...
# > Ignore[d] [Topic Type] Prefix[es] in Index: [prefix] [prefix] ...
# > [Add/Replace] Ignore[d] Prefix[es] in Index: ...
# > [Add/Replace] Ignore[d] [Topic Type] Prefix[es] in Index: ...
#
# Specifies prefixes that should be ignored when sorting symbols for an index. Can be specified in general or for a specific
# <TopicType>. The prefixes will still appear, the symbols will just be sorted as if they're not there. For example, specifying
# "ADO_" for functions will mean that "ADO_DoSomething" will appear under D instead of A.
#
#
# Basic Language Support Properties:
#
# These attributes are only available for languages with basic language support.
#
#
# > Line Comment[s]: [symbol] [symbol] ...
#
# Defines a space-separated list of symbols that are used for line comments, if any. ex. "Line Comment: //".
#
#
# > Block Comment[s]: [opening symbol] [closing symbol] [opening symbol] [closing symbol] ...
#
# Defines a space-separated list of symbol pairs that are used for block comments, if any. ex. "Block Comment: /* */".
#
#
# > Package Separator: [symbol]
#
# Defines the default package separator symbol, such as . or ::. This is for presentation only and will not affect how
# Natural Docs links are parsed. The default is a dot.
#
#
# > [Topic Type] Prototype Ender[s]: [symbol] [symbol] ...
#
# When defined, Natural Docs will attempt to collect prototypes from the code following the specified <TopicType>. It grabs
# code until the first ender symbol or the next Natural Docs comment, and if it contains the topic name, it serves as its
# prototype. Use \n to specify a line break. ex. "Function Prototype Enders: { ;", "Variable Prototype Enders: = ;".
#
#
# > Line Extender: [symbol]
#
# Defines the symbol that allows a prototype to span multiple lines if normally a line break would end it.
#
#
# > Enum Values: [global|under type|under parent]
#
# Defines how enum values are referenced. The default is global.
#
# global - Values are always global, referenced as 'value'.
# under type - Values are under the enum type, referenced as 'package.enum.value'.
# under parent - Values are under the enum's parent, referenced as 'package.value'.
#
#
# > Perl Package: [perl package]
#
# Specifies the Perl package used to fine-tune the language behavior in ways too complex to do in this file.
#
#
# Full Language Support Properties:
#
# These attributes are only available for languages with full language support.
#
#
# > Full Language Support: [perl package]
#
# Specifies the Perl package that has the parsing routines necessary for full language support.
#
#
# Revisions:
#
# 1.32:
#
# - Package Separator is now a basic language support only property.
# - Added Enum Values setting.
#
# 1.3:
#
# - The file was introduced.
###############################################################################
# Group: File Functions
#
# Function: Load
#
# Loads both the master and the project version of <Languages.txt>.
#
sub Load
{
my $self = shift;
# Hashrefs where the keys are all-lowercase extensions/shebang strings, and the values are arrayrefs of the languages
# that defined them, earliest first, all lowercase.
my %tempExtensions;
my %tempShebangStrings;
$self->LoadFile(1, \%tempExtensions, \%tempShebangStrings); # Main
if (!exists $languages{'shebang script'})
{ NaturalDocs::ConfigFile->AddError('You must define "Shebang Script" in the main languages file.'); };
if (!exists $languages{'text file'})
{ NaturalDocs::ConfigFile->AddError('You must define "Text File" in the main languages file.'); };
my $errorCount = NaturalDocs::ConfigFile->ErrorCount();
if ($errorCount)
{
NaturalDocs::ConfigFile->PrintErrorsAndAnnotateFile();
NaturalDocs::Error->SoftDeath('There ' . ($errorCount == 1 ? 'is an error' : 'are ' . $errorCount . ' errors')
. ' in ' . NaturalDocs::Project->MainConfigFile('Languages.txt'));
}
$self->LoadFile(0, \%tempExtensions, \%tempShebangStrings); # User
$errorCount = NaturalDocs::ConfigFile->ErrorCount();
if ($errorCount)
{
NaturalDocs::ConfigFile->PrintErrorsAndAnnotateFile();
NaturalDocs::Error->SoftDeath('There ' . ($errorCount == 1 ? 'is an error' : 'are ' . $errorCount . ' errors')
. ' in ' . NaturalDocs::Project->UserConfigFile('Languages.txt'));
};
# Convert the temp hashes into the real ones.
while (my ($extension, $languages) = each %tempExtensions)
{
$extensions{$extension} = $languages->[-1];
};
while (my ($shebangString, $languages) = each %tempShebangStrings)
{
$shebangStrings{$shebangString} = $languages->[-1];
};
};
#
# Function: LoadFile
#
# Loads a particular version of <Languages.txt>.
#
# Parameters:
#
# isMain - Whether the file is the main file or not.
# tempExtensions - A hashref where the keys are all-lowercase extensions, and the values are arrayrefs of the all-lowercase
# names of the languages that defined them, earliest first. It will be changed by this function.
# tempShebangStrings - A hashref where the keys are all-lowercase shebang strings, and the values are arrayrefs of the
# all-lowercase names of the languages that defined them, earliest first. It will be changed by this
# function.
#
sub LoadFile #(isMain, tempExtensions, tempShebangStrings)
{
my ($self, $isMain, $tempExtensions, $tempShebangStrings) = @_;
my ($file, $status);
if ($isMain)
{
$file = NaturalDocs::Project->MainConfigFile('Languages.txt');
$status = NaturalDocs::Project->MainConfigFileStatus('Languages.txt');
}
else
{
$file = NaturalDocs::Project->UserConfigFile('Languages.txt');
$status = NaturalDocs::Project->UserConfigFileStatus('Languages.txt');
};
my $version;
# An array of properties for the current language. Each entry is the three consecutive values ( lineNumber, keyword, value ).
my @properties;
if ($version = NaturalDocs::ConfigFile->Open($file))
{
# The format hasn't changed significantly since the file was introduced.
if ($status == ::FILE_CHANGED())
{
NaturalDocs::Project->ReparseEverything();
NaturalDocs::SymbolTable->RebuildAllIndexes(); # Because the ignored prefixes could change.
};
my ($keyword, $value, $comment);
while (($keyword, $value, $comment) = NaturalDocs::ConfigFile->GetLine())
{
$value .= $comment;
$value =~ s/^ //;
# Process previous properties.
if (($keyword eq 'language' || $keyword eq 'alter language') && scalar @properties)
{
if ($isMain && $properties[1] eq 'language')
{ push @mainLanguageNames, $properties[2]; };
$self->ProcessProperties(\@properties, $version, $tempExtensions, $tempShebangStrings);
@properties = ( );
};
if ($keyword =~ /^ignored? extensions?$/)
{
$value =~ tr/.*//d;
my @extensions = split(/ /, lc($value));
foreach my $extension (@extensions)
{ delete $tempExtensions->{$extension}; };
}
else
{
push @properties, NaturalDocs::ConfigFile->LineNumber(), $keyword, $value;
};
};
if (scalar @properties)
{
if ($isMain && $properties[1] eq 'language')
{ push @mainLanguageNames, $properties[2]; };
$self->ProcessProperties(\@properties, $version, $tempExtensions, $tempShebangStrings);
};
}
else # couldn't open file
{
if ($isMain)
{ die "Couldn't open languages file " . $file . "\n"; };
};
};
#
# Function: ProcessProperties
#
# Processes an array of language properties from <Languages.txt>.
#
# Parameters:
#
# properties - An arrayref of properties where each entry is the three consecutive values ( lineNumber, keyword, value ).
# It must start with the Language or Alter Language property.
# version - The <VersionInt> of the file.
# tempExtensions - A hashref where the keys are all-lowercase extensions, and the values are arrayrefs of the all-lowercase
# names of the languages that defined them, earliest first. It will be changed by this function.
# tempShebangStrings - A hashref where the keys are all-lowercase shebang strings, and the values are arrayrefs of the
# all-lowercase names of the languages that defined them, earliest first. It will be changed by this
# function.
#
sub ProcessProperties #(properties, version, tempExtensions, tempShebangStrings)
{
my ($self, $properties, $version, $tempExtensions, $tempShebangStrings) = @_;
# First validate the name and check whether the language has full support.
my $language;
my $fullLanguageSupport;
my ($lineNumber, $languageKeyword, $languageName) = @$properties[0..2];
my $lcLanguageName = lc($languageName);
my ($keyword, $value);
if ($languageKeyword eq 'alter language')
{
$language = $languages{$lcLanguageName};
if (!defined $language)
{
NaturalDocs::ConfigFile->AddError('The language ' . $languageName . ' is not defined.', $lineNumber);
return;
}
else
{
$fullLanguageSupport = (!$language->isa('NaturalDocs::Languages::Simple'));
};
}
elsif ($languageKeyword eq 'language')
{
if (exists $languages{$lcLanguageName})
{
NaturalDocs::ConfigFile->AddError('The language ' . $value . ' is already defined. Use "Alter Language" if you want '
. 'to override its settings.', $lineNumber);
return;
};
# Case is important with these two.
if ($lcLanguageName eq 'shebang script')
{ $languageName = 'Shebang Script'; }
elsif ($lcLanguageName eq 'text file')
{ $languageName = 'Text File'; };
# Go through the properties looking for whether the language has basic or full support and which package to use to create
# it.
for (my $i = 3; $i < scalar @$properties; $i += 3)
{
($lineNumber, $keyword, $value) = @$properties[$i..$i+2];
if ($keyword eq 'full language support')
{
$fullLanguageSupport = 1;
eval
{
$language = $value->New($languageName);
};
if ($::EVAL_ERROR)
{
NaturalDocs::ConfigFile->AddError('Could not create ' . $value . ' object.', $lineNumber);
return;
};
last;
}
elsif ($keyword eq 'perl package')
{
eval
{
$language = $value->New($languageName);
};
if ($::EVAL_ERROR)
{
NaturalDocs::ConfigFile->AddError('Could not create ' . $value . ' object.', $lineNumber);
return;
};
};
};
# If $language was not created by now, it's a generic basic support language.
if (!defined $language)
{ $language = NaturalDocs::Languages::Simple->New($languageName); };
$languages{$lcLanguageName} = $language;
}
else # not language or alter language
{
NaturalDocs::ConfigFile->AddError('You must start this line with "Language", "Alter Language", or "Ignore Extensions".',
$lineNumber);
return;
};
# Decode the properties.
for (my $i = 3; $i < scalar @$properties; $i += 3)
{
($lineNumber, $keyword, $value) = @$properties[$i..$i+2];
if ($keyword =~ /^(?:(add|replace) )?extensions?$/)
{
my $command = $1;
# Remove old extensions.
if (defined $language->Extensions() && $command eq 'replace')
{
foreach my $extension (@{$language->Extensions()})
{
if (exists $tempExtensions->{$extension})
{
my $languages = $tempExtensions->{$extension};
my $i = 0;
while ($i < scalar @$languages)
{
if ($languages->[$i] eq $lcLanguageName)
{ splice(@$languages, $i, 1); }
else
{ $i++; };
};
if (!scalar @$languages)
{ delete $tempExtensions->{$extension}; };
};
};
};
# Add new extensions.
# Ignore stars and dots so people could use .ext or *.ext.
$value =~ s/\*\.|\.//g;
my @extensions = split(/ /, lc($value));
foreach my $extension (@extensions)
{
if (!exists $tempExtensions->{$extension})
{ $tempExtensions->{$extension} = [ ]; };
push @{$tempExtensions->{$extension}}, $lcLanguageName;
};
# Set the extensions for the language object.
if (defined $language->Extensions())
{
if ($command eq 'add')
{ push @extensions, @{$language->Extensions()}; }
elsif (!$command)
{
NaturalDocs::ConfigFile->AddError('You need to specify whether you are adding to or replacing the list of extensions.',
$lineNumber);
};
};
$language->SetExtensions(\@extensions);
}
elsif ($keyword =~ /^(?:(add|replace) )?shebang strings?$/)
{
my $command = $1;
# Remove old strings.
if (defined $language->ShebangStrings() && $command eq 'replace')
{
foreach my $shebangString (@{$language->ShebangStrings()})
{
if (exists $tempShebangStrings->{$shebangString})
{
my $languages = $tempShebangStrings->{$shebangString};
my $i = 0;
while ($i < scalar @$languages)
{
if ($languages->[$i] eq $lcLanguageName)
{ splice(@$languages, $i, 1); }
else
{ $i++; };
};
if (!scalar @$languages)
{ delete $tempShebangStrings->{$shebangString}; };
};
};
};
# Add new strings.
my @shebangStrings = split(/ /, lc($value));
foreach my $shebangString (@shebangStrings)
{
if (!exists $tempShebangStrings->{$shebangString})
{ $tempShebangStrings->{$shebangString} = [ ]; };
push @{$tempShebangStrings->{$shebangString}}, $lcLanguageName;
};
# Set the strings for the language object.
if (defined $language->ShebangStrings())
{
if ($command eq 'add')
{ push @shebangStrings, @{$language->ShebangStrings()}; }
elsif (!$command)
{
NaturalDocs::ConfigFile->AddError('You need to specify whether you are adding to or replacing the list of shebang '
. 'strings.', $lineNumber);
};
};
$language->SetShebangStrings(\@shebangStrings);
}
elsif ($keyword eq 'package separator')
{
if ($fullLanguageSupport)
{
# Prior to 1.32, package separator was used with full language support too. Accept it without complaining, even though
# we ignore it.
if ($version >= NaturalDocs::Version->FromString('1.32'))
{
NaturalDocs::ConfigFile->AddError('You cannot define this property when using full language support.', $lineNumber);
};
}
else
{ $language->SetPackageSeparator($value); };
}
elsif ($keyword =~ /^(?:(add|replace) )?ignored? (?:(.+) )?prefix(?:es)? in index$/)
{
my ($command, $topicName) = ($1, $2);
my $topicType;
if ($topicName)
{
if (!( ($topicType, undef) = NaturalDocs::Topics->NameInfo($topicName) ))
{
NaturalDocs::ConfigFile->AddError($topicName . ' is not a defined topic type.', $lineNumber);
};
}
else
{ $topicType = ::TOPIC_GENERAL(); };
if ($topicType)
{
my @prefixes;
if (defined $language->IgnoredPrefixesFor($topicType))
{
if ($command eq 'add')
{ @prefixes = @{$language->IgnoredPrefixesFor($topicType)}; }
elsif (!$command)
{
NaturalDocs::ConfigFile->AddError('You need to specify whether you are adding to or replacing the list of '
. 'ignored prefixes.', $lineNumber);
};
};
push @prefixes, split(/ /, $value);
$language->SetIgnoredPrefixesFor($topicType, \@prefixes);
};
}
elsif ($keyword eq 'full language support' || $keyword eq 'perl package')
{
if ($languageKeyword eq 'alter language')
{
NaturalDocs::ConfigFile->AddError('You cannot use ' . $keyword . ' with Alter Language.', $lineNumber);
};
# else ignore it.
}
elsif ($keyword =~ /^line comments?$/)
{
if ($fullLanguageSupport)
{
NaturalDocs::ConfigFile->AddError('You cannot define this property when using full language support.', $lineNumber);
}
else
{
my @symbols = split(/ /, $value);
$language->SetLineCommentSymbols(\@symbols);
};
}
elsif ($keyword =~ /^block comments?$/)
{
if ($fullLanguageSupport)
{
NaturalDocs::ConfigFile->AddError('You cannot define this property when using full language support.', $lineNumber);
}
else
{
my @symbols = split(/ /, $value);
if ((scalar @symbols) % 2 == 0)
{ $language->SetBlockCommentSymbols(\@symbols); }
else
{ NaturalDocs::ConfigFile->AddError('Block comment symbols must appear in pairs.', $lineNumber); };
};
}
elsif ($keyword =~ /^(?:(.+) )?prototype enders?$/)
{
if ($fullLanguageSupport)
{
NaturalDocs::ConfigFile->AddError('You cannot define this property when using full language support.', $lineNumber);
}
else
{
my $topicName = $1;
my $topicType;
if ($topicName)
{
if (!( ($topicType, undef) = NaturalDocs::Topics->NameInfo($topicName) ))
{
NaturalDocs::ConfigFile->AddError($topicName . ' is not a defined topic type.', $lineNumber);
};
}
else
{ $topicType = ::TOPIC_GENERAL(); };
if ($topicType)
{
$value =~ s/\\n/\n/g;
my @symbols = split(/ /, $value);
$language->SetPrototypeEndersFor($topicType, \@symbols);
};
};
}
elsif ($keyword eq 'line extender')
{
if ($fullLanguageSupport)
{
NaturalDocs::ConfigFile->AddError('You cannot define this property when using full language support.', $lineNumber);
}
else
{
$language->SetLineExtender($value);
};
}
elsif ($keyword eq 'enum values')
{
if ($fullLanguageSupport)
{
NaturalDocs::ConfigFile->AddError('You cannot define this property when using full language support.', $lineNumber);
}
else
{
$value = lc($value);
my $constant;
if ($value eq 'global')
{ $constant = ::ENUM_GLOBAL(); }
elsif ($value eq 'under type')
{ $constant = ::ENUM_UNDER_TYPE(); }
elsif ($value eq 'under parent')
{ $constant = ::ENUM_UNDER_PARENT(); };
if (defined $constant)
{ $language->SetEnumValues($constant); }
else
{
NaturalDocs::ConfigFile->AddError('Enum Values must be "Global", "Under Type", or "Under Parent".', $lineNumber);
};
};
}
else
{
NaturalDocs::ConfigFile->AddError($keyword . ' is not a valid keyword.', $lineNumber);
};
};
};
#
# Function: Save
#
# Saves the main and user versions of <Languages.txt>.
#
sub Save
{
my $self = shift;
$self->SaveFile(1); # Main
$self->SaveFile(0); # User
};
#
# Function: SaveFile
#
# Saves a particular version of <Topics.txt>.
#
# Parameters:
#
# isMain - Whether the file is the main file or not.
#
sub SaveFile #(isMain)
{
my ($self, $isMain) = @_;
my $file;
if ($isMain)
{
if (NaturalDocs::Project->MainConfigFileStatus('Languages.txt') == ::FILE_SAME())
{ return; };
$file = NaturalDocs::Project->MainConfigFile('Languages.txt');
}
else
{
# Have to check the main too because this file lists the languages defined there.
if (NaturalDocs::Project->UserConfigFileStatus('Languages.txt') == ::FILE_SAME() &&
NaturalDocs::Project->MainConfigFileStatus('Languages.txt') == ::FILE_SAME())
{ return; };
$file = NaturalDocs::Project->UserConfigFile('Languages.txt');
};
# Array of segments, with each being groups of three consecutive entries. The first is the keyword ('language' or
# 'alter language'), the second is the value, and the third is a hashref of all the properties.
# - For properties that can accept a topic type, the property values are hashrefs mapping topic types to the values.
# - For properties that can accept 'add' or 'replace', there is an additional property ending in 'command' that stores it.
# - For properties that can accept both, the 'command' thing is applied to the topic types rather than the properties.
my @segments;
my @ignoredExtensions;
my $currentProperties;
my $version;
if ($version = NaturalDocs::ConfigFile->Open($file))
{
# We can assume the file is valid.
while (my ($keyword, $value, $comment) = NaturalDocs::ConfigFile->GetLine())
{
$value .= $comment;
$value =~ s/^ //;
if ($keyword eq 'language')
{
$currentProperties = { };
# Case is important with these two.
if (lc($value) eq 'shebang script')
{ $value = 'Shebang Script'; }
elsif (lc($value) eq 'text file')
{ $value = 'Text File'; };
push @segments, 'language', $value, $currentProperties;
}
elsif ($keyword eq 'alter language')
{
$currentProperties = { };
push @segments, 'alter language', $languages{lc($value)}->Name(), $currentProperties;
}
elsif ($keyword =~ /^ignored? extensions?$/)
{
$value =~ tr/*.//d;
push @ignoredExtensions, split(/ /, $value);
}
elsif ($keyword eq 'package separator' || $keyword eq 'full language support' || $keyword eq 'perl package' ||
$keyword eq 'line extender' || $keyword eq 'enum values')
{
$currentProperties->{$keyword} = $value;
}
elsif ($keyword =~ /^line comments?$/)
{
$currentProperties->{'line comments'} = $value;
}
elsif ($keyword =~ /^block comments?$/)
{
$currentProperties->{'block comments'} = $value;
}
elsif ($keyword =~ /^(?:(add|replace) )?extensions?$/)
{
my $command = $1;
if ($command eq 'add' && exists $currentProperties->{'extensions'})
{ $currentProperties->{'extensions'} .= ' ' . $value; }
else
{
$currentProperties->{'extensions'} = $value;
$currentProperties->{'extensions command'} = $command;
};
}
elsif ($keyword =~ /^(?:(add|replace) )?shebang strings?$/)
{
my $command = $1;
if ($command eq 'add' && exists $currentProperties->{'shebang strings'})
{ $currentProperties->{'shebang strings'} .= ' ' . $value; }
else
{
$currentProperties->{'shebang strings'} = $value;
$currentProperties->{'shebang strings command'} = $command;
};
}
elsif ($keyword =~ /^(?:(.+) )?prototype enders?$/)
{
my $topicName = $1;
my $topicType;
if ($topicName)
{ ($topicType, undef) = NaturalDocs::Topics->NameInfo($topicName); }
else
{ $topicType = ::TOPIC_GENERAL(); };
my $currentTypeProperties = $currentProperties->{'prototype enders'};
if (!defined $currentTypeProperties)
{
$currentTypeProperties = { };
$currentProperties->{'prototype enders'} = $currentTypeProperties;
};
$currentTypeProperties->{$topicType} = $value;
}
elsif ($keyword =~ /^(?:(add|replace) )?ignored? (?:(.+) )?prefix(?:es)? in index$/)
{
my ($command, $topicName) = ($1, $2);
my $topicType;
if ($topicName)
{ ($topicType, undef) = NaturalDocs::Topics->NameInfo($topicName); }
else
{ $topicType = ::TOPIC_GENERAL(); };
my $currentTypeProperties = $currentProperties->{'ignored prefixes in index'};
if (!defined $currentTypeProperties)
{
$currentTypeProperties = { };
$currentProperties->{'ignored prefixes in index'} = $currentTypeProperties;
};
if ($command eq 'add' && exists $currentTypeProperties->{$topicType})
{ $currentTypeProperties->{$topicType} .= ' ' . $value; }
else
{
$currentTypeProperties->{$topicType} = $value;
$currentTypeProperties->{$topicType . ' command'} = $command;
};
};
};
NaturalDocs::ConfigFile->Close();
};
if (!open(FH_LANGUAGES, '>' . $file))
{
# The main file may be on a shared volume or some other place the user doesn't have write access to. Since this is only to
# reformat the file, we can ignore the failure.
if ($isMain)
{ return; }
else
{ die "Couldn't save " . $file; };
};
print FH_LANGUAGES 'Format: ' . NaturalDocs::Settings->TextAppVersion() . "\n\n";
# Remember the 80 character limit.
if ($isMain)
{
print FH_LANGUAGES
"# This is the main Natural Docs languages file. If you change anything here,\n"
. "# it will apply to EVERY PROJECT you use Natural Docs on. If you'd like to\n"
. "# change something for just one project, edit the Languages.txt in its project\n"
. "# directory instead.\n";
}
else
{
print FH_LANGUAGES
"# This is the Natural Docs languages file for this project. If you change\n"
. "# anything here, it will apply to THIS PROJECT ONLY. If you'd like to change\n"
. "# something for all your projects, edit the Languages.txt in Natural Docs'\n"
. "# Config directory instead.\n\n\n";
if (scalar @ignoredExtensions == 1)
{
print FH_LANGUAGES
'Ignore Extension: ' . $ignoredExtensions[0] . "\n";
}
elsif (scalar @ignoredExtensions)
{
print FH_LANGUAGES
'Ignore Extensions: ' . join(' ', @ignoredExtensions) . "\n";
}
else
{
print FH_LANGUAGES
"# You can prevent certain file extensions from being scanned like this:\n"
. "# Ignore Extensions: [extension] [extension] ...\n"
};
};
print FH_LANGUAGES
"\n\n"
. "#-------------------------------------------------------------------------------\n"
. "# SYNTAX:\n"
. "#\n"
. "# Unlike other Natural Docs configuration files, in this file all comments\n"
. "# MUST be alone on a line. Some languages deal with the # character, so you\n"
. "# cannot put comments on the same line as content.\n"
. "#\n"
. "# Also, all lists are separated with spaces, not commas, again because some\n"
. "# languages may need to use them.\n"
. "#\n";
if ($isMain)
{
print FH_LANGUAGES
"# Language: [name]\n"
. "# Defines a new language. Its name can use any characters.\n"
. "#\n";
}
else
{
print FH_LANGUAGES
"# Language: [name]\n"
. "# Alter Language: [name]\n"
. "# Defines a new language or alters an existing one. Its name can use any\n"
. "# characters. If any of the properties below have an add/replace form, you\n"
. "# must use that when using Alter Language.\n"
. "#\n";
};
print FH_LANGUAGES
"# The language Shebang Script is special. It's entry is only used for\n"
. "# extensions, and files with those extensions have their shebang (#!) lines\n"
. "# read to determine the real language of the file. Extensionless files are\n"
. "# always treated this way.\n"
. "#\n"
. "# The language Text File is also special. It's treated as one big comment\n"
. "# so you can put Natural Docs content in them without special symbols. Also,\n"
. "# if you don't specify a package separator, ignored prefixes, or enum value\n"
. "# behavior, it will copy those settings from the language that is used most\n"
. "# in the source tree.\n"
. "#\n"
. "# Extensions: [extension] [extension] ...\n";
if ($isMain)
{
print FH_LANGUAGES
"# Defines the file extensions of the language's source files. You can use *\n"
. "# to mean any undefined extension.\n"
. "#\n"
. "# Shebang Strings: [string] [string] ...\n"
. "# Defines a list of strings that can appear in the shebang (#!) line to\n"
. "# designate that it's part of the language.\n"
. "#\n";
}
else
{
print FH_LANGUAGES
"# [Add/Replace] Extensions: [extension] [extension] ...\n"
. "# Defines the file extensions of the language's source files. You can\n"
. "# redefine extensions found in the main languages file. You can use * to\n"
. "# mean any undefined extension.\n"
. "#\n"
. "# Shebang Strings: [string] [string] ...\n"
. "# [Add/Replace] Shebang Strings: [string] [string] ...\n"
. "# Defines a list of strings that can appear in the shebang (#!) line to\n"
. "# designate that it's part of the language. You can redefine strings found\n"
. "# in the main languages file.\n"
. "#\n";
};
print FH_LANGUAGES
"# Ignore Prefixes in Index: [prefix] [prefix] ...\n"
. (!$isMain ? "# [Add/Replace] Ignored Prefixes in Index: [prefix] [prefix] ...\n#\n" : '')
. "# Ignore [Topic Type] Prefixes in Index: [prefix] [prefix] ...\n"
. (!$isMain ? "# [Add/Replace] Ignored [Topic Type] Prefixes in Index: [prefix] [prefix] ...\n" : '')
. "# Specifies prefixes that should be ignored when sorting symbols in an\n"
. "# index. Can be specified in general or for a specific topic type.\n"
. "#\n"
. "#------------------------------------------------------------------------------\n"
. "# For basic language support only:\n"
. "#\n"
. "# Line Comments: [symbol] [symbol] ...\n"
. "# Defines a space-separated list of symbols that are used for line comments,\n"
. "# if any.\n"
. "#\n"
. "# Block Comments: [opening sym] [closing sym] [opening sym] [closing sym] ...\n"
. "# Defines a space-separated list of symbol pairs that are used for block\n"
. "# comments, if any.\n"
. "#\n"
. "# Package Separator: [symbol]\n"
. "# Defines the default package separator symbol. The default is a dot.\n"
. "#\n"
. "# [Topic Type] Prototype Enders: [symbol] [symbol] ...\n"
. "# When defined, Natural Docs will attempt to get a prototype from the code\n"
. "# immediately following the topic type. It stops when it reaches one of\n"
. "# these symbols. Use \\n for line breaks.\n"
. "#\n"
. "# Line Extender: [symbol]\n"
. "# Defines the symbol that allows a prototype to span multiple lines if\n"
. "# normally a line break would end it.\n"
. "#\n"
. "# Enum Values: [global|under type|under parent]\n"
. "# Defines how enum values are referenced. The default is global.\n"
. "# global - Values are always global, referenced as 'value'.\n"
. "# under type - Values are under the enum type, referenced as\n"
. "# 'package.enum.value'.\n"
. "# under parent - Values are under the enum's parent, referenced as\n"
. "# 'package.value'.\n"
. "#\n"
. "# Perl Package: [perl package]\n"
. "# Specifies the Perl package used to fine-tune the language behavior in ways\n"
. "# too complex to do in this file.\n"
. "#\n"
. "#------------------------------------------------------------------------------\n"
. "# For full language support only:\n"
. "#\n"
. "# Full Language Support: [perl package]\n"
. "# Specifies the Perl package that has the parsing routines necessary for full\n"
. "# language support.\n"
. "#\n"
. "#-------------------------------------------------------------------------------\n\n";
if ($isMain)
{
print FH_LANGUAGES
"# The following languages MUST be defined in this file:\n"
. "#\n"
. "# Text File, Shebang Script\n";
}
else
{
print FH_LANGUAGES
"# The following languages are defined in the main file, if you'd like to alter\n"
. "# them:\n"
. "#\n"
. Text::Wrap::wrap('# ', '# ', join(', ', @mainLanguageNames)) . "\n";
};
print FH_LANGUAGES "\n"
. "# If you add a language that you think would be useful to other developers\n"
. "# and should be included in Natural Docs by default, please e-mail it to\n"
. "# languages [at] naturaldocs [dot] org.\n";
my @topicTypeOrder = ( ::TOPIC_GENERAL(), ::TOPIC_CLASS(), ::TOPIC_FUNCTION(), ::TOPIC_VARIABLE(),
::TOPIC_PROPERTY(), ::TOPIC_TYPE(), ::TOPIC_CONSTANT() );
for (my $i = 0; $i < scalar @segments; $i += 3)
{
my ($keyword, $name, $properties) = @segments[$i..$i+2];
print FH_LANGUAGES "\n\n";
if ($keyword eq 'language')
{ print FH_LANGUAGES 'Language: ' . $name . "\n\n"; }
else
{ print FH_LANGUAGES 'Alter Language: ' . $name . "\n\n"; };
if (exists $properties->{'extensions'})
{
print FH_LANGUAGES ' ';
if ($properties->{'extensions command'})
{ print FH_LANGUAGES ucfirst($properties->{'extensions command'}) . ' '; };
my @extensions = split(/ /, $properties->{'extensions'}, 2);
if (scalar @extensions == 1)
{ print FH_LANGUAGES 'Extension: '; }
else
{ print FH_LANGUAGES 'Extensions: '; };
print FH_LANGUAGES lc($properties->{'extensions'}) . "\n";
};
if (exists $properties->{'shebang strings'})
{
print FH_LANGUAGES ' ';
if ($properties->{'shebang strings command'})
{ print FH_LANGUAGES ucfirst($properties->{'shebang strings command'}) . ' '; };
my @shebangStrings = split(/ /, $properties->{'shebang strings'}, 2);
if (scalar @shebangStrings == 1)
{ print FH_LANGUAGES 'Shebang String: '; }
else
{ print FH_LANGUAGES 'Shebang Strings: '; };
print FH_LANGUAGES lc($properties->{'shebang strings'}) . "\n";
};
if (exists $properties->{'ignored prefixes in index'})
{
my $topicTypePrefixes = $properties->{'ignored prefixes in index'};
my %usedTopicTypes;
my @topicTypes = ( @topicTypeOrder, keys %$topicTypePrefixes );
foreach my $topicType (@topicTypes)
{
if ($topicType !~ / command$/ &&
exists $topicTypePrefixes->{$topicType} &&
!exists $usedTopicTypes{$topicType})
{
print FH_LANGUAGES ' ';
if ($topicTypePrefixes->{$topicType . ' command'})
{ print FH_LANGUAGES ucfirst($topicTypePrefixes->{$topicType . ' command'}) . ' Ignored '; }
else
{ print FH_LANGUAGES 'Ignore '; };
if ($topicType ne ::TOPIC_GENERAL())
{ print FH_LANGUAGES NaturalDocs::Topics->TypeInfo($topicType)->Name() . ' '; };
my @prefixes = split(/ /, $topicTypePrefixes->{$topicType}, 2);
if (scalar @prefixes == 1)
{ print FH_LANGUAGES 'Prefix in Index: '; }
else
{ print FH_LANGUAGES 'Prefixes in Index: '; };
print FH_LANGUAGES $topicTypePrefixes->{$topicType} . "\n";
$usedTopicTypes{$topicType} = 1;
};
};
};
if (exists $properties->{'line comments'})
{
my @comments = split(/ /, $properties->{'line comments'}, 2);
if (scalar @comments == 1)
{ print FH_LANGUAGES ' Line Comment: '; }
else
{ print FH_LANGUAGES ' Line Comments: '; };
print FH_LANGUAGES $properties->{'line comments'} . "\n";
};
if (exists $properties->{'block comments'})
{
my @comments = split(/ /, $properties->{'block comments'}, 3);
if (scalar @comments == 2)
{ print FH_LANGUAGES ' Block Comment: '; }
else
{ print FH_LANGUAGES ' Block Comments: '; };
print FH_LANGUAGES $properties->{'block comments'} . "\n";
};
if (exists $properties->{'package separator'})
{
# Prior to 1.32, Package Separator was allowed for full language support. Ignore it when reformatting.
if ($version >= NaturalDocs::Version->FromString('1.32') || !exists $properties->{'full language support'})
{ print FH_LANGUAGES ' Package Separator: ' . $properties->{'package separator'} . "\n"; };
};
if (exists $properties->{'enum values'})
{
print FH_LANGUAGES ' Enum Values: ' . ucfirst(lc($properties->{'enum values'})) . "\n";
};
if (exists $properties->{'prototype enders'})
{
my $topicTypeEnders = $properties->{'prototype enders'};
my %usedTopicTypes;
my @topicTypes = ( @topicTypeOrder, keys %$topicTypeEnders );
foreach my $topicType (@topicTypes)
{
if ($topicType !~ / command$/ &&
exists $topicTypeEnders->{$topicType} &&
!exists $usedTopicTypes{$topicType})
{
print FH_LANGUAGES ' ';
if ($topicType ne ::TOPIC_GENERAL())
{ print FH_LANGUAGES NaturalDocs::Topics->TypeInfo($topicType)->Name() . ' '; };
my @enders = split(/ /, $topicTypeEnders->{$topicType}, 2);
if (scalar @enders == 1)
{ print FH_LANGUAGES 'Prototype Ender: '; }
else
{ print FH_LANGUAGES 'Prototype Enders: '; };
print FH_LANGUAGES $topicTypeEnders->{$topicType} . "\n";
$usedTopicTypes{$topicType} = 1;
};
};
};
if (exists $properties->{'line extender'})
{
print FH_LANGUAGES ' Line Extender: ' . $properties->{'line extender'} . "\n";
};
if (exists $properties->{'perl package'})
{
print FH_LANGUAGES ' Perl Package: ' . $properties->{'perl package'} . "\n";
};
if (exists $properties->{'full language support'})
{
print FH_LANGUAGES ' Full Language Support: ' . $properties->{'full language support'} . "\n";
};
};
close(FH_LANGUAGES);
};
###############################################################################
# Group: Functions
#
# Function: LanguageOf
#
# Returns the language of the passed source file.
#
# Parameters:
#
# sourceFile - The source <FileName> to get the language of.
#
# Returns:
#
# A <NaturalDocs::Languages::Base>-derived object for the passed file, or undef if the file is not a recognized language.
#
sub LanguageOf #(sourceFile)
{
my ($self, $sourceFile) = @_;
my $extension = NaturalDocs::File->ExtensionOf($sourceFile);
if (defined $extension)
{ $extension = lc($extension); };
my $languageName;
if (!defined $extension)
{ $languageName = 'shebang script'; }
else
{ $languageName = $extensions{$extension}; };
if (!defined $languageName)
{ $languageName = $extensions{'*'}; };
if (defined $languageName)
{
if ($languageName eq 'shebang script')
{
if (exists $shebangFiles{$sourceFile})
{
if (defined $shebangFiles{$sourceFile})
{ return $languages{$shebangFiles{$sourceFile}}; }
else
{ return undef; };
}
else # (!exists $shebangFiles{$sourceFile})
{
my $shebangLine;
if (open(SOURCEFILEHANDLE, '<' . $sourceFile))
{
read(SOURCEFILEHANDLE, $shebangLine, 2);
if ($shebangLine eq '#!')
{ $shebangLine = <SOURCEFILEHANDLE>; }
else
{ $shebangLine = undef; };
close (SOURCEFILEHANDLE);
}
elsif (defined $extension)
{ die 'Could not open ' . $sourceFile; }
# Ignore extensionless files that can't be opened. They may be system files.
if (!defined $shebangLine)
{
$shebangFiles{$sourceFile} = undef;
return undef;
}
else
{
$shebangLine = lc($shebangLine);
foreach my $shebangString (keys %shebangStrings)
{
if (index($shebangLine, $shebangString) != -1)
{
$shebangFiles{$sourceFile} = $shebangStrings{$shebangString};
return $languages{$shebangStrings{$shebangString}};
};
};
$shebangFiles{$sourceFile} = undef;
return undef;
};
};
}
else # language name ne 'shebang script'
{ return $languages{$languageName}; };
}
else # !defined $language
{
return undef;
};
};
#
# Function: OnMostUsedLanguageKnown
#
# Called when the most used language is known.
#
sub OnMostUsedLanguageKnown
{
my $self = shift;
my $language = $languages{lc( NaturalDocs::Project->MostUsedLanguage() )};
if ($language)
{
if (!$languages{'text file'}->HasIgnoredPrefixes())
{ $languages{'text file'}->CopyIgnoredPrefixesOf($language); };
if (!$languages{'text file'}->PackageSeparatorWasSet())
{ $languages{'text file'}->SetPackageSeparator($language->PackageSeparator()); };
if (!$languages{'text file'}->EnumValuesWasSet())
{ $languages{'text file'}->SetEnumValues($language->EnumValues()); };
};
};
1;