/*


 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.
 * Updated: MJF 21-May-90 -- Added DECLARE_ONCE for CCC -X.
 * Updated: MJF 01-Mar-91 -- Added fix when expanding member functions
 *                           which return a template class
 * Updated: GPD	25-Jun-91 -- Fix #line nesting. When template expansions done.
 *
 * Template, DECLARE and IMPLEMENT defmacro
 *
 * TO DO:
 *    Generate a warning message when a template is encountered for a class
 *    that has already had a DECLARE or IMPLEMENT run on it.
 *
 *    Scan for the "inline" keyword at the beginning of the "garbage pail"
 *    template.
 *
 *     template needs a more robust heuristic for finding the class name
 */

/*
// C++ paramertized type macro support
// This follows the conventions outlined in the 1988 USENIX C++ conference
// paper titled "Parameterized Types for C++" by Bjarne Stroustrup
//
// To use, insert the following
//	#pragma defmacro template <template>
//
//   template provides a specialized  way  of  defining  a  macro
//   which supports parameterized classes.  A parameterized class
//   is a class in which one or more pieces can  be  declared  at
//   compile  time.  A typical example is a container class where
//   the type of the contained object may change.  For example, a
//   Vector  class would be paramertized for the type of the vec-
//   tor elements.

//   Templates are expanded in two parts:  the  declarative  part
//   needed  by  every  program file which uses the parameterized
//   class, and the implementation part which needs  to  be  com-
//   piled  once  for  the  class.   The  DECLARE macro expands a
//   template's declarative part, and the IMPLEMENT macro expands
//   a template's implementation part.
//
//   There are 3 kinds of template:
//
//   template<parms> class NAME { class_description };
//        Defines a  template  the  declaration  of  class  NAME.
//        PARAMETERS is:
//           parms ::= type name [, parms]
//        where type is ignored (its included for  AT&T  compata-
//        bility)  and  name  is  the name of the parameter which
//        will be substituted when the template is expanded  (Its
//        like a macro argument in a #define).
//
//   template<parms> NAME result_type NAME<parms>::function { ... };
//        Defines  a  method  for  the implementation of the NAME
//        class.
//
//   template<class ARG> NAME inline rtype NAME<parms>::func { ... };
//        Defines  an  inline  method  for the declaration of the
//        NAME class.
//
//   template<class ARG> NAME {anything};
//        This form is used to  define  anything  else  you  want
//        associated with a template.  Use this form for defining
//        typedef's or overloaded friend functions.  When this is
//        found  before  the class template, the contents will be
//        expanded before the class declaration.   When  this  is
//
// Example:
//	template<class T> Vector {
//	typedef Boolean (*Compare) (T&, T&);
//	};
//
//	template<class T> class Vector {
//		T* v;
//		int sz;
//	public:
//		Vector<T>(int);
//		T& operator[](int);
//		T& elem(int i) { return v[i]; }
//		// ...
//	};
//
//	template<class T> Vector
//	T& vector<T>::operator[](int i)
//	{
//		if (i<0 || sz<=i) error("vector: range error");
//		return v[i];
//	}
//	vector<T>(size)
//	{
//		v = malloc(size);
//	}
//	// more method definitions
//	; // semicolon terminates
//
//	DECLARE Vector<char*>;   // create definitions for a vector of strings
//	IMPLEMENT Vector<char*>; // generate code to support  vector of strings
//	vector<char*> vs(30);	 // declare a vector of 30 strings
//
*/

#include "defmacio.h"
#include "cppdef.h"

/* maximum number of template parameters */
#define max_parms 32
/* token buffer size */
#define BSIZE 512

typedef enum { class_template,
	       declare_template,
	       implement_template } template_type;

typedef struct Template {
  struct Template* next;	/* Next template or NULL */
  template_type type;		/* type of template */
  char** args;			/* argument names */
  char* body;			/* program text */
  int line;			/* Line number */
  char* file;			/* file name */
} TEMPLATE;

struct Temp_Head { TEMPLATE* head;	  /* First template */
		   TEMPLATE* tail;  /* Last template */
		   int nparms;	    /* Number of parameters */
		 };

static struct Hash_Table* template_table; /* Template hash table */

static char* name_n = NULL;	/* Name of current implementation */

TEMPLATE* append_template(self, name, nparms, head)
  char* self;
  char* name;
  int nparms;
  TEMPLATE** head;
{
  TEMPLATE* templ = (TEMPLATE*) getmem(sizeof(TEMPLATE));
  struct Temp_Head* th;
  if (template_table == NULL)
    template_table = init_Hash_Table();
  th = get_hash(template_table, name);
  if (th == NULL) {			  /* New template name, add head */
    th = (struct Temp_Head *) getmem(sizeof(struct Temp_Head));
    put_hash(template_table, savestring(name), th);
    th->head = templ;
    th->nparms = nparms;
  } else {				  /* Else Old template */
    th->tail->next = templ;		  /* Link new template after last */
    if (nparms != nparms) {		  /* Check # parms */
      fprintf(stderr, "%s: %s wrong number of parameters, expected %d\n",
	      self, name, nparms);
      return NULL;
    }
  }
  th->tail = templ;			  /* New template is the last */
  templ->next = NULL;
  if(head != NULL) *head = th->head;	  /* Return head when requested */
  return templ;				  /* Return new template struct */
}

/*
 * Gather up the parameters
 */
static char** template_parms(self, nparmsp, is_template)
  char* self;			/* Macro name */
  Boolean is_template;		/* True when template, else implement */
  int* nparmsp;			/* Place to return parameter count */
{
  char c;			/* Current character */
  char buff[BSIZE];		/* character buffer */
  char* parms[max_parms];	/* vector of parameters */
  int nparms = 0;		/* number of parameters */

  while (TRUE) {
    if (is_template && copytoken(buff) == NULL) /* throw out the type */
      return NULL;
    c = skip_blanks();
    if (c == '*') c = skip_blanks();	  /* Allow * after type */
    if(!isalnum(c) && c != '"') {
      fprintf(stderr,
	 "%s: Syntax error, strange character after parameter type %s: '%c'\n",
	 self, buff, c);
      return NULL;
    }
    unget();
    parms[nparms++] = scan_token(",>"); /* get a parameter */
    c = skip_blanks();
    if (c == '>') break;
    if (c != ',') {
      fprintf(stderr, "%s: Syntax error, '%c' instead of comma after parameter %s\n",
	      self, c, parms[nparms-1]);
      return NULL;
    }
    if(nparms >= max_parms) {
      fprintf(stderr, "%s: More than %d parameters\n", self, max_parms);
      return NULL;
    }
  }
  { char** result = (char**) getmem(sizeof(char*) * (nparms+1));
    result[nparms] = NULL;
    if(nparmsp != NULL) *nparmsp = nparms;
    while(--nparms>=0) result[nparms] = parms[nparms];
    return result;
  }
}

/*
 * Handle the declare translation
 */
enum Do_What { do_declare, do_implement, do_implement_n };

static int declare_implement (do_what, do_line)
   enum Do_What do_what;	  /* What to do */
   Boolean do_line;		  /* When true, use #line */
{
  extern void macro_substitute(/* char* body, int nparm,
				  char** parnames, char** parvalues */);
  char c;			/* current character */
  char buff[BSIZE];	        /* character buffer */
  char* name;			/* class name */
  char* self;			/* macro name */
  char** parms;			/* Buffer for parameters */
  char** parmp;
  int nparms;			/* Number of parameters */
  int n;		        /* Template number to process */

  if (copytoken(buff) == NULL)	/* get the macro name */
    return 1;
  self = savestring(buff);
  if (do_what == do_implement_n) {
    if (copytoken(buff) == NULL)	/* get the template number */
      return 1;
    n = atoi(buff);
  }
  if (copytoken(buff) == NULL)	/* get the class name */
    return 1;
  name = savestring(buff);
  if(template_table == NULL) {
    fprintf(stderr, "%s: Template for %s isn't defined\n", self, name);
    return 1;
  }
  /*
   * Templates sometimes contain IMPLEMENT statements.  This fouls
   * up the implement_n mechanism, because a whole class will get
   * implemented on one iteration.  To stop this, we record the
   * name of the top-level implementation, and ignore any nested ones.
   */
  switch (do_what) {
  case do_implement_n:
    name_n = name; break;
  case do_implement:
    if (name_n != NULL && strcmp(name, name_n) != 0) {
      /* Ignore nested IMPLEMENT */
      fprintf(stderr, "Skipping IMPLEMENT %s\n", name); /* DEBUG */
      return 0;
    }
  } /* end case */
  /*
   * Gather up the parameters
   */
  c = skip_blanks();
  if (c != '<') {
    fprintf(stderr, "%s: Syntax error, missing < after %s %s\n",
	    self, self, name);
    return 1;
  }
  if ((parms = template_parms(self, &nparms, NULL)) == NULL)
    return 1;
  /*
   * insert typedef's for pointer types
   * (this is a work around for a cfront 1.2 bug)
   */
  for (parmp = parms; *parmp != NULL; parmp++) {
    char* pname = *parmp;
    char* ptype = pname;
    int last = strlen(pname)-1;
    if (pname[last] == '*') {
      ptype = savestring(pname);
      if(parmstring(ptype) != 0) return 1;
      *parmp = ptype;
      if (do_what == do_declare) {
	sprintf(buff, "\n#ifndef %s_defined\n", ptype); puts(buff);
	sprintf(buff, "#define %s_defined 1\n", ptype); puts(buff);
	sprintf(buff, "typedef %s %s;\n", pname, ptype); puts(buff);
	puts("#endif\n");
      }
    } /* end if a pointer parameter */
  } /* end while for all parms */
  { TEMPLATE* templ;
    struct Temp_Head* th = get_hash(template_table, name);
    if(th == NULL) {
      fprintf(stderr, "%s: Template %s isn't defined\n", self, name);
      return 1;
    }
    for(templ = th->head; templ != NULL; templ = templ->next) {
      char lbuff[256];
      FILEINFO* finfo = MacOutFile;
      switch (do_what) {
      case do_declare:   if(templ->type == implement_template) continue; break;
      case do_implement: if(templ->type != implement_template) continue; break;
      case do_implement_n:
	if(templ->type != implement_template) continue;
	if (n < 0) return 0;
	if (n-- > 0) continue;
	/* else fall through to implement template n */
      }
      while (finfo && !finfo->fp)
	finfo = finfo->parent;
      if (finfo)
      {
	sprintf(lbuff,"\n#line %d \"%s\"\n", finfo->line, finfo->filename);
	puts(lbuff);
      }
      if (do_line) {
	char* bodyp = templ->body;
	int newline = 0;
        /*  Check for space at beginning of body */
	for(; isspace(*bodyp); bodyp++)
	  if(*bodyp == '\n') {
	    newline = 1;
	    break;
	  }
	sprintf(buff, "\n#line %d \"%s\"", newline + templ->line, templ->file);
	puts(buff);
	if(!newline) putchar('\n');
      }
      macro_substitute(templ->body, nparms, templ->args, parms);
      if (finfo)
	puts(lbuff);
    }
  }
  if (do_what == do_implement_n && n >= 0) {
    puts("\nBarf\n**** Template number too large ****\n");
    fprintf(stderr, "Last Implementation\n");
    exit(2);
  }


/*
 *  help user do a declare only once in each file
 *  by defining a variable (used by declare_once defmacro for CCC -X)
 */
  if (do_what == do_declare) {   
    char ptype[512]; 
    strcpy(ptype,name);
    strcat(ptype,"<");
    parmp = parms;
    strcat(ptype,*parmp);
    parmp++;
    while (*parmp != NULL) {
      strcat (ptype,",");
      strcat (ptype,*parmp);
      parmp++;
    }
    strcat(ptype,">");
    parmstring(ptype);
    sprintf(buff, "\n#ifndef %s_declared\n", ptype);  puts(buff);
    sprintf(buff, "#define %s_declared 1\n", ptype); puts(buff);
    puts("#endif\n");
  }

  /*
   * Invoke the hook macros
   */
  if(do_what == do_declare) {
    puts("\n#ifdef DECLARE_HOOK");
    puts("\nDECLARE_HOOK(");
  } else {
    puts("\n#ifdef IMPLEMENT_HOOK");
    puts("\nIMPLEMENT_HOOK(");
  }
  puts(name);
  putchar('<');
  for (parmp = parms; *parmp != NULL;) {
    puts(*parmp);
    if (*++parmp == NULL) break;
    puts(" ,");
  }
  puts(">)\n#endif\n");
  return 0;
}

/*
 * Handle the template translation
 */
int template (argc, argv) 
  int argc;
  char** argv;
{
  char c;			/* current character */
  char buff[BSIZE];		/* character buffer */
  char* self;			/* our name */
  char** parms;			/* template parameters */
  int nparms;			/* parameter count */
  char* file = savestring(current_file);
  if(copytoken(buff) == NULL)	/* Grab macro name */
    return 1;
  self = savestring(buff);
  c = skip_blanks();
  if (c != '<') {
    fprintf(stderr, "%s: Syntax error, missing < after %s\n", self, self);
    return(1);
  }
  /*
   * Get the parameters
   */
  if ((parms = template_parms(self, &nparms, TRUE)) == NULL)
    return 1;
  /*
   * Find out what kind of template
   */
  { TEMPLATE* templ;		/* new template */
    STRING body = (scan_start(), work_string);
    append_blanks(body);
    unget();
    if(copytoken(buff) == NULL)	/* Get first token after template parms */
      return 1;
    append_STRING(body, buff);
    c = append_blanks(body);
    unget();
    /*
     * class template
     */
    if(!strcmp(buff, "class")) {
      extern int parmtype();
      char* name;
      if (copytoken(buff) == NULL) /* token after class is the class name */
	return 1;
      name = savestring(buff);
      templ = append_template(self, buff, nparms, NULL);
      if (templ == NULL) return 1;
      append_STRING(body, buff);
      while ((c = getchar()) != EOF) append_char(body, c);
      append_char(body, ';');
      append_char(body, EOS);
      templ->type = class_template;
      templ->args = parms;
      templ->body = savestring(body->buff);
      templ->line = current_line;
      templ->file = file;
      /* define a parmtype defmacro for this class name */
      new_defmacro(name, FALSE, FALSE, '>', '<', parmtype, "parmtype", NULL);
    } else {
      /*
       * auxillary template
       */
      if (c == '{') {	        /* An auxillary declaration template */
	TEMPLATE* t;
	{ char* buffp = body->buff; /* Update line count */
	  while (buffp < body->buffp)
	    if(*buffp++ == '\n') current_line++;
	}
	body->buffp = body->buff; /* Empty out body */
	{ char p;		  /* Copy everything except surrounding {} */
	  getchar();
	  for (p = getchar(); (c = getchar()) != EOF; p = c)
	    append_char(body, p);
	  append_char(body, EOS);
	}
	templ = append_template(self, buff, nparms, &t);
	if (templ == NULL) return 1;
	templ->args = parms;
	templ->body = savestring(body->buff);
	templ->line = current_line;
	templ->file = file;
	templ->type = declare_template;	  /* Determine template type */
	for (; t != NULL; t = t->next)
	  if(t->type == class_template) { /* If after a class template */
	    templ->type = implement_template;
	    break;
	  }
      }
      /*
       * function template
       */
      else {
	template_type type = implement_template;
	if (!strcmp(buff, "inline")) type = declare_template;
	while ((c = getchar()) != EOF) append_char(body, c);
	append_char(body, EOS);
	{ char* bodyp = body->buff;
	  char* startname;
	  char* endname;
 	  do {				  /* Search for name<..>:: */
 	    int nest;
/*
	    while((c = *bodyp++) != EOS && c != '<');
	    if(c == EOS) {
	      fprintf(stderr, "Can't find template class\n");
	      return 1;
	    }
*/
 	    nest=0;
 	    while ((c = *bodyp++) != EOS)
 	    {
 	      if (c == ':' && nest == 0)
 		break;
 	      else if (c == '<')
 		nest++;
 	      else if (c == '>')
 		nest--;
 	    }
 	    if (c == EOS) {
 	      fprintf(stderr, "Can't find template class\n");
 	      return 1;
 	    }
 					  /* Scan back to end of name */
 	    nest=0;
 	    while (bodyp != body->buff)
 	    {
 	      c = *--bodyp;
 	      if (c == '>')
 		nest++;
 	      else if (nest == 1 && c == '<')
 		break;
 	      else if (c == '<')
 		nest--;
 	    }
 	    if (bodyp == body->buff) {
 	      fprintf(stderr, "Can't find template class\n");
 	      return 1;
 	    }
 	    bodyp++; 
					  /* Scan backwards for name */
	    for (endname = bodyp-1; isspace(*(endname-1)); endname--);	    
	    for (startname = endname; startname>body->buff; startname--) {
	      c = *(startname-1);
	      if(!isalnum(c) && c!='_' && c!='$') break;
	    }
	    strncpy(buff, startname, endname-startname);
	    buff[endname-startname] = EOS;
					  /* Ensure token after < is parm */
	    while(isspace(*bodyp)) bodyp++;
	  } while (strncmp(bodyp, *parms, strlen(*parms)));
	  templ = append_template(self, buff, nparms, NULL);
	  if (templ == NULL) return 1;
	  templ->type = type;
	  templ->args = parms;
	  templ->body = savestring(body->buff);
	  templ->line = current_line;
	  templ->file = file;
	}
      }
    }
  }
  free(self);
  return 0;
}

int declare (argc, argv)
  int argc;
  char** argv;
{
  return declare_implement(do_declare, argc>1);
}    

int implement (argc, argv)
  int argc;
  char** argv;
{
  if (argc > 2 && name_n == NULL) name_n = savestring(argv[2]);
  return declare_implement(do_implement, argc>1);
}    

/*
 * Implement the Nth template
 *   Example:
 *      IMPLEMENT 3 Vector<char*>
 */
int implement_n (argc, argv)
  int argc;
  char** argv;
{
  return declare_implement(do_implement_n, argc>1);
}    

int declare_once (argc, argv)
  int argc;
  char** argv;
{
  char buff[BSIZE];	        /* character buffer */
  char* self;			/* macro name */
  char* parmtype;		/* parm type name */
  char* parmname;

  if (copytoken(buff) == NULL)	/* get the macro name */
    return 1;
  self = savestring(buff);

  parmtype = scan_token(">"); /* get full parameters type name */
  parmname = savestring(parmtype);
  if(parmstring(parmtype) != 0) return 1;
  sprintf(buff, "\n#ifndef %s_declared\n", parmtype); puts(buff);
  sprintf(buff, "#define %s_declared 1\n", parmtype); puts(buff);
  sprintf(buff, "DECLARE %s\n", parmname); puts(buff);
  puts("#endif\n");
  return 0;
}    
