/*


 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 CLASS macro
 *
 * The #pragma defmacro class call will pass arbitrary parameters to the
 * defmacro. We use this mechanism to tell the class macro to stick
 * macro calls into the class definition.  In this way the class macro
 * doesn't generate code itself, all code is defined in user modifiable
 * header files.  The macros will be called with the class name and the
 * base-class name.  When slots or methods are specified a BODY argument
 * is provided with a macro call for each slot or method.  The slot macros
 * are passed the slot type and name, and the method macros are passed the
 * result type, name and arglist.  The arglist is passed in double quotes,
 * because of the embedded commas (see the #~ MACRO operator).
 * 
 * There is a need to select from categories of things within the class:
 * 
 * slots methods            // Only one may be specified
 * virtual inline normal friend static
 *                          // if none specified, use virtual inline normal
 * private protected public // if none specified, all slots or methods used
 * inside outside	    // where to expand the macro call
 * 
 * These are specfied with the classmac defmacro with
 * the following grammar:
 *   classmac(macro_name, :REST keywords)
 * where keywords is zero or more of 
 *    inside | slots | methods | virtual | inline | normal |
 *    private | protected | public
 *
 * When a keyword has a value, the value is the name of a macro to call
 * when the preceding keywords apply.  The macros will be expanded outside
 * and after the class definition, unless the "inside" keyword is found
 * anywhere in the parameter list. In this case the macro is expanded 
 * inside the class definition, at the end.
 *
 * Example:
 * 
 * #pragma defmacro class "class"
 * #pragma defmacro classmac "classmac" delimiter=)
 * classmac(DECLARE_Generic, inside)
 * classmac(IMPLEMENT_Generic)
 * classmac(generate_map_over_slots, slots=generate_map_slot)
 * classmac(my_generate_methods, methods, public=public_methods, \
 *                               methods, protected, private=misc_methods)
 * 
 * struct method_entry {
 *   Symbol* name;
 *   Symbol* type;
 *   char* args;
 *   Boolean is_public;
 * };
 * 
 * // Invoked from the class macro
 * MACRO my_generate_methods(class_name, base_class, BODY: methods) {
 *   method_entry class_name##_method_table[] = { methods }; }
 * 
 * // expanded within my_generate_methods
 * MACRO public_methods(type, name, args) {
 *   {SYM(name), SYM(type), args, TRUE} }
 * 
 * MACRO misc_methods(type, name, args) {
 *   {SYM(name), SYM(type), args, FALSE} }
 * 
 * MACRO generate_map_over_slots(class_name, base_class, BODY: slots) {
 *  Boolean class_name::map_over_slots(Slot_Mapper procedure, void* rock=NULL){
 *   return base_class::map_over_slots(procedure rock) slots;
 *  }
 * }
 * 
 * MACRO generate_map_slot(type, name, value) {
 *   || ((*procedure)(this, #name, &name, SYM(type), rock)) }
 * protected:
 *   virtual Symbol** type_list();
 * public:
 *   Long(long value) { num = value; };          // Make a long
 *   long operator long() { return this->num; }; // Get the value out
 *   virtual Boolean map_over_slots(Slot_Mapper procedure, void* rock = NULL);
 * };
 * 
 * extern Symbol* Generic_types[];
 * Symbol* Long_types[] = {sym(Long), (Symbol*) Generic_types, NULL};
 * Symbol** Long::type_list() {return Long_types;};
 * 
 * Boolean Long::map_over_slots (Slot_Mapper procedure, void* rock = NULL) {
 *   return (
 *     Object::map_over_slots(procedure rock) // Do inherited slots first
 *     || ((procedure)(this, "num", &num, SYMBOL("long"), rock));
 *   )
 * }
 *
 */

#include "defmacio.h"
#include "macro.h"

static char* classkeys[] =
 {"slots", "methods", "virtual", "inline", "normal", "friend", "static",
  "private", "protected", "public", "inside", NULL};

#define s_slots     0x001
#define s_methods   0x002
#define s_virtual   0x004
#define s_inline    0x008
#define s_normal    0x010
#define s_friend    0x020
#define s_static    0x040
#define s_private   0x080
#define s_protected 0x100
#define s_public    0x200
#define s_inside    0x400

/*
 * There's a CMAC for every classmac encountered.  The first one is pointed
 * to by Cmac_head.  Additional classmac's are added at Cmac_tail.
 * There's a CBODY for every macro in the body of a CMAC.
 */
typedef struct Cbody {
  struct Cbody* next;			  /* Next macro or NULL */
  char* name;				  /* macro name */
  unsigned int specifiers;		  /* specifier bit set */
} CBODY;

typedef struct Cmac {
  struct Cmac* next;			  /* Next macro or NULL */
  struct Cbody* body;			  /* List of body macro spec's */
  char* macro;				  /* macro name */
  int is_inside;			  /* True when inside, else outside */
} CMAC;

static CMAC* Cmac_head = NULL;		  /* First class macro */
static CMAC* Cmac_tail = NULL;		  /* Last class macro */

#define BSIZE 512
/*
 * Return the mask for a class keyword, else zero
 */
static int class_key(arg)
    char* arg;
{
  char** key = classkeys;
  int type = 1;
  while (*key != NULL) {		  /* Find keyword */
    if (strcmp(*key, arg) == 0) break;
    key++;
    type = type << 1;
  }
  if (*key == NULL)
    return 0;
  else
    return type;
}

/*
 * Parse the arguments to the class macro
 */
int classmac(argc, argv)
     int   argc;
     char* argv[];
{
  Arg* args = NULL;
  Arg* argp;
  char macname[BSIZE];
  if(copytoken(macname) == NULL)	  /* get macro name */
    return(1);
  if((args = macro_args(macname)) == &arg_error) /* Gather arguments */
    return(1);
  {
    int mactype = 0;
    CMAC* mac = (CMAC*) getmem(sizeof(CMAC));
    CBODY** next = &mac->body;
    mac->body = NULL;
    mac->macro = args->name;		  /* First parameter is macro name */
    mac->is_inside = FALSE;
    mac->next = NULL;
    if (Cmac_head == NULL) {		  /* Link in first macro */
      Cmac_head = mac;
      Cmac_tail = mac;
    } else {				  /* Link in next macro */
      Cmac_tail->next = mac;
      Cmac_tail = mac;
    }
    /* Loop over arguments */
    for (argp = args->next; argp != NULL; argp = argp->next) {
      int type = class_key(argp->name);
      if (type == 0) {
	fprintf(stderr, "%s(%s ...) Unknown keyword %s\n",
		macname, mac->macro, argp->name);
	return(1);
      }
      if (type == s_inside)		  /* Check for inside */
	mac->is_inside = TRUE;
      else
	mactype |= type;
      if (*argp->value) {	  /* When '=' found, define macro */
	if ((mactype & (s_virtual | s_inline | s_normal)) == 0)
	  mactype |= (s_virtual | s_inline | s_normal);
	if ((mactype & (s_private | s_protected | s_public)) == 0)
	  mactype |= (s_private | s_protected | s_public);
	{ CBODY* body = (CBODY*) getmem(sizeof(CBODY));	
	  body->name = argp->value;
	  body->specifiers = mactype;
	  body->next = NULL;
	  *next = body;
	  next = &body->next;
	  mactype = 0;
	}
      }
    }
    if (mactype != 0) {
      fprintf(stderr, "%s(%s ...) macro to call not specified\n",
	      macname, mac->macro, argp->name);
      return(1);
    }
  }
  return 0;
}

/*
 * Instead of malloc'ing for every token, all tokens are kept in
 * work_string, seperated by '\0's. Since work_string may grow, 
 * we don't keep pointers into it, but use offsets instead.
 *
 * This macro converts a work-string offset into a char*
 */
#define WSTRING(n) (work_string->buff+(n))

/*
 * There's a Cmember struct for each member (slot method or friend)
 * of a class.  There's a vector of members kept on the stack.
 */
typedef struct Cmember {
  unsigned int specifiers;		  /* specifier bit set */
  unsigned int type;			  /* member type index */
  unsigned int name;			  /* macro name index */
  unsigned int value;			  /* Value index if slot */
  unsigned int parms;			  /* Parameter index if function */
} CMEMBER;

/*
 * Read in a member (data or function)
 * Fills in tokens with member type, name, parameters/value
 * Fills in delimiters with the delimiter BEFORE the associated token.
 */
static int class_read(tokens, delimiters, blanks)
     int tokens[20];
     char  delimiters[20];
     STRING blanks;
{   
  int ntoken = 0;
  char* token;
  char delim = ' ';
  delimiters[0] = ' ';

  for(;;) {				  /* Loop over tokens in member */
    char c;
    char next_delim;
    blanks->buffp = blanks->buff;
    c = append_blanks(blanks);
    if (c == EOF) break;		  /* unexpected end of file */
    puts(blanks->buff);
    /* Get next token */
    next_delim = EOS;
    switch (c) {
    case ',':
      if (ntoken > 0) {
	unget();		       /* exit and leave comma for next time */
	return ntoken;
      }
      break;
    case ';':
      putchar(c);
      if (ntoken==0) {
	continue;		  /* }; - ignore ; */
      }
      /* fall through */
    case EOF:
    case '}':
      return ntoken;			  /* normal return */
    case ':':
      putchar(c);
      delim = c;
      c = append_blanks(blanks);
      if (c == '}') return 0;
      puts(blanks->buff);
      break;
    case '<':				  /* operator<< or parmtype */
      if(!strncmp("operator", WSTRING(tokens[ntoken-1]), 8)) {
	putchar(c);
	work_string->buffp--;
	append_char(work_string, c);
	append_char(work_string, EOS);
	continue;
      } /* else fall through */
    case '[':				  /* operator[] */
      unget();
      work_string->buffp--;
      delim = (c=='[') ? ']' : '>';
      token = scan_list(delim);	  /* Append to prevous token */
      puts(token);
      continue;
    case '#':				  /* #if or something */
      do putchar(c)			  /* Just copy it */
      while ((c = getchar()) != '\n' && c != EOF);
      putchar(c);
      continue;
    case '(': delim = c; next_delim = ')'; break;
    case '{': delim = c; next_delim = '}'; break;
    case '_':				  /* Beginning of identifier */
    case '$': next_delim = ' '; break;	  /* Beginning of identifier */
    }

    if (isalnum(c) || next_delim != EOS) {
      unget();
      if (next_delim == EOS) next_delim = ' ';
      token = scan_list(next_delim);	  /* Get next token */
      puts(token);
      tokens[ntoken] = token - work_string->buff;
      delimiters[ntoken++] = delim;
    } else if (ntoken == 0 && c != '~' && c != ',') {
      fprintf(stderr,"class: Strange character '%c' at beginning of member\n"
	      , c);
      return 0;
    } else if (c == ':' && delimiters[ntoken-1] == ':') {
      putchar(c);
      work_string->buffp--;		  /* :: found, append next token */
      append_STRING(work_string, "::");
      token = scan_list(' ');
      puts(token);
    } else if (c == '=') {
      putchar(c);
      next_delim = '=';
    } else {
      putchar(c);
      tokens[ntoken] = work_string->buffp - work_string->buff;
      delimiters[ntoken++] = c;
      append_char(work_string, EOS);
      next_delim = ' ';
    }
    delim = next_delim;
    if (delim == '}')
      return ntoken;			  /* End of inline */
  } /* end token loop */
  /* unexpected end of file */
  return ntoken;
}

unsigned int print_flags(n)		  /* ***** DEBUG FUNCTION ***** */
     unsigned int n;
{
  int i;
  for (i=0; i<16; i++)
    if ((1<<i) & n)
      fprintf(stderr, "%s ", classkeys[i]);
  fprintf(stderr, "\n");
  return n;
}


/* Copy body of class */
static int class_body(members, max_members)
    CMEMBER members[];
    int max_members;
{
  STRING blanks = make_STRING(80);
  int tokens[20];
  char  delimiters[20];
  int protection = s_public;
  int i;
  int nmember;

  scan_start();
  *work_string->buffp++ = EOS;		  /* First char for null string */

  for(nmember=0; nmember< max_members; nmember++) { /* Loop over members */
    CMEMBER* m = &members[nmember];
    int start;
    int end;
    int ntoken;
    int member_type = 0;
    m->type = 0;
    m->name = 0;
    m->value = 0;
    m->parms = 0;
    /*
     * Read a member
     */
    ntoken = class_read(tokens, delimiters, blanks);
    if (ntoken == 0) goto last_member;
    if (delimiters[0] == ',') {	  /* comma means use same type as previous */
      CMEMBER* pm = &members[nmember-1];
      m->type = pm->type;
      m->specifiers = pm->specifiers;
      start = 0;
    } else {
      /*
       * Get keywords at beginning of line
       */
      for (start=0; start<ntoken; start++) { /* Loop over tokens */
	int mask = class_key(WSTRING(tokens[start]));
	if (mask & (s_public | s_private | s_protected)) {
	  protection = mask;
	}
	else if (mask & (s_virtual | s_inline | s_normal | s_friend | s_static)){
	  member_type |= mask;
	}
	else break;
      }					  /* Type is normal if isn't unusual */
      if ((member_type&(s_virtual|s_inline|s_normal|s_friend|s_static)) == 0)
	member_type |= s_normal;
    }
    /*
     * What's left is the result-type, name, arglist, body and semicolon.
     * Because we don't know what a type looks like, search BACKWARD
     * for these items.  What's left is the result type.
     */
    for (end = ntoken-1; end >= start; end--) {
      switch (delimiters[end]) {
      case '{':				  /* Body */
	if (member_type & s_methods)
	  member_type |= s_inline;	  /* if a method must be inline */
	ntoken--;			  /* Don't save body */
	break;
      case ')':				  /* Parameter list */
	if (end > start)		  /* If token after parm list */
	  *(WSTRING(tokens[end-1])-1) = ' '; /* concatenate parm list */
	break;
      case '(':				  /* Parameter list */
	m->parms = tokens[end];
	m->name = tokens[--end];	  /* Token before parm list is name */
	member_type |= s_methods;	  /* Must be a method */
	goto exit;			  /* Everything else is type */
      case '>':
      case ' ':				  /* slot Name */
	m->name = tokens[end];		  /* Token before parm list is name */
	member_type |= s_slots;		  /* Must be a slot */
	goto exit;			  /* Everything else is type */
      case '=':
	if (!(member_type & s_virtual)) {
	    m->name = tokens[end-1];
	    m->value = tokens[end];
	    for (i=end+1; i<ntoken; i++)     /* Concatenate values */
	      *(WSTRING(tokens[i])-1) = ' ';
	    member_type |= s_slots;
	    end--;
	  }
	goto exit;
      case ':':				  /* field width (ignored) */
	break;
      default:				  /* debug */
	fprintf(stderr, "class: Strange character \'%c\' found after ",
		delimiters[end]);
	for (i=0; i<end; i++)
	  fprintf(stderr, "%c%s",
		  delimiters[i], (tokens[i]<0) ? "" : WSTRING(tokens[i]));
	fprintf(stderr, "\n");
      }
    }
    fprintf(stderr, "class: Couldn't find member after "); /* DEBUG */
    fprintf(stderr, "%s%c",
	    (tokens[i]<0) ? "" : WSTRING(tokens[i]), delimiters[i]);
    fprintf(stderr, "\n");
    continue;
  exit:
    if (m->type != 0) continue;	  /* if comma before member */
    m->type = tokens[start];
    for (i=start+1; i<end; i++) {
      *(WSTRING(tokens[i])-1) = ' '; /* concatenate types */
      if ((*WSTRING(tokens[i])) == EOS)
	*(WSTRING(tokens[i])-1) = delimiters[i];
    }
    m->specifiers = member_type | protection;
  } /* end for token */
 last_member:
  destroy_STRING(blanks);
  if (nmember >= max_members)
    cerror("More than 200 members in a class", "");
  return nmember;
}

void class_macros(class, base, is_inside, members, nmember)
    char* class;
    char* base;
    int is_inside;
    CMEMBER members[];
    int nmember;
{
  CMAC* mac;
  char line[200];
  for (mac=Cmac_head; mac != NULL; mac = mac->next) { /* for each macro */
    int n;
    if (mac->is_inside == is_inside) {
      sprintf(line, "\n%s(%s, %s)", mac->macro, class, base); puts(line);
      if (mac->body != NULL) {
	puts(" {\n");
	for(n=0; n<nmember; n++) {		  /* for each member */
	  CMEMBER* m = &members[n];
	  CBODY* body;
	  unsigned int mspec = m->specifiers;
	  for (body=mac->body; body!=NULL; body=body->next) { /* for bodies */
	    unsigned int bspec = body->specifiers;
	    if ((mspec&bspec&(s_slots|s_methods)) &&
		(mspec&bspec&(s_private|s_protected|s_public)) &&
		(mspec&bspec&(s_virtual|s_inline|s_normal|s_friend|s_static))){
	      sprintf(line, "%s(%s, %s, %s)\n",
		      body->name, WSTRING(m->type), WSTRING(m->name),
		      WSTRING((mspec & s_slots) ? m->value : m->parms));
	      puts(line);
	    }
	  }
	}				  /* for members in class */
	putchar('}');
      }
      putchar('\n');
    }
  } /* for all macros */
}

int class_macro(argc, argv)
     int argc;
     char* argv[];
{
  char c;
  char buff[BSIZE];
  char* classname;
  STRING basename = make_STRING(80);
  if(copytoken(buff) == NULL)	  /* Skip class keyword */
    return(1);
  puts(buff);
  classname = savestring(scan_next(' ')); /* Get class name */
  putchar(' ');
  puts(classname);
  putchar(' ');
  c = skip_blanks();
  putchar(c);
  switch (c) {
  case '*':			  /* Type (e.g. extern class foo* bar;) */
  case ';':			  /* Forward reference */
    { char* buffp = buff;
      char* namep = classname;
      while((c = *namep++) != EOS &&
	    (isalpha(c) || c == '_' || c == '$'))
	*buffp++ = c;
      if (c == '<') {		  /* A template, ensure parmtype is defined */
	extern int parmtype();
	*buffp = EOS;
	new_defmacro(savestring(buff), FALSE, FALSE, '>', '<',
		     parmtype, "parmtype", NULL);
      }
    }
    free(classname);
    classname = NULL;
    while ((c = getchar()) != EOF) putchar(c); /* copy rest of input */
    break;
  case '{':				  /* No base class */
    break;
  case ':':
    { char* base;
    next_base:
      base = scan_next(' ');		  /* Copy the base class name */
      puts(base);
      if(!strcmp(base, "public") ||	  /* If first word was public, */
	 !strcmp(base, "private") ||	  /* or private */
	 !strcmp(base, "virtual")) {	  /* or virtual */
	putchar(' ');
	goto next_base;			  /* Ignore and get base class name */
      }
      append_STRING(basename, base);
      c = skip_blanks();
      if(c == ',') {
	putchar(c);
	append_char(basename, c);
	goto next_base;
      }
      if(c != '{') {
	fprintf(stderr, "class: Syntax error, '%c' instead of { after base class\n", c);
	return 1;
      }
    }
    putchar(c);
    break;
  default: 
    fprintf(stderr, "class: Syntax error, class %s %c\n", classname, c);    
    return 1;
  }
  if (classname != NULL) {
    CMEMBER members[200];
    int nmember = class_body(members, 200);
    class_macros(classname, basename->buff, TRUE, members, nmember);/*Inside*/
    puts("};\n"); /* terminate class definition */
    class_macros(classname, basename->buff, FALSE, members,nmember);/*Outside*/
  }
  destroy_STRING(basename);
  return 0;
}
