/* options.c - options package routines */

/* SimpleScalar(TM) Tool Suite
 * Copyright (C) 1994-2003 by Todd M. Austin, Ph.D. and SimpleScalar, LLC.
 * All Rights Reserved. 
 * 
 * THIS IS A LEGAL DOCUMENT, BY USING SIMPLESCALAR,
 * YOU ARE AGREEING TO THESE TERMS AND CONDITIONS.
 * 
 * No portion of this work may be used by any commercial entity, or for any
 * commercial purpose, without the prior, written permission of SimpleScalar,
 * LLC (info@simplescalar.com). Nonprofit and noncommercial use is permitted
 * as described below.
 * 
 * 1. SimpleScalar is provided AS IS, with no warranty of any kind, express
 * or implied. The user of the program accepts full responsibility for the
 * application of the program and the use of any results.
 * 
 * 2. Nonprofit and noncommercial use is encouraged. SimpleScalar may be
 * downloaded, compiled, executed, copied, and modified solely for nonprofit,
 * educational, noncommercial research, and noncommercial scholarship
 * purposes provided that this notice in its entirety accompanies all copies.
 * Copies of the modified software can be delivered to persons who use it
 * solely for nonprofit, educational, noncommercial research, and
 * noncommercial scholarship purposes provided that this notice in its
 * entirety accompanies all copies.
 * 
 * 3. ALL COMMERCIAL USE, AND ALL USE BY FOR PROFIT ENTITIES, IS EXPRESSLY
 * PROHIBITED WITHOUT A LICENSE FROM SIMPLESCALAR, LLC (info@simplescalar.com).
 * 
 * 4. No nonprofit user may place any restrictions on the use of this software,
 * including as modified by the user, by any other authorized user.
 * 
 * 5. Noncommercial and nonprofit users may distribute copies of SimpleScalar
 * in compiled or executable form as set forth in Section 2, provided that
 * either: (A) it is accompanied by the corresponding machine-readable source
 * code, or (B) it is accompanied by a written offer, with no time limit, to
 * give anyone a machine-readable copy of the corresponding source code in
 * return for reimbursement of the cost of distribution. This written offer
 * must permit verbatim duplication by anyone, or (C) it is distributed by
 * someone who received only the executable form, and is accompanied by a
 * copy of the written offer of source code.
 * 
 * 6. SimpleScalar was developed by Todd M. Austin, Ph.D. The tool suite is
 * currently maintained by SimpleScalar LLC (info@simplescalar.com). US Mail:
 * 2395 Timbercrest Court, Ann Arbor, MI 48105.
 * 
 * Copyright (C) 1994-2003 by Todd M. Austin, Ph.D. and SimpleScalar, LLC.
 */


#include <stdio.h>
#include <stdlib.h>
#ifndef _MSC_VER
#include <unistd.h>
#else /* _MSC_VER */
#define chdir	_chdir
#define getcwd	_getcwd
#endif
#include <string.h>
#include <ctype.h>
#include <float.h>

#include "host.h"
#include "misc.h"
#include "options.h"

/* create a new option database */
struct opt_odb_t *
opt_new(orphan_fn_t orphan_fn)	/* user-specified orphan parser */
{
  struct opt_odb_t *odb;

  odb = (struct opt_odb_t *)calloc(1, sizeof(struct opt_odb_t));
  if (!odb)
    fatal("out of virtual memory");

  odb->options = NULL;
  odb->orphan_fn = orphan_fn;
  odb->header = NULL;
  odb->notes = NULL;

  return odb;
}

/* free an option database */
void
opt_delete(struct opt_odb_t *odb)	/* option database */
{
  struct opt_opt_t *opt, *opt_next;
  struct opt_note_t *note, *note_next;

  /* free all options */
  for (opt=odb->options; opt; opt=opt_next)
    {
      opt_next = opt->next;
      opt->next = NULL;
      free(opt);
    }

  /* free all notes */
  for (note = odb->notes; note != NULL; note = note_next)
    {
      note_next = note->next;
      note->next = NULL;
      free(note);
    }
  odb->notes = NULL;

  free(odb);
}

/* add option OPT to option database ODB */
static void
add_option(struct opt_odb_t *odb,	/* option database */
	   struct opt_opt_t *opt)	/* option variable */
{
  struct opt_opt_t *elt, *prev;

  /* sanity checks on option name */
  if (opt->name[0] != '-')
    panic("option `%s' must start with a `-'", opt->name);

  /* add to end of option list */
  for (prev=NULL, elt=odb->options; elt != NULL; prev=elt, elt=elt->next)
    {
      /* sanity checks on option name */
      if (elt->name[0] == opt->name[0] && !strcmp(elt->name, opt->name))
	panic("option `%s' is multiply defined", opt->name);
    }

  if (prev != NULL)
    prev->next = opt;
  else /* prev == NULL */
    odb->options = opt;
  opt->next = NULL;
}

/* register an integer option variable */
void
opt_reg_int(struct opt_odb_t *odb,	/* option data base */
	    char *name,			/* option name */
	    char *desc,			/* option description */
	    int *var,			/* target variable */
	    int def_val,		/* default variable value */
	    int print,			/* print during `-dumpconfig'? */
	    char *format)		/* optional value print format */
{
  struct opt_opt_t *opt;

  opt = (struct opt_opt_t *)calloc(1, sizeof(struct opt_opt_t));
  if (!opt)
    fatal("out of virtual memory");

  opt->name = name;
  opt->desc = desc;
  opt->nvars = 1;
  opt->nelt = NULL;
  opt->format = format ? format : "%12d";
  opt->oc = oc_int;
  opt->variant.for_int.var = var;
  opt->print = print;
  opt->accrue = FALSE;

  /* place on ODB's option list */
  opt->next = NULL;
  add_option(odb, opt);

  /* set default value */
  *var = def_val;
}

/* register an integer option list */
void
opt_reg_int_list(struct opt_odb_t *odb,/* option database */
		 char *name,		/* option name */
		 char *desc,		/* option description */
		 int *vars,		/* pointer to option array */
		 int nvars,		/* total entries in option array */
		 int *nelt,		/* number of entries parsed */
		 int *def_val,		/* default value of option array */
		 int print,		/* print during `-dumpconfig'? */
		 char *format,		/* optional user print format */
		 int accrue)		/* accrue list across uses */
{
  int i;
  struct opt_opt_t *opt;

  opt = (struct opt_opt_t *)calloc(1, sizeof(struct opt_opt_t));
  if (!opt)
    fatal("out of virtual memory");

  opt->name = name;
  opt->desc = desc;
  opt->nvars = nvars;
  opt->nelt = nelt;
  opt->format = format ? format : "%d";
  opt->oc = oc_int;
  opt->variant.for_int.var = vars;
  opt->print = print;
  opt->accrue = accrue;

  /* place on ODB's option list */
  opt->next = NULL;
  add_option(odb, opt);

  /* set default value */
  for (i=0; i < *nelt; i++)
    vars[i] = def_val[i];
}

/* register an unsigned integer option variable */
void
opt_reg_uint(struct opt_odb_t *odb,	/* option database */
	     char *name,		/* option name */
	     char *desc,		/* option description */
	     unsigned int *var,		/* pointer to option variable */
	     unsigned int def_val,	/* default value of option variable */
	     int print,			/* print during `-dumpconfig'? */
	     char *format)		/* optional user print format */
{
  struct opt_opt_t *opt;

  opt = (struct opt_opt_t *)calloc(1, sizeof(struct opt_opt_t));
  if (!opt)
    fatal("out of virtual memory");

  opt->name = name;
  opt->desc = desc;
  opt->nvars = 1;
  opt->nelt = NULL;
  opt->format = format ? format : "%12u";
  opt->oc = oc_uint;
  opt->variant.for_uint.var = var;
  opt->print = print;
  opt->accrue = FALSE;

  /* place on ODB's option list */
  opt->next = NULL;
  add_option(odb, opt);

  /* set default value */
  *var = def_val;
}

/* register an unsigned integer option list */
void
opt_reg_uint_list(struct opt_odb_t *odb,/* option database */
		  char *name,		/* option name */
		  char *desc,		/* option description */
		  unsigned int *vars,	/* pointer to option array */
		  int nvars,		/* total entries in option array */
		  int *nelt,		/* number of elements parsed */
		  unsigned int *def_val,/* default value of option array */
		  int print,		/* print opt during `-dumpconfig'? */
		  char *format,		/* optional user print format */
		  int accrue)		/* accrue list across uses */
{
  int i;
  struct opt_opt_t *opt;

  opt = (struct opt_opt_t *)calloc(1, sizeof(struct opt_opt_t));
  if (!opt)
    fatal("out of virtual memory");

  opt->name = name;
  opt->desc = desc;
  opt->nvars = nvars;
  opt->nelt = nelt;
  opt->format = format ? format : "%u";
  opt->oc = oc_uint;
  opt->variant.for_uint.var = vars;
  opt->print = print;
  opt->accrue = accrue;

  /* place on ODB's option list */
  opt->next = NULL;
  add_option(odb, opt);

  /* set default value */
  for (i=0; i < *nelt; i++)
    vars[i] = def_val[i];
}

/* register a single-precision floating point option variable */
void
opt_reg_float(struct opt_odb_t *odb,	/* option data base */
	      char *name,		/* option name */
	      char *desc,		/* option description */
	      float *var,		/* target option variable */
	      float def_val,		/* default variable value */
	      int print,		/* print during `-dumpconfig'? */
	      char *format)		/* optional value print format */
{
  struct opt_opt_t *opt;

  opt = (struct opt_opt_t *)calloc(1, sizeof(struct opt_opt_t));
  if (!opt)
    fatal("out of virtual memory");

  opt->name = name;
  opt->desc = desc;
  opt->nvars = 1;
  opt->nelt = NULL;
  opt->format = format ? format : "%12.4f";
  opt->oc = oc_float;
  opt->variant.for_float.var = var;
  opt->print = print;
  opt->accrue = FALSE;

  /* place on ODB's option list */
  opt->next = NULL;
  add_option(odb, opt);

  /* set default value */
  *var = def_val;
}

/* register a single-precision floating point option array */
void
opt_reg_float_list(struct opt_odb_t *odb,/* option data base */
		   char *name,		/* option name */
		   char *desc,		/* option description */
		   float *vars,		/* target array */
		   int nvars,		/* target array size */
		   int *nelt,		/* number of args parsed goes here */
		   float *def_val,	/* default variable value */
		   int print,		/* print during `-dumpconfig'? */
		   char *format,	/* optional value print format */
		   int accrue)		/* accrue list across uses */
{
  int i;
  struct opt_opt_t *opt;

  opt = (struct opt_opt_t *)calloc(1, sizeof(struct opt_opt_t));
  if (!opt)
    fatal("out of virtual memory");

  opt->name = name;
  opt->desc = desc;
  opt->nvars = nvars;
  opt->nelt = nelt;
  opt->format = format ? format : "%.4f";
  opt->oc = oc_float;
  opt->variant.for_float.var = vars;
  opt->print = print;
  opt->accrue = accrue;

  /* place on ODB's option list */
  opt->next = NULL;
  add_option(odb, opt);

  /* set default value */
  for (i=0; i < *nelt; i++)
    vars[i] = def_val[i];
}

/* register a double-precision floating point option variable */
void
opt_reg_double(struct opt_odb_t *odb,	/* option data base */
	       char *name,		/* option name */
	       char *desc,		/* option description */
	       double *var,		/* target variable */
	       double def_val,		/* default variable value */
	       int print,		/* print during `-dumpconfig'? */
	       char *format)		/* option value print format */
{
  struct opt_opt_t *opt;

  opt = (struct opt_opt_t *)calloc(1, sizeof(struct opt_opt_t));
  if (!opt)
    fatal("out of virtual memory");

  opt->name = name;
  opt->desc = desc;
  opt->nvars = 1;
  opt->nelt = NULL;
  opt->format = format ? format : "%12.4f";
  opt->oc = oc_double;
  opt->variant.for_double.var = var;
  opt->print = print;
  opt->accrue = FALSE;

  /* place on ODB's option list */
  opt->next = NULL;
  add_option(odb, opt);

  /* set default value */
  *var = def_val;
}

/* register a double-precision floating point option array */
void
opt_reg_double_list(struct opt_odb_t *odb, /* option data base */
		    char *name,		/* option name */
		    char *desc,		/* option description */
		    double *vars,	/* target array */
		    int nvars,		/* target array size */
		    int *nelt,		/* number of args parsed goes here */
		    double *def_val,	/* default variable value */
		    int print,		/* print during `-dumpconfig'? */
		    char *format,	/* option value print format */
		    int accrue)		/* accrue list across uses */
{
  int i;
  struct opt_opt_t *opt;

  opt = (struct opt_opt_t *)calloc(1, sizeof(struct opt_opt_t));
  if (!opt)
    fatal("out of virtual memory");

  opt->name = name;
  opt->desc = desc;
  opt->nvars = nvars;
  opt->nelt = nelt;
  opt->format = format ? format : "%.4f";
  opt->oc = oc_double;
  opt->variant.for_double.var = vars;
  opt->print = print;
  opt->accrue = accrue;

  /* place on ODB's option list */
  opt->next = NULL;
  add_option(odb, opt);

  /* set default value */
  for (i=0; i < *nelt; i++)
    vars[i] = def_val[i];
}

/* bind an enumeration string to an enumeration value */
static int
bind_to_enum(char *str,			/* string to bind to an enum */
	     char **emap,		/* enumeration string map */
	     int *eval,			/* enumeration value map, optional */
	     int emap_sz,		/* size of maps */
	     int *res)			/* enumeration string value result */
{
  int i;

  /* string enumeration string map */
  for (i=0; i<emap_sz; i++)
    {
      if (!strcmp(str, emap[i]))
	{
	  if (eval)
	    {
	      /* bind to eval value */
	      *res = eval[i];
	    }
	  else
	    {
	      /* bind to string index */
	      *res = i;
	    }
	  return TRUE;
	}
    }

  /* not found */
  *res = -1;
  return FALSE;
}

/* bind a enumeration value to an enumeration string */
static char *
bind_to_str(int val,			/* enumeration value */
	    char **emap,		/* enumeration string map */
	    int *eval,			/* enumeration value map, optional */
	    int emap_sz)		/* size of maps */
{
  int i;

  if (eval)
    {
      /* bind to first matching eval value */
      for (i=0; i<emap_sz; i++)
	{
	  if (eval[i] == val)
	    {
	      /* found */
	      return emap[i];
	    }
	}
      /* not found */
      return NULL;
    }
  else
    {
      /* bind to string at index */
      if (val >= emap_sz)
	{
	  /* invalid index */
	  return NULL;
	}
      /* else, index is in range */
      return emap[val];
    }
}

/* register an enumeration option variable, NOTE: all enumeration option
   variables must be of type `int', since true enum variables may be allocated
   with variable sizes by some compilers */
void
opt_reg_enum(struct opt_odb_t *odb,	/* option data base */
	     char *name,		/* option name */
	     char *desc,		/* option description */
	     int *var,			/* target variable */
	     char *def_val,		/* default variable value */
	     char **emap,		/* enumeration string map */
	     int *eval,			/* enumeration value map, optional */
	     int emap_sz,		/* size of maps */
	     int print,			/* print during `-dumpconfig'? */
	     char *format)		/* option value print format */
{
  int enum_val;
  struct opt_opt_t *opt;

  opt = (struct opt_opt_t *)calloc(1, sizeof(struct opt_opt_t));
  if (!opt)
    fatal("out of virtual memory");

  opt->name = name;
  opt->desc = desc;
  opt->nvars = 1;
  opt->nelt = NULL;
  opt->format = format ? format : "%12s";
  opt->oc = oc_enum;
  opt->variant.for_enum.var = var;
  opt->variant.for_enum.emap = emap;
  opt->variant.for_enum.eval = eval;
  opt->variant.for_enum.emap_sz = emap_sz;
  if (def_val)
    {
      if (!bind_to_enum(def_val, emap, eval, emap_sz, &enum_val))
	fatal("could not bind default value for option `%s'", name);
    }
  else
    enum_val = 0;
  opt->print = print;
  opt->accrue = FALSE;

  /* place on ODB's option list */
  opt->next = NULL;
  add_option(odb, opt);

  /* set default value */
  *var = enum_val;
}

/* register an enumeration option array, NOTE: all enumeration option variables
   must be of type `int', since true enum variables may be allocated with
   variable sizes by some compilers */
void
opt_reg_enum_list(struct opt_odb_t *odb,/* option data base */
		  char *name,		/* option name */
		  char *desc,		/* option description */
		  int *vars,		/* target array */
		  int nvars,		/* target array size */
		  int *nelt,		/* number of args parsed goes here */
		  char *def_val,	/* default variable value */
		  char **emap,		/* enumeration string map */
		  int *eval,		/* enumeration value map, optional */
		  int emap_sz,		/* size of maps */
		  int print,		/* print during `-dumpconfig'? */
		  char *format,		/* option value print format */
		  int accrue)		/* accrue list across uses */
{
  int i, enum_val;
  struct opt_opt_t *opt;

  opt = (struct opt_opt_t *)calloc(1, sizeof(struct opt_opt_t));
  if (!opt)
    fatal("out of virtual memory");

  opt->name = name;
  opt->desc = desc;
  opt->nvars = nvars;
  opt->nelt = nelt;
  opt->format = format ? format : "%s";
  opt->oc = oc_enum;
  opt->variant.for_enum.var = vars;
  opt->variant.for_enum.emap = emap;
  opt->variant.for_enum.eval = eval;
  opt->variant.for_enum.emap_sz = emap_sz;
  if (def_val)
    {
      if (!bind_to_enum(def_val, emap, eval, emap_sz, &enum_val))
	fatal("could not bind default value for option `%s'", name);
    }
  else
    enum_val = 0;
  opt->print = print;
  opt->accrue = accrue;

  /* place on ODB's option list */
  opt->next = NULL;
  add_option(odb, opt);

  /* set default value */
  for (i=0; i < *nelt; i++)
    vars[i] = enum_val;
}

/* pre-defined boolean flag operands */
#define NUM_FLAGS		28
static char *flag_emap[NUM_FLAGS] = {
  "true", "t", "T", "True", "TRUE", "1", "y", "Y", "yes", "Yes", "YES",
  "on", "On", "ON",
  "false", "f", "F", "False", "FALSE", "0", "n", "N", "no", "No", "NO",
  "off", "Off", "OFF"
};
static int flag_eval[NUM_FLAGS] = {
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

/* register a boolean flag option variable */
void
opt_reg_flag(struct opt_odb_t *odb,	/* option data base */
	     char *name,		/* option name */
	     char *desc,		/* option description */
	     int *var,			/* target variable */
	     int def_val,		/* default variable value */
	     int print,			/* print during `-dumpconfig'? */
	     char *format)		/* optional value print format */
{
  struct opt_opt_t *opt;

  opt = (struct opt_opt_t *)calloc(1, sizeof(struct opt_opt_t));
  if (!opt)
    fatal("out of virtual memory");

  opt->name = name;
  opt->desc = desc;
  opt->nvars = 1;
  opt->nelt = NULL;
  opt->format = format ? format : "%12s";
  opt->oc = oc_flag;
  opt->variant.for_enum.var = var;
  opt->variant.for_enum.emap = flag_emap;
  opt->variant.for_enum.eval = flag_eval;
  opt->variant.for_enum.emap_sz = NUM_FLAGS;
  opt->print = print;
  opt->accrue = FALSE;

  /* place on ODB's option list */
  opt->next = NULL;
  add_option(odb, opt);

  /* set default value */
  *var = def_val;
}

/* register a boolean flag option array */
void
opt_reg_flag_list(struct opt_odb_t *odb,/* option database */
		  char *name,		/* option name */
		  char *desc,		/* option description */
		  int *vars,		/* pointer to option array */
		  int nvars,		/* total entries in option array */
		  int *nelt,		/* number of elements parsed */
		  int *def_val,		/* default array value */
		  int print,		/* print during `-dumpconfig'? */
		  char *format,		/* optional value print format */
		  int accrue)		/* accrue list across uses */
{
  int i;
  struct opt_opt_t *opt;

  opt = (struct opt_opt_t *)calloc(1, sizeof(struct opt_opt_t));
  if (!opt)
    fatal("out of virtual memory");

  opt->name = name;
  opt->desc = desc;
  opt->nvars = nvars;
  opt->nelt = nelt;
  opt->format = format ? format : "%s";
  opt->oc = oc_flag;
  opt->variant.for_enum.var = vars;
  opt->variant.for_enum.emap = flag_emap;
  opt->variant.for_enum.eval = flag_eval;
  opt->variant.for_enum.emap_sz = NUM_FLAGS;
  opt->print = print;
  opt->accrue = accrue;

  /* place on ODB's option list */
  opt->next = NULL;
  add_option(odb, opt);

  /* set default value */
  for (i=0; i < *nelt; i++)
    vars[i] = def_val[i];
}

/* register a string option variable */
void
opt_reg_string(struct opt_odb_t *odb,	/* option data base */
	       char *name,		/* option name */
	       char *desc,		/* option description */
	       char **var,		/* pointer to string option variable */
	       char *def_val,		/* default variable value */
	       int print,		/* print during `-dumpconfig'? */
	       char *format)		/* optional value print format */
{
  struct opt_opt_t *opt;

  opt = (struct opt_opt_t *)calloc(1, sizeof(struct opt_opt_t));
  if (!opt)
    fatal("out of virtual memory");

  opt->name = name;
  opt->desc = desc;
  opt->nvars = 1;
  opt->nelt = NULL;
  opt->format = format ? format : "%12s";
  opt->oc = oc_string;
  opt->variant.for_string.var = var;
  opt->print = print;
  opt->accrue = FALSE;

  /* place on ODB's option list */
  opt->next = NULL;
  add_option(odb, opt);

  /* set default value */
  *var = def_val;
}

/* register a string option array */
void
opt_reg_string_list(struct opt_odb_t *odb,/* option data base */
		    char *name,		/* option name */
		    char *desc,		/* option description */
		    char **vars,	/* pointer to option string array */
		    int nvars,		/* target array size */
		    int *nelt,		/* number of args parsed goes here */
		    char **def_val,	/* default variable value */
		    int print,		/* print during `-dumpconfig'? */
		    char *format,	/* optional value print format */
		    int accrue)		/* accrue list across uses */
{
  int i;
  struct opt_opt_t *opt;

  opt = (struct opt_opt_t *)calloc(1, sizeof(struct opt_opt_t));
  if (!opt)
    fatal("out of virtual memory");

  opt->name = name;
  opt->desc = desc;
  opt->nvars = nvars;
  opt->nelt = nelt;
  opt->format = format ? format : "%s";
  opt->oc = oc_string;
  opt->variant.for_string.var = vars;
  opt->print = print;
  opt->accrue = accrue;

  /* place on ODB's option list */
  opt->next = NULL;
  add_option(odb, opt);

  /* set default value */
  for (i=0; i < *nelt; i++)
    vars[i] = def_val[i];
}

/* process command line arguments, returns index of next argument to parse */
int
process_option(struct opt_odb_t *odb,	/* option database */
	       int index,		/* index of the first arg to parse */
	       int argc,		/* total number of arguments */
	       char **argv)		/* argument string array */
{
  int cnt, ent, nvars;
  char *endp;
  double tmp;
  struct opt_opt_t *opt;

  /* locate the option in the option database */
  for (opt=odb->options; opt != NULL; opt=opt->next)
    {
      if (!strcmp(opt->name, argv[index]))
	break;
    }
  if (!opt)
    {
      /* no one registered this option */
      fatal("option `%s' is undefined", argv[index]);
    }
  index++;

  /* process option arguments */
  switch (opt->oc)
    {
    case oc_int:
      /* this option needs at least one argument */
      if (index >= argc
	  || (argv[index][0] == '-' && !isdigit((int)argv[index][1])))
	{
	  /* no arguments available */
	  fatal("option `%s' requires an argument", opt->name);
	}
      cnt = 0;
      if (opt->accrue)
	{
	  ent = opt->nelt ? *opt->nelt : 0;
	  nvars = 1;
	  if (ent >= opt->nvars)
	    fatal("too many invocations of option `%s'", opt->name);
	}
      else
	{
	  ent = 0;
	  if (opt->nelt)
	    *opt->nelt = 0;
	  nvars = opt->nvars;
	}
      /* parse all arguments */
      while (index < argc && cnt < nvars &&
	     (argv[index][0] != '-' || isdigit((int)argv[index][1])))
	{
	  opt->variant.for_int.var[ent] = strtol(argv[index], &endp, 0);
	  if (*endp)
	    {
	      /* could not parse entire argument */
	      fatal("could not parse argument `%s' of option `%s'",
		    argv[index], opt->name);
	    }
	  /* else, argument converted correctly */
	  if (opt->nelt)
	    (*opt->nelt)++;
	  cnt++; index++; ent++;
	}
      break;
    case oc_uint:
      /* this option needs at least one argument */
      if (index >= argc || argv[index][0] == '-')
	{
	  /* no arguments available */
	  fatal("option `%s' requires an argument", opt->name);
	}
      cnt = 0;
      if (opt->accrue)
	{
	  ent = opt->nelt ? *opt->nelt : 0;
	  nvars = 1;
	  if (ent >= opt->nvars)
	    fatal("too many invocations of option `%s'", opt->name);
	}
      else
	{
	  ent = 0;
	  if (opt->nelt)
	    *opt->nelt = 0;
	  nvars = opt->nvars;
	}
      /* parse all arguments */
      while (index < argc && cnt < nvars && argv[index][0] != '-')
	{
	  opt->variant.for_uint.var[ent] = strtoul(argv[index], &endp, 0);
	  if (*endp)
	    {
	      /* could not parse entire argument */
	      fatal("could not parse argument `%s' of option `%s'",
		    argv[index], opt->name);
	    }
	  /* else, argument converted correctly */
	  if (opt->nelt)
	    (*opt->nelt)++;
	  cnt++; index++; ent++;
	}
      break;
    case oc_float:
      /* this option needs at least one argument */
      if (index >= argc
	  || (argv[index][0] == '-' && !isdigit((int)argv[index][1])))
	{
	  /* no arguments available */
	  fatal("option `%s' requires an argument", opt->name);
	}
      cnt = 0;
      if (opt->accrue)
	{
	  ent = opt->nelt ? *opt->nelt : 0;
	  nvars = 1;
	  if (ent >= opt->nvars)
	    fatal("too many invocations of option `%s'", opt->name);
	}
      else
	{
	  ent = 0;
	  if (opt->nelt)
	    *opt->nelt = 0;
	  nvars = opt->nvars;
	}
      /* parse all arguments */
      while (index < argc && cnt < nvars &&
	     (argv[index][0] != '-' || isdigit((int)argv[index][1])))
	{
	  tmp = strtod(argv[index], &endp);
	  if (*endp)
	    {
	      /* could not parse entire argument */
	      fatal("could not parse argument `%s' of option `%s'",
		    argv[index], opt->name);
	    }
	  if (tmp < FLT_MIN || tmp > FLT_MAX)
	    {
	      /* over/underflow */
	      fatal("FP over/underflow for argument `%s' of option `%s'",
		    argv[index], opt->name);
	    }
	  /* else, argument converted correctly */
	  opt->variant.for_float.var[ent] = (float)tmp;
	  if (opt->nelt)
	    (*opt->nelt)++;
	  cnt++; index++; ent++;
	}
      break;
    case oc_double:
      /* this option needs at least one argument */
      if (index >= argc
	  || (argv[index][0] == '-' && !isdigit((int)argv[index][1])))
	{
	  /* no arguments available */
	  fatal("option `%s' requires an argument", opt->name);
	}
      cnt = 0;
      if (opt->accrue)
	{
	  ent = opt->nelt ? *opt->nelt : 0;
	  nvars = 1;
	  if (ent >= opt->nvars)
	    fatal("too many invocations of option `%s'", opt->name);
	}
      else
	{
	  ent = 0;
	  if (opt->nelt)
	    *opt->nelt = 0;
	  nvars = opt->nvars;
	}
      /* parse all arguments */
      while (index < argc && cnt < nvars &&
	     (argv[index][0] != '-' || isdigit((int)argv[index][1])))
	{
	  opt->variant.for_double.var[ent] = strtod(argv[index], &endp);
	  if (*endp)
	    {
	      /* could not parse entire argument */
	      fatal("could not parse argument `%s' of option `%s'",
		    argv[index], opt->name);
	    }
	  /* else, argument converted correctly */
	  if (opt->nelt)
	    (*opt->nelt)++;
	  cnt++; index++; ent++;
	}
      break;
    case oc_enum:
      /* this option needs at least one argument */
      if (index >= argc || argv[index][0] == '-')
	{
	  /* no arguments available */
	  fatal("option `%s' requires an argument", opt->name);
	}
      cnt = 0;
      if (opt->accrue)
	{
	  ent = opt->nelt ? *opt->nelt : 0;
	  nvars = 1;
	  if (ent >= opt->nvars)
	    fatal("too many invocations of option `%s'", opt->name);
	}
      else
	{
	  ent = 0;
	  if (opt->nelt)
	    *opt->nelt = 0;
	  nvars = opt->nvars;
	}
      /* parse all arguments */
      while (index < argc && cnt < nvars && argv[index][0] != '-')
	{
	  if (!bind_to_enum(argv[index],
			    opt->variant.for_enum.emap,
			    opt->variant.for_enum.eval,
			    opt->variant.for_enum.emap_sz,
			    &opt->variant.for_enum.var[ent]))
	    {
	      /* could not parse entire argument */
	      fatal("could not parse argument `%s' of option `%s'",
		    argv[index], opt->name);
	    }
	  /* else, argument converted correctly */
	  if (opt->nelt)
	    (*opt->nelt)++;
	  cnt++; index++; ent++;
	}
      break;
    case oc_flag:
      /* check if this option has at least one argument */
      if (index >= argc || argv[index][0] == '-')
	{
	  /* no arguments available, default value for flag is true */
	  opt->variant.for_enum.var[0] = TRUE;
	  break;
	}
      /* else, parse boolean argument(s) */
      cnt = 0;
      if (opt->accrue)
	{
	  ent = opt->nelt ? *opt->nelt : 0;
	  nvars = 1;
	  if (ent >= opt->nvars)
	    fatal("too many invocations of option `%s'", opt->name);
	}
      else
	{
	  ent = 0;
	  if (opt->nelt)
	    *opt->nelt = 0;
	  nvars = opt->nvars;
	}
      while (index < argc && cnt < nvars && argv[index][0] != '-')
	{
	  if (!bind_to_enum(argv[index],
			    opt->variant.for_enum.emap,
			    opt->variant.for_enum.eval,
			    opt->variant.for_enum.emap_sz,
			    &opt->variant.for_enum.var[ent]))
	    {
	      /* could not parse entire argument, default to true */
	      opt->variant.for_enum.var[ent] = TRUE;
	      break;
	    }
	  else
	    {
	      /* else, argument converted correctly */
	      if (opt->nelt)
		(*opt->nelt)++;
	      cnt++; index++; ent++;
	    }
	}
      break;
    case oc_string:
      /* this option needs at least one argument */
      if (index >= argc || argv[index][0] == '-')
	{
	  /* no arguments available */
	  fatal("option `%s' requires an argument", opt->name);
	}
      cnt = 0;
      if (opt->accrue)
	{
	  ent = opt->nelt ? *opt->nelt : 0;
	  nvars = 1;
	  if (ent >= opt->nvars)
	    fatal("too many invocations of option `%s'", opt->name);
	}
      else
	{
	  ent = 0;
	  if (opt->nelt)
	    *opt->nelt = 0;
	  nvars = opt->nvars;
	}
      /* parse all arguments */
      while (index < argc && cnt < nvars && argv[index][0] != '-')
	{
	  opt->variant.for_string.var[ent] = argv[index];
	  if (opt->nelt)
	    (*opt->nelt)++;
	  cnt++; index++; ent++;
	}
      break;
    default:
      panic("bogus option class");
    }

  return index;
}

/* forward declarations */
static void process_file(struct opt_odb_t *odb, char *fname, int depth);
static void dump_config(struct opt_odb_t *odb, char *fname);

/* process a command line, internal version that tracks `-config' depth */
static void
__opt_process_options(struct opt_odb_t *odb,	/* option data base */
		      int argc,			/* number of arguments */
		      char **argv,		/* argument array */
		      int depth)		/* `-config' option depth */
{
  int index, do_dumpconfig;
  char *dumpconfig_name;

  index = 0;
  do_dumpconfig = FALSE;
  dumpconfig_name = NULL;
  /* visit all command line arguments */
  while (index < argc)
    {
      /* process any encountered orphans */
      while (index < argc && argv[index][0] != '-')
	{
	  if (depth > 0)
	    {
	      /* orphans are not allowed during config file processing */
	      fatal("orphan `%s' encountered during config file processing",
		    argv[index]);
	    }
	  /* else, call the user-stalled orphan handler */
	  if (odb->orphan_fn)
	    {
	      if (!odb->orphan_fn(index+1, argc, argv))
		{
		  /* done processing command line */
		  goto done_processing;
		}
	    }
	  else
	    {
	      /* no one claimed this option */
	      fatal("orphan argument `%s' was unclaimed", argv[index]);
	    }
	  /* go to next option */
	}

      /* done with command line? */
      if (index == argc)
	{
	  /* done processing command line */
	  goto done_processing;
	}
      /* when finished, argv[index] is an option to parse */

      /* process builtin options */
      if (!strcmp(argv[index], "-config"))
	{
	  /* handle `-config' builtin option */
	  index++;
	  if (index >= argc || argv[index][0] == '-')
	    {
	      /* no arguments available */
	      fatal("option `-config' requires an argument");
	    }
	  process_file(odb, argv[index], depth);
	  index++;
	}
      else if (!strcmp(argv[index], "-dumpconfig"))
	{
	  /* this is performed *last* */
	  do_dumpconfig = TRUE;
	  /* handle `-dumpconfig' builtin option */
	  index++;
	  if (index >= argc
	      || (argv[index][0] == '-' && argv[index][1] != '\0'))
	    {
	      /* no arguments available */
	      fatal("option `-dumpconfig' requires an argument");
	    }
	  dumpconfig_name = argv[index];
	  index++;
	}
      else
	{
	  /* process user-installed option */
	  index = process_option(odb, index, argc, argv);
	}
    }
 done_processing:

  if (do_dumpconfig)
    dump_config(odb, dumpconfig_name);
}

/* process command line arguments */
void
opt_process_options(struct opt_odb_t *odb,	/* option data base */
		    int argc,			/* number of arguments */
		    char **argv)		/* argument array */
{
  /* need at least two entries to have an option */
  if (argc < 2)
    return;

  /* process the command, starting at `-config' depth zero */
  __opt_process_options(odb, argc-1, argv+1, /* top level */0);
}

/* handle `-config' builtin option */
#define MAX_LINE_ARGS		256
#define MAX_FILENAME_LEN	1024
static void
process_file(struct opt_odb_t *odb, char *fname, int depth)
{
  int largc;
  char *largv[MAX_LINE_ARGS];
  char line[1024], *p, *q;
  char cwd[MAX_FILENAME_LEN];
  char *header;
  FILE *fd;

  fd = fopen(fname, "r");
  if (!fd)
    fatal("could not open configuration file `%s'", fname);

  if (!getcwd(cwd, MAX_FILENAME_LEN))
    fatal("can't get cwd");

  header = strrchr(fname, '/');
  if (header != NULL)
    {
      /* filename is path - get header */
      *header = '\0';
      if (chdir(fname) == -1)
	fatal("can't chdir to %s\n", fname);
      *header = '/';
    }

  while (!feof(fd))
    {
#if 0
      fprintf(stderr, "!feof(fd): %d, strlen(line): %d, line: %s\n",
	      !feof(fd), strlen(line), line);
#endif

      line[0] = '\n';
      /* read a line from the file and chop */
      fgets(line, 1024, fd);
      if (line[0] == '\0' || line[0] == '\n')
	continue;
      if (line[strlen(line)-1] == '\n')
	line[strlen(line)-1] = '\0';

      /* process one line from the file */
      largc = 0; p = line;
      while (*p)
	{
	  /* skip whitespace */
	  while (*p != '\0' && (*p == ' ' || *p == '\t'))
	    p++;

	  /* ignore empty lines and comments */
	  if (*p == '\0' || *p == '#')
	    break;

	  /* skip to the end of the argument */
	  q = p;
	  while (*q != '\0' && *q != ' ' && *q != '\t')
	    q++;
	  if (*q)
	    {
	      *q = '\0';
	      q++;
	    }

	  /* marshall an option array */
	  largv[largc++] = mystrdup(p);

	  if (largc == MAX_LINE_ARGS)
	    {
	      if (chdir(cwd) == -1)
		fatal("can't chdir back to %s\n", cwd);
	      fatal("option line too complex in file `%s'", fname);
	    }

	  /* go to next argument */
	  p = q;
	}

      /* process the line */
      if (largc > 0)
	__opt_process_options(odb, largc, largv, depth+1);
      /* else, empty line */
    }

  fclose(fd);

  if (chdir(cwd) == -1)
    fatal("can't chdir back to %s\n", cwd);
}

/* print the value of an option */
void
opt_print_option(struct opt_opt_t *opt,/* option variable */
		 FILE *fd)		/* output stream */
{
  int i, nelt;

  switch (opt->oc)
    {
    case oc_int:
      if (opt->nelt)
	nelt = *(opt->nelt);
      else
	nelt = 1;
      for (i=0; i<nelt; i++)
	{
	  fprintf(fd, opt->format, opt->variant.for_int.var[i]);
	  fprintf(fd, " ");
	}
      break;
    case oc_uint:
      if (opt->nelt)
	nelt = *(opt->nelt);
      else
	nelt = 1;
      for (i=0; i<nelt; i++)
	{
	  fprintf(fd, opt->format, opt->variant.for_uint.var[i]);
	  fprintf(fd, " ");
	}
      break;
    case oc_float:
      if (opt->nelt)
	nelt = *(opt->nelt);
      else
	nelt = 1;
      for (i=0; i<nelt; i++)
	{
	  fprintf(fd, opt->format, (double)opt->variant.for_float.var[i]);
	  fprintf(fd, " ");
	}
      break;
    case oc_double:
      if (opt->nelt)
	nelt = *(opt->nelt);
      else
	nelt = 1;
      for (i=0; i<nelt; i++)
	{
	  fprintf(fd, opt->format, opt->variant.for_double.var[i]);
	  fprintf(fd, " ");
	}
      break;
    case oc_enum:
      if (opt->nelt)
	nelt = *(opt->nelt);
      else
	nelt = 1;
      for (i=0; i<nelt; i++)
	{
	  char *estr = bind_to_str(opt->variant.for_enum.var[i],
				   opt->variant.for_enum.emap,
				   opt->variant.for_enum.eval,
				   opt->variant.for_enum.emap_sz);
	  if (!estr)
	    panic("could not bind enum `%d' for option `%s'",
		  opt->variant.for_enum.var[i], opt->name);

	  fprintf(fd, opt->format, estr);
	  fprintf(fd, " ");
	}
      break;
    case oc_flag:
      if (opt->nelt)
	nelt = *(opt->nelt);
      else
	nelt = 1;
      for (i=0; i<nelt; i++)
	{
	  char *estr = bind_to_str(opt->variant.for_enum.var[i],
				   opt->variant.for_enum.emap,
				   opt->variant.for_enum.eval,
				   opt->variant.for_enum.emap_sz);
	  if (!estr)
	    panic("could not bind boolean `%d' for option `%s'",
		  opt->variant.for_enum.var[i], opt->name);
	  
	  fprintf(fd, opt->format, estr);
	  fprintf(fd, " ");
	}
      break;
    case oc_string:
      if (!opt->nvars)
	{
	  fprintf(fd, "%12s ", "<null>");
	  break;
	}
      if (opt->nelt)
	nelt = *(opt->nelt);
      else
	nelt = 1;
      if (nelt == 0)
	{
	  fprintf(fd, "%12s ", "<null>");
	  break;
	}
      else
	{
	  for (i=0; i<nelt; i++)
	    {
	      fprintf(fd, opt->format,
		      (opt->variant.for_string.var[i]
		       ? opt->variant.for_string.var[i]
		       : "<null>"));
	      fprintf(fd, " ");
	    }
	}
      break;
    default:
      panic("bogus option class");
    }
}

/* print any options header */
static void
print_option_header(struct opt_odb_t *odb,/* options database */
		    FILE *fd)		/* output stream */
{
  if (!odb->header)
    return;

  fprintf(fd, "\n%s\n", odb->header);
}

/* print option notes */
static void
print_option_notes(struct opt_odb_t *odb,/* options database */
		   FILE *fd)		/* output stream */
{
  struct opt_note_t *note;

  if (!odb->notes)
    return;

  fprintf(fd, "\n");
  for (note=odb->notes; note != NULL; note=note->next)
    fprintf(fd, "%s\n", note->note);
  fprintf(fd, "\n");
}

/* builtin options */
static struct opt_opt_t dumpconfig_opt =
  { NULL, "-dumpconfig", "dump configuration to a file",
    0, NULL, NULL, FALSE, FALSE, oc_string };
static struct opt_opt_t config_opt =
  { &dumpconfig_opt, "-config", "load configuration from a file",
    0, NULL, NULL, FALSE, FALSE, oc_string };
static struct opt_opt_t *builtin_options = &config_opt;

/* return non-zero if the option is a NULL-valued string option */
int					/* non-zero if null string option */
opt_null_string(struct opt_opt_t *opt)
{
  return (opt != NULL
	  && opt->oc == oc_string
	  && (opt->nvars == 0
	      || (opt->nelt != NULL && *opt->nelt == 0)
	      || (opt->nelt == NULL
		  && (opt->variant.for_string.var == NULL
		      || opt->variant.for_string.var[0] == NULL))));
}

/* print all options and current values */
void
opt_print_options(struct opt_odb_t *odb,/* option data base */
		  FILE *fd,		/* output stream */
		  int terse,		/* print terse options? */
		  int notes)		/* include notes? */
{
  struct opt_opt_t *opt;

  if (!odb)
    {
      /* no options */
      return;
    }

  /* print any options header */
  if (notes)
    print_option_header(odb, fd);

  /* dump out builtin options */
  for (opt=builtin_options; opt != NULL; opt=opt->next)
    {
      if (terse)
	fprintf(fd, "# %-27s # %s\n", opt->name, opt->desc);
      else
	{
	  fprintf(fd, "# %s\n", opt->desc);
	  fprintf(fd, "# %-22s\n\n", opt->name);
	}
    }

  /* dump out options from options database */
  for (opt=odb->options; opt != NULL; opt=opt->next)
    {
      if (terse)
	{
	  if (!opt->print || opt_null_string(opt))
	      fprintf(fd, "# %-14s ", opt->name);
	  else
	    fprintf(fd, "%-16s ", opt->name);
	  opt_print_option(opt, fd);
	  if (opt->desc)
	    fprintf(fd, "# %-22s", opt->desc);
	  fprintf(fd, "\n");
	}
      else
	{
	  if (opt->desc)
	    fprintf(fd, "# %s\n", opt->desc);
	  if (!opt->print || opt_null_string(opt))
	    fprintf(fd, "# %-20s ", opt->name);
	  else
	    fprintf(fd, "%-22s ", opt->name);
	  opt_print_option(opt, fd);
	  fprintf(fd, "\n\n");
	}
    }

  /* print option notes */
  if (notes)
    print_option_notes(odb, fd);
}

/* print help information for an option */
static void
print_help(struct opt_opt_t *opt,	/* option variable */
	   FILE *fd)			/* output stream */
{
  char *s = NULL;

  fprintf(fd, "%-16s ", opt->name);
  switch (opt->oc)
    {
    case oc_int:
      if (opt->nvars == 1)
	s = "<int>";
      else
	s = "<int list...>";
      break;
    case oc_uint:
      if (opt->nvars == 1)
	s = "<uint>";
      else
	s = "<uint list...>";
      break;
    case oc_float:
      if (opt->nvars == 1)
	s = "<float>";
      else
	s = "<float list...>";
      break;
    case oc_double:
      if (opt->nvars == 1)
	s = "<double>";
      else
	s = "<double list...>";
      break;
    case oc_enum:
      if (opt->nvars == 1)
	s = "<enum>";
      else
	s = "<enum list...>";
      break;
    case oc_flag:
      if (opt->nvars == 1)
	s = "<true|false>";
      else
	s = "<true|false list...>";
      break;
    case oc_string:
      if (opt->nvars == 0 || opt->nvars == 1)
	s = "<string>";
      else
	s = "<string list...>";
      break;
    default:
      panic("bogus option class");
    }
  fprintf(fd, "%-16s # ", s);
  opt_print_option(opt, fd);
  fprintf(fd, "# %-22s\n", opt->desc);
}

/* print option help page with default values */
void
opt_print_help(struct opt_odb_t *odb,	/* option data base */
	       FILE *fd)		/* output stream */
{
  struct opt_opt_t *opt;

  /* print any options header */
  print_option_header(odb, fd);

  fprintf(fd, "#\n");
  fprintf(fd, "%-16s %-16s # %12s # %-22s\n",
	  "# -option", "<args>", "<default>", "description");
  fprintf(fd, "#\n");

  /* print out help info for builtin options */
  for (opt=builtin_options; opt != NULL; opt=opt->next)
    print_help(opt, fd);

  /* print out help info for options in options database */
  for (opt=odb->options; opt != NULL; opt=opt->next)
    print_help(opt, fd);

  /* print option notes */
  print_option_notes(odb, fd);
}

/* handle `-dumpconfig' builtin option, print options from file argument */
static void
dump_config(struct opt_odb_t *odb,	/* option data base */
	    char *fname)		/* output file name, "-" == stdout */
{
  FILE *fd;

  /* open output file stream */
  if (!strcmp(fname, "-"))
    fd = stderr;
  else
    {
      fd = fopen(fname, "w");
      if (!fd)
	fatal("could not open file `%s'", fname);
    }

  /* print current option values to output stream */
  opt_print_options(odb, fd, /* long */FALSE, /* !notes */FALSE);

  /* close output stream, if not a standard stream */
  if (fd != stdout && fd != stderr)
    fclose(fd);
}

/* find an option by name in the option database, returns NULL if not found */
struct opt_opt_t *
opt_find_option(struct opt_odb_t *odb,	/* option database */
		char *opt_name)	/* option name */
{
  struct opt_opt_t *opt;

  /* search builtin options */
  for (opt = builtin_options; opt != NULL; opt = opt->next)
    {
      if (!strcmp(opt->name, opt_name))
	{
	  /* located option */
	  return opt;
	}
    }

  /* search user-installed options */
  for (opt = odb->options; opt != NULL; opt = opt->next)
    {
      if (!strcmp(opt->name, opt_name))
	{
	  /* located option */
	  return opt;
	}
    }
  /* not found */
  return NULL;
}

/* register an options header, the header is printed before the option list */
void
opt_reg_header(struct opt_odb_t *odb,	/* option database */
	       char *header)		/* options header string */
{
  odb->header = header;
}

/* register an option note, notes are printed after the list of options */
void
opt_reg_note(struct opt_odb_t *odb,	/* option database */
	     char *note_str)		/* option note */
{
  struct opt_note_t *note, *elt, *prev;

  note = (struct opt_note_t *)calloc(1, sizeof(struct opt_note_t));
  if (!note)
    fatal("out of virtual memory");

  /* record note */
  note->next = NULL;
  note->note = note_str;

  /* add to end of option notes list */
  for (prev=NULL, elt=odb->notes; elt != NULL; prev=elt, elt=elt->next)
    /* nada */;

  if (prev != NULL)
    prev->next = note;
  else /* prev == NULL */
    odb->notes = note;
  note->next = NULL;
}


#ifdef TEST

int
f_orphan_fn(int i, int argc, char **argv)
{
  fprintf(stdout, "orphans detected at index %d, arg = `%s'...\n", i, argv[i]);

  /* done processing */
  return FALSE;
}

#define MAX_VARS 4
void
main(int argc, char **argv)
{
  struct opt_odb_t *odb;

  int n_int_vars, n_uint_vars, n_float_vars, n_double_vars;
  int n_enum_vars, n_enum_eval_vars, n_flag_vars, n_string_vars;
  int int_var, int_vars[MAX_VARS];
  unsigned int uint_var, uint_vars[MAX_VARS];
  float float_var, float_vars[MAX_VARS];
  double double_var, double_vars[MAX_VARS];
  int flag_var, flag_vars[MAX_VARS];
  char *string_var, *string_vars[MAX_VARS];

  enum etest_t { enum_a, enum_b, enum_c, enum_d, enum_NUM };
  static char *enum_emap[enum_NUM] =
    { "enum_a", "enum_b", "enum_c", "enum_d" };
  static int enum_eval[enum_NUM] =
    { enum_d, enum_c, enum_b, enum_a };
  int enum_var, enum_vars[MAX_VARS];
  int enum_eval_var, enum_eval_vars[MAX_VARS];

  /* get an options processor */
  odb = opt_new(f_orphan_fn);


  opt_reg_int(odb, "-opt:int", "This is an integer option.",
	      &int_var, 1, /* print */TRUE, /* default format */NULL);
  opt_reg_int_list(odb, "-opt:int:list", "This is an integer list option.",
		   int_vars, MAX_VARS, &n_int_vars, 2,
		   /* print */TRUE, /* default format */NULL);
  opt_reg_uint(odb, "-opt:uint", "This is an unsigned integer option.",
	       &uint_var, 3, /* print */TRUE, /* default format */NULL);
  opt_reg_uint_list(odb, "-opt:uint:list",
		    "This is an unsigned integer list option.",
		    uint_vars, MAX_VARS, &n_uint_vars, 4,
		    /* print */TRUE, /* default format */NULL);
  opt_reg_float(odb, "-opt:float", "This is a float option.",
		&float_var, 5.0, /* print */TRUE, /* default format */NULL);
  opt_reg_float_list(odb, "-opt:float:list", "This is a float list option.",
		     float_vars, MAX_VARS, &n_float_vars, 6.0,
		     /* print */TRUE, /* default format */NULL);
  opt_reg_double(odb, "-opt:double", "This is a double option.",
		 &double_var, 7.0, /* print */TRUE, /* default format */NULL);
  opt_reg_double_list(odb, "-opt:double:list", "This is a double list option.",
		      double_vars, MAX_VARS, &n_double_vars, 8.0,
		      /* print */TRUE, /* default format */NULL);
  opt_reg_enum(odb, "-opt:enum", "This is an enumeration option.",
	       &enum_var, "enum_a", enum_emap, /* index map */NULL, enum_NUM,
	       /* print */TRUE, /* default format */NULL);
  opt_reg_enum_list(odb, "-opt:enum:list", "This is a enum list option.",
		    enum_vars, MAX_VARS, &n_enum_vars, "enum_b",
		    enum_emap, /* index map */NULL, enum_NUM,
		    /* print */TRUE, /* default format */NULL);
  opt_reg_enum(odb, "-opt:enum:eval", "This is an enumeration option w/eval.",
	       &enum_eval_var, "enum_b", enum_emap, enum_eval, enum_NUM,
	       /* print */TRUE, /* default format */NULL);
  opt_reg_enum_list(odb, "-opt:enum:eval:list",
		    "This is a enum list option w/eval.",
		    enum_eval_vars, MAX_VARS, &n_enum_eval_vars, "enum_a",
		    enum_emap, enum_eval, enum_NUM,
		    /* print */TRUE, /* default format */NULL);
  opt_reg_flag(odb, "-opt:flag", "This is a boolean flag option.",
	       &flag_var, FALSE, /* print */TRUE, /* default format */NULL);
  opt_reg_flag_list(odb, "-opt:flag:list",
		    "This is a boolean flag list option.",
		    flag_vars, MAX_VARS, &n_flag_vars, TRUE,
		    /* print */TRUE, /* default format */NULL);
  opt_reg_string(odb, "-opt:string", "This is a string option.",
		 &string_var, "a:string",
		 /* print */TRUE, /* default format */NULL);
  opt_reg_string_list(odb, "-opt:string:list", "This is a string list option.",
		    string_vars, MAX_VARS, &n_string_vars, "another:string",
		    /* print */TRUE, /* default format */NULL);

  /* parse options */
  opt_process_options(odb, argc, argv);

  /* print options */
  fprintf(stdout, "## Current Option Values ##\n");
  opt_print_options(odb, stdout, /* long */FALSE, /* notes */TRUE);

  /* all done */
  opt_delete(odb);
  exit(0);
}

#endif /* TEST */
