#!/usr/local/bin/perl

# Perl script to parse VRPN message parameter declarations
# from a definition file (def-file) and then build
# an associative array to define them.
# The script then reads in one or more code files
# and inserts code in them based on keywords in the code
# files and the message parameters defined in the def-file.

# usage:  def.pl <def-file-name> <code-file-name>+

# Tom Hudson, June 1999

# Recursive descent parser pointed out by Chris Weigle.
# A redesign might use half-a-dozen RDParsers instead of two.

use Parse::RecDescent;
#$::RD_HINT = 1;     # turn on verbose debugging
#$::RD_TRACE = 1;     # turn on verbose debugging

local (%messageDefinitionArray) = ();
local ($numDefinitions) = 0;

# debugging flags

local ($dumpMessageArray) = 0;

# Tie::IxHash allows us to retrieve from the hash table in
# insertion order, which would be esp. nice for debugging.

# use Tie::IxHash;
# tie %messageDefinitionArray, "Tie::IxHash";

# If this were a full-featured language, we'd use a data structure
# to hold the syntax tree we create for the data types.  However,
# Perl (to the best of my knowledge) doesn't have data structures,
# so we have to encode it in a string, then parse that string
# every time we want to read it.
# One way to do this would be to have a recursive descent parser
# for every output, but instead we rewrite the standard definition
# into a simpler format that we don't need a RD parser to parse.
# BNF looks something like:
#  typeRecord:       typeEntry
#                  | typeEntry ':' typeRecord
#  typeEntry:        typeName variableName optionalArray
#                  | 'struct' typeName '{'
#                     structTypeRecord
#                    '}' variableName optionalArray
#  structTypeRecord: typeEntry
#                  | typeEntry ';' structTypeRecord
#  optionalArray:    count
#                  | empty
# Probably should have done it using many RD parsers, especially
# since they seem to be reentrant.

$definitionGrammar = q[

  start: line(s)

  line: /VRPN_MESSAGE/ typeName /{/ structOrFieldSequence /}/
    { $main::messageDefinitionArray{$item[2]} = $item[4];
      #  print $item[2] . " is " . $item[4] . "\n";
      $main::numDefinitions++; }
    | /[^\n]*\n/
    { }

  typeName: /(\w)+/
    { $return = $item[1]; }

  structOrFieldSequence:
      structDefinition structOrFieldSequence
      { $return = $item[1] . $item[2]; }
    | field structOrFieldSequence
      { $return = $item[1] . $item[2]; }
    | structDefinition
      { $return = $item[1]; }
    | field
      { $return = $item[1]; }
    | //
      { $return = ""; }

  structDefinition:
      /struct/ structTypeName /{/
        fieldSequence
      /}/ structName count
      { $return = "struct " . $item[2] . " {"
                  . join(";", split(/:/, $item[4]))
                  . "}" . $item[6] . " " . $item[7]; }

  structName:
      /(\w)+/
      { $return = $item[1]; }

  structTypeName:
      /(\w)+/
      { $return = $item[1]; }

  fieldSequence:
      field fieldSequence
      { $return = $item[1] . $item[2]; }
    | field
      { $return = $item[1]; }

  field:
      /(\w)+/ /(\w)+/ optionalCount
      { $return = $item[1] . " " . $item[2] . " " . $item[3] . ":" . $item[4]; }

  optionalCount:
      count optionalCount
      { $return = $item[1] . $item[2]; }
    | //
      { $return = ""; }

  count:
      /\[/ /(\d)+/ /\]/ # fixed, numeric count
      { $return = "[" . $item[2] . "]"; }
    | /\[/ /(\w)+/ /\]/ # variable count
      { $return = "[" . $item[2] . "]"; }

];

$definitionParser = new Parse::RecDescent ($definitionGrammar);
$definitionInput = "";


#   take name of file to open for definitions from command line $ARGV[0]

open(DEFHANDLE, "< " . $ARGV[0]);
while (<DEFHANDLE>) {
  $definitionInput .= $_;
};
close(DEFHANDLE);

$definitionParser->start($definitionInput);

# traverse messageDefinitionArray and print

if ($dumpMessageArray) {
  foreach my $typeName (keys(%messageDefinitionArray)) {
    print $typeName . " is:\n";
    # print $typeName . " is " . $messageDefinitionArray{$typeName} . "\n";
    foreach my $arg (split(/:/, $messageDefinitionArray{$typeName})) {
      if (substr($arg,0,6) eq "struct") {
        my $openbrace = index($arg, "{");
        my $closebrace = index($arg, "}");  # must only be one!
        my $openbracket = index($arg, "[", $closebrace);
        my $argbody = substr($arg, $openbrace + 1,
                             $closebrace - $openbrace - 1);
        my $structtypename = substr($arg, 7, $openbrace - 7);
        my $structname = substr($arg, $closebrace + 1,
                         $openbracket - $closebrace - 2);
        my $count = substr($arg, $openbracket);
        # print $argbody;
        print "  struct " . $structtypename . " {\n";
        foreach my $subarg (split(/;/, $argbody)) {
          print "    " . $subarg . "\n";
        }
        print "  } " . $structname
              . " " . $count . "\n";
      } else {
        print "  " . $arg . "\n";
      }
    }
  }
}

# print "Parsed " . $numDefinitions . " definitions.\n";

# Emits a function to decode the named type into network form.
# Returns 0, or -1 on error.
#
#  int myClass::decode_foo (const char * buffer, ...) {
#    if (!buffer || ...) return -1;
#    vrpn_unbuffer(&buffer, ...);
#    return 0;
#  }

sub emitDecoder {
  my $typeName = $_[0];

  # function declaration / parameter list

  print "int " . $classPrefix . "decode_" . $typeName . " (\n";
  print "    const char * buffer,\n";

  my @argarray = split(/:/, $messageDefinitionArray{$typeName});

  for ($i = 0; $i <= $#argarray; $i++) {
    my @thisarg = split(/ /, $argarray[$i]);
    print "    ";

    my $varname = $thisarg[1];

    if (substr($thisarg[0],0,6) eq "struct") {
      print "struct " . $varname . " ";
      my $closebrace = index($argarray[$i], "}");
      $openbracket = index($argarray[$i], "[", $closebrace);
      $varname = substr($argarray[$i], $closebrace + 1,
                        $openbracket - $closebrace - 2);

    } else {
      $openbracket = index($argarray[$i], "[");
      if ($thisarg[0] ne "char") {
        print "vrpn_"
      }
      print $thisarg[0] . " ";
    }

    print "* " . $varname;
    if ($i != $#argarray) {
      print ",";
    }
    print "\n";
  }

  print ") {\n";

  # function body

  # test for non-NULL

  print "  if (!buffer";
  for ($i = 0; $i <= $#argarray; $i++) {
    my @thisarg = split(/ /, $argarray[$i]);

    my $varname = $thisarg[1];

    if (substr($thisarg[0],0,6) eq "struct") {
      $closebrace = index($argarray[$i], "}");
      $openbracket = index($argarray[$i], "[", $closebrace);
      $varname = substr($argarray[$i], $closebrace + 1,
                        $openbracket - $closebrace - 2);
    }
    print "\n   || !" . $varname;
  }
  print ") return -1;\n";

  # OK, tough stuff:  unbuffer each entry.
  # Need to handle nested loops!

  emitUnbufferList(@argarray);

  print "  return 0;\n";
  print "}\n";
  print "\n";

  1;
};

# Emits a function to encode the named type into network form.
# Returns a buffer which it is the caller's responsibility to
# delete [].  Places the length of the buffer in the parameter
# len.
#
#  char * myClass::encode_foo (int * len, ...) {
#    char * msgbuf = NULL;
#    char * mptr;
#    int mlen;
#    if (!len) return NULL;
#    *len = sizeof(...);
#    msgbuf = new char [*len];
#    if (!msgbuf) {
#      fprintf(stderr, "encode_foo:  Out of memory.\n");
#      *len = 0;
#    } else {
#      mptr = msgbuf;
#      mlen = *len;
#      vrpn_buffer(&mptr, &mlen, ...);
#    }
#    return msgbuf;
#  }

sub emitEncoder {
  my $typeName = $_[0];

  # function declaration / parameter list

  print "char * " . $classPrefix . "encode_" . $typeName . " (\n";
  print "    int * len,\n";

  my @argarray = split(/:/, $messageDefinitionArray{$typeName});

  for ($i = 0; $i <= $#argarray; $i++) {
    my @thisarg = split(/ /, $argarray[$i]);
    print "    ";

    my $varname = $thisarg[1];
    my $openbracket = $[-1;

    if (substr($thisarg[0],0,6) eq "struct") {
      print "struct " . $varname . " ";
      my $closebrace = index($argarray[$i], "}");
      $openbracket = index($argarray[$i], "[", $closebrace);
      $varname = substr($argarray[$i], $closebrace + 1,
                        $openbracket - $closebrace - 2);

    } else {
      $openbracket = index($argarray[$i], "[");
      if ($thisarg[0] ne "char") {
        print "vrpn_"
      }
      print $thisarg[0] . " ";
    }
    if ($openbracket != $[-1) {
      print "* ";
    }
    print $varname;
    if ($i != $#argarray) {
      print ",";
    }
    print "\n";
  }

  print ") {\n";

  # function body

  print "  char * msgbuf = NULL;\n";
  print "  char * mptr;\n";
  print "  int mlen;\n";
  print "  if (!len) return NULL;\n";

  # figure out size of message by adding up types of every entry.

  print "  *len =\n";
  emitMsgSizes(@argarray);

  print "  msgbuf = new char [*len];\n"
      . "  if (!msgbuf) {\n"
      . "    fprintf(stderr, \"encode_" . $typeName . ":  " .
                     "Out of memory.\\n\");\n"
      . "    *len = 0;\n"
      . "  } else {\n"
      . "    mptr = msgbuf;\n"
      . "    mlen = *len;\n";

  # OK, tough stuff:  buffer each entry.
  # Need to handle nested loops!

  emitBufferList(@argarray);

  print "  }\n";
  print "  return msgbuf;\n";
  print "}\n";
  print "\n";

  1;
};

# Emits a function that unmarshalls (see decode_foo) onto the stack
# then executes user-specified code.

sub emitHandler {
  my $typeName = $_[0];

  # function declaration / parameter list

  print "\n";
  print "int " . $classPrefix . "handle_" . $typeName . " ("
               . "void * userdata, vrpn_HANDLERPARAM p) {\n";
  print "  const char * buffer = p.buffer;\n";

  my @argarray = split(/:/, $messageDefinitionArray{$typeName});

  for (my $i = 0; $i <= $#argarray; $i++) {
    my @thisarg = split(/ /, $argarray[$i]);

    my $varname = $thisarg[1];

    my @arrayDimensions = split(/\[/, $argarray[$i]);
    my $numArrayDimensions = $#arrayDimensions;

    #print "ARGARRAY[I] = " . $argarray[$i] . "\n";
    #foreach my $ad (@arrayDimensions) {
      #print "DIMENSION = " . $ad . "\n";
    #}
    #print "# ARRAY DIMENSIONS = " . $numArrayDimensions . "\n";

    print "  ";
    if (substr($thisarg[0],0,6) eq "struct") {
      print "struct " . $varname . " ";
      my $closebrace = index($argarray[$i], "}");
      $openbracket = index($argarray[$i], "[", $closebrace);
      $varname = substr($argarray[$i], $closebrace + 1,
                        $openbracket - $closebrace - 2);

      $trailingBrace = rindex($argarray[$i], "}");
      $trailingPart = substr($argarray[$i], $trailingBrace);
      @arrayDimensions = split(/\[/, $trailingPart);
      $numArrayDimensions = $#arrayDimensions;
    } else {
      if ($thisarg[0] ne "char") {
        print "vrpn_"
      }
      print $thisarg[0] . " ";
    }

    # hack:  assume array dimension is a constant if
    # we can get a nonzero numeric value for each of the dimensions

    my $dimensionIsConstant = 1;
    for (my $j = 1; $j <= $numArrayDimensions; $j++) {
      my $ad = $arrayDimensions[$j];
      #print "DIMENSION = " . $ad . "\n";
      if ($ad == 0) {
        $dimensionIsConstant = 0;
      }
    }
      
    if (($numArrayDimensions > 0) &&
        !$dimensionIsConstant) {
      for (my $j = 1; $j <= $numArrayDimensions; $j++) {
        print "*";
      }
      print " ";
    }

    print $varname;

    if (($numArrayDimensions > 0) &&
        $dimensionIsConstant) {
      print " ";
      print substr($argarray[$i], index($argarray[$i], "["));
    }

    print ";\n";
  }

  print "\n";

  # function body

  # test for non-NULL

  # OK, tough stuff:  unbuffer each entry.
  # Need to handle nested loops!

  emitAndedUnbufferList(@argarray);

  # User is expected to write the rest of the code.

  print "\n";

  1;
};

#
# TODO:  extra parameters for register_handler
#

sub emitRegister {
  my $typeName = $_[0];
  my $argument = $_[1];

  my $qTypeName = $typeName . "_type";
  print "  vrpn_int32 " . $qTypeName . ";\n";
  print "  " . $qTypeName . " = connection->register_message_type(\""
             . $typeName . "\");\n";
  print "  connection->register_handler(" . $qTypeName . ", handle_"
             . $typeName . ", " . $argument . ");\n";

}

# Given a message type name, write out declarations for all the
# structures the message uses.

# Quick 'n' dirty;  doesn't pretty-print.

# Need to do three ugly things:
#   Find variable-sized arrays in the declaration and replace them
# with pointers.
#   Insert "vrpn_" in type names where appropriate
# x Insert trailing ';' before the closing '}'

sub emitStructures {
  my $typeName = $_[0];

  my @argarray = split(/:/, $messageDefinitionArray{$typeName});

  foreach my $arg (@argarray) {
    my @thisarg = split(/ /, $arg);
    if ($thisarg[0] eq "struct") {
      my $openBrace = index($arg, "{");
      my $closeBrace = rindex($arg, "}");
      #print substr($arg, 0, $closeBrace) . ";};\n";

      my $structFieldString = substr($arg, $openBrace + 1,
                                     $closeBrace - $openBrace - 1);
      my $structDeclaration = substr($arg, 0, $openBrace);

      #print "FIELDS = " . $structFieldString . "\n";
      #print "DECLARATION = " . $structDeclaration . "\n";

      my @structFields = split(/;/, $structFieldString);

      print $structDeclaration . " {\n";

      foreach my $field (@structFields) {
        my @words = split(/ /, $field);
        my $typename = $words[0];
        my $varname = $words[1];
        my $arrayspec = $words[2]; 
        if ($typename eq "struct") {
          $typename = $words[0] . " " . $words[1];
          $varname = $words[2];
          $arrayspec = $words[3];
        } elsif ($typename ne "char") {
          $typename = "vrpn_" . $typename;
        }

        # figure out if nonconstant-sized array

        # hack:  assume array dimension is a constant if
        # we can get a nonzero numeric value for each of the dimensions

        my @counts = split(/]/, $arrayspec);
        my $numopenbracket = $#counts;

        my $dimensionIsConstant = 1;
        for (my $j = 0; $j <= $numopenbracket; $j++) {
          my $ad = substr($counts[$j], 1);
          #print "DIMENSION = " . $ad . "\n";
          if ($ad == 0) {
            $dimensionIsConstant = 0;
          }
        }

        if (!$dimensionIsConstant) {
          $varname = " " . $varname;
          for (my $j = 0; $j <= numopenbracket; $j++) {
            $varname = "*" . $varname;
          }
          $arrayspec = "";
        }
          

        print "  " . $typename . " " . $varname . " " . $arrayspec . ";\n";

      }

      print "};\n";
      print "\n";
    }
  }
}


# Figure out how large the message will be once we encode it:
#   take sizeof every element and sum.

sub emitMsgSizes {
  my @argarray = @_;
  # print "Sizes:  " . $#argarray . "\n";

  for (my $i = 0; $i <= $#argarray; $i++) {
    my @thisarg = split(/ /, $argarray[$i]);
    print "    sizeof(";

    my $typename = $thisarg[0];
    my $openbracket = $[-1;

    if ($typename eq "struct") {
      print "struct " . $thisarg[1];
      my $closebrace = index($argarray[$i], "}");
      $openbracket = index($argarray[$i], "[", $closebrace);
    } else {
      $openbracket = index($argarray[$i], "[");
      if ($typename ne "char") {
        print "vrpn_"
      }
      print $typename;
    }
    print ")";

    # multiply out all the counts
    # This can be numeric or variable, so we can't do the math here,
    # but have to do it at runtime.

    my $count = 1;
    my $countstr = $thisarg[$#thisarg];
    if ($openbracket != $[-1) {
      foreach my $countsubstr (split (/]/, $countstr)) {
        print " * " . substr($countstr, 1, length($countstr) - 2);
      }
    }

    if ($i != $#argarray) {
      print " +";
    } else {
      print ";";
    }
    print "\n";
  }
};

# Emit code to buffer (marshal) "one thing"
# Arguments:
#    thing prefix indent
#      thing is a complex space-separated string (don't you love Perl?):
#        <type name> <variable name> [<array specifications>]
#      prefix is a structure name (possibly with array index)
#      indent is a string of spaces that we use to make pretty output

sub emitBufferItem {
  my $arg = $_[0];
  my $prefix = $_[1];
  my $indent = $_[2];

  my @thisarg = split(/ /, $arg);

  my $typename = $thisarg[0];
  my $varname = $thisarg[1];
  my $openbracket = index($arg, "[");

  if ($openbracket != $[-1) {
    # is it a one-dimensional character array?
    my $lastopenbracket = rindex($arg, "[");
    if (($typename eq "char") &&
        ($lastopenbracket == $openbracket)) {
      my $count = substr($arg, $openbracket + 1,
                         index($arg, "]") - $openbracket - 1);
      print $indent . "    vrpn_buffer(&mptr, &mlen, " . $prefix . $varname
                           . ", " . $count . ");\n";
    } elsif ($typename eq "struct") {
      # array of structures;  assume one-dimensional

      print $indent . "    int s;\n";
      print $indent . "    for (s = 0; s < 1";
      my $countstr = $thisarg[$#thisarg];
      foreach my $countsubstr (split (/]/, $countstr)) {
         print " * " . substr($countstr, 1, length($countstr) - 2);
      }
      print "; s++) {\n";

      my $openbrace = index($arg, "{");
      my $lastclosebrace = rindex($arg, "}");
      my $subargstr = substr($arg,
                             $openbrace + 1,
                             $lastclosebrace - $openbrace - 2);
      # print "Subargstr:  " . $subargstr . "\n";
      my @subarg = split(/;/, $subargstr);
      # print "#subarg:  " . $subarg . "\n";

      $openbracket = index($arg, "[", $lastclosebrace);
      my $structname = substr($arg, $lastclosebrace + 1,
                       $openbracket - $lastclosebrace - 2);


      for (my $i = 0; $i <= $#subarg; $i++) {
        # print $i . " emitting: " . $subarg[$i] . "\n";
        emitBufferItem($subarg[$i], $structname . "[s].");
      }

      print "    }\n";
    } else {

      # either an array of non-char
      # or a multidimensional array of any type
      # or ...

      print "BUG BUG BUG\n";

    }
  } else {

    # default case:  not a struct, not an array, not anything special,
    # just an ordinary parameter

    print $indent . "    vrpn_buffer(&mptr, &mlen, "
                  . $prefix . $varname . ");\n";
  }

};

# Given a list of things to buffer,
# generate the code to buffer each.

sub emitBufferList {
  my @argarray = @_;

  for (my $i = 0; $i <= $#argarray; $i++) {
     emitBufferItem($argarray[$i], "", "");
  }
};

# Emit code to unbuffer (demarshal) "one thing"
#

sub emitUnbufferItem {
  my $arg = $_[0];
  my $prefix = $_[1];
  my $indent = $_[2];
  my $arrayspec = $_[3];
  my $recursioncount = $_[4];
  my $dereferenceNonconstantArraySizes = $_[5];

  my @thisarg = split(/ /, $arg);

  my $typename = $thisarg[0];
  my $varname = $thisarg[1];
  my $firstOpenBracket = index($arg, "[");
  my $lastclosebrace = rindex($arg, "}");
  my $trailingOpenBracket = index($arg, "[", $lastclosebrace);
  my $lastopenbracket = rindex($arg, "[");

  my $qualifiedVarname = $prefix . $varname . $arrayspec;

  # is it not an array?
  if ($trailingOpenBracket == $[-1) {

    if ($typename eq "struct") {

      # struct

      my $openbrace = index($arg, "{");
      my $subargstr = substr($arg,
                             $openbrace + 1,
                             $lastclosebrace - $openbrace - 1);
      my @subarg = split(/;/, $subargstr);

      #print "ARG = " . $arg . "\n";

      my $structname = substr($arg, $lastclosebrace + 1);
                       # $trailingOpenBracket - $lastclosebrace - 2);

      #print "STRUCTNAME = " . $structname . "\n";

      for (my $i = 0; $i <= $#subarg; $i++) {
        emitUnbufferItem($subarg[$i], $prefix . $structname . $arrayspec . ".",
                         "  " . $indent, "", $recursioncount + 1);
        # this doesn't need to be $recursioncount + 1, it could just be
        # $recursioncount, since we're really using it for a
        # number-of-enclosing-loops count, but it doesn't hurt.
      }

    } else {

      # default case:  not a struct, not an array, not anything special

      print $indent . "  vrpn_unbuffer(&buffer, " . $qualifiedVarname . ");\n";

    }

  } elsif (($typename eq "char") &&
          ($lastopenbracket == $trailingOpenBracket)) {

    # one-dimensional character array

    my $count = substr($arg, $trailingOpenBracket + 1,
                       index($arg, "]") - $trailingOpenBracket - 1);

    # hack:  $count == 0 means this array size is nonconstant
    if ($dereferenceNonconstantArraySizes && ($count == 0)) {
      $count = "*" . $count;
    }

    # hack:  remove any leading '&'
    # (But hey, strings are a little non-orthogonal.  We could probably
    #  just go ahead and unbuffer one character at a time inside a loop,
    #  but it's more efficient to take advantage of vrpn_unbuffer()'s
    #  char special case.  If this ever breaks, remove the char special
    #  case and add a single-character unbuffer function to vrpn.)

    my $charVarname = $qualifiedVarname;
    if (index($qualifiedVarname, "&") == 0) {
      $charVarname = substr($charVarname, 1);
    }

    # if the dimension is non-constant we have to allocate the array.

    if ($count == 0) {
      print $indent . "  " . $charVarname . " = new char [" . $count . "];\n";
    }

    print $indent . "  vrpn_unbuffer(&buffer, " . $charVarname .
                    ", " . $count . ");\n";

  } else {

    # array:
    #   emit code to allocate the array on the heap IFF size is non-constant
    #   emit code for an iterator (variable declaration and for loop)
    #   construct an $arrayspec for this array dimension
    #   change the string we pass on to not include this dimension
    #   recurse

    #print "ARG = " . $arg . "\n";

    my $countstr = $thisarg[$#thisarg];
    my @counts = split(/]/, $countstr);
    my $numopenbracket = $#counts;

    my $numdimensions = $numopenbracket;

    my $loopvar = "vd_i" . $recursioncount;

    # allocation time

    # hack:  assume array dimension is a constant if
    # we can get a nonzero numeric value for each of the dimensions

    my $dimensionIsConstant = 1;
    for (my $j = 0; $j <= $numopenbracket; $j++) {
      my $ad = $counts[$j];
      #print "DIMENSION = " . $ad . "\n";
      if ($ad == 0) {
        $dimensionIsConstant = 0;
      }
    }

    my $thisCount = substr($counts[0], 1);
    
    if (!$dimensionIsConstant) {
      if ($dereferenceNonconstantArraySizes) {
        $thisCount = "*" . $thisCount;
      }

      my $bareVarname = $qualifiedVarname;
      if (index($bareVarname, "&") == 0) {
        $bareVarname = substr($bareVarname, 1);
      }

      my $effectiveTypeName = $typename;
      #print "BVN = " . $bareVarname . "\n";
      #print "ETN = " . $effectiveTypeName . "\n";

      if ($effectiveTypeName eq "struct") {

        # effectiveTypeName and bareVarname reverse

        $effectiveTypeName = $bareVarname;

        $bareVarname = substr($arg, $lastclosebrace + 1);
        #print "BVN = " . $bareVarname . "\n";
        #print "ETN = " . $effectiveTypeName . "\n";

        my $bracketIndex = index($bareVarname, "[");
        if ($bracketIndex != $[-1) {
          $bareVarname = substr($bareVarname, 0, $bracketIndex);
          #print "ETN = " . $effectiveTypeName . "\n";
        }
      } elsif ($effectiveTypeName ne "char") {
        $effectiveTypeName = "vrpn_" . $effectiveTypeName;
      }

      print $indent . "  " . $bareVarname . " = new "
                    . $effectiveTypeName . " ";

      # print any necessary "*"s
      for (my $i = 1; $i <= $#counts; $i++) {
        print "*";
      }

      print " [" . $thisCount . "];\n";
    }

    # define an index variable for this dimension of the array
    print $indent . "  int " . $loopvar . ";\n";

    # start a loop over this dimension of the array
    print $indent . "  for (" .
          $loopvar . " = 0;  " .
          $loopvar . " < " . $thisCount . ";  " .
          $loopvar . "++) {\n";

    # "fix up" string we're passing down and construct the new arrayspec
    # The string we pass down to the next recursion should have the FIRST
    # array specifier ([...]) removed

    my $trailingCloseBracket = index($arg, "]", $trailingOpenBracket);

    #print "TRAILING OPEN = " . $trailingOpenBracket . "\n";
    #print "TRAILING CLOSED = " . $trailingCloseBracket . "\n";

    my $newarg = substr($arg, 0, $trailingOpenBracket) .
                 substr($arg, $trailingCloseBracket + 1);
    my $newarrayspec = $arrayspec . "[" . $loopvar . "]";

    # recurse
    emitUnbufferItem($newarg, $prefix,
                     $indent . "  ",
                     $newarrayspec,
                     $recursioncount + 1,
                     $dereferenceNonconstantArraySizes);

    # end the for loop
    print $indent . "  }\n";

  }

};

# Given a list of things emit unbuffering code for all of them.

sub emitUnbufferList {
  my @argarray = @_;

  for (my $i = 0; $i <= $#argarray; $i++) {
     emitUnbufferItem($argarray[$i], "", "", "", 0, 1);
  }

};

# Given a list of things emit unbuffering code for all of them
# with & prefixed.  (A hack inside emitUnbufferItem will remove the
# '&' before one-dimensional character arrays.)

sub emitAndedUnbufferList {
  my @argarray = @_;

  for (my $i = 0; $i <= $#argarray; $i++) {
     emitUnbufferItem($argarray[$i], "&", "", "", 0, 0);
  }

};

local $class = "";
local $classPrefix = "";

# outputGrammar:
#   Parses C++ source files for directives to insert code fragments.
#   Recognized directives:
#     CLASSNAME <class name>
#       Subsequent functions definitions should be prefixed with <class name>::
#     NO CLASS
#       Cancels a CLASSNAME directive
#     ENCODE <type name>
#       Inserts a code fragment for a function
#         char * encode_<type name> (int * len, ...)
#         Allocates and returns a pointer to a character buffer holding
#         the marshalled message;  returns the length of the message in
#         len.  Components of the message are passed as the "..." arguments;
#         arrays should be passed as pointers.
#     DECODE <type name>
#       Inserts a code fragment for a function
#         int decode_<type name> (const char * buffer, ...)
#         Unpacks the buffer into its components, which should be passed
#         as pointers.  All arrays, whether fixed or variable-sized,
#         will be allocated on the heap and returned in pointers.
#     HANDLE <type name>
#     ...
#     }
#       Inserts a code fragment for a function
#         int handle_<type name> (void * userdata, vrpn_HANDLERPARAM p)
#         Declares the message's components on the stack and unpacks the
#         buffer into them.  Then falls through into the user code
#         indicated by "..." and terminated by "}".
#     REGISTER <type name>
#       Generates the following fragment:
#         vrpn_int32 <type name>_type;
#         <type name>_type = connection->register_message_type("<type name>");
#         connection->register_handler("<type name>", handle_<type name>,
#                                      NULL);
#       BUGS:  Does not allow specification of the userdata pointer (forces
#       it to NULL) or other parameters.


$outputGrammar = q[

  start: line(s)

  line:
      classname
    | prototype
    | encoder
    | decoder
    | handler
    | register
    | /[^\n]*\n/
      { my $thisspacepad = "";
        for (my $i = 0 ; $i < 1 + $prevcolumn - length($item[1]) ; $i++) {
          $thisspacepad .= " ";
        }
        print $thisspacepad . $item[1]; }

  classname:
      /CLASSNAME/ className
      { my $class = className;
        my $classPrefix = className . "::"; }
    | /NO CLASS/
      { my $class = "";
        my $classPrefix = ""; }

  prototype:
      /DECLARE STRUCTURES/ messageName
      { main::emitStructures ($item[2]); }

  encoder:
      /ENCODE/ typeName
      { main::emitEncoder ($item[2]); }

  decoder:
      /DECODE/ typeName
      { main::emitDecoder ($item[2]); }

  handler:
      /HANDLE/ typeName
      { main::emitHandler ($item[2]); }

  register:
      /REGISTER/ typeName optionalVariableName
      { main::emitRegister ($item[2], $item[3]); }

  typeName:
      /(\w)+/
      { $return = $item[1]; }

  className:
      /(\w)+/
      { $return = $item[1]; }

  messageName:
      /(\w)+/
      { $return = $item[1]; }

  optionalVariableName:
      /(\w)+/ /;/
      { $return = $item[1]; }
    |  /;/
      { $return = "NULL"; }
];

$outputParser = new Parse::RecDescent ($outputGrammar);
$outputInput = "";


#$::RD_HINT = 1;     # turn on verbose debugging
#$::RD_TRACE = 1;     # turn on verbose debugging

# Do the actual work.
#print "Entering loop.\n";

for (my $i = 1; $i <= 1; $i++) {

  # print "Reading " . $ARGV[$i] . ".\n";

  open(OUTPUTHANDLE, "< " . $ARGV[$i]);
  while (<OUTPUTHANDLE>) {
    $outputInput .= $_;
  };
  close(OUTPUTHANDLE);

  $outputParser->start($outputInput);

}

# tests
#emitEncoder("InDirectZControl");
#emitEncoder("HelloMessage");
#emitEncoder("ReportScanDatasets");
#emitDecoder("InDirectZControl");
#emitDecoder("HelloMessage");
#emitDecoder("ReportScanDatasets");

1;