/* PROGRAM generate_pdt

Stephen O. Lidie, November 1990.
Lehigh University Computing Center
Bethlehem, PA  18015
(215)758-3982
Stephen.O.Lidie@CDC1.CC.Lehigh.EDU
lusol@Lehigh.EDU

Copyright (C) 1990 - 1994 by Stephen O. Lidie and Lehigh University.
			 All rights reserved.

Command Source:  /usr/local/bin/generate_pdt

Message Module Name:  genpdt.mm

generate_pdt, genpdt

	Interprets a PDT declaration and generates C statements that
	declare and initialize a PDT.  The command line	parser routine
        evaluate_parameters uses the PDT to scan a command line for
        parameters and to display help information about the command.

          Examples:

            genpdt -i pdt_in -o pdt_out

            genpdt <pdt_in >pdt_out

	In the last example note that since the genpdt input and
	output files default to stdin and stdout, respectively,
	normal I/O redirection can be used.

Parameters:

-help, ?, usage_help, full_help: Display Command Information

	Display information about this command, which includes
	a command description with examples, plus a synopsis of
	the command line parameters.  If you specify -full_help
	rather than -help complete parameter help is displayed
	if it's available.

-input, i: file = stdin

	Specifies the name of the input file containing a
	command's Parameter Description Table.  The PDT describes
	the names, aliases, types and default values of all the
	command's command line parameters, as well as the path
	name of the ar message module archive file containing
	help text for the command and its parameters.  

-output, o: file = stdout

	Specifies the name of the generate_pdt output file.  This
	file contains C language statements that are typically
	included in the source of your C application.  You then
	call the procedure evaluate_parameters, passing the PDT
	and the user's command line string; the command line is
	parsed and the results of the evaluation stored in the
	PDT.

-qualifier, q: string = ""

	Specifies a qualifying string that uniquely identifies
	this PDT.  By default the C statements created by
	generate_pdt define the symbols pdt and pvt to be used
	by your application, which point to the results of the
	evaluation of the user's command line input.  Some C
	applications support 'multiple entry points'; that is,
	the same C module acts as more than one user program.
	And typically these 'multiple programs' accept different
	command line arguments.  So, there is a requirement that
	multiple instances of the pdt/pvt symbols be defined.

	Another reason to qualify the pdt/pvt symbols is when
	embedding evaluate_parameters into an application.  Each
	application command processor requires its own instance
	of these variables to keep parameter names and values
	distinct.

	The value of the qualifier parameter is thus prepended
	to the names pdt/pvt, as well as the `P_' ordinals that
	index into the pvt.  Therefore if -quailier = "OQ_" the
	symbols OQ_pdt and OQ_pvt would be defined.



generate_pdt interprets a Parameter Description Table (PDT) declaration and
generates C statements that declare and initialize a PDT.  The PDT is used
by evaluate_parameters to parse a U*X command line in a consistent manner,
similar to NOS/VE's System Command Language.

A help message module name can also be specified in the PDT declaration.
The -help switch causes evaluate_parameters to search the message module 
archive file libevapmm.a for the specified help text.

Complete documentation can be found in the header file evap.h.

generate_pdt emulates only the smallest portion of SCL's capabilities:

  . Type checking only for switch, string, real, integer, boolean, file,
    key, application and name.
  . No records.
  . No ANY OF.
  . No parameter prompting.
  . No parameter attributes such as BY_NAME and SECURE (although you can
    fake the HIDDEN attribute; see Customization of the PDT Structure in
    evap.h)
  . Etcetera; a poor substitute for SCL.


    Sample Parameter Description Table (PDT):

    PDT (./libevapmm.a/sample.mm) sample
      verbose, v: switch
      command, c: string = D_SAMPLE_COMMAND, "ps -el"
      scale_factor, sf: real = 1.2340896e-1
      millisecond_update_interval, mui: integer = $required
      ignore_output_file_column_one, iofco: boolean = TRUE
      output, o: file = stdout
      queue, q: key plotter, postscript, text, printer, keyend = printer
      destination, d: application = `hostname`
      tty, t: list of name = ("/dev/console", "/dev/tty0", "/dev/tty1")
    PDTEND optional_file_list


    Sample generate_pdt call:

      generate_pdt -input my_pdt_in -output my_pdt_out
          or
      generate_pdt -i my_pdt_in -o my_pdt_out
          or
      generate_pdt <my_pdt_in >my_pdt_out


Stephen.O.Lidie@CDC1.CC.Lehigh.EDU, 90/12/28.
  . Original release.

Stephen.O.Lidie@CDC1.CC.Lehigh.EDU, 91/06/24.
  . Ignore junk lines after the PDTEND statement.
  . Do not define P_HELP in the PDT, since evap.h already defines it as zero.
  . Add type name.
  . Add support for display_command_information (-help) message modules.
  . Add support for PDTEND syntax for file_list flag.
  . Add qualifier parameter so that the PDT/PVT names can be qualified
    for 'multiple entry point' programs.  Similar to COMPASS QUAL blocks.

Stephen.O.Lidie@CDC1.CC.Lehigh.EDU, 91/07/31.
  . Supply the colon in the disci description instead of having evap do it.

Stephen.O.Lidie@CDC1.CC.Lehigh.EDU, 91/10/03.
  . For PDT version 1.2, expand the PDT syntax for a parameter's default
    value to include an environment variable.

Stephen.O.Lidie@CDC1.CC.Lehigh.EDU, 91/12/03.
  . Fix missing <cr> on PDTEND fails to generate a proper PDT bug.

lusol@Lehigh.EDU, 93/01/02
  . Stupid fix - use '\0' instead of NULL!

lusol@Lehigh.EDU, 93/02/28
  . Ignore tabs when spanning whitespace.
  . Fix segmentation fault when PDT entry has a missing : before type.
  . Print an error summary to stderr if any errors were detected.
  . Diagnose duplicate parameters/aliases.

lusol@Lehigh.EDU, 93/05/18
  . Expose -full_help for evaluate_parameters.

lusol@Lehigh.EDU, 93/08/20
  . Convert to ANSI C.
  . Expose -usage_help for evaluate_parameters.
  . Implement 'list of' parameters (PDT Version 2.0).
  . PDT lines beginning with # are comments.

lusol@Lehigh.EDU, 94/03/29
  . Replace help alias `disci' with `?'.
  . Fix segmentation error on a blank line.

*/

#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "./evap.h"
#include "../pdt/genpdt_pdt_out"

static char *rcsid = {"@@(#)$Id: genpdt.c,v 2.1.0.2 1994/05/13 19:03:27 lusol Exp $"};

/*
  generate_pdt global definitions.
*/

#define MAXIMUM_LINE_LENGTH 1024

void   copy(FILE *in,
	    FILE *out);
void   finish();
void   generate_pdt();
char   *get_token(char **token_pointer);
void   initialize();
int    main(int argc,
	    char *argv[]);
void   parse_line(char line[]);
char   *translate(char *how,
		  char *string);
int    trim(char *s);
int    wrt(FILE *fd,
	   char *line);

FILE   *d;			/* output #define file */
FILE   *h;			/* output header file */
FILE   *i;			/* input file */
FILE   *lf;			/* list file */
FILE   *o;			/* output file */
FILE   *p;			/* PDT header structure file */
FILE   *s;			/* output structure file */

char   *ifn;			/* input file name */
char   *ofn;			/* output file name */

char   *all_a[P_MAX_PARAMETER_HELP]; /* list of all alias names */
char   *all_p[P_MAX_PARAMETER_HELP]; /* list of all parameter names */
int    all_a_index = 0;
int    all_p_index = 0;
int    error_count = 0;
char   file_list[30];		/* file_list flag */
char   line[MAXIMUM_LINE_LENGTH]; /* a text line image */
char   *pdt_version = P_PDT_VERSION; /* provide some compatability! */
char   *translate();
char   *valid_types_string[P_MAXIMUM_TYPES] = {
       "P_TYPE_SWITCH",
       "P_TYPE_STRING",
       "P_TYPE_REAL",
       "P_TYPE_INTEGER",
       "P_TYPE_BOOLEAN",
       "P_TYPE_FILE",
       "P_TYPE_KEY",
       "P_TYPE_APPLICATION",
       "P_TYPE_NAME"
  };

short   end_of_pdt = FALSE;	/* TRUE when PDTEND detected */




void   initialize()
{
  if (pvt[P_input].specified) {
    ifn = pvt[P_input].value.file_value;
    if ((i = fopen (ifn, "r")) == NULL) {
      perror (ifn);
      exit (1);
    }
  } else
    i = stdin;

  if( pvt[P_output].specified) {
    ofn = pvt[P_output].value.file_value;
    if ((o = fopen (ofn, "w")) == NULL) {
      perror (ofn);
      exit (1);
    }
  } else
    o = stdout;

  if ((d = tmpfile()) == NULL) {
    printf("Problems opening temporary file 'd'.\n");
    exit (1);
  }

  if ((h = tmpfile()) == NULL) {
    printf("Problems opening temporary file 'h'.\n");
    exit (1);
  }

  if ((lf = tmpfile()) == NULL) {
    printf("Problems opening temporary file 'lf'.\n");
    exit (1);
  }

  if ((p = tmpfile()) == NULL) {
    printf("Problems opening temporary file 'p'.\n");
    exit (1);
  }

  if ((s = tmpfile()) == NULL) {
    printf("Problems opening temporary file 's'.\n");
    exit (1);
  }

  return;
}




void   generate_pdt ()
{

  int    l, m;

  fputs ("/*\n", h);

  for (fgets (line, MAXIMUM_LINE_LENGTH, i); ! feof (i);
        fgets (line, MAXIMUM_LINE_LENGTH, i) ) {
    if ( line[0] == '#' ) 
      continue;			/* ignore comment lines */
    l = strlen(line);
    if (line[l-1] == '\n')
      line[l-1] = '\0';
    parse_line (line);
    if (end_of_pdt)
      break; /* for, ignore trailing junk such as empty lines */
  } /* forend */

  if (! end_of_pdt) {		/* ensure PDT structure is completed */
    strcpy(line, "PDTEND optional_file_list");
    parse_line(line);
  }

  /* Ensure there are no duplicate parameter or alias names. */

  for ( m = 0; m < all_p_index; m++ ) {
    for ( l = 0; l < all_p_index; l++ ) {
      if ( m == l ) { continue; }
      if ( strcmp( all_p[m], all_p[l] ) == 0 ) {
	fprintf( h, "*** ERROR ***, duplicate parameter: %s.\n", all_p[m] );
	error_count++;
      }
    }
  }

  for ( m = 0; m < all_a_index; m++ ) {
    for ( l = 0; l < all_a_index; l++ ) {
      if ( m == l ) { continue; }
      if ( strcmp( all_a[m], all_a[l] ) == 0 ) {
	fprintf( h, "*** ERROR ***, duplicate alias: %s.\n", all_a[m] );
	error_count++;
      }
    }
  }

  fputs ("*/\n", h);
  rewind (h);
  copy (h, o);
  rewind (p);
  copy (p, o);
  fprintf (o, "  \"%s\"\n", file_list);
  fputs ("};\n", o);
  rewind( lf );
  copy( lf, o );
  rewind (s);
  copy (s, o);
  rewind (d);
  copy (d, o);
  return;
}




void   finish ()
{
  fclose (d);
  fclose (h);
  fclose (i);
  fclose (lf);
  fclose (o);
  fclose (p);
  fclose (s);
  if ( error_count != 0 )
    fprintf( stderr, "Error count from this generate_pdt run was %d!\n",
	    error_count );
  return;
}




void   parse_line (char line[])
/*
  Parse one line from the PDT input file, and generate a portion of
  the PDT structure.

  Yes, yes, yes, this is REALLY JUNK code.... it should be state driven and
  all that.  It was also some of my very first C, and it shows!
*/
{
  char   alias[MAXIMUM_LINE_LENGTH];
  char   default_variable[MAXIMUM_LINE_LENGTH]; /* environment variable */
  char   description[MAXIMUM_LINE_LENGTH];
  int    description_cursor;
  char   *description_pointer;
  char   *get_token();
  int    key_index = 0;		/* index into key_list */
  int    key_index2;
  char   *key_list[P_MAX_VALID_VALUES]; /* list of pointers to keywords */
  char   message_module_name[256]; /* message module name */
  static int parameter_ordinal = 1;
  char   *token;
  char   *token_pointer;
  char   type[MAXIMUM_LINE_LENGTH];
  int    type_ordinal;
  char   *type_string;
  char   value[MAXIMUM_LINE_LENGTH];
  short  list;
  char   list_name[256];
  char   *have_list_to_parse;

  token_pointer = line;
  token = get_token (&token_pointer);

  fputs (line, h);		/* put header line */
  fputs ("\n", h);

  if ((strcmp (token, "PDT") == 0) ||
        (strcmp (token, "pdt") == 0)) {

    strcpy(file_list, "optional_file_list");

    *message_module_name = '\0'; /* assume no message module specified */
    token=get_token(&token_pointer);
    if ( (token != NULL) && (*token == '(') ) { /* possible message module */
      strncpy( message_module_name, get_token( &token_pointer ), 255 );
    }

    fputs ("\n", p);
    fputs ("/*\n", p);
    fputs ("Define P_EVAP_EXTERN in separately compiled modules!\n", p);
    fputs ("This will declare pvt as an external so you can reference\n", p);
    fputs ("parameter values.  No storage is allocated in this case.\n", p);
    fputs ("*/\n", p);
    fputs ("#ifndef P_EVAP_EXTERN\n", p);
    fputs ("\n", p);
    fprintf (p, "struct pdt_header %spdt = {\n",
          pvt[P_qualifier].value.string_value);
    fprintf (p, "  \"PDT Version %s for U*X\",\n",
          pdt_version);
    if (strcmp(message_module_name, "") == 0) /* if no message module */
      fputs ("  NULL,\n", p);
    else
      fprintf (p, "  \"%s\",\n", message_module_name);
    /* PDT header structure started, begin the PVT structure now. */
    fputs ("\n", s);
    fprintf (s, "evap_Parameter_Value %spvt[] = {\n",
          pvt[P_qualifier].value.string_value);
    fputs ("\n", s);
    fputs ("  {\"help\", \"?\",\n", s);
    fputs ("   FALSE, TRUE, P_TYPE_SWITCH,\n", s);
    fputs ("   NULL,\n", s);
    fputs ("   \"FALSE\",\n", s);
    fputs ("   \", usage_help, full_help: Display Command Information\",\n",
	   s);
    fputs ("   0,\n", s);
    fputs ("   NULL,\n", s);
    fputs ("   NULL},\n", s);
    return;
  }

  if ((strcmp (token, "PDTEND") == 0) ||
        (strcmp (token, "pdtend") == 0)) {
    fputs ("\n", s);
    fputs ("  {NULL}  /* end of parameters */\n", s);
    fputs ("\n", s);
    fputs ("};\n", s);
    fputs ("#else\n", s);
    fprintf (s, "extern evap_Parameter_Value %spvt[];\n",
          pvt[P_qualifier].value.string_value);
    fputs ("#endif\n\n", s);
    fprintf (d, "#define %sP_NUMBER_OF_PARAMETERS %d\n",
          pvt[P_qualifier].value.string_value, parameter_ordinal);
    end_of_pdt = TRUE;

    /*
      Complete PDT header structure now. 
    */

    token = get_token(&token_pointer);
    if ((token_pointer != NULL) && (strcmp(token, "") != 0)) {
      if ((strcmp(token, "no_file_list") != 0) &&
            (strcmp(token, "required_file_list") != 0) &&
            (strcmp(token, "optional_file_list") != 0)) {
        fprintf (h, "*** ERROR ***, invalid file_list string.\n");
	error_count++;
      }
      else
        strcpy(file_list, token);
    }      
    return;
  }

  /*
    Handle the tokens for a parameter definition line.
  */

  if ( *token == '\0' ) { return; } /* if empty line */
  fputs ("\n", s);
  fprintf (s, "  {\"%s\", \"", token);  /* put parameter */
  fprintf (d, "#define %sP_%s %d\n", pvt[P_qualifier].value.string_value,
        token, parameter_ordinal++);
  if ( all_p_index < P_MAX_PARAMETER_HELP ) {
    all_p[all_p_index++] = strcpy( (char *)malloc( strlen( token ) ),
				  token );
  }
  strcpy( list_name, token );	/* save for possible list definition */
  *alias = '\0';
  default_variable[0] = '\0';	/* no default variable */
  description[0] = '\0';
  *type = '\0';
  *value = '\0';
  list = 0;
  have_list_to_parse = NULL;

  for (token=get_token(&token_pointer); *token != '\0';
        token=get_token(&token_pointer)) {

    if ( *token == ',') {
      strcpy (alias, get_token(&token_pointer));
      if ( all_a_index < P_MAX_PARAMETER_HELP ) {
	all_a[all_a_index++] = strcpy( (char *)malloc( strlen( token ) ),
				      alias );
      }
    } else if ( *token == ':') {

      /*
        Escape special characters in the description, then get type.
      */

      for (description_pointer = token_pointer, description_cursor = 0;
            *description_pointer != '\0';
            description_pointer++, description_cursor++) {
        if (*description_pointer == '"')
          description[description_cursor++] = '\\';
        description[description_cursor] = *description_pointer;
      } /* forend */
      description[description_cursor] = '\0';
      strcpy (type, get_token(&token_pointer));

      if ( strcmp( type, "list" ) == 0 ) {
	strcpy (type, get_token(&token_pointer));
	if ( strcmp( type, "of" ) == 0 ) {
	  strcpy (type, get_token(&token_pointer)); /* get real type */
	  list = 1;		/* set 'list of' flag */
	}
      }

      key_index = 0;
      if (strcmp(type, evap_Type_Strings[P_TYPE_KEY]) == 0) { /* keywords */
        key_list[key_index++] = token_pointer; /* pointer to first key */
        for (token=get_token(&token_pointer);
              (*token != '\0') && (strcmp(token, "keyend") != 0);
              token=get_token(&token_pointer)) {
          token = get_token(&token_pointer); /* get comma */
          if (strcmp(token, ",") != 0) {
            fprintf (h, "*** ERROR ***, expecting comma, found %s\n", token);
	    error_count++;
	  }
          if (key_index >= P_MAX_VALID_VALUES) {
            fprintf (h, "*** ERROR ***, keyword %d ignored\n", ++key_index);
	    error_count++;
	  }
          else
            key_list[key_index++] = token_pointer; /* next key pointer */
        } /* forend */
        if (key_index >= P_MAX_VALID_VALUES)
          key_index = P_MAX_VALID_VALUES - 1;
        else
          key_index--;		/* don't count keyend */
      } /* ifend */

    } /* ifend */
    else if ( *token == '=') {
      /* We can have:
	   scalar_value
	   (list values)
	   default_variable, scalar_value
	   default_variable, (list values)
      */
      strcpy(default_variable, get_token(&token_pointer));
      if ( *token == '(' ) {
	have_list_to_parse = token_pointer;
	default_variable[0] = '\0';
      } else {
	token = get_token(&token_pointer); /* look for comma */
	if ( *token == '\0' ) {	/* no default variable */
	  strcpy(value, default_variable);
	  default_variable[0] = '\0';
	} else {		/* assume comma found */
	  strcpy(value, get_token(&token_pointer));
	  if ( *token == '(' ) {
	    have_list_to_parse = token_pointer;
	  }
	} /* ifend default variable */
      } /* ifend list */
      if ( have_list_to_parse != NULL ) {
	*value = '\0';
	break; /* for */
      }
    } /* ifend token is a = */
  } /* forend */

  /*
    Complete the PDT structure now.  Member specified is initialized FALSE.
    Member changeable is initialize TRUE, parameter is changeable and
    advertised, FALSE means the opposite, -1 for changeable and unadvertised.
    Member type is this parameter's type.  Member unconverted_value is the
    value if specified or NULL.  Member description is given it's default
    value, and member valid_values is initialized with keyword values or an
    empty list.  Union member value is initialized by evaluate_parameters at
    runtime.
  
    For PDT version 1.2, add the default_variable member.

    For PDT version 2.0, add the members list and list_state.
  */

  fprintf (s, "%s\",\n", alias); /* put alias */

  for (type_ordinal = 0, type_string = NULL; type_ordinal < P_MAXIMUM_TYPES;
        type_ordinal++) {
    if (strcmp(type, evap_Type_Strings[type_ordinal]) == 0) {
      type_string = valid_types_string[type_ordinal];
      break; /* for */
    }
  } /* forend */
  if (type_string == NULL) {
    fprintf (h, "*** ERROR ***, unknown type %s\n", type);
    error_count++;
    type_string = type;
  }
  fprintf (s, "   FALSE, TRUE, %s,\n", type_string);

  if (default_variable[0] == '\0')
    fprintf (s, "   NULL,\n");
  else
    fprintf (s, "   \"%s\",\n", default_variable);

  if (strcmp(type, evap_Type_Strings[P_TYPE_SWITCH]) == 0)
    strcpy(value, "FALSE");
  fprintf (s, "   \"%s\",\n", value); /* put value */

  fprintf (s, "   \":%s\",\n", description); /* put description */

  fprintf( s, "   %d,\n", list); /* list_state (0 = not 'list of') */
  if ( list == 0 ) {
    fprintf( s, "   NULL,\n");
  } else {
    fprintf( s, "   P_LIST_%s,\n", list_name);
    fprintf( lf, "\nevap_List_Value P_LIST_%s[] = {\n", list_name );
    if ( have_list_to_parse != NULL ) {
      token_pointer = have_list_to_parse;
      for (token=get_token(&token_pointer); *token != '\0';
	   token=get_token(&token_pointer)) {
	if ( *token == ',' )
	  continue;
	fprintf( lf, "  {\"%s\"},\n", token);
      } /* forend list tokens */
    } /* ifend list to parse */
    fprintf( lf, "  NULL,\n" );
    fprintf( lf, "};\n" );
  }

  for(key_index--, key_index2=0; key_index2 <= key_index;
        fprintf (s, "   \"%s\",\n", get_token(&key_list[key_index2++])));
    ;
  fprintf (s, "   NULL},\n");	/* put list of valid values */

  return;
}





void   copy (FILE *in,
	     FILE *out)
/*
  Append one file to another.
*/
{
  for (fgets (line, MAXIMUM_LINE_LENGTH, in); ! feof (in);
        fgets (line, MAXIMUM_LINE_LENGTH, in) ) {
    fputs (line, out);
  }
}





char   *get_token (char **token_pointer)
/*
  Get one token from an input line.  A token here is defined as the special
  characters ",:=(", a string of characters bounded by double quotes, or
  a 'word' separated by white space.
*/
{
  char   *store_pointer;
  static char token[MAXIMUM_LINE_LENGTH];

  for (*token = '\0', store_pointer = token; (**token_pointer != '\0') &&
       ((**token_pointer == ' ') || (**token_pointer == '\t'));
       (*token_pointer)++);
    ; /* forend */

  if ( (**token_pointer == ',') || (**token_pointer == ':') ||
      (**token_pointer == '=') || (**token_pointer == '(') ) {
    *store_pointer++ = **token_pointer;
    (*token_pointer)++;
    *store_pointer = '\0';
    return token;
  }

  /*
    Special case a string token - return everything except the quotes.
  */

  if (**token_pointer == '\"') {
    for ((*token_pointer)++; (**token_pointer != '\0') &&
	 (**token_pointer != '\"');
	 *store_pointer++ = **token_pointer, (*token_pointer)++)
      ; /* forend */
    *store_pointer = '\0';
    (*token_pointer)++; /* skip trailing quote */
    return token;
  }

  for (; (**token_pointer != '\0') && ((**token_pointer != ' ') &&
				       (**token_pointer != ',') &&
				       (**token_pointer != ':') &&
				       (**token_pointer != '=') &&
				       (**token_pointer != ')') &&
				       (**token_pointer != '\t'));
       *store_pointer++ = **token_pointer, (*token_pointer)++)
    ; /* forend */

  *store_pointer = '\0';
  return token;
}




char   *translate (char *how,
		   char *string)
/*
  Translate string from upper to lower ("utl") or lower to upper ("ltu").
*/
{
  int    i;
  static char new_string[MAXIMUM_LINE_LENGTH];

  if (strcmp (how, "ltu") == 0)
    for (i = 0; *(string + i) != '\0'; i++)
      if (islower(*(string+i)))
        *(new_string+i) = toupper(*(string+i));
      else
        *(new_string+i) = *(string+i);
  else if (strcmp (how, "utl") == 0)
    for (i = 0; *(string + i) != '\0'; i++)
      if (islower(*(string+i)))
        *(new_string+i) = tolower(*(string+i));
      else
        *(new_string+i) = *(string+i);
  else
    return NULL;
  *(new_string+i) = '\0';
  return new_string;
}




int    trim (char *s)
/*
  Remove trailing spaces, tabs and newlines.
*/
{
  int    n;

  for (n = strlen(s)-1; n >= 0; n--)
    if (s[n] != ' ' && s[n] != '\t' && s[n] != '\n')
      break;
  s[n+1] = '\0';
  return n;
}




int    wrt (FILE *fd,
	    char *line)
/*
  Write a line after trimming.
*/
{
  int l;  /* length of line after trimming */

  l = trim (line);
  strcat (line, "\n");
  fputs (line, fd);
  return l+1+1;
}




int   main(int argc,
	   char *argv[])
{

  evap(&argc, &argv, pdt, NULL, pvt);
  initialize();
  generate_pdt();
  finish();
  return(0);

} /* end generate_pdt */
