/*


 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  2-Sep-90 -- Fix to expand_macro when using keyword args
 *
 * MACRO defmacro
 *
 * CPP defmacro for building macros which can include cpp directives.
 * MACRO works like #define, except the macro body is NOT limited to a
 * single line, and may contain arbitrary cpp directives.  Optional,
 * keyword and body parameters are accepted.
 *
 * To define a macro, the following must have been encountered in the input
 * stream: 
 * 	#pragma defmacro MACRO "macro" delimiter=}
 *
 * A macro definition has the form:
 *	MACRO name(arglist) { body }
 *
 * where name is the name of the macro, arglist are argument
 * specifiers seperated by comma's and body is what is substituted on a 
 * call to the macro. arglist may specify positional, optional,
 * optional keyword, required keyword, rest and body arguments.
 * The syntax is:
 * arglist := [KEY: | REST: | BODY] identifier [= identifier] [, arglist]
 *
 * The equals sign indicates a default argument value.
 * When KEY: is encountered, the rest of the arguments are all keyword
 * arguments. A rest parameter will be set to all remaining parameter
 * values during macro expansion.  REST: parm=name indicates that "parm"
 * will hold the rest of the parameters, and "name" will hold the number
 * of parameters in "parm".  BODY: parm indicates that "parm" will expand to
 * the text within braces {} after the macro call. The braces are removed.
 *
 * Example 1:
 *    MACRO set_val (size, value=NULL, KEY: low = 0, high = 100)
 *    { set_val_internal(size, val, low, high-low) }
 *
 *    set_val	is the macro name
 *    size	is a required positional parameter
 *    val	is an optional positional parameter
 *    low	is an optional keyword parameter
 *    high	is a required keyword parameter
 *
 * Example 2:
 *   // Build a table of char*'s
 *   MACRO build_table(name, REST: rest)
 *   { char* name[] = { build_table_internal(rest) NULL} }
 *
 *   MACRO build_table_internal(first, REST: rest=count)
 *   {#first,
 *   #if count // recurse on rest args
 *   build_table_internal(rest)
 *   #endif
 *   }
 *
 *   build_table(table, 1,2,3,4,5,6,7);   // expands to
 *   char* table[] = {"1", "2", "3", "4", "5", "6", "7"};
 * 
 * Example 3:
 *
 *   MACRO LOOP (type, variable, sequence, BODY: body) {
 *   { type varible;
 *     for(sequence.reset; sequence.next;) {
 *       variable = sequence.value();
 *       body
 *       }
 *   }}
 *
 *   LOOP(char, elt, list) { print(elt) } // Expands to:
 *
 *   { char varible;
 *     for(list.reset; list.next;) {
 *       elt = list.value();
 *       print(elt)
 *     }
 *   }
 *
 * Error messages: (first %s is macro name)
 *	%s: Can't find argument list
 *	%s: REST and BODY args must be last
 *	%s: Unknown parameter keyword: %s:
 *	%s: Syntax error, '%c' found after %s=%s
 *	%s: Syntax error, '%c' found after %s
 *	%s: The required argument %s is missing
 *	%s: Keyword missing for %s
 *	%s: The required keyword argument %s is missing
 *	%s: body argument not found
 *	%s: Unknown argument: %s
 *	%s: Missing ; after macro body
 */

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

typedef struct mac_def {
  char* name;
  Arg* args;
  char* body;
} Mac_Def;

#define BSIZE 512
#define MAXPARM 32

Arg arg_error;				  /* Global error flag */
/*
 * Parse macro args
 */
Arg*
macro_args(macname)
   char* macname;
{
  char c;
  char* name;
  char* value;
  Type type = p_positional;
  Arg* result = NULL;
  Arg* next = NULL;
  Arg* new;
  Boolean rest_or_body = FALSE;

  c = skip_blanks();
  if(c != '(') {
    fprintf(stderr, "%s: Can't find argument list", macname);
    return &arg_error;
  }
  do {
    if(type==p_body || type==p_rest)
      rest_or_body = TRUE;
    else if (rest_or_body) {
      fprintf(stderr, "%s: REST and BODY args must be last", macname);
      return &arg_error;
    }
    value="";
    name=scan_token(",):=");
    c = getchar();
    if (c==':') {
      if(!strcmp(name, "KEY"))
	type=p_key;
      else if (!strcmp(name, "REST"))
	type=p_rest;
      else if (!strcmp(name, "BODY"))
	type=p_body;
      else {
	fprintf(stderr, "%s: Unknown parameter keyword: %s", macname, name);
	return &arg_error;
      }
      name=scan_token(",):=");
      c = getchar();
    }
    switch (c) {
    case ')': break;
    case ',': break;
    case '=':
      value=scan_token(",)");
      c=getchar();
      if(c == ',' || c == ')') break;
      fprintf(stderr, "%s: Syntax error, '%c' found after %s=%s",
	      macname, c, name, value);
      return &arg_error;
    default:
      fprintf(stderr, "%s: Syntax error, '%c' found after %s", macname, c, name);
      return &arg_error;
    }
    new = (Arg*) getmem(sizeof(Arg));	  /* Create new Arg structure */
    if (result == NULL)
      result = new;
    else 
      next->next = new;
    next = new;
    new->next = NULL;
    new->name = name;
    new->value = value;
    new->type = type;      
  } while (c != ')');
  return result;
}
/*
 * Write body returning when a token is found
 */
static char*
get_token(body, tokenbuf, modifier)
  char* body;
  char* tokenbuf;
  Modifier* modifier;
{
  char c;
  char p = EOS;
  char* bodyp = body;
  char* tokenp = tokenbuf;
  *modifier = mod_none;
  while((c = *bodyp++) != EOS) {
    if(isalnum(c) || c=='_' || c=='$') {
      *tokenp++ = c;
      while((c = *bodyp++) != EOS) {
	if((isalnum(c) || c=='_' || c=='$'))
	  *tokenp++ = c;
	else {				  /* End of token */
	  *tokenp = EOS;
	  return bodyp-1;
	}
      }					  /* End of body with token */
      *tokenp = EOS;
      return bodyp-1;
    }
    else if(c == '#') {
      if (*bodyp == '#') {		  /* token catenation */
	*modifier = mod_concat;
	bodyp++;
	while(isspace(*bodyp)) bodyp++;
      } else {
	if (p != EOS) putchar(p);
	if (isalnum(*bodyp)) {		  /* token quoting */
	  *modifier = mod_quote;
	} else if(*bodyp == '~') {	  /* Special hack #~ for unquote */
	  bodyp++;
	  *modifier = mod_unquote;
	} else {
	  putchar(c);
	}
      }
    } else {
      if (*bodyp == '#')
	p = c;
      else {
	putchar(c);
	p = EOS;
      }
    }
  }
  *tokenp = EOS;
  return bodyp-1;			  /* End of body no token */
}
/* 
 * Write body substituting args for values
 */
void
macro_substitute(body, nparm, parnames, parvalues)
  char* body;
  int nparm;
  char** parnames;
  char** parvalues;
{ 
  Modifier modifier;
  char tokenbuf[BSIZE];
  int i;
  Boolean prev_match = FALSE;
  do {
    body = get_token(body, tokenbuf, &modifier);
    for(i=nparm; i--;) {
      if(!strcmp(tokenbuf, parnames[i])) {
	char* val = parvalues[i];
	prev_match = TRUE;
	switch (modifier) {
	case mod_quote:
	  put_string(val);
	  break;
	case mod_unquote:
	  { char c;
	    val++;
	    while((c=*val++) != EOS)
	      if(*val == EOS) break;
	      else if(!(c == '\\' && *val == '"'))
		putchar(c);
	  }
	  break;
	default:
	  puts(val);
	  break;
	}
	goto next;
      }
    }
    switch (modifier) {
    case mod_concat:
      if (prev_match) break;
      putchar('#');
    case mod_quote: putchar('#'); break;
    case mod_unquote: puts("#~"); break;
    }
    puts(tokenbuf);
    prev_match = FALSE;
  next:
    ;
  }
  while (*body != EOS);
}
/*
 * Expand a macro defined by define_macro
 */
expand_macro(argc, argv)
     int argc;
     char* argv[];
{
  Mac_Def* mac = (Mac_Def*) argv[1];
  char c;
  Arg* call_argp;
  Arg* def_argp = mac->args;
  Arg* args = NULL;
  Arg* argp;
  char* bodyarg = NULL;
  int nparm = 0;
  char* parnames[MAXPARM];
  char* parvalues[MAXPARM];
  char junk[BSIZE];
  int n;
  char nstr[4];

  if(copytoken(junk) == NULL)	  /* Skip macro name */
    return(1);
    
  if(def_argp && def_argp->type != p_body) {
    if((args = macro_args(mac->name)) == &arg_error) {  /* Gather arguments */
      fprintf(stderr, " in %s\n", junk);
      return(1);
    }
  }

  call_argp = args;			  /* Match calling and defining args */
  for (; def_argp != NULL; def_argp = def_argp->next) {
    switch (def_argp->type) {
    case p_positional:
      parnames[nparm] = def_argp->name;
      if(call_argp != NULL && *call_argp->value == EOS) {
	parvalues[nparm] = call_argp->name;
	call_argp = call_argp ->next;
      } else if (*def_argp->value != EOS)
	parvalues[nparm] = def_argp->value;
      else {
	fprintf(stderr, "%s: The required argument %s is missing\n",
		mac->name, def_argp->name);
	return 1;
      }
      break;
    case p_rest:
      parnames[nparm] = def_argp->name;
      *junk = EOS;
      n = 0;
      for(;call_argp != NULL; call_argp = call_argp->next) {
	strcat(junk, call_argp->name);
	if (*call_argp->value != EOS) {
	  strcat(junk, "=");
	  strcat(junk, call_argp->value);
	}
	if (call_argp->next)
	  strcat(junk, ", ");
	n++;
      }
      parvalues[nparm] = junk;
      /* If rest arg has a default value */
      if (*def_argp->value != EOS) {	  /* Use it as the name of a parameter */
	sprintf(nstr, "%d", n);		  /* which holds the number of rest args */
	nparm++;
	parnames[nparm] = def_argp->value;
	parvalues[nparm] = nstr;
      }
      break;
    case p_key:
      parnames[nparm] = def_argp->name;
      parvalues[nparm] = def_argp->value;
      { Arg* argp = call_argp;
	for(;argp != NULL; argp = argp->next) {
	  if(*argp->name == EOS) {
	    fprintf(stderr, "%s: Keyword missing for %s\n",
		    mac->name, argp->value);
	    return 1;
	  }
	  if(!strcmp(argp->name, def_argp->name)) {
	    parvalues[nparm] = argp->value;
	    argp->type = p_key;		  /* Munge type to indicate used */
	    break;
	  }
	}
      }
      if (*parvalues[nparm] == EOS) {
	fprintf(stderr, "%s: The required keyword argument %s is missing\n",
		mac->name, def_argp->name);
	return 1;
      }
      break;
    case p_body:
      c = skip_blanks();
      if (c != '{') {
	fprintf(stderr, "%s: body argument not found\n", mac->name);
	do { putchar(c); }		  /* error recovery */
	while ((c=getchar()) != EOF);
	return 1;
      }
      unget();
      bodyarg = scan_next('}');		  /* Grab the macro body */
      {char* bp = bodyarg + strlen(bodyarg) - 1;
       *bp = EOS;}			  /* Strip {} from body */
      parnames[nparm] = def_argp->name;
      parvalues[nparm] = bodyarg+1;
      break;
    }
    nparm++;
  }
  if (call_argp != NULL) {		  /* Check for unused keyargs */
    for(argp = call_argp; argp != NULL; argp = argp->next)
      if (argp->type != p_key) {
	fprintf(stderr, "%s: Unknown argument: %s\n",
		mac->name, argp->name);
	return 1;
      }
  } 
					  /* Substitute args for values */
  macro_substitute(mac->body, nparm, parnames, parvalues);
  for(argp = args; argp != NULL;) {
    Arg* old = argp;
    argp = argp->next;
    free(old->name);
    if (*old->value) free(old->value);
    free(old);
  }
  return 0;
}
/*
 * Define a new macro
 */
int
define_macro(argc, argv)
     int argc;
     char* argv[];
{
  char* name;
  Arg* args;
  char* body;
  Mac_Def* mac;
  char expanding = 0;

  { char macname[BSIZE];
    if(copytoken(macname) == NULL)	  /* Skip macro name */
      return(1);
    if(copytoken(macname) == NULL)	  /* Copy the macro name */
      return(1);
    if(!strcmp(macname, "EXPANDING")) {
      expanding = 1;
      if(copytoken(macname) == NULL)	  /* Copy the macro name */
	return(1);
    }
    name = savestring(macname);
  }
  if((args = macro_args("MACRO")) == &arg_error) {  /* Gather arguments */
    fprintf(stderr, " in %s\n", name);
    return(1);
  }
  skip_blanks();
  unget();
  body = savestring(scan_next('}'));		  /* Grab the macro body */
  { char* bp;
    while(*body++ != '{');		  /* strip start of body */
    bp = body + strlen(body);		  /* Strip end of body */
    *--bp = EOS;
  }
  mac = (Mac_Def*) getmem(sizeof(Mac_Def));  /* Create new macro structure */
  mac->name = name;
  mac->args = args;
  mac->body = body;
  { char delim = ')';
    Boolean recursive = FALSE;
    char* arglist[3];
    arglist[0] = name;
    arglist[1] = (char*) mac;
    arglist[2] = NULL;
    if (args != NULL) {
      Arg* a = args;
      while(a->next!=NULL) a = a->next;	  /* Find last parameter */
      if(a->type == p_body) delim = '}';
      if(a->type == p_rest || a->type == p_body) recursive = TRUE;
    }
    new_defmacro(name, expanding, recursive, delim, EOS,
		 expand_macro, name, arglist);
  }
  return 0;
}
