about summary refs log tree commit diff
path: root/docs/tool/Modules/NaturalDocs/Languages.pm
diff options
context:
space:
mode:
authorMagnus Auvinen <magnus.auvinen@gmail.com>2008-08-02 08:21:29 +0000
committerMagnus Auvinen <magnus.auvinen@gmail.com>2008-08-02 08:21:29 +0000
commit61bfe2d70cae6be8c4086a210a5451135ccca9ea (patch)
tree62bf7808b1b2bfe5f56fe1e329871fb0991d0687 /docs/tool/Modules/NaturalDocs/Languages.pm
parenta13b94f9e0bca8ea892311d9d9e0c0bc48616ea7 (diff)
downloadzcatch-61bfe2d70cae6be8c4086a210a5451135ccca9ea.tar.gz
zcatch-61bfe2d70cae6be8c4086a210a5451135ccca9ea.zip
added doc tool
Diffstat (limited to 'docs/tool/Modules/NaturalDocs/Languages.pm')
-rw-r--r--docs/tool/Modules/NaturalDocs/Languages.pm1475
1 files changed, 1475 insertions, 0 deletions
diff --git a/docs/tool/Modules/NaturalDocs/Languages.pm b/docs/tool/Modules/NaturalDocs/Languages.pm
new file mode 100644
index 00000000..dde54b9d
--- /dev/null
+++ b/docs/tool/Modules/NaturalDocs/Languages.pm
@@ -0,0 +1,1475 @@
+###############################################################################
+#
+#   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;