%{
/*
/****************************************************************** 
 *
 * nlvky.y
 *
 *     Yacc grammar for the nlv-keys.cfg file.
 *
 *
 * Author : Dan Gunter
 * Created: September 1998
 *
 * Modification history
 * --------------------
 *
 ****************************************************************** 
 */

/*========= C DECLARATIONS ============================================*/

#include <stdio.h>     /* hello, cruel world */
#include <ctype.h>     /* isalpha, et. al. */
#include <keysclass.h> /* keyset and key manipulation fn's */


#ifndef nlvkylval
#define nlvkylval yylval
#endif

struct ltype;
struct ltype {
   char *data;
   struct ltype *next;
};

/* A buffer for groupaliases */
char aliasbuf[1024];

/* Formula functions */
void write_formula( int ksidx, char* n, char* f );

/* List functions and shortcuts */
void *list_func( char *str, int f );
char *list_to_string( struct ltype *top );
void list_to_array( struct ltype *top, char ***strlist, int *n );
#define add_to_list( str ) do { list_func((str),1); } while(0)
#define clear_list() do { list_func(NULL,0); } while(0)
#define get_list() (list_func(NULL,2))

/*
 * "Object" of type keysclass
 */
extern Nlv_State nlvState;

/*
 * Fill in a keyset structure as we go along 
 */
void keyset_start(void);
void keyset_name(char *);
void keyset_gtype(int);
void keyset_ids( int n, char **strlist );
void keyset_hosts( int n, char **strlist );
void keyset_val_id( char *s, int min, double max );
void keyset_val_di( char *s, double min, int max );
void keyset_val_ii( char *s, int min, int max );
void keyset_val( char *, double min, double max );
void keyset_annotations( int n, char **strlist );
void keyset_timeorder( int n );
void keyset_rotateclients( int n );
void keyset_autoscale( int n );
void keyset_finish(void);
/* 
 * 'wks' is the global variable used in the above routines 
 */
struct keyset_values {
  char          *name;
  int            gtype;
  int            index;
  int            numids;
  char         **ids;
  int            numhosts;
  char         **hosts;
  int            numann;
  char         **ann;
  char          *valField;
  double         vMin, 
                 vMax;
  Nlv_CalcParam *ksCalcParams;
  int            ksCalcParamNum;
  char         **keynames;
  int            keynum;
  int            timeordered;
  int            rotateclients;
  int            autoscale;
} wks;

/*
 * Title of the graph
 * (not keyset-specific)
 */
char *graph_title = NULL;

/*
 * Global cleanup function.
 */
void config_finish( void );

/* 
 * Functions for context-specific token verification.
 * These all return 1 for YES and 0 for NO.
 */
int isvalid_keyset( char *s );
int isvalid_key( char *s );
int isvalid_formname( char *s );
int isvalid_listkey ( char *s );

#define STREQ(a,b) (strcmp(a,b)==0)

%}

/*========= YACC TOKENS ===============================================*/

%union {
    int    ival;
    double dval;
    char  *strval;
}
%token <strval> TITLE
%token <strval> NAME
%token <strval> QUOTED_STRING
%token <strval> FORMULA
%token <ival>   HOSTALIAS
%token <ival>   EVENTSET
%token <ival>   EVENTS
%token <ival>   TYPE
%token <ival>   KSTYPE
%token <ival>   ID
%token <ival>   HOSTID
%token <ival>   CALC
%token <ival>   ANNOTATE
%token <ival>   MMCHOICE
%token <strval> TIMEORD
%token <strval> ROTATECL
%token <strval> AUTOSCALE
%token <strval> CACTION
%token <ival>   BOOL
%token <ival>   INT
%token <ival>   LIST_START
%token <ival>   LIST_END
%token <ival>   VAL
%token <dval>   FLOAT

/* Analysis keywords (take no action) */

%token <ival> ANALYSIS
%token <ival> EPSILON
%token <ival> KEYS
%token <ival> BRANGE
%token <ival> RLIMIT
%token <ival> EOFEXIT
%token <ival> FIRSTEVENT

%%

/*========= YACC GRAMMAR ============================================*/

configfile: init header body finish
          ;

init: /* empty */ { wks.index = -1; }
    ;

finish: /* empty */ { config_finish(); }
    ;

header:
      | hostalias header
      | TITLE NAME { if ( graph_title ) free(graph_title); 
                     graph_title = strdup( $2 );
                   }
      | TITLE QUOTED_STRING { if (graph_title) free(graph_title);
                     $2[strlen($2)-1] = '\0';
                     graph_title = strdup( ++$2 );
                   } 
      | ANALYSIS EOFEXIT BOOL header  { ; }
      ;

hostalias: HOSTALIAS aliastarget namelist {
  struct ltype *t;
  int    n;
  char **strlist;

  /* get a pointer to top of llist */
  t = get_list();
  /* make our list into an array of strings */
  list_to_array( t, &strlist, &n );
  /* add this to the hostalias record in the keysclass "object" */
  Nlv_kyAddHostalias( nlvState.ky, n, strlist, aliasbuf );
}
         ;

aliastarget: NAME { strcpy(aliasbuf,$1); }
          | INT { sprintf(aliasbuf,"%d",$1); }
          | FLOAT { sprintf(aliasbuf,"%f",$1); }
          ;

namelist: NAME          { clear_list(); 
                          add_to_list( $1 ); 
                         }
        | INT { char *str = (char *)malloc(128); 
	        clear_list();
		sprintf( str, "%d", $1 );
	        add_to_list( str );
	      }
        | FLOAT { char *str = (char *)malloc(128); 
	        clear_list();
		sprintf( str, "%lf", $1 );
	        add_to_list( str );
	      }
        | list_start namevalues LIST_END 
        ;

keylist: NAME { 
                clear_list();
                if ( isvalid_key( $1 ) )
                  add_to_list( $1 );
                else
                  nlvkyerror("Invalid key");
	        list_to_array( get_list(), &wks.keynames, &wks.keynum );
               }
         | list_start keyvalues LIST_END { 
                list_to_array( get_list(), &wks.keynames, &wks.keynum );
               }
	 ;

list_start: LIST_START { clear_list(); }
          ;

namevalues: NAME          { add_to_list( $1 ); }
        |   INT { char *str = (char *)malloc(128); 
		  sprintf( str, "%d", $1 );
	          add_to_list( str );
	         }
        | FLOAT { char *str = (char *)malloc(128); 
	        clear_list();
		sprintf( str, "%lf", $1 );
	        add_to_list( str );
	      }
       |  namevalues NAME { add_to_list( $2 ); }
       |  namevalues INT { char *str = (char *)malloc(128); 
		           sprintf( str, "%d", $2 );
	                   add_to_list( str );
	                 }
        | namevalues FLOAT { char *str = (char *)malloc(128); 
	        clear_list();
		sprintf( str, "%lf", $2 );
	        add_to_list( str );
	      }
       ;

keyvalues: NAME          { if ( isvalid_key( $1 ) )
                             add_to_list( $1 );
                           else
                             nlvkyerror("Invalid key"); 
                          }
         |  keyvalues NAME  { if ( isvalid_key( $2 ))
                              add_to_list( $2 );
                            else
                              nlvkyerror("Invalid key");
                          }
       ;

body:
    | body keyset
    ;

keyset: set_name LIST_START set_options LIST_END { keyset_finish(); }
      ;

set_name: EVENTSET NAME {
  if (isvalid_keyset($2)) {
    keyset_start();
    keyset_name( $2 );
  }
  else
    nlvkyerror("Invalid keyset name");
}
          ;

set_options:
       | set_options LIST_START set_option LIST_END
       ;

set_option:
       EVENTS keylist { ; }
      | TYPE KSTYPE { keyset_gtype( $2 ); }
      | TIMEORD BOOL { keyset_timeorder( $2 ); }
      | ROTATECL BOOL { keyset_rotateclients( $2 ); }
      | AUTOSCALE MMCHOICE { keyset_autoscale( $2 ); }
      | ANNOTATE namelist {
	               struct ltype *t;
		       /* make sure all values are at least legal keys */
                       for ( t = get_list(); t; t = t->next )
			 if ( !isvalid_listkey(t->data) )
			   nlvkyerror("*Invalid ANNOTATION key");
		       /* put linked list values into keyset structure */
		       list_to_array( get_list(), &(wks.ann), &(wks.numann) );
		       keyset_annotations( wks.numann, wks.ann );
                     }
       | ID namelist {
	               struct ltype *t;
		       /* make sure all values are at least legal keys */
                       for ( t = get_list(); t; t = t->next )
			 if ( !isvalid_listkey(t->data) )
			   nlvkyerror("*Invalid ID key");
		       /* put linked list values into keyset structure */
		       list_to_array( get_list(), &(wks.ids), &(wks.numids) );
		       keyset_ids( wks.numids, wks.ids );
                     }
       | HOSTID namelist {
	                 struct ltype *t;
			 /* make sure all values are at least legal keys */
                         for ( t = get_list(); t; t = t->next )
			   if ( !isvalid_listkey(t->data) )
			     nlvkyerror("*Invalid GROUP key");
			 /* put linked list values into keyset structure */
		         list_to_array( get_list(), &(wks.hosts), &(wks.numhosts) );
		         keyset_hosts( wks.numhosts, wks.hosts );
                         }
       | VAL NAME FLOAT FLOAT { if ( isvalid_listkey( $2 ))
                                   keyset_val( $2, $3, $4 );
                                 else
                                   nlvkyerror("Invalid key for 'val'");
                              }
       | VAL NAME FLOAT INT { if ( isvalid_listkey( $2 ))
                                   keyset_val_di( $2, $3, $4 );
                                 else
                                   nlvkyerror("Invalid key for 'val'");
                              }
       | VAL NAME INT FLOAT{ if ( isvalid_listkey( $2 ))
                                   keyset_val_id( $2, $3, $4 );
                                 else
                                   nlvkyerror("Invalid key for 'val'");
                              }
       | VAL NAME INT INT { if ( isvalid_listkey( $2 ))
                                   keyset_val_ii( $2, $3, $4 );
                                 else
                                   nlvkyerror("Invalid key for 'val'");
                              }
       | ANALYSIS EPSILON FLOAT { ; }
       | ANALYSIS KEYS namelist { ; }
       | ANALYSIS BRANGE namelist { ; }
       | ANALYSIS RLIMIT NAME NAME FLOAT FLOAT { ; }
       | ANALYSIS RLIMIT NAME NAME NAME NAME { ; }
       | ANALYSIS FIRSTEVENT NAME { ; }
       ;

%%


/*========= C IMPLEMENTATIONS =========================================*/

/*
 *----------------------------------------------------------------------
 *
 * write_formula -- Write the user-defined formula to a temporary file.
 *
 * Calls:
 *
 * Algorithm:
 *
 *----------------------------------------------------------------------
 */
void write_formula( int ksidx, char* n, char* f )
{
  printf("keyset=%d name=%s formula=%s\n", ksidx, n, f );
  free(n);
  free(f);
}

/*
 *----------------------------------------------------------------------
 *
 * list_func -- Clear, add to, and give access to a linked list.
 *
 * Calls: malloc, free, strcpy
 *
 * Algorithm: The top of the list is held in a static variable,
 *            and the appropriate list operation is chosen by
 *            an integer argument.
 *
 *----------------------------------------------------------------------
 */


void *list_func( char *str, int f )
{
   static struct ltype *top;
   struct ltype *p, *ins, *newnode;

   switch( f ) {
     /* clear */
   case 0:
     for ( p=top; top; p=top ) {
       top = top->next;
       if ( p->data ) free( p->data );
       free( p );
     }
     top = NULL;
     return NULL;
     /* add */
   case 1:
     if ( !str )
       fprintf(stderr,"Warning: Empty hostalias list in config file\n");
     else {
       for ( ins=NULL, p=top; p; ins=p, p=p->next );
       newnode = (struct ltype *)malloc( sizeof(struct ltype));
       newnode->data = (char *)malloc((strlen(str)+1)*sizeof(char));
       strcpy( newnode->data, str );
       newnode->next = NULL;
       if ( ins )
	 ins->next = newnode;
       else
	 top = newnode;
     }
     return NULL;
     /* get */
   case 2:
     return top;
   }
}

/*
 *----------------------------------------------------------------------
 *
 * list_to_string -- Transform a linked list of strings to a string.
 *
 * Calls: realloc, calloc, strlen, strcat
 *
 * Algorithm: Effortlessly realloced.
 *
 *----------------------------------------------------------------------
 */

char *list_to_string( struct ltype *top )
{
  char *r;
  struct ltype *p;

  for ( r=NULL, p=top; p; p = p->next ) {
    if ( p->data ) {
      if ( r )
	r = (char *)realloc( r, (strlen(p->data)+1) * sizeof(char) );
      else
	r = (char *)calloc( strlen(p->data)+1, sizeof(char) );
      strcat( r, p->data );
    }
  }
  return r;
}

/*
 *----------------------------------------------------------------------
 *
 * list_to_array -- Transform linked list to array.
 *                  Array strings are a COPY of list strings.
 *
 * Calls: calloc, strdup
 *
 * Algorithm: find list length
 *            allocate that many slots in array
 *            traverse the list and COPY strings
 *
 *----------------------------------------------------------------------
 */

void list_to_array( struct ltype *top, char ***strlist, int *n )
{
  struct ltype *p;
  int len;

  /* calculate list length */
  for ( len=0, p=top; p; p=p->next, len++ );
  /* allocate space for each character pointer */
  *strlist = ( char ** )malloc( len * sizeof(char *));
  /* copy strings from linked list to array */
  for ( p=top, *n=0; p; p=p->next, (*n)++ )
    (*strlist)[*n] = strdup( p->data );
}

/*
 *----------------------------------------------------------------------
 *
 * isvalid_keyset -- Check if token is valid as a keyset name.
 *
 * Calls:
 *
 * Algorithm: String must be non-empty and at least of length 2
 *            1st character must be a '+' or '-'
 *            2nd character must be alphabetic
 *            subsequent characters must be alphanumeric or '_'
 *
 *----------------------------------------------------------------------
 */
int isvalid_keyset ( char *s )
{
  int valid = 1; /* assume valid */

  /* non-empty of length at least 2 */
  if ( !s || strlen(s) < 2 )
    valid = 0;
  else {
    /* first character must be '+' or '-' */
    if ( *s != '+' && *s != '-' )
      valid = 0;
    else {
      s++;
       /* second character must be alpha */
      if ( !isalpha( *s ) )
	valid = 0;
      else {
	s++;
	/* subsequent characters must be alphanumeric or '_' */
	for ( ; valid && *s; s++ )
	  if ( *s != '_' && !isalnum(*s) )
	    valid = 0;
      }
    }
  }
  return valid;
}

/*
 *----------------------------------------------------------------------
 *
 * isvalid_listkey --
 *
 * Calls:
 *
 * Algorithm: String must be non-empty and at least of length 1
 *            All characters must be alphanumeric or '_' or '.'
 *
 *----------------------------------------------------------------------
 */
int isvalid_listkey ( char *s )
{
  int valid = 1; /* assume valid */

  /* non-empty of length at least 1 */
  if ( !s || strlen(s) < 1 )
    valid = 0;
  else {
    /* subsequent characters must be alphanumeric or '_' */
    for ( ; valid && *s; s++ )
      if ( *s != '_' && *s != '.' && !isalnum(*s) )
	valid = 0;
  }
  return valid;
}

/*
 *----------------------------------------------------------------------
 *
 * isvalid_key -- 
 *
 * Calls:
 *
 * Algorithm:
 *
 *----------------------------------------------------------------------
 */
int isvalid_key( char *s )
{
  if ( isvalid_keyset( s ) ) {
    /* check for duplicate keys */
    struct ltype *p;
    char buf[ 128 ];
    for ( p=get_list(); p ; p=p->next ) {
      if ( strcmp( (s+1), (p->data+1)) == 0 ) {
	sprintf(buf,"*Duplicate key '%s'",(s+1));
	nlvkyerror(buf);
      }
    }
    return 1;
  }
  return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * isvalid_formname -- Check if token is valid as a formula name.
 *
 * Calls:
 *
 * Algorithm: of length at least 1
 *            first character must be alpha
 *            subsequent characters may be alpha or '_'
 *
 *----------------------------------------------------------------------
 */
int isvalid_formname( char *s )
{
  int valid = 1; /* assume valid */

  if ( !s || !(*s) )
    valid = 0;
  else {
    if ( !isalpha(*s) )
      valid = 0;
    else {
      for ( ; valid && *s; s++ )
	if ( !isalnum(*s) )
	  valid = 0;
    }
  }

  return valid;
}

/*
 *----------------------------------------------------------------------
 *
 * keyset_start      |
 * keyset_finish     |
 * keyset_name       |
 * keyset_gtype      |
 * keyset_ids        |
 * keyset_hosts      +-- setters for keyset values
 * keyset_val        |
 * keyset_autoscale  |
 * keyset_timeorder  |
 *
 * Calls:
 *
 * Algorithm: Use global variable 'wks' (working keyset).
 *
 *----------------------------------------------------------------------
 */
void keyset_start(void)
{
  int i;

  /* increment keyset index and clear struct */
  i = wks.index;
  memset( &wks, 0, sizeof(wks) );
  wks.index  = i + 1;

  /* reset key number to 0 */
  wks.keynum = 0;

  /* Put invalid value in timeordered */
  wks.timeordered = -1;
  /* Put invalid value in rotateclients */
  wks.rotateclients = -1;
  /* Put invalid value in autoscale */
  wks.autoscale = -2;
}

void keyset_name( char *s )
{
  wks.name = s;
}
void keyset_gtype( int t )
{
  wks.gtype = t;
}
void keyset_ids( int n, char **strlist )
{
  wks.numids = n;
  wks.ids = strlist;
}
void keyset_hosts( int n, char **strlist )
{
  wks.numhosts = n;
  wks.hosts    = strlist;
}
void keyset_annotations( int n, char **strlist )
{
  wks.numann = n;
  wks.ann = strlist;
}
void keyset_timeorder( int n )
{
   wks.timeordered = n;
}
void keyset_rotateclients( int n )
{
   wks.rotateclients = n;
}
void keyset_autoscale( int n )
{
  wks.autoscale = n;
}

void keyset_val_id( char *s, int min, double max ) 
{ 
  keyset_val( s, (double) min, max ); 
}

void keyset_val_di( char *s, double min, int max )
{ 
  keyset_val( s, min, (double)max );
}

void keyset_val_ii( char *s, int min, int max )
{ 
  keyset_val( s, (double)min, (double)max ); 
} 

void keyset_val( char *s, double min, double max )
{
  wks.valField = s;
  wks.vMin = min;
  wks.vMax = max;
}

void keyset_finish(void)
{
  Nlv_KeySet *ksPtr;
  int i;

  /* default fields */
  if ( wks.numids < 1 ) {
    wks.numids = 1;
    wks.ids = (char **)malloc(sizeof(char*));
    *wks.ids   = strdup("HOST");
  }
  if ( wks.numhosts < 1 ) {
    wks.numhosts = 1;
    wks.hosts = (char **)malloc(sizeof(char*));
    *wks.hosts   = strdup("HOST");
  }
  if ( wks.numann < 1 ) {
    wks.numann = 3;
    wks.ann = (char **)malloc(3*sizeof(char *));
    wks.ann[0] = strdup("TIME");
    wks.ann[1] = strdup("HOST");
    wks.ann[2] = strdup("PROG");
  }
  if ( wks.timeordered < 0 ) {
    wks.timeordered = 0;
  }
  if ( wks.rotateclients < 0 ) {
    wks.rotateclients = 0;
  }
  if ( wks.autoscale == -2 ) {
    wks.autoscale = 0;
  }

  /***
   * For debugging..
   * dumpkeys();
   **/

  ksPtr = Nlv_kyAddKeySet( nlvState.ky,
			   wks.name,
			   wks.gtype,
			   wks.index,
			   wks.numids,
			   wks.ids,
			   wks.numhosts,
			   wks.hosts,
			   wks.numann,
			   wks.ann,
			   wks.valField,
			   wks.vMin,
			   wks.vMax,
			   wks.ksCalcParams,
			   wks.ksCalcParamNum,
			   wks.timeordered,
			   wks.rotateclients,
			   wks.autoscale );
  if ( ksPtr == NULL )
    nlvkyerror("Unable to add keyset");
  for ( i=0; i < wks.keynum; i++ ) {
    Nlv_kyAddKey( nlvState.ky, ksPtr, wks.keynames[i] );
  }
}

/*
 * Do any cleanup/finalizations when file parsing
 * is done.
 */
void config_finish()
{
  Nlv_kySetTitle( nlvState.ky, graph_title );
}

dumpkeys() 
{
  int i;
  /* some debugging */
  fprintf(stderr,"(debug) keyset '%s':\n", wks.name);
  fprintf(stderr,"\tevents={");
  for ( i=0; i < wks.keynum; i++ ) fprintf(stderr,"%s ",wks.keynames[i]);
  fprintf(stderr,"}\n\tids={");
  for(i=0;i<wks.numids;i++) fprintf(stderr,"%s ", wks.ids[i]);
  fprintf(stderr,"}\n\tgroups={");
  for(i=0;i<wks.numhosts;i++) fprintf(stderr,"%s ", wks.hosts[i]);
  fprintf(stderr,"}\n");
}
