You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@apr.apache.org by fi...@apache.org on 2001/02/18 14:36:15 UTC

cvs commit: apr/build MakeEtags PrintPath cvstodsp5.pl dsp5tocvs.pl make_export.awk mkdep.sh mkdir.sh rules.mk.in scandoc.pl scandoc_template.pl

fielding    01/02/18 05:36:15

  Added:       build    MakeEtags PrintPath cvstodsp5.pl dsp5tocvs.pl
                        make_export.awk mkdep.sh mkdir.sh rules.mk.in
                        scandoc.pl scandoc_template.pl
  Log:
  Moved from apr/helpers to apr/build (without changes).
  scandoc has been renamed to scandoc.pl
  default.pl has been renamed to scandoc_template.pl
  
  Revision  Changes    Path
  1.1                  apr/build/MakeEtags
  
  Index: MakeEtags
  ===================================================================
  #!/bin/sh
  
  # This file illustrates how to generate a useful TAGS file via etags
  # for emacs.  This should be invoked from the src directory i.e.:
  #   > helpers/MakeEtags
  # and will create a TAGS file in the src directory.
  
  # This script falls under the Apache License.
  # See http://www.apache.org/docs/LICENSE
  
  # Once you have created src/TAGS in emacs you'll need to setup
  # tag-table-alist with an entry to assure it finds the single src/TAGS
  # file from the many source directories.  Something along these lines:
  # (setq tag-table-alist
  #	'(("/home/me/work/apache-1.3/src/" 
  #	   . "/home/me/work/apache-1.3/src/")
  #	 ))
  
  # This requires a special version of etags, i.e. the
  # one called "Exuberant ctags" available at:
  #    http://fly.hiwaay.net/~darren/ctags/
  # Once that is setup you'll need to point to the
  # executable here:
  
  etags=~/local/bin/etags
  
  # Exuberant etags is necessary since it can ignore some defined symbols
  # that obscure the function signatures.
  
  ignore=AP_DECLARE,AP_DECLARE_NONSTD,__declspec
  
  # Create an etags file at the root of the source
  # tree, then create symbol links to it from each
  # directory in the source tree.  By passing etags
  # absolute pathnames we get a tag file that is
  # NOT portable when we move the directory tree.
  
  find . -name '*.[ch]' -print | $etags -I "$ignore"  -L -
  
  
  
  
  1.1                  apr/build/PrintPath
  
  Index: PrintPath
  ===================================================================
  #!/bin/sh
  # Look for program[s] somewhere in $PATH.
  #
  # Options:
  #  -s
  #    Do not print out full pathname. (silent)
  #  -pPATHNAME
  #    Look in PATHNAME instead of $PATH
  #
  # Usage:
  #  PrintPath [-s] [-pPATHNAME] program [program ...]
  #
  # Initially written by Jim Jagielski for the Apache configuration mechanism
  #  (with kudos to Kernighan/Pike)
  #
  # This script falls under the Apache License.
  # See http://www.apache.org/docs/LICENSE
  
  ##
  # Some "constants"
  ##
  pathname=$PATH
  echo="yes"
  
  ##
  # Find out what OS we are running for later on
  ##
  os=`(uname) 2>/dev/null`
  
  ##
  # Parse command line
  ##
  for args in $*
  do
      case $args in
  	-s  ) echo="no" ;;
  	-p* ) pathname="`echo $args | sed 's/^..//'`" ;;
  	*   ) programs="$programs $args" ;;
      esac
  done
  
  ##
  # Now we make the adjustments required for OS/2 and everyone
  # else :)
  #
  # First of all, all OS/2 programs have the '.exe' extension.
  # Next, we adjust PATH (or what was given to us as PATH) to
  # be whitespace seperated directories.
  # Finally, we try to determine the best flag to use for
  # test/[] to look for an executable file. OS/2 just has '-r'
  # but with other OSs, we do some funny stuff to check to see
  # if test/[] knows about -x, which is the prefered flag.
  ##
  
  if [ "x$os" = "xOS/2" ]
  then
      ext=".exe"
      pathname=`echo -E $pathname |
       sed 's/^;/.;/
  	  s/;;/;.;/g
  	  s/;$/;./
  	  s/;/ /g
  	  s/\\\\/\\//g' `
      test_exec_flag="-r"
  else
      ext=""	# No default extensions
      pathname=`echo $pathname |
       sed 's/^:/.:/
  	  s/::/:.:/g
  	  s/:$/:./
  	  s/:/ /g' `
      # Here is how we test to see if test/[] can handle -x
      testfile="pp.t.$$"
  
      cat > $testfile <<ENDTEST
  #!/bin/sh
  if [ -x / ] || [ -x /bin ] || [ -x /bin/ls ]; then
      exit 0
  fi
  exit 1
  ENDTEST
  
      if `/bin/sh $testfile 2>/dev/null`; then
  	test_exec_flag="-x"
      else
  	test_exec_flag="-r"
      fi
      rm -f $testfile
  fi
  
  for program in $programs
  do
      for path in $pathname
      do
  	if [ $test_exec_flag $path/${program}${ext} ] && \
  	   [ ! -d $path/${program}${ext} ]; then
  	    if [ "x$echo" = "xyes" ]; then
  		echo $path/${program}${ext}
  	    fi
  	    exit 0
  	fi
  
  # Next try without extension (if one was used above)
  	if [ "x$ext" != "x" ]; then
              if [ $test_exec_flag $path/${program} ] && \
                 [ ! -d $path/${program} ]; then
                  if [ "x$echo" = "xyes" ]; then
                      echo $path/${program}
                  fi
                  exit 0
              fi
          fi
      done
  done
  exit 1
  
  
  
  
  1.1                  apr/build/cvstodsp5.pl
  
  Index: cvstodsp5.pl
  ===================================================================
  use IO::File;
  use File::Find;
  
  chdir '..';
  find(\&tovc5, '.');
  
  sub tovc5 { 
  
      if (m|.dsp$|) {
          $oname = $_;
  	$tname = '.#' . $_;
          $verchg = 0;
  	$srcfl = new IO::File $oname, "r" || die;
  	$dstfl = new IO::File $tname, "w" || die;
  	while ($src = <$srcfl>) {
  	    if ($src =~ s|Format Version 6\.00|Format Version 5\.00|) {
  		$verchg = -1;
  	    }
  	    if ($src =~ s|^(# ADD CPP .*)/ZI (.*)|$1/Zi $2|) {
  		$verchg = -1;
  	    }
  	    if ($src =~ s|^(# ADD BASE CPP .*)/ZI (.*)|$1/Zi $2|) {
  		$verchg = -1;
  	    }
  	    if ($src !~ m|^# PROP AllowPerConfigDependencies|) {
  		print $dstfl $src; }
  	    else {
  		$verchg = -1;
  
  	    }
  	}
  	undef $srcfl;
  	undef $dstfl;
  	if ($verchg) {
  	    unlink $oname || die;
  	    rename $tname, $oname || die;
  	    print "Converted VC6 project " . $oname . " to VC5 in " . $File::Find::dir . "\n"; 
  	}
  	else {
  	    unlink $tname;
  	}
      }
  }
  
  
  1.1                  apr/build/dsp5tocvs.pl
  
  Index: dsp5tocvs.pl
  ===================================================================
  use IO::File;
  use File::Find;
  
  chdir '..';
  find(\&tovc6, '.');
  
  sub tovc6 { 
  
      if (m|.dsp$|) {
          $oname = $_;
  	$tname = '.#' . $_;
  	$verchg = 0;
  	$srcfl = new IO::File $_, "r" || die;
  	$dstfl = new IO::File $tname, "w" || die;
  	while ($src = <$srcfl>) {
  	    if ($src =~ s|Format Version 5\.00|Format Version 6\.00|) {
  		$verchg = -1;
  	    }
  	    if ($src =~ s|^(# ADD CPP .*)/Zi (.*)|$1/ZI $2|) {
  		$verchg = -1;
  	    }
  	    if ($src =~ s|^(# ADD BASE CPP .*)/Zi (.*)|$1/ZI $2|) {
  		$verchg = -1;
  	    }
  	    if ($src =~ s|^(!MESSAGE .*)\\\n|$1|) {
  		$cont = <$srcfl>;
  		$src = $src . $cont;
  		$verchg = -1;
  	    }
              print $dstfl $src; 
  	    if ($verchg && $src =~ m|^# Begin Project|) {
  		print $dstfl "# PROP AllowPerConfigDependencies 0\n"; 
  	    }
  	}
  	undef $srcfl;
  	undef $dstfl;
  	if ($verchg) {
  	    unlink $oname || die;
  	    rename $tname, $oname || die;
  	    print "Converted VC5 project " . $oname . " to VC6 in " . $File::Find::dir . "\n"; 
  	}
  	else {
  	    unlink $tname;
  	}
      }
  }
  
  
  
  1.1                  apr/build/make_export.awk
  
  Index: make_export.awk
  ===================================================================
  # Based on Ryan Bloom's make_export.pl
  
  /^#[ \t]*if(def)? (AP[RU]?_|!?defined).*/ {
  	if (old_filename != FILENAME) {
  		if (old_filename != "") printf("%s", line)
  		macro_no = 0
  		found = 0
  		count = 0
  		old_filename = FILENAME
  		line = ""
  	}
  	macro_stack[macro_no++] = macro
  	macro = substr($0, length($1)+2)
  	count++
  	line = line macro "\n"
  	next
  }
  
  /^#[ \t]*endif/ {
  	if (count > 0) {
  		count--
  		line = line "/" macro "\n"
  		macro = macro_stack[--macro_no]
  	}
  	if (count == 0) {
  		if (found != 0) {
  			printf("%s", line)
  		}
  		line = ""
  	}
  	next
  }
  
  /^[ \t]*(AP[RU]?_DECLARE[^(]*[(])?(const[ \t])?[a-z_]+[ \t\*]*[)]?[ \t]+[*]?([A-Za-z0-9_]+)\(/ {
  	if (count) {
  		found++
  	}
  	for (i = 0; i < count; i++) {
  		line = line "\t"
  	}
  	sub("^[ \t]*(AP[UR]?_DECLARE[^(]*[(])?(const[ \t])?[a-z_]+[ \t\*]*[)]?[ \t]+[*]?", "");
  	sub("[(].*", "");
  	line = line $0 "\n"
  
  	if (count == 0) {
  		printf("%s", line)
  		line = ""
  	}
  	next
  }
  
  END {
  	printf("%s", line)
  }
  
  
  
  1.1                  apr/build/mkdep.sh
  
  Index: mkdep.sh
  ===================================================================
  #!/bin/sh
  #
  # 1) remove everything after the DO NOT REMOVE
  # 2) generate the dependencies, adding them to the end of Makefile.new
  # 3) move the Makefile.new back into place
  #
  # Note that we use && to ensure that Makefile is not changed if an error
  # occurs during the process
  #
  sed -ne '1,/^# DO NOT REMOVE/p' Makefile > Makefile.new \
      && gcc -MM  $* | sed -e "s/\.o:/\.lo:/" >> Makefile.new \
      && mv Makefile.new Makefile
  
  
  
  1.1                  apr/build/mkdir.sh
  
  Index: mkdir.sh
  ===================================================================
  #!/bin/sh
  ## 
  ##  mkdir.sh -- make directory hierarchy
  ##
  ##  Based on `mkinstalldirs' from Noah Friedman <fr...@prep.ai.mit.edu>
  ##  as of 1994-03-25, which was placed in the Public Domain.
  ##  Cleaned up for Apache's Autoconf-style Interface (APACI)
  ##  by Ralf S. Engelschall <rs...@apache.org>
  ##
  #
  # This script falls under the Apache License.
  # See http://www.apache.org/docs/LICENSE
  
  
  umask 022
  errstatus=0
  for file in ${1+"$@"} ; do 
      set fnord `echo ":$file" |\
                 sed -e 's/^:\//%/' -e 's/^://' -e 's/\// /g' -e 's/^%/\//'`
      shift
      pathcomp=
      for d in ${1+"$@"}; do
          pathcomp="$pathcomp$d"
          case "$pathcomp" in
              -* ) pathcomp=./$pathcomp ;;
          esac
          if test ! -d "$pathcomp"; then
              echo "mkdir $pathcomp" 1>&2
              mkdir "$pathcomp" || errstatus=$?
          fi
          pathcomp="$pathcomp/"
      done
  done
  exit $errstatus
  
  
  
  
  1.1                  apr/build/rules.mk.in
  
  Index: rules.mk.in
  ===================================================================
  # ====================================================================
  # The Apache Software License, Version 1.1
  #
  # Copyright (c) 2000-2001 The Apache Software Foundation.  All rights
  # reserved.
  #
  # Redistribution and use in source and binary forms, with or without
  # modification, are permitted provided that the following conditions
  # are met:
  #
  # 1. Redistributions of source code must retain the above copyright
  #    notice, this list of conditions and the following disclaimer.
  #
  # 2. Redistributions in binary form must reproduce the above copyright
  #    notice, this list of conditions and the following disclaimer in
  #    the documentation and/or other materials provided with the
  #    distribution.
  #
  # 3. The end-user documentation included with the redistribution,
  #    if any, must include the following acknowledgment:
  #       "This product includes software developed by the
  #        Apache Software Foundation (http://www.apache.org/)."
  #    Alternately, this acknowledgment may appear in the software itself,
  #    if and wherever such third-party acknowledgments normally appear.
  #
  # 4. The names "Apache" and "Apache Software Foundation" must
  #    not be used to endorse or promote products derived from this
  #    software without prior written permission. For written
  #    permission, please contact apache@apache.org.
  #
  # 5. Products derived from this software may not be called "Apache",
  #    nor may "Apache" appear in their name, without prior written
  #    permission of the Apache Software Foundation.
  #
  # THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  # OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  # DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  # ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  # USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  # OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  # SUCH DAMAGE.
  # ====================================================================
  #
  # This software consists of voluntary contributions made by many
  # individuals on behalf of the Apache Software Foundation.  For more
  # information on the Apache Software Foundation, please see
  # <http://www.apache.org/>.
  #
  
  #
  # rules.mk: standard rules for APR
  #
  
  @SET_MAKE@
  
  #
  # Configuration variables
  #
  top_builddir=@top_builddir@
  
  CC=@CC@
  AWK=@AWK@
  LIBTOOL=@LIBTOOL@
  
  CFLAGS=@CFLAGS@ @OPTIM@
  CPPFLAGS=@CPPFLAGS@ $(INCLUDES)
  LIBS=@LIBS@
  LDFLAGS=@LDFLAGS@
  
  RM=@RM@
  SHELL=@SHELL@
  
  MKEXPORT=@APR_MKEXPORT@
  MKDEP=@APR_MKDEP@
  SCANDOC=@APR_SCANDOC@
  
  ### make LTFLAGS somewhat variable?
  LTFLAGS = --silent
  
  #
  # Basic macro setup
  #
  COMPILE      = $(CC) $(CFLAGS) $(CPPFLAGS)
  LT_COMPILE   = $(LIBTOOL) --mode=compile $(LTFLAGS) $(COMPILE) -c $< && touch $@
  
  LINK         = $(LIBTOOL) --mode=link $(LTFLAGS) $(COMPILE) $(LDFLAGS) -o $@
  
  #
  # Standard build rules
  #
  all: all-recursive
  depend: depend-recursive
  clean: clean-recursive
  distclean: distclean-recursive
  extraclean: extraclean-recursive
  
  install: all-recursive
  
  
  all-recursive depend-recursive clean-recursive distclean-recursive \
    extraclean-recursive:
  	@otarget=`echo $@ | sed s/-recursive//`; \
  	list='$(SUBDIRS)'; \
  	for i in $$list; do \
  	    if test -d "$$i"; then \
  		target="$$otarget"; \
  		echo "Making $$target in $$i"; \
  		if test "$$i" = "."; then \
  		    made_local=yes; \
  		    target="local-$$target"; \
  		fi; \
  		(cd $$i && $(MAKE) $$target) || exit 1; \
  	    fi; \
  	done; \
          if test "$$otarget" = "all" && test -z "$(TARGETS)"; then \
  	    made_local=n/a; \
  	fi; \
  	if test -z "$$made_local"; then \
  	    $(MAKE) "local-$$otarget" || exit 1; \
  	fi
  
  local-clean: x-local-clean
  	$(RM) -f *.o *.lo *.a *.la *.so $(CLEAN_TARGETS) $(PROGRAMS)
  	$(RM) -rf .libs
  
  local-distclean: local-clean x-local-distclean
  	$(RM) -f Makefile $(DISTCLEAN_TARGETS)
  
  local-extraclean: local-distclean
  	@if test -n "$(EXTRACLEAN_TARGETS)"; then \
  	    echo $(RM) -f $(EXTRACLEAN_TARGETS) ; \
  	    $(RM) -f $(EXTRACLEAN_TARGETS) ; \
  	fi
  
  local-all: $(TARGETS)
  
  local-depend:
  	@if test -n "`ls *.c 2> /dev/null`"; then \
  	    echo $(MKDEP) $(CFLAGS) $(CPPFLAGS) *.c ; \
  	    $(MKDEP) $(CFLAGS) $(CPPFLAGS) *.c ; \
  	fi
  
  # to be filled in by the actual Makefile
  x-local-clean x-local-distclean:
  
  
  #
  # Implicit rules for creating outputs from input files
  #
  .SUFFIXES:
  .SUFFIXES: .c .lo .o
  
  .c.o:
  	$(COMPILE) -c $<
  
  .c.lo:
  	$(LT_COMPILE)
  
  .PHONY: all depend clean distclean extraclean install \
  	all-recursive depend-recursive clean-recursive distclean-recursive \
  	extraclean-recursive
  	local-all local-depend local-clean local-distclean local-extraclean \
  	x-local-clean x-local-distclean
  
  
  
  1.1                  apr/build/scandoc.pl
  
  Index: scandoc.pl
  ===================================================================
  #!/usr/bin/perl
  #
  # ScanDoc - Version 0.12,  A C/C++ Embedded Documentation Analyser
  # ----------------------------------------------------------------
  #
  # Distributed under the "Artistic License".  See the file 
  # "COPYING" that accompanies the ScanDoc distribution.
  #
  # See http://scandoc.sourceforge.net/ for more information and
  # complete documentation.
  #
  # (c) 1997 - 2000 Talin and others.
  
  require "ctime.pl";
  require "getopts.pl";
  
  # 1 = on (verbose); 0 = off 
  $debug = 0;
  
  # Get the current date
  $date = &ctime(time);
  
  # Set the default tab size
  $tabSize = 4;
  
  $minorVersion = 12;
  $majorVersion = 0;
  $scandocURL   = "http://scandoc.sourceforge.net/";
  
  # Set up default templates
  &Getopts( 'i:d:p:t:' );
  
  if ($#ARGV < 0) {
    die "Usage: -i <doc-template> -p <output-path> -t<tabsize> -d<sym>=<value> [ <input-files> ... ]\n";
  }
  
  # Read the template
  if (!defined $opt_i) {
    $opt_i = "default.pl";
  }
  &readTemplate( $opt_i );
  
  # Set the destination path.
  $destPath = "";
  $destPath = $opt_p if (defined($opt_p));
  
  # Set the tab size.
  $tabSize = $opt_t if (defined($opt_t));
  
  # Handle defines
  if ($opt_d) {
    foreach $def (split( /,/, $opt_d )) {
      if ($def =~ /\s*(\w*)\=(.*)/) {
        $${1} = $2;
      }
      else {
        $${1} = 1;
      }
    }
  }
  
  # For each input filename, parse it
  while ($srcfile = shift(@ARGV)) {
  
    $linenumber = 0;
    open( FILE, $srcfile ) || die "Can't open file $srcfile\n";
    print STDERR "Reading \"$srcfile\"\n";
    
    $docTag = 'description';
    $docEmpty = 1;
    $packageName = '.general';
    $author = '';
    $version = '';
    $class = 0;
    $_ = '';
    
    while (&parseDeclaration( '' )) {}
  }
  
  # Collate subclasses and associate with class record.
  foreach $className (keys %subclasses) {
    my $class = &classRecord( $className );
    
    if ($class) {
      my @subs = ();
      # print STDERR "$className ", join( ',', @{$subclasses{ $className }} ), "\n";
      foreach $subName ($subclasses{ $className }) {
        if (&classRecord( $subName )) {
  	push @subs, $subName;
        }
        $class->{ 'subs' } = @subs;
      }
    }
  }
  
  # Turn packages into objects. Special case for "default" package.
  foreach $pkg (keys %packages)
  {
    # print STDERR $pkg, "\n";
    bless $packages{ $pkg }, PackageRecord;
    if ($pkg eq '.general') {
      $packages{ $pkg }{ 'name' } = "General";
    }
    else {
      $packages{ $pkg }{ 'name' } = $pkg;
    }
    # print STDERR $packages{ $pkg }->Classes(), "\n";
  }
  
  # Execute template file
  # print STDERR $docTemplate; # For debugging
  eval $docTemplate;
  print STDERR $@;
  
  exit;
  
  # ======================= Subroutines ================================
  
  # Read a line of input, and remove blank lines and preprocessor directives.
  sub rdln {
    my ($skip_next_line) = 0;
    if (defined ($_)) {
      my ($previous_line) = $_;
      while ( (/^(\s*|\#.*)$/ || $skip_next_line ) && ($_ = <FILE>)) {
        if ($previous_line =~ m/\\\s*/) { $skip_next_line = 1; }
        else { $skip_next_line = 0; }
        $previous_line = $_;
        $linenumber++; 
        if ($debug) { print STDERR "(0:$srcfile) $linenumber.\n"; } 
      }
    }
    # Dispose of Apache specific macros
    removeApacheMacros();
  }
  
  # Don't skip "#"
  sub rdln2 {
    if (defined ($_)) {
      while (/^(\s*)$/ && ($_ = <FILE>)) {$linenumber++; if ($debug) { print STDERR "(0:$srcfile) $linenumber.\n"; } }
    }
  }
  
  # Remove comments from current line
  sub removeComment {
    s|//.*||;
  }
  
  # parsing functions
  sub matchKW		{ &rdln; return (s/^\s*($_[0])//, $1) if defined ($_); return (0, 0); }
  #sub matchStruct		{ &rdln; return (s/^\s*(struct|class)//, $1) if defined ($_); return (0, 0); }
  #sub matchPermission	{ &rdln; return (s/^\s*(public|private|protected)// && $1) if defined ($_); return (0,0); }
  sub matchID		{ &rdln; return (s/^\s*([A-Za-z_]\w*)//, $1) if defined ($_); return (0,0); }
  sub matchColon		{ &rdln; return (s/^\s*\://) if defined ($_); return 0; }
  sub matchComma		{ &rdln; return (s/^\s*\,//) if defined ($_); return 0; }
  sub matchSemi		{ &rdln; return (s/^\s*\;//) if defined ($_); return 0; }
  sub matchRBracket	{ &rdln; return (s/^\s*\{//) if defined ($_); return 0; }
  sub matchLBracket	{ &rdln; return (s/^\s*\}//) if defined ($_); return 0; }
  sub matchRParen		{ &rdln; return (s/^\s*\(//) if defined ($_); return 0; }
  sub matchLParen		{ &rdln; return (s/^\s*\)//) if defined ($_); return 0; }
  sub matchRAngle		{ &rdln; return (s/^\s*\<//) if defined ($_); return 0; }
  sub matchLAngle		{ &rdln; return (s/^\s*\>//) if defined ($_); return 0; }
  sub matchDecl           { &rdln; return (s/^(\s*[\s\w\*\[\]\~\&\n\:]+)//, $1) if defined ($_); return (0, 0); }
  sub matchOper		{ &rdln; return (s/^\s*([\~\&\^\>\<\=\!\%\*\+\-\/\|\w]*)// && $1) if defined ($_); return 0; }
  sub matchFuncOper	{ &rdln; return (s/^\s*(\(\))// && $1) if defined ($_); return 0; }
  sub matchAny		{ &rdln; return (s/^\s*(\S+)//, $1) if defined ($_); return (0, 0); }
  sub matchChar		{ &rdln; return (s/^(.)//, $1) if defined ($_); return (0, 0); }
  sub matchChar2	        { &rdln2; return (s/^(.)//, $1) if defined ($_); return (0, 0); }
  sub matchString 	{ &rdln; return (s/^\"(([^\\\"]|(\\.)))*\"//, $1) if defined ($_); return (0, 0); }
  
  # Skip to next semicolon
  sub skipToSemi {
    
    while (!&matchSemi) {
      
      &rdln;
      s|//.*||;			# Eat comments
        if (&matchLBracket) {
  	&skipBody;
  	next;
        }
      last if !s/^\s*([^\s\{\;]+)//;
      # print STDERR "$1 ";
    }
  }
  
  # Skip function body
  sub skipBody {
    local( $nest );
    
    $nest = 1;
    
    for (;;) {
      if (&matchRBracket) { $nest++; }
      elsif (&matchLBracket) {
        $nest--;
        last if !$nest;
      }
      else { 
        last if ((($valid,) = &matchKW( "[^\{\}]")) && !$valid);
      }
    }
  }
  
  # Skip a string. (multiline)
  sub skipString {
    local( $char, $lastchar);
    $lastchar = "\"";
    
    for (;;) {
      ($valid, $char) = &matchChar2;
      if (($char eq "\"") && ($lastchar ne "\\")) { last; }
      if ($lastchar eq "\\") { $lastchar = " "; }
      else { $lastchar = $char; }
    }
  }
  
  
  # Skip everything in parenthesis.
  sub skipParenBody {
    local( $nest );
    
    $nest = 1;
    
    for (;;) {
      if (&matchRParen) { $nest++; }
      elsif (&matchLParen) {
        $nest--;
        last if !$nest;
      }
      else { 
        last if ((($valid,) = &matchKW( "[^\(\)]")) && !$valid);
      }
    }
  }
  
  # Parse (*name) syntax
  sub parseParenPointer {
    if (s/^(\s*\(\s*\*)//) {
      $decl .= $1;
      $nest = 1;
      
      for (;;) {
        # Preserve spaces, eliminate in-line comments
        &removeComment;
        while (s/^(\s+)//) { $decl .= $1; &rdln; }
        
        if (&matchRParen) { $nest++; $decl .= "("; }
        elsif (&matchLParen) {
  	$decl .= ")";
  	$nest--;
  	last if !$nest;
        }
        elsif ((($valid, $d) = &matchKW( "[^\(\)]*")) && $valid) { $decl .= $d; }
        else { last; }
      }
      
      # Just in case there are array braces afterwards.
      while ((($valid, $d) = &matchDecl) && $valid) { $decl .= $d; }
    }
  }
  
  # Parse template arguments
  sub matchAngleArgs {
    
    if (&matchRAngle) {
      local ($args, $nest);
      
      $args = "&lt;";
      $nest = 1;
      
      for (;;) {
        if (&matchRAngle) { $nest++; $args .= "&lt;"; }
        elsif (&matchLAngle) {
  	$nest--;
  	$args .= "&gt;";
  	last if !$nest;
        }
        elsif ((($valid, $d) = &matchChar) && $valid) { $args .= $d; }
        else { last; }
      }
      return $args;
    }
    else { return ''; }
  }
  
  # convert tabs to spaces
  sub expandTabs {
    local	($text) = @_;
    local 	($n);
    
    while (($n = index($text,"\t")) >= 0) {
      substr($text, $n, 1) = " " x ($tabSize-($n % $tabSize));
    }
    
    return $text;
  }
  
  # Process a line of text from a "special" comment
  sub handleCommentLine {
    local ($_) = @_;
    
    if ($docEmpty) {
      # Eliminate blank lines at the head of the doc.
      return if (/^\s*$/);
    }
    
    # First, expand tabs.
    $_ = &expandTabs( $_ );
  	
    # Remove gratuitous \s*\s  (james)
    s/(^|\n)\s*\*\s/$1/g;
    
    # If it's one of the standard tags
    if (s/^\s*\@(see|package|version|author|param|return|result|exception|keywords|deffunc|defvar|heading|todo)\s*//) {
      my $tag = $1;
      $tag = 'return' if ($tag eq 'result');
      
      # for param and exception, split the param name and the text
      # seperate them with tabs.
      if ($tag eq "param" || $tag eq "exception") {
        s/^\s*(\w+)\s*(.*)/\t$1\t$2/;
      }
      elsif ($tag eq "heading") {
        # 'heading' is processed by the template, if at all.
        $_ = "\@heading\t$_";
        $tag = "description";
      }
      elsif ($tag eq 'todo') {
        if ($todolist{ $srcfile } ne '') {
  	$todolist{ $srcfile } .= "\n";
        }
      }
      
      # If it's @deffunc or @defvar
      if ($tag =~ /def(.*)/) {
        
        $type = $1;
        
        # @deffunc and @defvar force a comment to be written out as if there was a
        # declaration.
        # Designed for use with macros and other constructs I can't parse.
        
        if (/(\S+)\s+(.*)$/) {
  	$name = $1;
  	$decl = $2;
  	$dbname = &uniqueName( "$baseScope$name" );
  	
  	my $entry = { 'type'    => $type,
  		      'name'    => $name,
  		      'longname'=> $name,
  		      'fullname'=> "$name $decl",
  		      'scopename'=>"$baseScope$name",
  		      'uname'   => $dbname,
  		      'decl'    => $decl,
  		      'package' => $packageName };
  
          bless $entry, MemberRecord;
  
  	if ($class) {
  	  $entry->{ 'class' } = "$context";
  	  $class->{ 'members' }{ $dbname } = $entry;
  	} 
  	else {
  	  $packages{ $packageName }{ 'globals' }{ $dbname } = $entry;
  	}
  	$docTag = 'description';
  	&dumpComments( $entry );
  	return;
        }
      }
      elsif ($tag eq 'package') {
        s/^\s*//;
        s/\s*$//;
        $packageName = $_;
        $docTag = 'description';
        return;
      }
      elsif ($tag eq 'author') {
        $author = $_;
        $docTag = 'description';
        return;
      }
      elsif ($tag eq 'version') {
        $version = $_;
        $docTag = 'description';
        return;
      }
      
      $docTag = $tag;
    }
    elsif (/^\s*@\w+/) {
      # any other line that begins with an @ should be inserted into the main
      # description for later expansion.
      $docTag = 'description';
    }
    
    # "To-do" lists are handled specially, and not associated with a class.
    if ($docTag eq 'todo') {
      $todolist{ $srcfile } .= $_;
      return;
    }
    
    # Append to current doc tag, regardless of whether it's a new line
    # or a continuation. Also mark this doc as non-empty.
    $docTags{ $docTag } .= $_;
    $docEmpty = 0;
    
    # @see doesn't persist.
    if ($docTag eq 'see') { $docTag = 'description'; }
    
    # print STDERR ":$_";
  }
  
  # Clear doc tag information at end of class or file
  sub clearComments {
    
    $docTag = 'description';
    $docEmpty = 1;
    %docTags = ();
  }
  
  # Add doc tag information to current documented item
  sub dumpComments {
    local ($hashref) = @_;
    
    if ($docEmpty == 0) {
      
      if ($author ne  '') { $hashref->{ 'author'  } = $author;  }
      if ($version ne '') { $hashref->{ 'version' } = $version; }
      $hashref->{ 'sourcefile' } = $srcfile;
      
      # Store the tags for this documentation into the global doc symbol table
      foreach $key (keys %docTags) {
        my $data = $docTags{ $key };
  
        $data =~ s/\s*$//;
        
        $hashref->{ $key } = $data;
      }
    }
    
    &clearComments();
  }
  
  # Generate a unique name from the given name.
  sub uniqueName {
    local ($name) = @_;
    
    # Duplicate doc entries need to be distinguished, so give them a different label.
    while ($docs{ $name }) {
      if ($name =~ /-(\d+)$/) {
        $name = $` . "-" . ($1 + 1);
      }
      else { $name .= "-2"; }
    }
    
    $docs{ $name } = 1;
    return $name;
  }
  
  # Get the current class record.
  sub classRecord {
    local ($className) = @_;
    local ($pkg) = $classToPackage{ $className };
    
    if ($pkg) {
      return $packages{ $pkg }{ 'classes' }{ $className };
    }
    return 0;
  }
  
  # Parse a declaration in the file
  sub parseDeclaration {
    
    local ($context) = @_;
    local ($baseScope) = '';
    local ($decl);
    my ($token);
    
    if ($context) { $baseScope = $context . "::"; }
    
    &rdln;
  
    if (!defined ($_)) { return 0; }
    
    if (s|^\s*//\*\s+||) {
      # Special C++ comment
      &handleCommentLine( $' );
      $_ = ''; &rdln;
    }	
    elsif (s|^\s*//||) { 
      # Ordinary C++ comment
      $_ = '';
      &rdln;
    }
    elsif (s|^\s*\/\*\*\s+||) {
      # Special C comments
      
      s/\={3,}|\-{3,}|\*{3,}//;			# Eliminate banner strips
      $text = '';
      $docTag = 'description';
      
      # Special comment
      while (!/\*\//) { &handleCommentLine( $_ ); $text .= $_; $_ = <FILE>; $linenumber++; if ($debug) { print STDERR "(1) $linenumber\n."; }}
      s/\={3,}|\-{3,}|\*{3,}//;			# Eliminate banner strips
      /\*\//;
      &handleCommentLine( $` );
      $text.= $`; $_ = $';
    }
    elsif (s|^\s*\/\*||) {
      # Ordinary C comment
      $text = "";
      
      while (!/\*\//) { $text .= $_; $_ = <FILE>; $linenumber++; if ($debug) { print STDERR "(2) $linenumber\n."; }}
      /\*\//;
      $text.= $`; $_ = $';
    }
    elsif ((($valid, $tag) = &matchKW( "template")) && $valid) {
      # Template definition
      $args = &matchAngleArgs;
      &rdln;
      
      ##$tmplParams = $args; JAMES
      $result = &parseDeclaration( $context );
      ##$tmplParams = ''; JAMES
      return $result;
    }
    elsif ((($valid, $tag) = &matchKW("class|struct")) && $valid) {
      # Class or structure definition
      local ($className,$class);
      
      if ((($valid, $className) = &matchID) && $valid) {
        
        return 1 if (&matchSemi);		# Only a struct tag
        
        # A class instance
        if ((($valid,)=&matchID) && $valid) {
  	&matchSemi;
  	return 1;
        }
        
        my $fullName = "$baseScope$className"; ##$tmplParams"; JAMES
        # print STDERR "CLASS $fullName\n";
        
        my @bases = ();
        
        if (&matchColon) {
  	
  	for (;;) {
  	  my $p;
  	  &matchKW( "virtual" );
  	  $perm = "private";
  	  if ((($valid, $p) = &matchKW( "public|private|protected" )) && $valid) { $perm = $p; }
  	  &matchKW( "virtual" );
  	  
  	  last if !(  (($valid, $base) = &matchID) && $valid  );
  	  
  	  push @bases, $base;
  	  push @{ $subclasses{ $base } }, $fullName;
  	  # print STDERR " : $perm $base\n";
  	  last if !&matchComma;
  	}
        }
        
        #	print STDERR "\n";
        # print STDERR "parsing class $fullName\n";
  
        if ($docEmpty == 0) {
  	$class = { 'type'    => $tag,
  		   'name'    => $fullName,
  		   'longname'=> "$tag $className",
  		   'fullname'=> "$tag $className",
  		   'scopename'=> "$tag $fullName",
  		   'uname'   => $fullName,
  		   'bases'   => \@bases,
  		   'package' => $packageName,
  		   'members' => {} };
  	
  	# print STDERR "$className: @bases\n";
  	
  	bless $class, ClassRecord;
  	
  	print STDERR "   parsing class $fullName\n";
  	# $classToPackage{ $className } = $packageName;
  	$classToPackage{ $fullName } = $packageName;
  	# $classList{ $className } = $class;
  	$classList{ $fullName } = $class;
  	$packages{ $packageName }{ 'classes' }{ $fullName } = $class;
  	&dumpComments( $class );
        }
        
        if (&matchRBracket) {
  	local ($perm) = ("private");
  	
  	while (!&matchLBracket) {
  	  my $p;
  	  if ((($valid, $p) = &matchKW( "public\:|private\:|protected\:" )) && $valid) {
  	    $perm = $p;
  	  }
  	  else {
  	    &parseDeclaration( $fullName )
  	      || die "Unmatched brace! line = $linenumber\n";
  	  }
  	}
  	
  	&matchSemi;
        }
        
        &clearComments;
      }
    }
    elsif ( ((($valid,)=&matchKW( "enum")) && $valid) || ((($valid,)=&matchKW( "typedef" )) && $valid)) {
      &skipToSemi;
    }
    elsif ((($valid,)=&matchKW( "friend\s*class" )) && $valid) {
      &skipToSemi;
    }
    elsif ((($valid, $token) = &matchKW("extern\\s*\\\"C\\\"")) && $valid) {
      &matchRBracket;
      while (!&matchLBracket) {
        &parseDeclaration( '' ) || die "Unmatched brace! line = $linenumber\n";
      }
      &matchSemi;
    }
    # elsif ($kw = &matchID) {
    #   $type = "$kw ";
    #
    #   if ($kw =~/virtual|static|const|volatile/) {
    #	$type .= &typ;
    #   }
    # }
    elsif ((($valid, $decl) = &matchDecl) && $valid) {
      my ($instanceClass) = "";
      
      # print STDERR "DECLARATION=$decl, REST=$_, baseScope=$baseScope\n";
  
      return 1 if ($decl =~ /^\s*$/);
  
      if (!($class)) {
        if ($decl =~ s/(\S*\s*)(\S+)\:\:(\S+)\s*$/$1$3/) {
          $instanceClass = $2;
        }
      }
  
      # Eliminate in-line comments
      &removeComment;
  
      # Check for multi-line declaration
      while ((($valid, $d) = &matchDecl) && $valid) { $decl .= $d; }
      
      # Handle template args, but don't let operator overloading confuse us!
      $tempArgs = '';
      if (!($decl =~ /\boperator\b/) && ($tempArgs = &matchAngleArgs)) {
        $tempArgs = $decl . $tempArgs;
        $decl = '';
        while ((($valid, $d) = &matchDecl) && $valid) { $decl .= $d; }
      }
      
      # Look for (*name) syntax
      &parseParenPointer;
      
      # Special handling for operator... syntax
      $oper = "";
      if ($decl =~ s/\boperator\b(.*)/operator/) {
        $oper = $1;
        $oper .= &matchOper;
        # If, after all that there's no opers, then try a () operator
        if (!($oper =~ /\S/)) { $oper .= &matchFuncOper; }
      }
  
      ($type,$mod,$decl) = $decl =~ /([\s\w]*)([\s\*\&]+\s?)(\~?\w+(\[.*\])*)/;
      
      $type = $tempArgs . $type;
      $decl .= $oper;
      
      if ($mod =~ /\s/) { $type .= $mod; $mod = ""; }
      
      for (;;) {
        
        # print STDERR "Looping: $type/$mod/$decl\n";
        
        if (&matchRParen) {
  	$nest = 1;
  	$args = "";
  	
  	for (;;) {
  	  # print STDERR "Argloop $_\n";
  	  
  	  # Process argument lists.
  	  
  	  # Preserve spaces, eliminate in-line comments
  	  # REM: Change this to save inline comments and automatically
  	  # generate @param clauses
  	  s|//.*||;
  	  while (s/^(\s+)//) { $args .= " "; &rdln; }
  	  
  	  if (&matchRParen) { $nest++; $args .= "("; }
  	  elsif (&matchLParen) {
  	    $nest--;
  	    last if !$nest;
  	    $args .= ")";
  	  }
  	  elsif ((($valid, $d) = &matchKW( "[\,\=\.\:\-]" )) && $valid) { $args .= $d; }
  	  elsif ((($valid, $d) = &matchDecl) && $valid) { $args .= $d; }
  	  elsif ((($valid, $d) = &matchAngleArgs) && $valid) { $args .= $d; }
  	  elsif ((($valid, $d) = &matchString) && $valid) { $args .= "\"$d\""; }
  	  else { last; }
  	}
  				
  	# print STDERR "$type$mod$baseScope$decl($args);\n";
  	
  	&matchKW( "const" );
  	
  	# Search for any text within the name field
  	# if ($docTag && $decl =~ /\W*(~?\w*).*/)
  	if ($docEmpty == 0) {
  	  $type =~ s/^\s+//;
  	  $mod  =~ s/\&/\&amp;/g;
  	  $args =~ s/\&/\&amp;/g;
  	  $args =~ s/\s+/ /g;
  	  $dbname = &uniqueName( "$baseScope$decl" );
  	  
  	  my $entry = { 'type'    => 'func',
  			'name'    => $decl,
  			'longname'=> "$decl()",
  			'fullname'=> "$type$mod$decl($args)",
  			'scopename'=>"$type$mod$baseScope$decl($args)",
  			'uname'   => $dbname,
  			'decl'    => "$type$mod$decl($args)",
  			'package' => $packageName };
  	  
  	  bless $entry, MemberRecord;
  	  
  	  if ($class) {
  	    $entry->{ 'class' } = "$context";
  	    $class->{ 'members' }{ $dbname } = $entry;
  	  }
  	  elsif ($instanceClass) {
  	    $class = &classRecord ($instanceClass);
  	    if (!($class)) {
  	      print STDERR "WARNING: Skipping \"$instanceClass\:\:$decl\".  Class \"$instanceClass\" not declared ($linenumber).\n";
  	    } else {
  	      $entry->{ 'class' } = "$instanceClass";
  	      $class->{ 'members' }{ $dbname } = $entry;
  	      $class = 0;
  	    }
  	  }
  	  else {
  	    $packages{ $packageName }{ 'globals' }{ $dbname } = $entry;
  	  }
  	  &dumpComments( $entry );
  	}
  	else { &clearComments; }
  	
  	s|//.*||;
  	
  	# Constructor super-call syntax
  	if (&matchColon) {
  	  
  	  # Skip over it.
  	  for (;;) {
  	    &rdln;
  	    last if /^\s*(\{|\;)/;
  	    last if !((($valid,)=&matchAny) && $valid);
  	  }
  	}
  	
  	last if &matchSemi;
  	if (&matchRBracket) { &skipBody; last; }
  	last if !&matchComma;
  	last if !((($valid, $decl) = &matchDecl) && $valid);
  	
  	# Look for (*name) syntax
  	&parseParenPointer;
  	
  	$decl =~ s/^\s*//;
  	$oper = "";
  	if ($decl =~ /\boperator\b/) {
  	  $decl =~ s/\boperator\b(.*)/operator/;
  	  $oper = $1 . &matchOper;
  	}
  	($mod,$d) = $decl =~ /^\s*([\*\&]*)\s*(\~?\w+(\[.*\])*)/;
  	$decl .= $oper;
  	$decl = $d if $d ne "";
        }
        else {
  	s|//.*||;
  	
  	$final = 0;
  	
  	if ((($valid,)=&matchKW( "\=" )) && $valid) {
  	  for (;;) {
  	    
  	    if (&matchRBracket) {
  	      &skipBody;
  	      $final = 1;
  	      last;
  	    }
  	    
  	    if (&matchSemi) {
  	      $final = 1;
  	      last;
  	    }
  	    
  	    # var = new ... (...)
  	    if ((($valid,)=&matchKW("new")) && $valid) {
  	      &matchKW("[A-Za-z_0-9 ]*");
  	      if (&matchRParen) {
  	        &skipParenBody;
  	      }
  	    }
  	    
  	    # var = (.....) ...
  	    if (&matchRParen) {
  	      &skipParenBody;
  	    }
  	    
  	    # var = ... * ...
  	    &matchKW ("[\/\*\-\+]*");
  	    
  	    # var = "..."
  	    if ((($valid,) = &matchKW ("[\"]")) && $valid) {
  	      &skipString;
  	    }
  	    #&matchString;
  	    
  	    last if /^\s*,/;
  	    #last if !((($valid,)=&matchAny) && $valid);
  	    last if !((($valid,)=&matchKW("[A-Za-z_0-9 \-]*")) && $valid);
  	    if (&matchSemi) {
  	        $final = 1;
  	        last;
  	    }
  	  }
  	}
  	
  	s|//.*||;
  	
  	# void ~*&foo[];
  	# void foo[];
  	# void far*foo[];
  	# print STDERR "Decl: $type$mod$baseScope$decl;\n";
  
  	# Search for any text within the name field
  	if ($docEmpty == 0 && ($decl =~ /\W*(~?\w*).*/))
  	  {
  	    $mod  =~ s/\&/\&amp;/g;
  	    $name = $decl;
  	    
  	    $dbname = &uniqueName( "$baseScope$1" );
  	    
  	    my $entry = { 'type'     => 'var',
  			  'name'     => $1,
  			  'longname' => "$name",
  			  'fullname' => "$type$mod$decl",
  			  'scopename'=> "$baseScope$type$mod$decl",
  			  'uname'    => $dbname,
  			  'decl'     => "$type$mod$decl",
  			  'package'  => $packageName };
  	    
  	    bless $entry, MemberRecord;
  	    
  	    if ($class) {
  	      $entry->{ 'class' } = "$context";
  	      $class->{ 'members' }{ $dbname } = $entry;
  	    }
  	    else {
  	      $packages{ $packageName }{ 'globals' }{ $dbname } = $entry;
  	    }
  	    &dumpComments( $entry );
  	  }
  	else { &clearComments; }
  	
  	last if $final;
  	last if &matchSemi;
  	last if !&matchComma;
  	last if !((($valid, $decl) = &matchDecl) && $valid);
  	
  	# Look for (*name) syntax
  	&parseParenPointer;
  	
  	$decl =~ s/^\s*//;
  	($mod,$d) = $decl =~ /^\s*([\*\&]*)(\~?\w+(\[.*\])*)/;
  	$decl = $d if $d ne "";
        }
      }
    }
    elsif ($context ne "" && /^\s*\}/) {
      # print STDERR "Popping!\n";
      return 1;
    }
    elsif (&matchRBracket) {
      &skipBody;
    }
    elsif ((($valid, $token) = &matchAny) && $valid) {
      # Comment in for debugging
      # print STDERR "token: $token \n";
    }
    else { return 0; }
    
    return 1;
  }
  
  # read a file into a string ( filename, default-value )
  sub readFile {
    local ( $filename, $result ) = @_;
    
    if ($filename && open( FILE, $filename )) {
      $result = "";
      while (<FILE>) { $result .= $_; }
      close( FILE );
    }
    return $result;
  }
  
  # Read the entire document template and translate into PERL code.
  sub readTemplate {
    local ( $filename ) = @_;
    $docTemplate = '';
    $indent = '';
    $literal = 1;  # We're in literal mode.
    
    if (!-e $filename) {
      if (-e "./templates/$filename") { $filename = "./templates/$filename"; }
      elsif (-e "../templates/$filename") { $filename = "../templates/$filename"; }
      else { die "Could not find template '$filename'.\n"; }
    }
    
    open( FILE, $filename ) || die "Error opening '$filename'.\n";
    while (<FILE>) {
      last if (/END/);
      
      # if we found a code entry.
      for (;;) {
        &expandTabs( $_ );
        if ($literal) {
  	# Check for beginning of code block.
  	if (s/^(.*)\<\<//) {
  	  $line = $1; 
  	  if (substr( $line, 0, length( $indent ) ) eq $indent) {
  	    substr( $line, 0, length( $indent ) ) = '';
  	  }
  	  
  	  if ($line ne '') {
  	    $line =~ s/\"/\\\"/g;
  	    $line =~ s/\$\((\w+)\.(\w+)\)/\" \. \$$1->$2() \. \"/g;
  	    $docTemplate .= "${indent}print\"$line\";";
  	  }
  	  # else { $docTemplate .= "\n"; }
  	  $literal = 0;
  	}
  	else {
  	  if (substr( $_, 0, length( $indent ) ) eq $indent) {
  	    substr( $_, 0, length( $indent ) ) = "";
  	  }
  	  chop;
  	  s/\"/\\\"/g;
  	  s/\$\((\w+)\.(\w+)\)/\" \. \$$1->$2() \. \"/g;
  	  $_ = $indent . "print \"" . $_ . "\\n\";\n";
  	  last;
  	}
        }
        else {
  	# Check for beginning of literal block.
  	if (s/^(\s*)\>\>//) {
  	  $indent = $1;
  	  $literal = 1;
  	}
  	elsif (s/^(\s*)(.*)\>\>//) {
  	  $docTemplate .= "$indent$2";
  	  $literal = 1;
  	}
  	else {
  	  last;
  	}
        }
      }
      
      $docTemplate .= $_;
    }
    close( FILE );
    # print $docTemplate;
  }
  
  # Functions intended to be called from doc template file.
  
  # Open a new output file
  sub file {
    my $mfile = $_[ 0 ];
    
    open( STDOUT, ">$destPath$mfile" ) || die "Error writing to '$mfile'\n";
  }
  
  # return list of package objects
  sub packages {
    my ($p, @r);
    @r = ();
    
    foreach $p (sort keys %packages) {
      push @r, $packages{ $p };
    }
    return @r;
  }
  
  # return list of source files which have to-do lists
  sub todolistFiles {
    my ($p, @r);
    @r = ();
    
    foreach $p (sort keys %todolist) {
      push @r, $p;
    }
    return @r;
  }
  
  # return list of tab-delimited to-do-list texts.
  sub todolistEntries {
    local $_ = $todolist{ $_[0] };
    s/^\s+//;				# Remove whitespace from beginning
    s/\s+$/\n/;				# Remove whitespace from end
    return split( /\n/, $_ );
  }
  
  # Convert package name to URL.
  sub packageURL {
    my $p = $_[0];
    
    if ($p eq 'General') { $p = '.general'; }
    if ($p eq '') { $p = '.general'; }
    
    if (ref $packages{ $p }) {
      return $packages{ $p }->url();
    }
    return 0;
  }
  
  # Get the see-also list for an object
  sub seealsoList {
    my $self = shift;
    my ($see, $name, $url, $p, @r);
    @r = ();
    
    if (defined ($self->{ 'see' })) {
      foreach $_ (split(/\n/,$self->{ 'see' })) {
        
        if (/^\<a\s+href/) { # if already an HREF.
  	$name = $_;
  	$url = 0;
        }
        elsif (/([^\#]*)\#(.*)/) { # If a package name is present
  	$url = &packageURL( $1 ) . '#' . $2;
  	$name = $2;
        }
        else {
  	$name = $_;
  	$url = "#$_";
  	
  	# This doesn't appear to do anything - so I commented it.  (james)
  	# Look up the package in the index and use it to construct the html filename.
  	#if (/^([^\:]*)\:\:(.*)/) {
  	#  $className = ($1 eq '') ? '' : $classToPackage{ $1 };
  	#  $p = $packageToFile{ $className };
  	#  if ($p ne '') {
  	#    $url = &packageURL( $1 ) . '#' . $_;
  	#  }
  	#}
        }
        
        $url =~ s/^\:*//;		# Remove leading colons from name
        $url =~ s/::/-/g;		# Replace :: with dash
        
        my $entry = { 'name' => $name,
  		    'url'  => $url };
        
        bless $entry, DocReference;
        
        push @r, $entry;
      }
    }
    return @r;
  }
  
  sub removeApacheMacros {
  #    print "removing from $_";
      s|AP_DECLARE\((.*?)\)|$1|;
  }
  
  # Class for parsed package
  package PackageRecord;
  
  sub classes {
    my $self = shift;
    my $classes = $self->{ 'classes' };
    return map $classes->{ $_ }, (sort keys %$classes);
  }
  
  sub globals {
    my $self = shift;
    my $globals = $self->{ 'globals' };
    return map $globals->{ $_ }, (sort keys %$globals);
  }
  
  sub globalvars {
    my $self = shift;
    my $globals = $self->{ 'globals' };
    my ($p, @r);
    @r = ();
    
    foreach $p (sort keys %$globals) {
      my $m = $globals->{ $p };
      if ($m->{ 'type' } ne 'func') { push @r, $m; }
    }
    return @r;
  }
  
  sub globalfuncs {
    my $self = shift;
    my $globals = $self->{ 'globals' };
    my ($p, @r);
    @r = ();
    
    foreach $p (sort keys %$globals) {
      my $m = $globals->{ $p };
      if ($m->{ 'type' } eq 'func') { push @r, $m; }
    }
    return @r;
  }
  
  sub name {
    my $self = shift;
    return $self->{ 'name' };
  }
  
  sub url {
    my $self = shift;
    return "default-pkg.html" if ($self->{ 'name' } eq '.general');
    return $self->{ 'name' } . '.html';
  }
  
  sub anchor {
    my $self = shift;
    my $url = $self->{ 'name' };
    return $url;
  }
  
  # Class for parsed class
  package ClassRecord;
  
  sub keywords    { return ${$_[0]}{ 'keywords' }; }
  sub author      { return ${$_[0]}{ 'author' }; }
  sub version     { return ${$_[0]}{ 'version' }; }
  sub name        { return ${$_[0]}{ 'name' }; }
  sub longname    { return ${$_[0]}{ 'longname' }; }
  sub fullname    { return ${$_[0]}{ 'fullname' }; }
  sub scopename   { return ${$_[0]}{ 'scopename' }; }
  sub sourcefile  { return ${$_[0]}{ 'sourcefile' }; }
  #sub description { return &::processDescription( ${$_[0]}{ 'description' } ); }
  sub description { return ${$_[0]}{ 'description' }; }
  sub seealso     { &::seealsoList( $_[0] ); }
  
  sub url {
    my $self = shift;
    return 0 unless $self->{ 'package' };
    my $pname = ::packageURL( $self->{ 'package' } );
    my $url = $self->{ 'uname' };
    $url =~ s/::/-/g;
    return "$pname#$url";
  }
  
  sub anchor {
    my $self = shift;
    my $url = $self->{ 'uname' };
    $url =~ s/::/-/g;
    return $url;
  }
  
  sub members {
    my $self = shift;
    my $members = $self->{ 'members' };
    my ($p, @r);
    @r = ();
    
    foreach $p (sort keys %$members) {
      push @r, $members->{ $p };
    }
    return @r;
  }
  
  sub membervars {
    my $self = shift;
    my $members = $self->{ 'members' };
    my ($p, @r);
    @r = ();
    
    foreach $p (sort keys %$members) {
      my $m = $members->{ $p };
      if ($m->{ 'type' } ne 'func') { push @r, $m; }
    }
    return @r;
  }
  
  sub memberfuncs {
    my $self = shift;
    my $members = $self->{ 'members' };
    my ($p, @r);
    @r = ();
    
    foreach $p (sort keys %$members) {
      my $m = $members->{ $p };
      if ($m->{ 'type' } eq 'func') { push @r, $m; }
    }
    return @r;
  }
  
  sub baseclasses {
    my $self = shift;
    my $bases = $self->{ 'bases' };
    my ($p, $class, @r);
    @r = ();
    
    foreach $p (@$bases) {
      
      unless ($class = $::classList{ $p }) {
        # It's one we don't know about, so just make something up
        $class = { 'name'    => $p,
  		 'longname'=> "class $p",
  		 'fullname'=> "class $p",
  		 'scopename'=>"class $p",
  		 'uname'   => $p,
  		 'members' => {} };
        
        if ($::classToPackage{ $p }) {
  	$class->{ 'package' } = $::classToPackage{ $p };
        }
        
        bless $class, ClassRecord;
      }
      push @r, $class;
    }
    return @r;
  }
  
  sub subclasses {
    my $self = shift;
    my $subs;
    my ($p, $class, @r);
    @r = ();
    
    if (defined ($self->{ 'subs' })) {
      $subs = $self->{ 'subs' };
      foreach $p (sort @$subs) {
        $class = $::classList{ $p };
        push @r, $class;
      }
    }
    return @r;
  }
  
  # Class for parsed class member or global
  package MemberRecord;
  
  sub type         { return ${$_[0]}{ 'type' }; }
  sub keywords     { return ${$_[0]}{ 'keywords' }; }
  sub author       { return ${$_[0]}{ 'author' }; }
  sub version      { return ${$_[0]}{ 'version' }; }
  sub name         { return ${$_[0]}{ 'name' }; }
  sub longname     { return ${$_[0]}{ 'longname' }; }
  sub fullname     { return ${$_[0]}{ 'fullname' }; }
  sub scopename    { return ${$_[0]}{ 'scopename' }; }
  sub returnValue  { return ${$_[0]}{ 'return' }; }
  sub sourcefile   { return ${$_[0]}{ 'sourcefile' }; }
  sub description  { return ${$_[0]}{ 'description' }; }
  sub seealso      { &::seealsoList( $_[0] ); }
  
  sub url {
    my $self = shift;
    return 0 unless $self->{ 'package' };
    my $pname = ::packageURL( $self->{ 'package' } );
    my $url = $self->{ 'uname' };
    $url =~ s/::/-/g;
    return "$pname#$url";
  }
  
  sub anchor {
    my $self = shift;
    my $url = $self->{ 'uname' };
    $url =~ s/::/-/g;
    $url;
  }
  
  sub params {
    my $self = shift;
    my $params = $self->{ 'param' };
    my @r;
    @r = ();
    
    return 0 unless ($params);
    
    my @paramList = split( /\t/, $params );
    
    for ($i = 1; $i < $#paramList; $i += 2) {
      my $entry = { 'name'        => $paramList[ $i ],
  		  'description' => $paramList[ $i + 1 ] };
      
      bless $entry, ArgRecord;
      
      push @r, $entry;
    }
    return @r;
  }
  
  sub exceptions {
    my $self = shift;
    my $params = $self->{ 'exception' };
    my @r;
    @r = ();
    
    return 0 unless ($params);
    
    my @paramList = split( /\t/, $params );
    
    for ($i = 1; $i < $#paramList; $i += 2) {
      my $entry = { 'name'        => $paramList[ $i ],
  		  'description' => $paramList[ $i + 1 ] };
      
      bless $entry, ArgRecord;
      
      push @r, $entry;
    }
    return @r;
  }
  
  package ArgRecord;
  sub name        { return ${$_[0]}{ 'name' }; }
  sub description { return ${$_[0]}{ 'description' }; }
  
  package DocReference;
  sub name        { return ${$_[0]}{ 'name' }; }
  sub url         { return ${$_[0]}{ 'url' }; }
  
  
  
  1.1                  apr/build/scandoc_template.pl
  
  Index: scandoc_template.pl
  ===================================================================
  <<
  # Scandoc template file.
  #
  # This is an example set of templates that is designed to create several 
  # different kinds of index files. It generates a "master index" which intended 
  # for use with a frames browser; A "package index" which is the root page of 
  # the index, and then "package files" containing documentation for all of the 
  # classes within a single package.
  
  ######################################################################
  
  ## For quick and superficial customization, 
  ## simply change these variables
  
  $project_name     = '[Apache Portable RunTime]';
  #$company_logo     = '<img src="../images/ScanDocBig.jpg">'; # change this to an image tag.
  $copyright        = '&copy 2000 [Apache Software Foundation]';
  $image_directory  = "../images/";
  $bullet1_image    = $image_directory . "ball1.gif";
  $bullet2_image    = $image_directory . "ball2.gif";
  $bgcolor1         = "#FFFFFF";
  $bgcolor2         = "#FFFFFF";
  
  ######################################################################
  
  ## Begin generating frame index file.
  
  file "index.html";
  >><html>
    <head>
      <meta http-equiv="Content-Type" content="text/html; iso-8859-1">
      <title>$project_name</title>
    </head>
    <frameset cols="190,*">
      <frame src="master.html"  name="Master Index" noresize>
      <frame src="packages.html" name="Documentation">
      <noframes>
        <body bgcolor="$bgcolor2" stylesrc="index.html">
          <p>Some Documentation</p>
        </body>
      </noframes>
    </frameset>
  </html>
  <<
  
  ######################################################################
  
  ## Begin generating master index file (left-hand frame).
  
  file "master.html";
  >><html>
    <head>
      <title>Master Index</title>
    </head>
    <body bgcolor="$bgcolor1" text=#0000ff link=#0020ff vlink=#0020ff>
      <center><img src="${image_directory}ScanDocSmall.jpg" border="0" /></center>
      <p>
      <a href="packages.html" target="Documentation">Master Index</a>
      </p>
      <p>
        <font size="2">
          <nobr>
  <<
  
  ## For each package, generate an index entry.
  
  foreach $p (packages()) {
    $_ = $p->url;
    s/\s/_/g;
    >><a href="$_" target="Documentation"><b>$(p.name)</b></a><br>
      <dir>
    <<
    foreach $e ($p->classes()) {
      $_ = $e->url;
      s/\s/_/g;
      >><li><a href="$_" target="Documentation">$(e.fullname)</a>
      <<
    }
    foreach $e ($p->globals()) {
      $_ = $e->url;
      s/\s/_/g;
      >><li><a href="$_" target="Documentation">$(e.fullname)</a>
      <<
    }
    >></dir><<
  }
  
  >>
            <a href="to-do.html" target="Documentation"><b>To-Do List</b></a><br>
          </nobr>
        </font>
      </p>
    </body>
  </html>
  <<
  
  ######################################################################
  
  ## Begin generating package index file
  
  file "packages.html";
  >><html>
    <head>
      <title>$project_name -- Packages</title>
    </head>
    <body bgcolor="$bgcolor2">
  
      <center>$company_logo
      <h1>Documentation for $project_name</h1>
      </center>
      <h2>Package List</h2>
  <<
  
  ## For each package, generate an index entry.
  
  foreach $p (packages()) {
    $_ = $p->url;
    s/\s/_/g;
    >><a href = "$_">$(p.name)</a><br>
    <<
  }
  
  >>
      <p>
      <hr size=4>
      $copyright<br>
      Generated by <a href="$scandocURL"><b>ScanDoc $majorVersion.$minorVersion</b></a><br>
      Last Updated: $date<br>
    </body>
  </html>
  
  <<
  
  ######################################################################
  
  ## Generate "To-do list"
  
  file "to-do.html";
  >><html>
    <head>
      <title>$project_name -- To-Do list</title>
    </head>
    <body bgcolor="$bgcolor2">
  
      $company_logo
  
      <h1>To-do list for $project_name</h1>
  <<
  
  if (&todolistFiles()) {
    >><hr size=4><p>
    <<
    foreach $f (&todolistFiles()) {
      my @m = &todolistEntries( $f );
      if ($f =~ /([^\/]+)$/) { $f = $1; }
      >><b>$f:</b><ul>
      <<
      foreach $text (@m) {
        if ($text) {
          print "<li>", &processDescription( $text ), "\n";
        }
      }
      >></ul>
      <<
    }
  }
  
  >>
      <hr size=4>
      $copyright<br>
      Generated by <a href="$scandocURL"><b>ScanDoc $majorVersion.$minorVersion</b></a><br>
      Last Updated: $date<br>
    </body>
  </html>
  <<
  
  ######################################################################
  
  ## Generate individual files for each package.
  
  my $p;
  foreach $p (packages()) {
    $_ = $p->name;
    s/\s/_/g;
    file $_ . ".html";
    >><html>
    <head>
      <title>$project_name -- $(p.name)</title>
    </head>
    <body bgcolor="$bgcolor2">
      <center>
        <font size=6><b>$project_name</b></font>
        <hr size=4><p>
      </center>
  
      <h2>Package Name: $(p.name)</h2>
      <b>
  <<
  
  ## Generate class and member index at the top of the file.
  
  foreach $c ($p->classes()) {
    $_ = $c->url;
    s/\s/_/g;
    >><h3><img src="$bullet1_image" width=18 height=17 align=texttop>
      <a href="$_">$(c.fullname)</h3></a>
      <ul>
    <<
    foreach $m ($c->members()) {
      $_ = $m->url;
      s/\s/_/g;
      >><li><a href="$_">$(m.longname)</a>
      <<
    }
    >></ul>
    <<
  }
  
  >>
  </b>
  <<
  
  ## Generate detailed class documentation
  foreach $c ($p->classes()) {
   ## Output searchable keyword list
   if ($c->keywords()) {
     print "<!-- ", $c->keywords(), " -->\n";
   }
  
   >><hr size="4">
     <a name="$(c.anchor)"></a>
     <h1>$(c.fullname)</h1>
     <table bgcolor="ffffff" border="0" cellspacing="4">
       <tr>
         <th align=center colspan=2>
         </th>
       </tr>
    <<
    
    # Output author tag
    if ($c->author()) {
      >><tr><th width=20% align=right>Author:</th><<
      >><td>$(c.author)</td></tr><<
    }
  
    # Output package version
    if ($c->version()) {
      >><tr><th width=20% align=right>Version:</th><<
      >><td>$(c.version)</td></tr><<
    }
  
    # Output Source file
    if ($c->sourcefile()) {
      >><tr><th width=20% align=right>Source:</th><<
      >><td>$(c.sourcefile)</td></tr><<
    }
  
    # Output base class list
    if ($c->baseclasses()) {
      >><tr><th width=20% align=right>Base classes:</th>
      <td><<
      my @t = ();
      foreach $b ($c->baseclasses()) {
        my $name = $b->name();
        if ($url = $b->url()) {
          $_ = $url;
          s/\s/_/g;
          push @t, "<a href=\"$_\">$name</a>";
        }
        else { push @t, $name; }
      }
      print join( ', ', @t );
      >></td></tr>
      <<
    }	
  
    # Output subclasses list
    if ($c->subclasses()) {
      >><tr><th width=20% align=right>Subclasses:</th>
        <td><<
      my @t = ();
      foreach $s ($c->subclasses()) {
        my $name = $s->name();
        if ($url = $s->url()) {
          $_ = $url;
          s/\s/_/g;
          push @t, "<a href=\"$_\">$name</a>";
        }
        else { push @t, $name; }
      }
      print join( ', ', @t );
      >></td></tr><<
    }
  
    # Output main class description
    >></tr>
    </table>
    <p>
    <<
    print &processDescription( $c->description() );
  	
    # Output "see also" information
    if ($c->seealso()) {
      >><p><dt><b>See Also</b><dd>
      <<
      my @r = ();
      foreach $a ($c->seealso()) {
        my $name = $a->name();
        if ($url = $a->url()) {
          $_ = $url;
          s/\s/_/g;
          push @r, "<a href=\"$_\">$name</a>";
        }
        else { push @r, $name; }
      }
      print join( ',', @r );
      >><p>
      <<
    }
  
    # Output class member index
    if ($c->members()) {
      print "<h2>Member Index</h2>\n";
      print "<ul>";
      foreach $m ($c->members()) {
        $_ = $m->url;
        s/\s/_/g;
        >><li><a href="$_">$(m.fullname)</a>
  	<<
      }
      >></ul><<
    }
   
    # Output class member variable documentation
    if ($c->membervars()) {
      print "<h2>Class Variables</h2>\n";
      print "<blockquote>\n";
      foreach $m ($c->membervars()) { &variable( $m ); }
      print "</blockquote>\n";
    }
  
    # Output class member function documentation
    if ($c->memberfuncs()) {
      print "<h2>Class Methods</h2>\n";
      print "<blockquote>\n";
      foreach $m ($c->memberfuncs()) { &function( $m ); }
      print "</blockquote>\n";
    }
  }
  
  # Output global variables
  if ($p->globalvars()) {
    >><h2>Global Variables</h2>
      <blockquote>
    <<
    foreach $m ($p->globalvars()) { &variable( $m ); }
    print "</blockquote>\n";
  }
  
  # Output global functions
  if ($p->globalfuncs()) {
    >><h2>Global Functions</h2>
      <blockquote>
    <<
    foreach $m ($p->globalfuncs()) { &function( $m ); }
    print "</blockquote>\n";
  }
  
  >>
      <hr size=4>
      $copyright<br>
      Generated by <a href="$scandocURL"><b>ScanDoc $majorVersion.$minorVersion</b></a><br>
      Last Updated: $date<br>
    </body>
  </html>
  <<
  } # end of foreach (packages) loop
  
  ######################################################################
  
  ## Subroutine to generate documentation for a member function or global function
  
  sub function {
    local ($f) = @_;
    
    if ($f->keywords()) {
      >><!-- $(f.keywords) -->
        <<
    }
    >>
    <a name="$(f.anchor)"></a>
    <dl>
      <dt>
       <b><img src="$bullet2_image" width=19 height=17 align=texttop>$(f.fullname);</b>
      <dd>
    <<
    print &processDescription( $f->description() );
    >>
    <p><dl>
    <<
    if ($f->params()) {
      >>
        <dt><b>Parameters</b><dd>
  	<table width="85%">
      <<
      foreach $a ($f->params()) {
        >><tr valign=top><th align=right>
  	$(a.name)</th><td><<
        print &processDescription( $a->description() );
        >></td></tr>
        <<
      }
      >></table>
        <<
    }
  	
    if ($f->returnValue()) {
      >><dt><b>Return Value</b>
        <dd><<
      print &processDescription( $f->returnValue() );
      >><p><<
    }
    
    if ($f->exceptions()) {
      >><dt><b>Exceptions</b><dd>
        <table width=85%><tr><td colspan=2><hr size=3></td></tr>
      <<
      foreach $a ($f->exceptions()) {
        >><tr valign=top><th align=right>
  	$(a.name)</th><td><<
  	  print &processDescription( $a->description() );
        >></td></tr>
        <<
      }
      >><tr><td colspan=2><hr size=3></td></tr></table>
      <<
    }
  	
    if ($f->seealso()) {
      >><dt><b>See Also</b><dd>
        <<
      my @r = ();
      foreach $a ($f->seealso()) {
        my $name = $a->name();
        if ($url = $a->url()) {
          $_ = $url;
          s/\s/_/g;
  	push @r, "<a href=\"$_\">$name</a>";
        }
        else { push @r, $name; }
      }
      print join( ',', @r );
      >><p><<
    }
    >></dl></dl>
    <<
  }
  
  ######################################################################
  
  ## Subroutine to generate documentation for a member variable or global variable.
  
  sub variable {
    local ($v) = @_;
    
    if ($v->keywords()) {
      print "<!-- $(v.keywords) -->";
    }
  
    >>
      <a name="$(v.name)"></a>
        <dl><dt>
  	<b><img src="$bullet2_image" width=19 height=17 align=texttop>$(v.fullname);</b>
    <dd>
    <<print &processDescription( $v->description() );>>
    <p><dl>
    <<
    if ($v->seealso()) {
      >><dt><b>See Also</b><dd>
        <<
  	$comma = 0;
      foreach $a ($v->seealso()) {
        $_ = $a->url;
        s/\s/_/g;
        if ($comma) { print ","; }
        $comma = 1;
        >><a href="$_">$(a.name)</a>
  	<<
      }
      >><p>
      <<
    }
    >></dl></dl>
    <<
  }
  
  ######################################################################
  
  sub processDescription {
    local ($_) = @_;
  
    # handle HTML markup issues.
    s/</&lt;/g;
    s/>/&gt;/g;
  
    s/^\s+//;				# Remove whitespace from beginning
    s/\s+$/\n/;				# Remove whitespace from end
    s/\n\n/<p>\n/g;			# Replace multiple CR's with paragraph markers
    s:\@heading(.*)\n:<p><h2>$1</h2>:;	# Handle heading text
    
    # Handle embedded image tags
    s:\@caution:<p><img src=\"${image_directory}/caution.gif\" align=left>:;
    s:\@warning:<p><img src=\"${image_directory}/warning.gif\" align=left>:;
    s:\@bug:<p><img src=\"${image_directory}/bug.gif\">:;
    s:\@tip:<p><img src=\"${image_directory}/tip.gif\">:;
  
    return $_;
  }