#!/usr/bin/perl -w
# -*- cperl -*-
#
# gtk-symbol-versions - GTK symbol version table generator.
# Copyright (C) 1998  Damon Chaplin
#               2007,2008,2009  Stefan Kost
#               2015  Orion Poplawski
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#

#############################################################################
# Script      : gtkc-symbol-versions
# Description : This creates a linker symbol version script from the docs
#############################################################################

use warnings;
use strict;
use Getopt::Long;

push @INC, '/usr/share/gtk-doc/data';
require "gtkdoc-common.pl";

# Options

# name of documentation module
my $MODULE;
my $TMPL_DIR;
my @SOURCE_DIRS;
my $SOURCE_SUFFIXES = "";
my $IGNORE_FILES = "";
my $OUTPUT_DIR = ".";
my $PRINT_VERSION;
my $PRINT_HELP;
my $EXPAND_CONTENT_FILES = "";
my $INLINE_MARKUP_MODE;
my $DEFAULT_INCLUDES;
my $OUTPUT_FORMAT;
my $NAME_SPACE = "";
my $OUTPUT_ALL_SYMBOLS;
my $OUTPUT_SYMBOLS_WITHOUT_SINCE;

my %optctl = ('module' => \$MODULE,
              'source-dir' => \@SOURCE_DIRS,
              'source-suffixes' => \$SOURCE_SUFFIXES,
              'ignore-files' => \$IGNORE_FILES,
              'output-dir' => \$OUTPUT_DIR,
              'tmpl-dir' => \$TMPL_DIR,
              'version' => \$PRINT_VERSION,
              'help' => \$PRINT_HELP,
              'expand-content-files' => \$EXPAND_CONTENT_FILES,
              'sgml-mode' => \$INLINE_MARKUP_MODE,
              'xml-mode' => \$INLINE_MARKUP_MODE,
              'default-includes' => \$DEFAULT_INCLUDES,
              'output-format' => \$OUTPUT_FORMAT,
              'name-space' => \$NAME_SPACE,
              'outputallsymbols' => \$OUTPUT_ALL_SYMBOLS,
              'outputsymbolswithoutsince' => \$OUTPUT_SYMBOLS_WITHOUT_SINCE
              );
GetOptions(\%optctl, "module=s", "source-dir:s", "source-suffixes:s",
    "ignore-files:s", "output-dir:s", "tmpl-dir:s", "version", "outputallsymbols",
    "outputsymbolswithoutsince",
    "expand-content-files:s", "help",
    "sgml-mode", "xml-mode", "default-includes:s",
    "output-format:s", "name-space:s");

if ($PRINT_VERSION) {
    print "0.1\n";
    exit 0;
}

if (!$MODULE) {
    $PRINT_HELP = 1;
}

if ($PRINT_HELP) {
    print <<EOF;
gtk-symbol-version versions 0.1 - generate linker symbol version script

--module=MODULE_NAME       Name of the doc module being parsed
--source-dir=DIRNAME       Directories which contain inline reference material
--source-suffixes=SUFFIXES Suffixes of source files to scan, comma-separated
--ignore-files=FILES       Files or directories which should not be scanned
                           May be used more than once for multiple directories
--output-dir=DIRNAME       Directory to put the generated DocBook files in
--tmpl-dir=DIRNAME         Directory in which template files may be found
--expand-content-files=FILES Extra DocBook files to expand abbreviations in.
--{xml,sgml}-mode          Allow DocBook markup in inline documentation.
--default-includes=FILENAMES Specify default includes for section Synopsis
--name-space=NS            Omit namespace in index.
--version                  Print the version of this program
--help                     Print this help
EOF
    exit 0;
}

my ($empty_element_end);

my $ROOT_DIR = ".";

# All the files are written in subdirectories beneath here.
$TMPL_DIR = $TMPL_DIR ? $TMPL_DIR : "$ROOT_DIR/tmpl";

# These global arrays store information on signals. Each signal has an entry
# in each of these arrays at the same index, like a multi-dimensional array.
my @SignalObjects;        # The GtkObject which emits the signal.
my @SignalNames;        # The signal name.
my @SignalReturns;        # The return type.
my @SignalFlags;        # Flags for the signal
my @SignalPrototypes;        # The rest of the prototype of the signal handler.

# These global arrays store information on Args. Each Arg has an entry
# in each of these arrays at the same index, like a multi-dimensional array.
my @ArgObjects;                # The GtkObject which has the Arg.
my @ArgNames;                # The Arg name.
my @ArgTypes;                # The Arg type - gint, GtkArrowType etc.
my @ArgFlags;                # How the Arg can be used - readable/writable etc.
my @ArgNicks;                # The nickname of the Arg.
my @ArgBlurbs;          # Docstring of the Arg.
my @ArgDefaults;        # Default value of the Arg.
my @ArgRanges;                # The range of the Arg type
# These global hashes store declaration info keyed on a symbol name.
my %Declarations;
my %DeclarationTypes;
my %DeclarationConditional;
my %DeclarationOutput;
my %Deprecated;
my %Since;
my %StructHasTypedef;

# These global hashes store the existing documentation.
my %SymbolDocs;
my %SymbolTypes;
my %SymbolParams;
my %SymbolSourceFile;
my %SymbolSourceLine;

# These global hashes store documentation scanned from the source files.
my %SourceSymbolFound;
my %SourceSymbolSourceFile;
my %SourceSymbolSourceLine;

# all documentation goes in here, so we can do coverage analysis
my %AllSymbols;
my %AllIncompleteSymbols;
my %AllUnusedSymbols;
my %AllDocumentedSymbols;

# Undeclared yet documented symbols
my %UndeclaredSymbols;

# These global arrays store GObject, subclasses and the hierarchy (also of
# non-object derived types).
my @Objects;
my @ObjectLevels;
my %ObjectRoots;

my %Interfaces;
my %Prerequisites;

# holds the symbols which are mentioned in $MODULE-sections.txt and in which
# section they are defined
my %KnownSymbols;
my %SymbolSection;
my %SymbolSectionId;

# collects index entries
my %IndexEntriesFull;
my %IndexEntriesSince;
my %IndexEntriesDeprecated;

# Standard C preprocessor directives, which we ignore for '#' abbreviations.
my %PreProcessorDirectives;
$PreProcessorDirectives{"assert"} = 1;
$PreProcessorDirectives{"define"} = 1;
$PreProcessorDirectives{"elif"} = 1;
$PreProcessorDirectives{"else"} = 1;
$PreProcessorDirectives{"endif"} = 1;
$PreProcessorDirectives{"error"} = 1;
$PreProcessorDirectives{"if"} = 1;
$PreProcessorDirectives{"ifdef"} = 1;
$PreProcessorDirectives{"ifndef"} = 1;
$PreProcessorDirectives{"include"} = 1;
$PreProcessorDirectives{"line"} = 1;
$PreProcessorDirectives{"pragma"} = 1;
$PreProcessorDirectives{"unassert"} = 1;
$PreProcessorDirectives{"undef"} = 1;
$PreProcessorDirectives{"warning"} = 1;

# remember used annotation (to write minimal glossary)
my %AnnotationsUsed;

my %AnnotationDefinition = (
    # the GObjectIntrospection annotations are defined at:
    # https://live.gnome.org/GObjectIntrospection/Annotations
    'allow-none' => "NULL is OK, both for passing and for returning.",
    'nullable' => "NULL may be passed as the value in, out, in-out; or as a return value.",
    'optional' => "NULL may be passed instead of a pointer to a location.",
    'array' => "Parameter points to an array of items.",
    'attribute' => "Deprecated free-form custom annotation, replaced by (attributes) annotation.",
    'attributes' => "Free-form key-value pairs.",
    'closure' => "This parameter is a 'user_data', for callbacks; many bindings can pass NULL here.",
    'constructor' => "This symbol is a constructor, not a static method.",
    'destroy' => "This parameter is a 'destroy_data', for callbacks.",
    'default' => "Default parameter value (for in case the <acronym>shadows</acronym>-to function has less parameters).",
    'element-type' => "Generics and defining elements of containers and arrays.",
    'error-domains' => "Typed errors. Similar to throws in Java.",
    'foreign' => "This is a foreign struct.",
    'get-value-func' => "The specified function is used to convert a struct from a GValue, must be a GTypeInstance.",
    'in' => "Parameter for input. Default is <acronym>transfer none</acronym>.",
    'inout' => "Parameter for input and for returning results. Default is <acronym>transfer full</acronym>.",
    'in-out' => "Parameter for input and for returning results. Default is <acronym>transfer full</acronym>.",
    'method' => "This is a method",
    'not-error' => "A GError parameter is not to be handled like a normal GError.",
    'out' => "Parameter for returning results. Default is <acronym>transfer full</acronym>.",
    'out caller-allocates' => "Out parameter, where caller must allocate storage.",
    'out callee-allocates' => "Out parameter, where caller must allocate storage.",
    'ref-func' => "The specified function is used to ref a struct, must be a GTypeInstance.",
    'rename-to' => "Rename the original symbol's name to SYMBOL.",
    'scope call' => "The callback is valid only during the call to the method.",
    'scope async' => "The callback is valid until first called.",
    'scope notified' => "The callback is valid until the GDestroyNotify argument is called.",
    'set-value-func' => "The specified function is used to convert from a struct to a GValue, must be a GTypeInstance.",
    'skip' => "Exposed in C code, not necessarily available in other languages.",
    'transfer container' => "Free data container after the code is done.",
    'transfer floating' => "Alias for <acronym>transfer none</acronym>, used for objects with floating refs.",
    'transfer full' => "Free data after the code is done.",
    'transfer none' => "Don't free data after the code is done.",
    'type' => "Override the parsed C type with given type.",
    'unref-func' => "The specified function is used to unref a struct, must be a GTypeInstance.",
    'virtual' => "This is the invoker for a virtual method.",
    'value' => "The specified value overrides the evaluated value of the constant.",
    # Stability Level definition
    # https://bugzilla.gnome.org/show_bug.cgi?id=170860
    'Stable' => <<EOF,
The intention of a Stable interface is to enable arbitrary third parties to
develop applications to these interfaces, release them, and have confidence that
they will run on all minor releases of the product (after the one in which the
interface was introduced, and within the same major release). Even at a major
release, incompatible changes are expected to be rare, and to have strong
justifications.
EOF
    'Unstable' => <<EOF,
Unstable interfaces are experimental or transitional. They are typically used to
give outside developers early access to new or rapidly changing technology, or
to provide an interim solution to a problem where a more general solution is
anticipated. No claims are made about either source or binary compatibility from
one minor release to the next.

The Unstable interface level is a warning that these interfaces are  subject to
change without warning and should not be used in unbundled products.

Given such caveats, customer impact need not be a factor when considering
incompatible changes to an Unstable interface in a major or minor release.
Nonetheless, when such changes are introduced, the changes should still be
mentioned in the release notes for the affected release.
EOF
    'Private' => <<EOF
An interface that can be used within the GNOME stack itself, but that is not
documented for end-users.  Such functions should only be used in specified and
documented ways.
EOF
);

# Elements to consider non-block items in MarkDown parsing
my %MD_TEXT_LEVEL_ELEMENTS = ( "literal" => 1,
                               "emphasis" => 1,
                               "envar" => 1,
                               "filename" => 1,
                               "firstterm" => 1,
                               "function" => 1,
                               "manvolnum" => 1,
                               "option" => 1,
                               "replaceable" => 1,
                               "structname" => 1,
                               "title" => 1,
                               "varname" => 1 );
my %MD_ESCAPABLE_CHARS = ( "\\" => 1,
                           "`" => 1,
                           "*" => 1,
                           "_" => 1,
                           "{" => 1,
                           "}" => 1,
                           "[" => 1,
                           "]" => 1,
                           "(" => 1,
                           ")" => 1,
                           ">" => 1,
                           "#" => 1,
                           "+" => 1,
                           "-" => 1,
                           "." => 1,
                           "!" => 1 );
my %MD_GTK_ESCAPABLE_CHARS = ( "@" => 1,
                               "%" => 1 );

# Function and other declaration output settings.
my $PARAM_FIELD_COUNT = 2;

&ReadDeclarationsFile ("$ROOT_DIR/$MODULE-decl.txt", 0);
if (-f "$ROOT_DIR/$MODULE-overrides.txt") {
    &ReadDeclarationsFile ("$ROOT_DIR/$MODULE-overrides.txt", 1);
}

for my $dir (@SOURCE_DIRS) {
    &ReadSourceDocumentation ($dir);
}

#&OutputSymbolsWithoutSince;
&OutputVersionScript;

#############################################################################
# Function    : OutputSinceIndexes
# Description : This writes the 'since' api indexlists that can be included into
#               the main document into an <index> tag.
#############################################################################

sub OutputVersionScript {
    my @sinces = keys %{{ map { $_ => 1 } values %Since }};
    my $old_index = "$OUTPUT_DIR/$MODULE.sym";
    my $new_index = "$OUTPUT_DIR/$MODULE.new";

    open (OUTPUT, ">$new_index")
        || die "Can't create $new_index";

    foreach my $version (sort {$a <=> $b} @sinces) {
        #("Since : [$version]\n");
        # TODO make filtered hash
        #my %index = grep { $Since{$_} eq $version } %IndexEntriesSince;
        my %index = map { $_ => $Since{$_} } grep { $Since{$_} eq $version } keys %Since;

        &OutputVersion ($version, \%index);
    }

    close (OUTPUT);

    &UpdateFileIfChanged ($old_index, $new_index, 0);
}

#############################################################################
# Function    : OutputIndex
# Description : This writes an indexlist that can be included into the main-
#               document into an <index> tag.
#############################################################################

sub OutputVersion {
    my ($version, $apiindexref ) = @_;
    my %apiindex = %{$apiindexref};
    my $symbol;

    print (OUTPUT uc(${MODULE}) . "_$version {\n");
    print (OUTPUT "    global:\n");

    # do a case insensitive sort while chopping off the prefix
    foreach my $hash (
        sort { $$a{criteria} cmp $$b{criteria} }
        map { my $x = uc($_); $x =~ s/^$NAME_SPACE\_?(.*)/$1/i; { criteria => $x, original => $_, short => $1 } }
        keys %apiindex) {

        $symbol = $$hash{original};

        # generate a short symbol description
        my $symbol_type = "";
        if (defined($DeclarationTypes{$symbol})) {
            $symbol_type = lc($DeclarationTypes{$symbol});
        }
        if ($symbol_type eq "") {
            #("trying symbol $symbol\n");
            if ($symbol =~ m/(.*)::(.*)/) {
                my $oname = $1;
                my $osym = $2;
                my $i;
                #("  trying object signal ${oname}:$osym in ".$#SignalNames." signals\n");
                for ($i = 0; $i <= $#SignalNames; $i++) {
                    if ($SignalNames[$i] eq $osym) {
                        $symbol_type = "object signal";
                        last;
                    }
                }
            } elsif ($symbol =~ m/(.*):(.*)/) {
                my $oname = $1;
                my $osym = $2;
                my $i;
                #("  trying object property ${oname}::$osym in ".$#ArgNames." properties\n");
                for ($i = 0; $i <= $#ArgNames; $i++) {
                    #("    ".$ArgNames[$i]."\n");
                    if ($ArgNames[$i] eq $osym) {
                        $symbol_type = "object property";
                        last;
                    }
                }
            }
        }

        # Only hanlde functions for now
        if ($symbol_type eq "function") {
            print (OUTPUT "        $symbol;\n");
        }
    }
    print (OUTPUT "};\n");
}

#############################################################################
# Function    : ConvertSGMLChars
# Description : This is used for text in source code comment blocks, to turn
#               chars which are used in SGML into entities, e.g. '<' into
#               '&lt;'. Depending on $INLINE_MARKUP_MODE, this is done
#               unconditionally or only if the character doesn't seem to be
#               part of an SGML construct (tag or entity reference).
# Arguments   : $text - the text to turn into proper SGML.
#############################################################################

sub ConvertSGMLChars {
    my ($symbol, $text) = @_;

    if ($INLINE_MARKUP_MODE) {
        # For the XML/SGML mode only convert to entities outside CDATA sections.
        return &ModifyXMLElements ($text, $symbol,
                                   "<!\\[CDATA\\[|<programlisting[^>]*>",
                                   \&ConvertSGMLCharsEndTag,
                                   \&ConvertSGMLCharsCallback);
    } else {
        # For the simple non-sgml mode, convert to entities everywhere.

	# First, convert freestanding & to &amp;
        $text =~ s/&(?![a-zA-Z#]+;)/&amp;/g;
        $text =~ s/</&lt;/g;
        # Allow ">" at beginning of string for blockquote markdown
        $text =~ s/(?<=[^\w\n"'\/-])>/&gt;/g;

        return $text;
    }
}


sub ConvertSGMLCharsEndTag {
  if ($_[0] eq "<!\[CDATA\[") {
    return "]]>";
  } else {
    return "</programlisting>";
  }
}

sub ConvertSGMLCharsCallback {
  my ($text, $symbol, $tag) = @_;

  if ($tag =~ m/^<programlisting/) {
    # We can handle <programlisting> specially here.
    return &ModifyXMLElements ($text, $symbol,
                               "<!\\[CDATA\\[",
                               \&ConvertSGMLCharsEndTag,
                               \&ConvertSGMLCharsCallback2);
  } elsif ($tag eq "") {
    # If we're not in CDATA convert to entities.
    $text =~ s/&(?![a-zA-Z#]+;)/&amp;/g;        # Do this first, or the others get messed up.
    $text =~ s/<(?![a-zA-Z\/!])/&lt;/g;
    # Allow ">" at beginning of string for blockquote markdown
    $text =~ s/(?<=[^\w\n"'\/-])>/&gt;/g;

    # Handle "#include <xxxxx>"
    $text =~ s/#include(\s+)<([^>]+)>/#include$1&lt;$2&gt;/g;
  }

  return $text;
}

sub ConvertSGMLCharsCallback2 {
  my ($text, $symbol, $tag) = @_;

  # If we're not in CDATA convert to entities.
  # We could handle <programlisting> differently, though I'm not sure it helps.
  if ($tag eq "") {
    # replace only if its not a tag
    $text =~ s/&(?![a-zA-Z#]+;)/&amp;/g;        # Do this first, or the others get messed up.
    $text =~ s/<(?![a-zA-Z\/!])/&lt;/g;
    $text =~ s/(?<![a-zA-Z0-9"'\/-])>/&gt;/g;

    # Handle "#include <xxxxx>"
    $text =~ s/#include(\s+)<([^>]+)>/#include$1&lt;$2&gt;/g;
  }

  return $text;
}

#############################################################################
# Function    : ModifyXMLElements
# Description : Looks for given XML element tags within the text, and calls
#               the callback on pieces of text inside & outside those elements.
#               Used for special handling of text inside things like CDATA
#               and <programlisting>.
# Arguments   : $text - the text.
#               $symbol - the symbol currently being documented (only used for
#                      error messages).
#               $start_tag_regexp - the regular expression to match start tags.
#                      e.g. "<!\\[CDATA\\[|<programlisting[^>]*>" to match
#                      CDATA sections or programlisting elements.
#               $end_tag_func - function which is passed the matched start tag
#                      and should return the appropriate end tag string regexp.
#               $callback - callback called with each part of the text. It is
#                      called with a piece of text, the symbol being
#                      documented, and the matched start tag or "" if the text
#                      is outside the XML elements being matched.
#############################################################################
sub ModifyXMLElements {
    my ($text, $symbol, $start_tag_regexp, $end_tag_func, $callback) = @_;
    my ($before_tag, $start_tag, $end_tag_regexp, $end_tag);
    my $result = "";

    while ($text =~ m/$start_tag_regexp/s) {
      $before_tag = $`; # Prematch for last successful match string
      $start_tag = $&;  # Last successful match
      $text = $';       # Postmatch for last successful match string

      $result .= &$callback ($before_tag, $symbol, "");
      $result .= $start_tag;

      # get the matching end-tag for current tag
      $end_tag_regexp = &$end_tag_func ($start_tag);

      if ($text =~ m/$end_tag_regexp/s) {
        $before_tag = $`;
        $end_tag = $&;
        $text = $';

        $result .= &$callback ($before_tag, $symbol, $start_tag);
        $result .= $end_tag;
      } else {
        &LogWarning (&GetSymbolSourceFile ($symbol), &GetSymbolSourceLine($symbol),
            "Can't find tag end: $end_tag_regexp in docs for: $symbol.");
        # Just assume it is all inside the tag.
        $result .= &$callback ($text, $symbol, $start_tag);
        $text = "";
      }
    }

    # Handle any remaining text outside the tags.
    $result .= &$callback ($text, $symbol, "");

    return $result;
}

#############################################################################
# Function    : ReadSourceDocumentation
# Description : This reads in the documentation embedded in comment blocks
#                in the source code (for Gnome).
#
#                It recursively descends the source directory looking for .c
#                files and scans them looking for specially-formatted comment
#                blocks.
#
# Arguments   : $source_dir - the directory to scan.
#############m###############################################################

sub ReadSourceDocumentation {
    my ($source_dir) = @_;
    my ($file, $dir, @suffix_list, $suffix);

    # prepend entries from @SOURCE_DIR
    for my $dir (@SOURCE_DIRS) {
        # Check if the filename is in the ignore list.
        if ($source_dir =~ m%^\Q$dir\E/(.*)$% and $IGNORE_FILES =~ m/(\s|^)\Q$1\E(\s|$)/) {
            #("Skipping source directory: $source_dir");
            return;
        }
    }

    #("Scanning source directory: $source_dir");

    # This array holds any subdirectories found.
    my (@subdirs) = ();

    @suffix_list = split (/,/, $SOURCE_SUFFIXES);

    opendir (SRCDIR, $source_dir)
        || die "Can't open source directory $source_dir: $!";

    foreach $file (readdir (SRCDIR)) {
      if ($file =~ /^\./) {
        next;
      } elsif (-d "$source_dir/$file") {
        push (@subdirs, $file);
      } elsif (@suffix_list) {
        foreach $suffix (@suffix_list) {
          if ($file =~ m/\.\Q${suffix}\E$/) {
            &ScanSourceFile ("$source_dir/$file");
          }
        }
      } elsif ($file =~ m/\.[ch]$/) {
        &ScanSourceFile ("$source_dir/$file");
      }
    }
    closedir (SRCDIR);

    # Now recursively scan the subdirectories.
    foreach $dir (@subdirs) {
        &ReadSourceDocumentation ("$source_dir/$dir");
    }
}


#############################################################################
# Function    : ScanSourceFile
# Description : Scans one source file looking for specially-formatted comment
#                blocks. Later &MergeSourceDocumentation is used to merge any
#                documentation found with the documentation already read in
#                from the template files.
#
# Arguments   : $file - the file to scan.
#############################################################################

sub ScanSourceFile {
    my ($file) = @_;
    my $basename;

    # prepend entries from @SOURCE_DIR
    for my $dir (@SOURCE_DIRS) {
        # Check if the filename is in the ignore list.
        if ($file =~ m%^\Q$dir\E/(.*)$% and $IGNORE_FILES =~ m/(\s|^)\Q$1\E(\s|$)/) {
            #("Skipping source file: $file");
            return;
        }
    }

    if ($file =~ m/^.*[\/\\]([^\/\\]*)$/) {
        $basename = $1;
    } else {
        &LogWarning ($file, 1, "Can't find basename for this filename.");
        $basename = $file;
    }

    # Check if the basename is in the list of files to ignore.
    if ($IGNORE_FILES =~ m/(\s|^)\Q${basename}\E(\s|$)/) {
        #("Skipping source file: $file");
        return;
    }

    #("Scanning source file: $file");

    open (SRCFILE, $file)
        || die "Can't open $file: $!";
    my $in_comment_block = 0;
    my $symbol;
    my $in_part = "";
    my ($description, $return_desc);
    my ($since_desc, $stability_desc, $deprecated_desc);
    my $current_param;
    while (<SRCFILE>) {
        # Look for the start of a comment block.
        if (!$in_comment_block) {
            if (m%^\s*/\*.*\*/%) {
                #one-line comment - not gtkdoc
            } elsif (m%^\s*/\*\*\s%) {
                #("Found comment block start\n");

                $in_comment_block = 1;

                # Reset all the symbol data.
                $symbol = "";
                $in_part = "";
                $description = "";
                $return_desc = "";
                $since_desc = "";
                $deprecated_desc = "";
                $stability_desc = "";
                $current_param = -1;
            }
            next;
        }

        # We're in a comment block. Check if we've found the end of it.
        if (m%^\s*\*+/%) {
            if (!$symbol) {
                # maybe its not even meant to be a gtk-doc comment?
                &LogWarning ($file, $., "Symbol name not found at the start of the comment block.");
            } else {
                # Handle Section docs
                if ($symbol =~ m/SECTION:\s*(.*)/) {
                    my $real_symbol=$1;
                    my $key;

                    if (scalar %KnownSymbols) {
                        if ((! defined($KnownSymbols{"$TMPL_DIR/$real_symbol:Long_Description"})) || $KnownSymbols{"$TMPL_DIR/$real_symbol:Long_Description"} != 1) {
                            &LogWarning ($file, $., "Section $real_symbol is not defined in the $MODULE-sections.txt file.");
                        }
                    }

                    $SourceSymbolFound{"$TMPL_DIR/$real_symbol:Long_Description"}=1;
                    $SourceSymbolSourceFile{"$TMPL_DIR/$real_symbol:Long_Description"} = $file;
                    $SourceSymbolSourceLine{"$TMPL_DIR/$real_symbol:Long_Description"} = $.;
                    #$SourceSymbolTypes{$symbol} = "SECTION";
                } else {
                    #("SYMBOL DOCS found in source for : '$symbol' ",length($description), "\n");
                    $SourceSymbolFound{$symbol} = 1;
                    # FIXME $SourceSymbolTypes{$symbol} = "STRUCT,SIGNAL,ARG,FUNCTION,MACRO";
                    #if (defined $DeclarationTypes{$symbol}) {
                    #    $SourceSymbolTypes{$symbol} = $DeclarationTypes{$symbol}
                    #}
                    $SourceSymbolSourceFile{$symbol} = $file;
                    $SourceSymbolSourceLine{$symbol} = $.;
                }

                if ($since_desc) {
                     ($since_desc, my @extra_lines) = split ("\n", $since_desc);
                     $since_desc =~ s/^\s+//;
                     $since_desc =~ s/\s+$//;
                     #("Since($symbol) : [$since_desc]\n");
                     $Since{$symbol} = &ConvertSGMLChars ($symbol, $since_desc);
                     if(scalar @extra_lines) {
                         &LogWarning ($file, $., "multi-line since docs found");
                     }
                }

                if ($deprecated_desc) {
                    if (!exists $Deprecated{$symbol}) {
                         # don't warn for signals and properties
                         #if ($symbol !~ m/::?(.*)/) {
                         if (defined $DeclarationTypes{$symbol}) {
                             &LogWarning ($file, $.,
                                 "$symbol is deprecated in the inline comments, but no deprecation guards were found around the declaration.".
                                 " (See the --deprecated-guards option for gtkdoc-scan.)");
                         }
                    }
                    $Deprecated{$symbol} = &ConvertSGMLChars ($symbol, $deprecated_desc);
                }
            }

            $in_comment_block = 0;
            next;
        }

        # Get rid of ' * ' at start of every line in the comment block.
        s%^\s*\*\s?%%;
        # But make sure we don't get rid of the newline at the end.
        if (!$_) {
            $_ = "\n";
        }
        #("scanning :$_");

        # If we haven't found the symbol name yet, look for it.
        if (!$symbol) {
            if (m%^\s*(SECTION:\s*\S+)%) {
                $symbol = $1;
                #("SECTION DOCS found in source for : '$symbol'\n");
            } elsif (m%^\s*([\w:-]*\w)\s*:?\s*(\([-a-z0-9_ ]+\)\s*)*$%) {
                $symbol = $1;
                #("SYMBOL DOCS found in source for : '$symbol'\n");
            }
            next;
        }

        if ($in_part eq "description") {
            # Get rid of 'Description:'
            s%^\s*Description:%%;
        }

        if (m%^\s*(returns|return\s+value):%i) {
            # we're in param section and have not seen the blank line
            if($in_part ne "") {
              $return_desc = $';
              $in_part = "return";
              next;
            }
        } elsif (m%^\s*since:%i) {
            # we're in param section and have not seen the blank line
            if($in_part ne "") {
              $since_desc = $';
              $in_part = "since";
              next;
            }
        } elsif (m%^\s*deprecated:%i) {
            # we're in param section and have not seen the blank line
            if($in_part ne "") {
              $deprecated_desc = $';
              $in_part = "deprecated";
              next;
            }
        } elsif (m%^\s*stability:%i) {
            $stability_desc = $';
            $in_part = "stability";
            next;
        }

        if ($in_part eq "description") {
            $description .= $_;
            next;
        } elsif ($in_part eq "return") {
            $return_desc .= $_;
            next;
        } elsif ($in_part eq "since") {
            $since_desc .= $_;
            next;
        } elsif ($in_part eq "stability") {
            $stability_desc .= $_;
            next;
        } elsif ($in_part eq "deprecated") {
            $deprecated_desc .= $_;
            next;
        }

        # We must be in the parameters. Check for the empty line below them.
        if (m%^\s*$%) {
            $in_part = "description";
            next;
        }
    }
    close (SRCFILE);
}

#############################################################################
# Function    : OutputSymbolsWithoutSince
# Description : Outputs list of all symbols without a since tag to a file
#
# Arguments   : none
#############################################################################

sub OutputSymbolsWithoutSince {
     my $n_documented = 0;
     my $total = 0;
     my $symbol;
     my $percent;
     my $msg;

     foreach $symbol (sort (keys (%SourceSymbolFound))) {
         if (!defined $Since{$symbol}) {
             print $symbol . "\n";
         }
     }
}


#############################################################################
# Function    : MergeSourceDocumentation
# Description : This merges documentation read from a source file into the
#                documentation read in from a template file.
#
#                Parameter descriptions override any in the template files.
#                Function descriptions are placed before any description from
#                the template files.
#
# Arguments   : none
#############################################################################

sub MergeSourceDocumentation {
    my $symbol;
    my @Symbols;

    if (scalar %SymbolDocs) {
        @Symbols=keys (%SymbolDocs);
        #("num existing entries: ".(scalar @Symbols)."\n");
    }
    else {
        # filter scanned declarations, with what we suppress from -sections.txt
        my %tmp = ();
        foreach $symbol (keys (%Declarations)) {
            if (defined($KnownSymbols{$symbol}) && $KnownSymbols{$symbol} == 1) {
                $tmp{$symbol}=1;
            }
        }
        # , add the rest from -sections.txt
        foreach $symbol (keys (%KnownSymbols)) {
            if ($KnownSymbols{$symbol} == 1) {
                $tmp{$symbol}=1;
            }
        }
        # and add whats found in the source
        foreach $symbol (keys (%SourceSymbolFound)) {
            $tmp{$symbol}=1;
        }
        @Symbols = keys (%tmp);
        #("num source entries: ".(scalar @Symbols)."\n");
    }
    foreach $symbol (@Symbols) {
        $AllSymbols{$symbol} = 1;

        my $have_tmpl_docs = 0;

        if (exists ($SourceSymbolFound{$symbol})) {
            my $type = $DeclarationTypes {$symbol};

            #("merging [$symbol] from source\n");

            my $item = "Parameter";
            if (defined ($type)) {
                if ($type eq 'STRUCT') {
                    $item = "Field";
                } elsif ($type eq 'ENUM') {
                    $item = "Value";
                } elsif ($type eq 'UNION') {
                    $item = "Field";
                }
            } else {
                $type="SIGNAL";
            }
        } else {
            if ($have_tmpl_docs) {
                $AllDocumentedSymbols{$symbol} = 1;
                #("merging [$symbol] from template\n");
            }
            else {
                #("[$symbol] undocumented\n");
            }
        }
   }
   #("num doc entries: ".(scalar %SymbolDocs)."\n");
}

#############################################################################
# LIBRARY FUNCTIONS -        These functions are used in both gtkdoc-mkdb and
#                        gtkdoc-mktmpl and should eventually be moved to a
#                        separate library.
#############################################################################

#############################################################################
# Function    : ReadDeclarationsFile
# Description : This reads in a file containing the function/macro/enum etc.
#                declarations.
#
#                Note that in some cases there are several declarations with
#                the same name, e.g. for conditional macros. In this case we
#                set a flag in the %DeclarationConditional hash so the
#                declaration is not shown in the docs.
#
#                If a macro and a function have the same name, e.g. for
#                gtk_object_ref, the function declaration takes precedence.
#
#                Some opaque structs are just declared with 'typedef struct
#                _name name;' in which case the declaration may be empty.
#                The structure may have been found later in the header, so
#                that overrides the empty declaration.
#
# Arguments   : $file - the declarations file to read
#                $override - if declarations in this file should override
#                        any current declaration.
#############################################################################

sub ReadDeclarationsFile {
    my ($file, $override) = @_;

    if ($override == 0) {
        %Declarations = ();
        %DeclarationTypes = ();
        %DeclarationConditional = ();
        %DeclarationOutput = ();
    }

    open (INPUT, $file)
        || die "Can't open $file: $!";
    my $declaration_type = "";
    my $declaration_name;
    my $declaration;
    my $is_deprecated = 0;
    while (<INPUT>) {
        if (!$declaration_type) {
            if (m/^<([^>]+)>/) {
                $declaration_type = $1;
                $declaration_name = "";
                #("Found declaration: $declaration_type\n");
                $declaration = "";
            }
        } else {
            if (m%^<NAME>(.*)</NAME>%) {
                $declaration_name = $1;
            } elsif (m%^<DEPRECATED/>%) {
                $is_deprecated = 1;
            } elsif (m%^</$declaration_type>%) {
                #("Found end of declaration: $declaration_name\n");
                # Check that the declaration has a name
                if ($declaration_name eq "") {
                    &LogWarning ($file, $., "$declaration_type has no name.\n");
                }

                # If the declaration is an empty typedef struct _XXX XXX
                # set the flag to indicate the struct has a typedef.
                if ($declaration_type eq 'STRUCT'
                    && $declaration =~ m/^\s*$/) {
                    #("Struct has typedef: $declaration_name\n");
                    $StructHasTypedef{$declaration_name} = 1;
                }

                # Check if the symbol is already defined.
                if (defined ($Declarations{$declaration_name})
                    && $override == 0) {
                    # Function declarations take precedence.
                    if ($DeclarationTypes{$declaration_name} eq 'FUNCTION') {
                        # Ignore it.
                    } elsif ($declaration_type eq 'FUNCTION') {
                        if ($is_deprecated) {
                            $Deprecated{$declaration_name} = "";
                        }
                        $Declarations{$declaration_name} = $declaration;
                        $DeclarationTypes{$declaration_name} = $declaration_type;
                    } elsif ($DeclarationTypes{$declaration_name}
                              eq $declaration_type) {
                        # If the existing declaration is empty, or is just a
                        # forward declaration of a struct, override it.
                        if ($declaration_type eq 'STRUCT') {
                            if ($Declarations{$declaration_name} =~ m/^\s*(struct\s+\w+\s*;)?\s*$/) {
                                if ($is_deprecated) {
                                    $Deprecated{$declaration_name} = "";
                                }
                                $Declarations{$declaration_name} = $declaration;
                            } elsif ($declaration =~ m/^\s*(struct\s+\w+\s*;)?\s*$/) {
                                # Ignore an empty or forward declaration.
                            } else {
                                &LogWarning ($file, $., "Structure $declaration_name has multiple definitions.");
                            }
                        } else {
                            # set flag in %DeclarationConditional hash for
                            # multiply defined macros/typedefs.
                            $DeclarationConditional{$declaration_name} = 1;
                        }
                    } else {
                        &LogWarning ($file, $., "$declaration_name has multiple definitions.");
                    }
                } else {
                    if ($is_deprecated) {
                        $Deprecated{$declaration_name} = "";
                    }
                    $Declarations{$declaration_name} = $declaration;
                    $DeclarationTypes{$declaration_name} = $declaration_type;
                }

                $declaration_type = "";
                $is_deprecated = 0;
            } else {
                $declaration .= $_;
            }
        }
    }
    close (INPUT);
}


#############################################################################
# Function    : GetSymbolSourceFile
# Description : Get the filename where the symbol docs where taken from.
# Arguments   : $symbol - the symbol name
#############################################################################

sub GetSymbolSourceFile {
    my ($symbol) = @_;

    if (defined($SourceSymbolSourceFile{$symbol})) {
        return $SourceSymbolSourceFile{$symbol};
    } elsif (defined($SymbolSourceFile{$symbol})) {
        return $SymbolSourceFile{$symbol};
    } else {
        return "";
    }
}

#############################################################################
# Function    : GetSymbolSourceLine
# Description : Get the file line where the symbol docs where taken from.
# Arguments   : $symbol - the symbol name
#############################################################################

sub GetSymbolSourceLine {
    my ($symbol) = @_;

    if (defined($SourceSymbolSourceLine{$symbol})) {
        return $SourceSymbolSourceLine{$symbol};
    } elsif (defined($SymbolSourceLine{$symbol})) {
        return $SymbolSourceLine{$symbol};
    } else {
        return 0;
    }
}

