package APIdict;
#
# Package to deal with API documentation defined by entries in a flat
# editable file.
#
# Edit history:
#
#  2001-07-19 Ken Coar
#	Add the ability to write the dictionary (sans comments) to
#	an XML file.  The DTD to which we are hewing is still
#	unpublished.
#
#  2001-06-19 Ken Coar
#	Added some documentation, and added xref-counts to the stats result.
#
#  2001-05-04 Ken Coar
#	Add 'hyperlink' Boolean setting.
#
#  2000-09-15 Ken Coar
#	Pull the relevant parts out of the mkapidict script so that
#	other tools can use them (like a source scanner).
#
#	This is the first Perl 'module' I've written, so be gentle..
#

use HTML::Entities;
use POSIX;

#
# Public methods
#

$hyperlinks = 1;
$embed = 1;

#
# Package constructor
#
#   $dict = new APIdict [('dictionary-file')];
#
sub new {
    my($self) = bless {}, shift;
    $self->init(@_);
    $self;
}

#
# All other public methods appear in lexical order.
#

#
# Replace $a_text with whatever to which it links.
sub insert_link {
    my($self, $a_text, $prefix, $postfix, $a_target, $ml) = @_;
    my($ref);
    my($xml) = ($ml =~ /xml/i);

    #
    # This is an incredibly brute-force O(n^2) algorithm, but at least it's
    # clear and only run when updating the docco.
    #
    if (! $a_target) {
        $a_target = $a_text;
    }
    if ($hyperlinks) {
        if ($xml) {
            $ref = "<ref>$a_text</ref>";
        }
        else {
            $ref = "<a href=\"$a_target\">$prefix$a_text$postfix</a>";
        }
    }
    else {
        $ref = "$prefix$a_text$postfix";
    }
    return $ref;
}

#
# Turn any inline references to other entities into links.
#
sub add_links {
    my($self, $wip, $rname, $a_prefix, $a_postfix, $ml, %HREF) = @_;
    my($ref);

    #
    # This is an incredibly brute-force O(n^2) algorithm, but at least it's
    # clear and only run when updating the docco.
    #
    foreach $ref (keys(%Entity)) {
	local($href) = $HREF{"$ref"};
	$href = ($href ? "$href" : "apidoc_$ref.html");
	if ($ref ne $rname) {
            $nref = &insert_link($self, $ref, $prefix, $postfix, $href, $ml);
            $wip =~ s:([^-</]*)\b$ref\b:$1$nref:g;
	}
    }
    return $wip;
}

#
# Return the one-character category (type) of the specified entity.
#
sub category {
    my($self, $item) = @_;
    my($rtype);

    if (! $Entity{$item}) {
        return undef;
    }
    ($rtype) = split(m:\|:, $Entity{$item});
    return uc($rtype);
}

#
# Return the human-readable name of a category (such as 'Routine' or
# 'Constant').
#
sub category_name {
    my($self, $prefix) = @_;

    return $Prefix{uc($prefix)};
}

#
# Return an unsorted array of the names of defined 'cell' entities.
#
sub cell_list {
    my($self, $item) = @_;

    return keys(%Cell);
}

#
# Save the dictionary to the specified file, which may or may not
# be the same as the original one read.
#
sub checkpoint {
    my($self) = @_;

    return &writefile($self, $DictFile);
}

#
# Return an unsorted array of the names of defined constant entities.
#
sub constant_list {
    my($self, $item) = @_;

    return keys(%Constant);
}

#
# Check to see if the specified name is in the dictionary.
#
sub defined {
    my($self, $item) = @_;

    return exists($Entity{$item});
}

#
# Methods for defining new entries.
#
sub define_cell {
    my($self, @args) = @_;

    return &make_entry('D', @args);
}

sub define_constant {
    my($self, @args) = @_;

    return &make_entry('C', @args);
}

sub define_macro {
    my($self, @args) = @_;

    return &make_entry('M', @args);
}

sub define_routine {
    my($self, @args) = @_;

    return &make_entry('R', @args);
}

sub define_structure {
    my($self, @args) = @_;

    return &make_entry('S', @args);
}

sub define_xref {
    my($self, @args) = @_;

    return &make_entry('X', @args);
}

#
# Methods for returning aspects of an entry.
#

sub definition {
    my($self, $item) = @_;

    return $Definition{$item};
}

sub description {
    my($self, $item) = @_;

    return $Description{$item};
}

sub description_file {
    my($self, $item) = @_;

    return $Description_file{$item};
}

#
# Simply return the raw dictionary.
#
sub dictionary {
    return join("", @Dictionary);
}

#
# Return an unsorted array of the names of all defined entities,
# irrespective of their categories/types.
#
sub entity_list {
    my($self, $item) = @_;

    return keys(%Entity);
}

sub example {
    my($self, $item) = @_;

    return $Example{$item};
}

sub see_also {
    my($self, $item) = @_;

    return $SeeAlso{$item};
}

#
# Expand any xref references in a see-also list.
#
sub expanded_seealso {
    my($self, $item) = @_;
    my($work, $ref) = ("", $SeeAlso{$item});
    my(@in, @result);
    my(%items);

    @in = split(m:,:, $ref);
    foreach (@in) {
        push(@result, &references($_, $item));
    }
    #
    # Sort the results and weed out any duplicates.  Use the associative
    # array properties to do the work for us.
    #
    foreach (@result) {
        $items{"$_"} = 1;
    }
    delete($items{"$item"});
    @result = sort {uc($a) cmp uc($b)} (keys(%items));
    return @result;
}

#
# Enable/disable the use of hyperlinks in add_links.
#
sub hyperlinks {
    my($self, $setting) = @_;

    if ($setting != undef) {
        $hyperlinks = $setting;
    }
    return $hyperlinks;
}

#
# Enable/disable the embedding of the separate description files.
#
sub embed_descriptions {
    my($self, $setting) = @_;

    if (defined $setting) {
        $embed = $setting;
    }
    return $embed;
}

#
# Boolean checks for entity types.
#
sub is_cell {
    my($self, $item) = @_;

    return exists($Cell{$item});
}

sub is_constant {
    my($self, $item) = @_;

    return exists($Constant{$item});
}

sub is_macro {
    my($self, $item) = @_;

    return exists($Macro{$item});
}

sub is_routine {
    my($self, $item) = @_;

    return exists($Routine{$item});
}

sub is_structure {
    my($self, $item) = @_;

    return exists($Structure{$item});
}

sub is_xref {
    my($self, $item) = @_;

    return exists($Xref{$item});
}

#
# Public methods for returning the lists of entries.
#
sub macro_list {
    my($self, $item) = @_;

    return keys(%Macro);
}

sub routine_list {
    my($self, $item) = @_;

    return keys(%Routine);
}

sub xref_list {
    my($self, $item) = @_;

    return keys(%Xref);
}

sub structure_list {
    my($self, $item) = @_;

    return keys(%Structure);
}

#
# Create a unique item name.  This seems weird for a dictionary, but it
# allows the module to be used to record the same information about other
# types of things, such as error messages.  Of course, this doesn't actually
# add the item to the dictionary; the caller has to do that separately.
# That should probably be the next step aftet this, to avoid something
# else being added with this name.
#
sub make_item {
    my($self, $text, $prefix) = @_;
    my($item, $counter, $length) = ("", 0, 16);

    $text =~ s:\W+::g;
    $item = $prefix . uc($text);
    do {
        $item = substr($item, 0, $length)
            . ($counter ? snprintf("%02d", $counter) : "");
        $counter++;
        $length = 14;
    } until (! &defined($self, $item));
    return $item;
}
    
#
# Read a dictionary file and load the arrays.
#
sub readfile {
    my($self, $dictfile) = @_;
    my($dir);
    my($rtype);
    local($DataLine) = 0;
    local($RecordNum) = 0;
    local($Continuing) = 0;

    $DictFile = $dictfile;
    #
    # If the dictionary file doesn't exist, assume we're creating a
    # new one and just saving the name for later writing/checkpointing.
    #
    if (! -r $dictfile) {
        return 1;
    }
    #
    # If the dictionary exists and we can't open it, though, that's
    # a problem.
    #
    open *DICTFILE, "<$dictfile"
        || die("Can't open dictionary file $dictfile");

    while (! eof(*DICTFILE)) {
        local(@field) = ("", "", "", "", "", "", "", "");
        local($rec);

        $rec = &get_record(1);
        next if (! $rec);
        chomp($rec);
        @field = split(m:\|:, $rec);
        $rec =~ s:\$\*:$field[1]:g;
        @field = split(m:\|:, $rec, -1);
        if ($#field != 5) {
            if ($#field < 5) {
                $dir = "few";
            }
            else {
                $dir = "many";
            }
            print STDERR "Too $dir fields (" . ($#field + 1)
                . " instead of 6) for record $RecordNum at "
                . "line $DataLine\n";
        }
        if ($field[0] !~ m:^[$prefix_chars]$:i) {
            print STDERR "Unrecognised record identifier /$field[0]/ "
                . "for record ending on line $DataLine;\n"
                . "\tmissing continuation character?\n";
            next;
        }
        if ($Entity{$field[1]}) {
            print STDERR "Item $field[1] redefined at line $DataLine.\n";
        }
        $Entity{$field[1]} = $rec;
        $Name{$field[1]} = $field[1];
        $rtype = uc(substr($field[0], 0, 1));
        $Rtype{$field[1]} = $rtype;
        if ($rtype eq 'R') {
            $Routine{$field[1]} = $rec;
        }
        elsif ($rtype eq 'M') {
            $Macro{$field[1]} = $rec;
        }
        elsif ($rtype eq 'S') {
            $Structure{$field[1]} = $rec;
        }
        elsif ($rtype eq 'C') {
            $Constant{$field[1]} = $rec;
        }
        elsif ($rtype eq 'D') {
            $Cell{$field[1]} = $rec;
        }
        elsif ($rtype eq 'X') {
            $Xref{$field[1]} = $field[2];
        }
        if ($rtype =~ /M|R/) {
            $Callable{$field[1]} = $rec;
        }
        $Definition{$field[1]} = $field[2];
        $rDefinition{$field[2]} = $field[1];
        $Example{$field[1]} = $field[3];
        if ($rtype ne 'X') {
            $SeeAlso{$field[1]} = $field[4];
            $Description{$field[1]} = $field[5];
            $Description_file{$field[1]} = "";
            if ($field[5] =~ m:^<(.*):) {
                my($fname) = $1;
                $Description_file{$field[1]} = $1;
                $fname =~ s:\$\*:$field[1]:g;
                if (-r $fname) {
                    open *DFILE, "<$fname";
                    $Description{$field[1]} = join("", <DFILE>);
                    close DFILE;
                }
                else {
                    $Description{$field[1]} = "";
                }
            }
        }
    }
}

#
# Return statistics about the dictionary.  The return value is an
# associative array with the following keys:
#
#   "entries"
#   "routines"
#   "cells"
#   "lines"
#   "macros"
#   "structures"
#   "constants"
#   "reference_lists"
#
sub statistics {
    my(@counter, %stats) = ();

    @counter = keys(%Entity);
    $stats{"entries"} = $#counter + 1;
    @counter = keys(%Macro);
    $stats{"macros"} = $#counter + 1;
    @counter = keys(%Constant);
    $stats{"constants"} = $#counter + 1;
    @counter = keys(%Routine);
    $stats{"routines"} = $#counter + 1;
    @counter = keys(%Cell);
    $stats{"cells"} = $#counter + 1;
    @counter = keys(%Structure);
    $stats{"structures"} = $#counter + 1;
    @counter = keys(%Xref);
    $stats{"reference_lists"} = $#counter + 1;
    $stats{"lines"} = $#Dictionary + 1;
    return %stats;
}

#
# Perform repair editing on a description
#
sub repair_desc {
    my($self, $desc) = @_;
    my($last) = $desc . " ";
    my($count) = 0;
    my($i, $inpara);
    my(@paras, @in, @out, $line);

    $desc = "<para>" . encode_entities(decode_entities($desc)) . "</para>";
    #
    # Convert the newlines to something that does not have special RE
    # significance.
    #
    $desc =~ s:\n:\001:gs;
    $desc =~ s:^\s+::g;
    $desc =~ s:\s+$::g;
    $desc =~ s:\s+\001:\001:g;

    #
    # Normalise the para tags to the start of the line.
    #
    $desc =~ s:(<para>):\001$1\001:gsi;
    $desc =~ s:(</para>):\001$1\001:gsi;

    #
    # Get rid of blanks..
    #
    $desc =~ s:\001\s*\001:\001:g;

    #
    # Turn <p>s into <para>s
    #
    $desc =~ s:\&lt;([/]*)[Pp]\&gt;:<${1}para>:gs;

    #
    # Now walk through the lines.
    #
    $inpara = 0;
    @in = split(m:\001:, $desc);

    foreach (@in) {
        if (! $_) {
            next;
        }
        if (m:<para>:) {
            if ($inpara) {
                push(@out, "</para>");
            }
            $inpara = 1;
            push(@out, $_);
        }
        elsif (m:</para>:) {
            if (! $inpara) {
                #
                # Uh, what to do?
                #
                push(@out, "<para>");
            }
            $inpara = 0;
            push(@out, $_);
        }
        else {
            if (! $inpara) {
                push(@out, "<para>");
                $inpara = 1;
            }
            push(@out, $_);
        }
    }
    $desc = join("\001", @out);

    #
    # Get rid of blanks..
    #
    $desc =~ s:<para>\001</para>::gsi;

    $desc =~ s:^[\001\s]*::gs;
    $desc =~ s:[\001\s]*$::gs;
    while ($desc =~ m:\001\s*\001:) {
        $desc =~ s:\001\s*\001:\001:gsi;
    }

    #
    # Indent the tags.
    #
    $desc =~ s:<para>:    <para>:gs;
    $desc =~ s:</para>:    </para>:gs;

    #
    # Restore the newlines.
    #
    $desc =~ s:\001:\n:gs;

    return $desc;
}

#
# Format an entry for output, in either our custom format or XML.
#
sub format_entry {
    my($self, $item, $format) = @_;
    my($record);

    if ($format !~ /xml/i) {
        $record = "$rtype|$item\\\n";
        $record .= "    |" . $Definition{$item} . "\\\n";
        $record .= "    |" . $Example{$item} . "\\\n";
        $record .= "    |" . $SeeAlso{$item} . "\\\n";
        $record .= "    |"
            . ($Description_file{$item} || $Description{$item})
            . "\n";
    }
    else {
        my($sealso, $decl, $desc, $example, $id);
        my(@list);

        $id = ($item =~ m:^\w+$:)
            ? $item
            : &make_item($self, substr($item, 0, 8), "ITM");
        if ($Definition{$item}) {
            my($val) = encode_entities(decode_entities($Definition{$item}));
            if ($Rtype{$item} =~ m:X:i) {
                @list = split(m:,\s*:, $val);
                $val = join("</ref>\n    <ref>", @list);
                $val = "\n    <ref>$val</ref>\n   ";
            }
            else {
                $val = &add_links($self, $val, $item, "", "", "xml", %Name);
            }
            $decl = "   <declaration>"
                . $val
                . "</declaration>\n";
        }
        else {
            $decl = "   <declaration />\n";
        }
        $desc_pre = "   <description";
        $desc_post = "";
        $desc = "";
        if (! ($Description{$item} || $Description_file{$item})) {
            $desc_pre .= " />\n";
        }
        else {
            if ((! $embed) && $Description_file{$item}) {
                $desc_pre .= " href=\""
                    . encode_entities(decode_entities($Description_file{$item}))
                    . "\" />\n";
            }
            elsif ($Description{$item}) {
                $desc_pre .= ">\n    ";
                $desc_post = "\n   </description>\n";
                #
                # Enclose the description in <para>
                #
                $desc = $Description{$item};
                #
                # Now start repairs..
                #
                $desc = repair_desc($self, $desc);
                $desc = &add_links($self, $desc, "\001", "", "", "xml", %Name);
            }
            else {
                $desc_pre .= " />\n";
            }
        }
        $example = "";
        if ($Example{$item}) {
            $example = "   <example>\n    <text>"
                . encode_entities(decode_entities($Example{$item}))
                . "</text>\n   </example>\n";
        }
        $seealso = "";
        if ($SeeAlso{$item}) {
            @list = split(/,\s*/, $SeeAlso{$item});
            $seealso = join("</ref>\n    <ref>", @list);
            $seealso = "   <seealso>\n"
                . "    <ref>$seealso</ref>\n"
                . "   </seealso>\n";
        }
        $record = " <item type=\""
            . $Prefix{$Rtype{$item}}
            . "\" id=\"$id\">\n"
            . "  <name>$item</name>\n"
            . "  <definition>\n"
            . $decl
            . $desc_pre . $desc . $desc_post
            . $example
            . $seealso;
        $record .= "  </definition>\n"
            . " </item>\n";
    }
    return $record;
}
#
# Make a backup of the specified output file (if it exists), and
# write our current dictionary into the original file.
#
sub writefile {
    my($self, $outfile) = @_;
    my($xml);

    $outfile = $DictFile if (! $outfile);
    $xml = ($outfile =~ m:\.xml$:i);
    &backup($outfile);
    open *NEWDICT, ">${outfile}"
        || die("Can't open new dictionary file");
    if ($xml) {
        print NEWDICT "<?xml version=\"1.0\" "
            . "encoding=\"UTF-8\" "
            . "standalone=\"no\" ?>\n"
            . "<?xml-stylesheet href=\"ap-dict.css\" type=\"text/css\" ?>\n"
            . "<!DOCTYPE lexicon SYSTEM\n"
            . " \"./ap-dict.dtd\">\n"
            . "<lexicon name=\"Apache 1.3 API dictionary\">\n";
        foreach (sort(keys(%Entity))) {
            print NEWDICT &format_entry($self, $_, "xml");
        }
    }
    else {
        foreach (@Dictionary) {
            print NEWDICT $_;
        }
    }
    if ($dirty) {
        $msg = "Definitions added by APIdict at "
            . strftime("%Y-%m-%d %H:%M", localtime());
        if ($xml) {
            $msg = "<comment>\n $msg\n</comment>\n";
        }
        else {
            $msg = "#;\n#; $msg\n#;\n";
        }
        print NEWDICT $msg;
        foreach $entry (sort(@uncommitted)) {
            my($rtype, $item) = split(m:=:, $entry);
            my($record);

            $record = &format_entry($self, $item, ($xml ? "xml" : "custom"));
            print NEWDICT $record;
            if ($xml) {
                $record = &format_entry($self, $item, "custom");
            }
            push(@Dictionary, $record);
        }
        $dirty = 0;
        @uncommitted = ();
    }
    if ($xml) {
        print NEWDICT "</lexicon>\n";
    }
    close *NEWDICT;
    return 0;
}

#
# Private methods
#

#
# Make a backup of the specified file by renaming it to *.1.  If
# a *.1 file exists, rename *that* to *.2 and so on, until we can
# move the original one to *.1.
#
sub backup {
    my($filename) = @_;
    my($index, $newindex, $basename) = ("", 1, $filename);

    $filename =~ /(.*)\.([0-9]+$)/;
    ($basename, $index) = ($1, $2);
    if ($index) {
        $newindex = $index + 1;
    }
    else {
        $basename = $filename;
    }
    if (-e "$basename.$newindex") {
        &backup("$basename.$newindex");
    }
    rename($filename, "$basename.$newindex");
}

sub err_is_defined {
    my($item) = @_;

    print STDERR "Item $item is already defined!\n";
    return 0;
}

#
# Initialiser method.  Read in the specified file (if one
# was mentioned) and create the associative arrays and package-private
# data cells.
#
sub init {
    my($self, @args) = @_;

    $dirty = 0;                 # Whether the dict has changed since read
    @uncommitted = ();          # List of items added
    @Dictionary = ();           # Raw text read from the dictionary file
    $DictFile = "";             # Name of dictionary file (for checkpoints)

    %Prefix = ('R' => 'Routine',
	       'M' => 'Macro',
	       'C' => 'Constant',
	       'S' => 'Structure',
	       'X' => 'Cross-reference',
	       'D' => 'Global Data Cell');
    $prefix_chars = join("", keys(%Prefix));

    %Rtype = ();
    %Entity = ();               # Array of Booleans for name-is-defined tests
    %Name = ();
    %Routine = ();              # Array of routine names
    %Macro = ();                # Array of macro names
    %Callable = ();             # Array of active items (routines + macros)
    %Constant = ();             # Array of constant names
    %Structure = ();            # Array of structure names
    %Cell = ();                 # Array of names of global data cells
    %Xref = ();                 # Array of related-names lists

    %Example = ();              # Array of examples for entities
    %Definition = ();           # Array of item definitions
    %rDefinition = ();          # Reverse-map definition to item name
    %Description = ();          # Item descriptions
    %Description_file = ();     # Names of description files
    %SeeAlso = ();              # Array of references for items

    if (@args) {
	return &readfile(@_);
    }
    return 1;
}

#
# Private method to add an entry to the dictionary.
#
sub make_entry {
    my($rtype, $item, $def, $example, $xref, $desc) = @_;

    if ($Entity{$item}) {
        return &err_is_defined($item);
    }
    $item = &sanitise($item);
    $def = &sanitise($item, $def);
    $example = &sanitise($item, $example);
    $xref = &sanitise($item, $xref);
    $desc = &sanitise($item, $desc);
    $Definition{$item} = $def;
    $rDefinition{$def} = $item;
    $Example{$item} = $example;
    $SeeAlso{$item} = $xref;
    $Description{$item} = "";
    if ($desc =~ /^<(.*)/) {
        $Description_file{$item} = $desc;
        $dfile = $1;
        if (-r $dfile) {
            if (open *DFILE, $desc) {
                my(@lines) = <DFILE>;
                close DFILE;
                $Description{$item} = join("", @lines);
            }
        }
    }
    else {
        $Description{$item} = $desc;
        $Description_file{$item} = "";
    }
    $desc = ($Description_file{$item}
             ? $Description_file{$item} : $Description{$item});
    $rec = "$rtype|$item|$def|$example|$xref|desc";
    $Rtype{$item} = $rtype;
    if ($rtype eq 'R') {
        $Routine{$item} = $rec;
    }
    elsif ($rtype eq 'M') {
        $Macro{$item} = $rec;
    }
    elsif ($rtype eq 'C') {
        $Constant{$item} = $rec;
    }
    elsif ($rtype eq 'S') {
        $Structure{$item} = $rec;
    }
    elsif ($rtype eq 'X') {
        $Xref{$item} = $rec;
    }
    elsif ($rtype eq 'D') {
        $Cell{$item} = $rec;
    }
    $Entity{$item} = 1;
    push(@uncommitted, "$rtype=$item");
    $dirty++;
}

#
# Check the argument to see if it can be expanded to any additional
# cross-references.
#
sub references {
    my($ref) = @_;
    my($key, @list, @result) = ();

    $key = $Xref{$ref};
    if (! $key) {
        return $ref;
    }
    @list = split(m:,:, $key);
    foreach (@list) {
        push(@result, &references($_));
    }
    return @result;
}

#
# Do any necessary substitutions, such as replacing '$*' with the item name
# and '|' with '&#124;'.
#
sub sanitise {
    local($item, $_) = @_;

    if (! &defined(0, $_)) {
        $_ = $item;
    }
    else {
        s:\$\*:$item:g;
    }
    s:\|:\&#124;:g;
    return $_;
}

#
# Read a record from the input stream.  Comments are trimmed, as is
# trailing whitespace.  Leading whitespace is trimmed IFF the argument to the
# functional evaluates as true.  A terminal slosh indicates that the next
# line should be read and the record re-processed.  All '\n' occurrences
# in the input are converted to newlines before being returned.
#
sub get_record {
    local ($trim_leading_ws, $continued) = (@_, 0);
    local ($trim_next) = 1;
    my ($line);

    $line = <DICTFILE> || return "";
    push(@Dictionary, $line);
    $DataLine++;
    chomp ($line);
    $line =~ s/#;.*//;
    $line =~ s/\s+$//;
    if ($trim_leading_ws) {
	$line =~ s/^\s+//;
    }
    if ($line =~ m:\\$:) {
	$line =~ s:\\n\s+\\$:\\n\\:;
	$line =~ s:\\$::;
	$trim_next = 0 if ($line =~ m:\\n$:);
	$line .= &get_record($trim_next, 1);
    }
    if ($line && (! $continued)) {
	$RecordNum++;
	$line =~ s:([^\\])\\n:$1\n:g;
	$line =~ s:\\\\:\\:g;
    }
    return ($line);
}

sub verbose {
    my(@lines) = @_;
    my($suffix);

    if ($Verbose) {
	foreach (@lines) {
	    if (m:\\c$:) {
		$suffix = "";
	      s:\\c$::;
	    }
	    else {
		$suffix = "\n";
	    }
	    print STDERR $_, $suffix;
	}
    }
    return 1;
}

sub debug {
    my(@lines) = @_;
    my($suffix);

    if ($Debug) {
	foreach (@lines) {
	    if (m:\\c$:) {
		$suffix = "";
	      s:\\c$::;
	    }
	    else {
		$suffix = "\n";
	    }
	    print STDERR $_, $suffix;
	}
    }
    return 1;
}

#
# {Sigh}  Modules must have values, and this is ours.
#
1;
