/*


 Copyright (C) 1990 Texas Instruments Incorporated.

 Permission is granted to any individual or institution to use, copy, modify,
 and distribute this software, provided that this complete copyright and
 permission notice is maintained, intact, in all copies and supporting
 documentation.

 Texas Instruments Incorporated provides this software "as is" without
 express or implied warranty.


 *
 * Edit history
 * Created: LGO 30-Mar-89 -- Initial design and implementation.
 *
 * The DEFPACKAGE macro for Symbolic C++.
 *
 * Syntax:
 * 	DEFPACKAGE NAME <PATH> [OPTIONS] 
 *
 * Where NAME is a symbol prefix, PATH is the name of an include file where
 * definitions are kept, and OPTIONS are optional, seprated by comma's and
 * one of the following:
 * 
 * count=identifier
 * 	This says that the include file should include a #define identifier
 * 	whose expansion is the number of symbols defined.
 * 
 * use_first=int
 *      When non-zero, the symbol value used is the first definition.
 *      Redefinition attempts are ignored. (this is used by the ONCE-ONLY
 *      macro)
 *
 * noblank=int
 *      When non-zero, remove all whitespace from symbol names
 *
 * case=upper
 * 	This says to upcase all symbol names.
 * case=lower
 * 	This says to downcase all symbol names.
 * case=cap
 * 	This says to capitalize the first letter of each symbol name, and
 * 	downcase the rest.
 * case=sensitive
 * 	This says to leave the case alone.  This is the default.
 * 
 * start=int
 * 	This says to start counting from int (default is zero).
 * increment=int
 * 	This says to increment counting by int (default is one).
 * template=int
 * 	This value is inclusive-or'ed with every symbol value.
 * max=int
 * 	This says to generate an error when the number of symbol exceeds int.
 *
 * Symbols are inserted in a package with the following macro:
 * 
 * 	DEFPACKAGE_SYMBOL(package, symbol, type, value, property, expander)
 *
 * where:
 *   package 	is the name of a package (the DEFPACKAGE must preceed)
 *   symbol 	is the name of the symbol
 *   type	[optional] is the type of the value
 *   value	[optional] is the value of the symbol or property
 *   property	[optional] is the name of the property (empty means use the
 *		value slot) 
 *   expander=identifier
 * 		This says to replace the DEFPACKAGE_SYMBOL invocation
 *		with the result of calling the "identifier" macro.
 *
 * The expander macro is called with the following arguments: 
 *
 *      expander(index, symbol, type, value)
 *
 * where:
 *   expander   is the expander macro specified in the DEFPACKAGE_SYMBOL
 *   index	is the symbol's index number
 *   symbol 	is the name of the symbol
 *   type	is the type of the value [may be empty]
 *   value	is the value of the symbol or property [may be empty]
 *
 * DEFPACKAGE_SYMBOL causes three macro definitions to be written to the
 * package's definition file:
 *
 *   pkgname_DEFINITIONS - expands into macro calls to define the symbols
 *   pkgname_VALUES	 - expands into macro calls to set symbol values
 *   pkgname_PROPERTIES	 - expands into macro calls to set symbol properties
 *
 * where pkgname is the name of the package.  Each of these macros takes a
 * single parameter which is the name of the function or macro to call for each
 * symbol.
 *
 * Examples:
 *   #define package##_DEFINITIONS(define_macro) \
 * 	define_macro(index, name)
 *
 *   #define package##_VALUES(value_macro) \
 *      value_macro(index, name, type, value)
 *
 *   #define package##_PROPERTIES(property_macro) \
 *	property_macro(index, name, property, type, value)
 *
 * where:
 *  index	is the symbol index.
 *  name	is the symbol name.
 *  type	is the type of the value
 *  value	is the value of the symbol
 *  property	is a pointer to the properties symbol
 *
 * See package.h for examples.
 *
 * In order to define sym_names, the sym_db  file must be  included in a single
 * .C file.  If  the makefile  has a  dependency between the   .C  file and the
 * sym_db file, then the  .C file will be  re-compiled  whenever someone adds a
 * new  symbol.   The .C  file which   includes  the sym_db  file can add other
 * support for symbols,  such as  allocating static vectors indexed by symbols,
 * defining hash tables, variable-length vectors and intern functions.
 */  

/*
 * This file exports the following functions:
 * PACKAGE*
 * package_define(char* name, char* filename, char* count,
 *                char* scase, 
 *                long start, long incr, long template, 
 *                long max, long use_first);
 *
 * int
 * define_symbol(argc, argv);
 *
 * void
 * symbol_define(PACKAGE* pkg, char* name, char* type, 
 *               char* value, char* property);
 *
 * void
 * finish_symbols();
 */
#include "defmacio.h"

typedef enum {c_sensitive, c_upper, c_lower, c_cap} CASE;

typedef struct package {
  struct package* next;		/* Next package in package_list */
  char* name;			/* Package name */
  char* filename;		/* Package definition file name */
  FILE* file;			/* Package definition file */
  struct Hash_Table* symbols;	/* Symbol table */
  Boolean is_changed;		/* TRUE when file must be written */
  long index;			/* Largest index in package */
  long count;			/* Number of symbols in the package */
  char* count_name;		/* Name of #define to hold symbol count */
  CASE scase;			/* one of upper, lower, cap, sensitive */
  char* case_name;		/* string version of scase */
  long start;			/* Starting symbol index */
  long incr;			/* symbol index increment */
  long template;		/* symbol index template */
  long max;			/* symbol index maximum */
  Boolean use_first;		/* TRUE to ignore symbol value redefinitions */
  Boolean nospace;		/* TRUE to remove whitespace from symbol names */
  char* header;			/* Header at start of definition file */
} PACKAGE;

static PACKAGE* package_list = NULL;

typedef struct property {
  struct property* next;	/* Pointer to next value, or NULL */
  char* property;		/* Property name string */
  char* value;			/* Value string */
  char* type;			/* Type of value */
} PROPERTY;

typedef struct symbol {
  char* name;			/* Symbol name */
  long index;			/* Symbol index */
  char* type;			/* Symbol type */
  char* value;			/* Symbol value */
  PROPERTY* props;		/* pointer to properties or NULL */
} SYMBOL;

static SYMBOL*
add_symbol(pkg, name, index)
  PACKAGE* pkg;
  char* name;
  long index;
{
  SYMBOL* symbol = (SYMBOL*) getmem(sizeof(SYMBOL));
  symbol->name = name;
  symbol->index = index;
  symbol->type = "";
  symbol->value = "";
  symbol->props = NULL;
  put_hash(pkg->symbols, name, symbol);
  if(index > pkg->index) pkg->index = index;
  pkg->count++;
  return symbol;
}

static long next_index(pkg)
 PACKAGE* pkg;
{
  long template = pkg->template;
  long result = ((pkg->index & ~template) + pkg->incr);
  if (pkg->max > 0 && result > pkg->max)
    cerror ("DEFPACKAGE-SYMBOL: too many symbols in the %s package", pkg->name);
  return result | template;
}

/*
 * Given a package object and the symbol name,
 * intern the symbol in the package, and insert the
 * symbol replacement into the input stream.
 */
SYMBOL*
symbol_define(pkg, name, type, value, property)
  PACKAGE* pkg;
  char* name;
  char* type;
  char* value;
  char* property;
{
  SYMBOL* symbol;
  if (pkg->nospace)			  /* Trim whitespace */
    name = c_trim_all(name, " \t\n");
					  /* Set symbol name case */
  switch (pkg->scase) {
  case c_upper: c_upcase(name);
  case c_lower: c_downcase(name);
  case c_cap: c_capitalize(name);
  }					  /* Lookup symbol */
  symbol = (SYMBOL*) get_hash(pkg->symbols, name);
  if (symbol == NULL) {			  /* Create new symbols */
    symbol = add_symbol(pkg, name, pkg->index);
    pkg->index = next_index(pkg);
    pkg->is_changed = TRUE;
  }
  if (*value != EOS) {
    if (*property == EOS) {		  /* Set value */
      if (!pkg->use_first || *symbol->value == EOS) {
	if (!pkg->is_changed &&
	    (strcmp(symbol->type, type) != 0 ||
	     strcmp(symbol->value, value) != 0))
	  pkg->is_changed = TRUE;
	symbol->type = type;
	symbol->value = value;
      }
    } else {				  /* Set property */
      PROPERTY* prop = symbol->props;
      for (; prop != NULL; prop = prop->next)
	if (!strcmp(prop->property, property)) break;
      if(prop == NULL) {
	prop = (PROPERTY*) getmem(sizeof(PROPERTY));
	prop->property = property;
	prop->next = symbol->props;
	symbol->props = prop;
	pkg->is_changed = TRUE;
      }
      if (!pkg->is_changed &&
	  (strcmp(prop->type, type) != 0 ||
	   strcmp(prop->value, value) != 0))
	pkg->is_changed = TRUE;
      prop->type = type;
      prop->value = value;
    }
  }
  return symbol;
}

/* Read in the file which describes the symbols in package */
/* The package file starts with the following:             */

static char* header =
"/*\n\
 * DEFPACKAGE %-20s definitions file.\n\
 *\n\
 * This file is automatically generated by the cpp DEFPACKAGE facility\n\
 * DO NOT EDIT THIS FILE, because it may be re-writen the next time CPP\n\
 * is run.\n\
 *\n\
 * This file is for:\n\
 * DEFPACKAGE %s";

static char* header_warning = "\n/* WARNING: Do not remove this line */\n";

static Boolean
token_scan(pkg, search, end)
  PACKAGE* pkg;
  char* search;
  char end;
{
  FILE* stream = pkg->file;
  int len = strlen(search);
  int i;
  for(i=0; i<len;) {
    char c = fgetc(stream);
    append_char(work_string, c);
    if(c == search[i]) { i++; continue;}
    if(c == EOF) return FALSE;
    if(c == end) return FALSE;
    if (i>0) {
      i=0;
      if(c == search[i]) { i++; continue;}
    }
  }
  *(work_string->buffp) = EOS;
  return TRUE;
}

/*
 * HACK ALERT
 *   Read a line from stream, and put it in the input buffer used by getchar
 *   This lets read_package use the scan_next utility in defmacio.c
 */
static void
read_inline(stream)
  FILE* stream;
{
#define INBUFLEN 200
  static char instring[INBUFLEN];
  fgets(instring, INBUFLEN, stream);
  set_inbuffer(instring);
}

static Boolean
read_package (pkg)
  PACKAGE* pkg;
{
  FILE* stream = pkg->file;
  char token[400];
  char error[100];
  char* symname;
  long symindex=-1;
					  /* Check for empty file */
  char c = fgetc(stream);
  if (c == EOF) pkg->is_changed = TRUE;
  else {
					  /* Scan the header */
    scan_start();
    append_char(work_string, c);    
    if(!token_scan(pkg, header_warning, EOF)) {
      cerror ("defpackage: Can't find \"Do not remove\" comment in the %s file",
	      pkg->filename);
      return FALSE;
    }			  /* Everything up to header_warning is header*/
    pkg->header = savestring(work_string->buff);
    /*
     * MACRO package##_DEFINITIONS(define_macro, value_macro, property_macro) {
     * 	define_macro  (index, name)
     * 	value_macro   (index, type, value)
     * 	property_macro(index, property, type, value)
     * }
     */
    if(!token_scan(pkg, "_DEFINITIONS", EOF)) {
      cerror ("defpackage: Can't find pkg_DEFINITIONS in the %s file",
	      pkg->filename);
      return FALSE;
    }
    token_scan(pkg, "\n", EOF);
    for(;;) {
      enum {Tdefine, Tvalue, Tproperty} type;
      char* type_name;
      char* value_name;
      char* property_name;
      long index;

      read_inline(stream);
      c = skip_blanks();		  /* Quit on close brace '}' */
      if (c == '}') break;
      unget();
      copytoken(token);
      if (strcmp(token, "define_macro") == 0) type = Tdefine;
      else if (strcmp(token, "value_macro") == 0) type = Tvalue;
      else if (strcmp(token, "property_macro") == 0) type = Tproperty;
      else {
	sprintf (error, "defpackage: unknown macro %s while reading %s",
		 token, pkg->filename);
	cerror(error, "");
	return FALSE;
      }
      if((c = skip_blanks()) != '(') {
	sprintf(error, "defpackage: Found '%c' instead of '(' after %s in %s",
		c, token, pkg->filename);
	cerror(error, "");
	return FALSE;
      }
      copytoken(error);
      index = strtol(error, &value_name, 10);
      if (*value_name != '\0') {
	sprintf (error, "defpackage: bad symbol number in %s while reading %s",
		 token, pkg->filename);
	cerror(error, "");
	return FALSE;
      }
      skip_blanks();			  /* Skip comma */
      property_name = "";
      switch (type) {
      case Tdefine:
	symname = scan_token(",)");
	symindex = index;
	add_symbol(pkg, symname, index);
	break;
      case Tproperty:
	property_name = scan_token(",)"); /* Get property */
	/* fall through */
      case Tvalue:
	getchar();			  /* Skip comma */
	type_name = scan_token(",)");	  /* Get type */
	getchar();			  /* Skip comma */
	value_name = scan_token(",)");	  /* Get value */
	if (index != symindex) {
	  sprintf(error, "defpackage: macros out of order reading %s(%d...) in %s",
		 token, index, pkg->filename);
	  cerror(error, "");
	  return FALSE;
	}
	symbol_define(pkg, symname, type_name, value_name, property_name);
	break;
      }
      if((c = skip_blanks()) != ')') {
	sprintf(error, "defpackage: '%c' instead of ')' while reading %s(%d...) in %s",
	       c, token, index, pkg->filename);
	cwarn(error, "");
      }
    }
  }
  return TRUE;
}

static void write_package (pkg)
  PACKAGE* pkg;
{
  SYMBOL* symbol;
  char* name = pkg->name;
  FILE* stream = pkg->file;		  /* Header */
  SYMBOL** symtab = (SYMBOL**) getmem(sizeof(SYMBOL*)*pkg->count);
  int i;
  if (pkg->header != NULL)		  /* If a header read in */
    fprintf(stream, pkg->header);	  /* use it */
  else {				  /* else use the default header */
    fprintf (stream, header, name, name);
					  /* defpackage options */
    fprintf (stream, " <%s> name=%s,\n",
	     pkg->name, pkg->filename, pkg->count_name);
    fprintf (stream, " *\t count=%s, case=%s,\n",
	     pkg->count_name, pkg->case_name);
    fprintf (stream, " *\t start=%d, increment=%d, template=%d, max=%d\n",
	     pkg->start, pkg->incr, pkg->template, pkg->max);
    fprintf (stream, " */\n\n");
    fprintf (stream, "%s", header_warning);
  }
					  /* COUNT #define */
  if(*pkg->count_name)
    fprintf (stream, "#define %s %d\n\n", pkg->count_name, pkg->count);
  /*
   * MACRO package##_DEFINITIONS(define_macro, value_macro, property_macro) {
   * 	define_macro  (index, name)
   * 	value_macro   (index, type, value)
   * 	property_macro(index, property, type, value)
   * }
   */
  fprintf (stream, "MACRO %s_DEFINITIONS", name);
  fprintf (stream, "(define_macro, value_macro, property_macro) {");
  for(i=0; i < pkg->count; i++) symtab[i] = NULL;
  symbol = (SYMBOL*) next_hash(pkg->symbols, TRUE);
  for(i=0; symbol != NULL; i++) {	  /* Sort symbols by index */
    if(symbol->index >= pkg->count) {
      char error[100];
      sprintf(error, "Symbol %s has a bad index %ld, ignored",
	      symbol->name, symbol->index);
      cerror(error, "");
    }
    else
      symtab[symbol->index] = symbol;
    symbol = (SYMBOL*) next_hash(pkg->symbols, FALSE);
  }
  for(i=0; i < pkg->count; i++) {
    symbol = symtab[i];
    if (symbol != NULL) {
      PROPERTY* prop = symbol->props;
      fprintf (stream, "\n  define_macro  (%ld, %s)",
	       symbol->index, symbol->name);
      if(*symbol->value)
	fprintf (stream, "\n  value_macro   (%ld, %s, %s)",
		 symbol->index,
		 (*symbol->type) ? symbol->type : "void*",
		 (*symbol->value) ? symbol->value : "0");
      while(prop) {
	fprintf (stream, "\n  property_macro(%ld, %s, %s, %s)",
		 symbol->index, prop->property,
		 (*prop->type) ? prop->type : "void*",
		 (*prop->value) ? prop->value : "0");
	prop = prop->next;
      }
    }
#ifdef paranioa
    else
      fprintf (stderr, "Warning: Missing symbol %d from package %s\n",
	       i, name);
#endif
  }
  fprintf (stream, "\n}\n");
}

void
package_define(name, filename, count_name, case_name,
	       start, incr, template, max, use_first, nospace)
  char* name;
  char* filename;
  char* count_name;
  char* case_name;
  long start;
  long incr;
  long template;
  long max;
  long use_first;
  long nospace;
{
  PACKAGE* pkg;		        /* check for package already defined */
  for(pkg = package_list; pkg=NULL; pkg=pkg->next)
    if (strcmp(pkg->name, name) == 0) {
      cerror("DEFPACKAGE: redefining the %s package", name);
      return;
    }
  pkg = (PACKAGE*) getmem(sizeof (PACKAGE));
  if ((pkg->file = fopen (filename, "r+")) == NULL) {/* If we can't open the file */
    cerror ("defpackage: cannot open %s for update", filename);
    return;
  }
  pkg->next = package_list;	/* Link new package on package list */
  package_list = pkg;
  pkg->is_changed = FALSE;
  pkg->symbols = init_Hash_Table();
  pkg->name = name;
  pkg->filename = filename;
  pkg->count = 0;
  pkg->count_name = count_name;
  pkg->start = start;
  pkg->index = start;
  pkg->incr = (incr>0) ? incr : 1;
  pkg->template = template;
  pkg->max = max;
  pkg->use_first = (use_first != 0);
  pkg->nospace = (nospace != 0);
  pkg->scase = c_sensitive;
  pkg->case_name = "sensitive";
  if (*case_name != EOS) {
    pkg->case_name = case_name;
    c_downcase(case_name);
    if (strcmp(case_name, "sensitive") == 0)
      pkg->scase = c_sensitive;
    else if (strcmp(case_name, "upper") == 0)
      pkg->scase = c_upper;
    else if (strcmp(case_name, "lower") == 0)
      pkg->scase = c_lower;
    else if (strcmp(case_name, "cap") == 0)
      pkg->scase = c_cap;
    else
      cerror ("defpackage: Illegal case: %s", case_name);
  }
  pkg->header = NULL;
  read_package(pkg);    /* Read package from file */
  if (pkg->count > 0) {
    pkg->is_changed = FALSE;
    pkg->index = next_index(pkg); /* setup the next index */
  }
}

/*
 * Given a symbol name in a package, return its value string
 */
char* lookup_symbol(pkgname, symname)
  char* pkgname;
  char* symname;
{
  char* value = NULL;
  static char* pkgname_cache = NULL;
  static PACKAGE* package = NULL;

  if (pkgname != pkgname_cache) {
    pkgname_cache = pkgname;
					  /* Lookup package */
    for(package=package_list; package!=NULL; package=package->next)
      if(!strcmp(package->name, pkgname)) break;
  }
  if (package != NULL) {
    SYMBOL* symbol = (SYMBOL*) get_hash(package->symbols, symname);
    if (symbol != NULL)
      value = symbol->value;
  }
  return value;
}

/*
 * DEFPACKAGE_SYMBOL(package, symbol, type, value, property, expander)
 */
int
define_symbol(argc, argv)
     int argc;
     char* argv[];
{
  char* parms[6];
  int i;
  PACKAGE* pkg;
  while(getchar() != '(') ;		  /* Skip over macro name */
  for(i=0; i<6; i++) parms[i] = "";	  /* Initialize parms */
  for(i=0; i<6; i++) {
    parms[i] = scan_token(",)");
    if (getchar() == ')') break;	  /* Quit on ) */
  }
					  /* Lookup package */
  for(pkg=package_list; pkg!=NULL; pkg=pkg->next)
    if(!strcmp(pkg->name, parms[0])) break;
  if(pkg == NULL) {
    cerror("DEFPACKAGE_SYMBOL: undefined package name: %s", parms[0]);
    return 1;
  } else {
    char result[100];
    SYMBOL* symbol = 
      symbol_define(pkg, parms[1], parms[2], parms[3], parms[4]);
    /*
     * insert replacement string
     *    expander(index, symbol, type, value)
     */
    char* expander = parms[5];
    if (*expander != EOS) {
      sprintf(result, "%s(%d, ", expander, symbol->index);
      puts(result);
      put_string(symbol->name);
      sprintf(result, ", %s, %s)",
	      (*symbol->type) ? symbol->type : "void*",
	      (*symbol->value) ? symbol->value : "0");
      puts(result);
    }
  }
  return 0;
}

/*
 * Called just before cpp terminates
 * We write out the new header file here (when necessary)
 */
static void
finalize_package(pkg)
  struct package* pkg;
{
  if(pkg->is_changed) {		/* If package was updated/changed */
 /*   fprintf(stderr, "Writing %s\n", pkg->filename);  *********** DEBUG ********* */
    rewind(pkg->file);
    write_package(pkg);		/* Write new header file */
  }
  close(pkg->file);
}

/*
 * This is called at the end of CPP to close and/or re-write package header files
 */
finish_symbols() {
  PACKAGE* pkg = package_list;
  for (; pkg != NULL; pkg = pkg->next)
    finalize_package(pkg);
}
