/*$__copyright$*/
/*
 * ShapeTools/shape program - macro.c
 *
 * Author: Axel Mahler (Axel.Mahler@cs.tu-berlin.de)
 *
 * $Header: macro.c[8.0] Mon Jun  6 15:09:08 1994 axel@cs.tu-berlin.de frozen $
 */
/* Thanks to Steve Emerson (steve@unidata.ucar.edu) for the 
   reimplementation of expandmacro() */

#ifndef lint
static char *AtFSid = "$Header: macro.c[8.0] Mon Jun  6 15:09:08 1994 axel@cs.tu-berlin.de frozen $";
#endif

#include <ctype.h>
#include "shape.h"

#define INCLUDE1 "include "
#define INCLUDE2 "include\t"

#define IMPORT "IMPORT"

#define MAXLINELENGTH 20000

#define MACRONAM 128
#define MACROVAL 1048

EXPORT char *environment_vars = NIL;
EXPORT FILE *temp;
EXPORT Bool shape_command = FALSE;
LOCAL  int macdepth;

LOCAL char *get_variant_macro(name)
     char *name;

     /* get macro value for variant */
{
  Bool macro_defined = FALSE;
  register int j = 0;
  int this_var;
  struct vardef *this_vardef;
  char *p, *p1, *p2, *p3;
  char macro[2048];
  static char retval[1024];
  static char *BLUMENKOHL="bLuMeNkOhL";

  retval[0] = '\0';

  for (this_var = 0; this_var < variants_active; this_var++) {
    
    this_vardef = &variantDefs[activeVariants[this_var]];
    if (this_vardef->name) {
      if ((!strcmp(name,"vpath")) && (this_vardef->vpath)) {
	if (retval[0] == '\0') {
	  strcat(retval, this_vardef->vpath);
	  macro_defined = TRUE;
	}
	else {
	  strcat(retval, " ");
	  strcat(retval, this_vardef->vpath);
	}
	continue;
      }
      
      if ((!strcmp(name, "vflags")) && (this_vardef->vflags)) {
	if (retval[0] == '\0') {
	  strcat(retval, this_vardef->vflags);
	  macro_defined = TRUE;
	}
	else {
	  strcat(retval, " ");
	  strcat(retval, this_vardef->vflags);
	}
	continue;
      }

      for(j = 0; j < MAXVMACROS; j++) {
	if (this_vardef->vmacros[j] == NIL) break;
	strcpy (macro, this_vardef->vmacros[j]);
	if (!*macro) break;

	p1 = strchr(macro, '=');
	p2 = strchr(macro, ' ');
	p3 = strchr(macro, '\t');
	
	if (p2 == NIL)
	  p2 = p1 + 1;
	
	if (p3 == NIL)
	  p3 = p1 + 1;
	
	if (( p1 < p2) && (p1 < p3))
	  p = p1;
	if (( p2 < p1) && (p2 < p3))
	  p = p2;
	if (( p3 < p2) && (p3 < p1))
	  p = p3;
	
	*p = '\0';
	p1++;

	if (!strcmp(name, macro)) {
	  
	  while((*p1 == ' ') || (*p1 == '\t') || (*p1 == '='))
	    p1++;
	  if(retval[0] == '\0') {
	    strcat(retval,p1);
	    macro_defined = TRUE;
	  }
	  else {
	    strcat(retval," ");
	    strcat(retval,p1);
	  }
	}
      }
    }
  }
  if(macro_defined)
    return(retval);
  else
    return(BLUMENKOHL);
}

LOCAL char *cleaned (string)
     char *string;
{
  static char *result_str = NIL;
  char *p;
  register int i;
  p = string;
  i = 0;
  
  if (result_str)
    result_str = check_realloc (result_str, (unsigned)(strlen (string)+1));
  else
    result_str = check_malloc ((unsigned)(strlen (string)+1));
  
  while (p && *p && isspace(*p))
    p++;
  while (*p) {
    if (isspace (*p)) {
      result_str[i] = (*p == '\n' && !*(p+1)) ? '\0' : ' ';
      while (p && *p && isspace(*p))
	p++;
      i++;
    }
    else {
      result_str[i] = *p;
      p++;
      i++;
    }
  }
  result_str[i--] = '\0';
  while (isspace(result_str[i])) {
    result_str[i] = '\0';
    i--;
  }

  return result_str;
}

#define RESBUF_SIZE 10240

LOCAL char *call_popen (str)
     char *str;
{
  char *x, *y, *result;
# define end_result (result + result_bytes)
  FILE *fd;
  register int i,j;
  int result_size = 0, result_bytes = 0, nbytes = 0;

  y = check_strdup (str);
  if (strchr (str, '$')) 
    x = check_strdup (expandmacro (y));
  else
    x = check_strdup (y);

  j = 0;
  i = 0;

  y = check_realloc (y, strlen (x) + 1);
  
  while (x[i]) {
    if((x[i] == '$') && (x[i+1] == '$'))
	i++;
      else {
	y[j] = x[i];
	i++;
	j++;
      }
  }
  y[j] = '\0';
  if ((fd = popen(y,"r")) == (FILE *) NIL)
    errexit(10,"popen");

  result = check_malloc (RESBUF_SIZE+1);
  result[0] = '\0';
  result_size = RESBUF_SIZE;
  do {
    if ((nbytes = fread (end_result,
			 sizeof(char), RESBUF_SIZE, fd)) == RESBUF_SIZE) {
      result = check_realloc (result, result_size + RESBUF_SIZE + 1);
      result_size += RESBUF_SIZE;
    }
    result_bytes += nbytes;
  } while (!feof (fd));

  *end_result = '\0';
  
  pclose (fd);
  free (x);
  free (y);
  return cleaned (result);
}


LOCAL void insertvpath(string)
     char *string;
{
  register char *p;
  register int i;
  for(i = 1; (p = strchr(string,':')) != NIL; i++)
    {
      *p = '\0';
      if((*string != '/') && (strchr(string,'$') == NIL))
	{
	  if ((curvpath[i] = malloc((unsigned) (strlen(string) + strlen(curvpath[0])
			     + 3 * sizeof(char)))) == NIL)
	    errexit(10,"malloc");
	  strcpy(curvpath[i],curvpath[0]);
	  strcat(curvpath[i],"/");
	  if ((*string == '.') && (*(string+1) == '/'))
	    {
	      string += 2;
	    }
	  else
	    {
	      if ((*string == '.') && (*(string+1) == '.') && (*(string+2) == '/'))
		{
		  *string += 3;
		}
	    }
	  strcat(curvpath[i],string);
	}
      else
	{
	  if ((curvpath[i] = malloc((unsigned) (strlen(string) + sizeof(char)))) == NIL)
	    errexit(10,"malloc");
	  strcpy(curvpath[i],string);
	}
      p++;
      string = p;
    }
  if((p == NIL) && (string != NIL))
    {
      if((*string != '/') && (strchr(string,'$') == NIL))
	{
	  if((curvpath[i] = malloc((unsigned) (strlen(string) + strlen(curvpath[0])
			     + 2 * sizeof(char)))) == NIL)
	    errexit(10,"malloc");
	  strcpy(curvpath[i],curvpath[0]);
	  strcat(curvpath[i],"/");
	  if ((*string == '.') && (*(string+1) == '/'))
	    {
	      string += 2;
	    }
	  else
	    {
	      if ((*string == '.') && (*(string+1) == '.') && (*(string+2) == '/'))
		{
		  *string += 3;
		}
	    }
	  strcat(curvpath[i],string);
	}
      else
	{
	  {
	    if((curvpath[i] = malloc((unsigned) (strlen(string) + sizeof(char)))) == NIL)
	      errexit(10,"malloc");
	    strcpy(curvpath[i],string);
	  }
	}
    }
}

LOCAL char *mirror_string (original)
     char *original;
{
  /*
   * write the string, pointed to by original backwards into a 
   * buffer that is allocated on the heap. A pointer to the 
   * buffer is returned. It's the responsibility of the caller
   * to free the associated memory.
   * If original is empty or a NULL pointer, a NULL pointer is 
   * returned. 
   */

  register int i, j;
  int olen;
  char *lanigiro;

  if (! (original && *original)) return NULL;

  olen = strlen (original);
  lanigiro = check_malloc (olen + 1);

  for ( i = 0, j = olen -1; j >= 0; i++, j--) {
    lanigiro[i] = original[j];
  }
  lanigiro[olen] = '\0';

  return lanigiro;
}
  

LOCAL char *substitute_string (subject, old, new)
     char *subject, *old, *new; {
       /*
        * replace all occurrences of substring "old" in each token in
	* "subject" by string "new". Return a pointer to a statically
	* allocated string that is the result of this operation. 
	*/

       int subject_len, result_len, old_len, new_len;
       char *result, *dlo, *wen, *tecjbus, *tluser;
       register char *p, *s;
       register int i, j;

       if (subject == NULL)
	 return "";
       else
	 subject_len = strlen (subject);

       old_len = old ? strlen (old) : 0;
       new_len = new ? strlen (new) : 0;

       result_len = subject_len + 1;

       if ((old_len == 0) || (old_len > subject_len)) {
	 strcpy (result, subject);
	 return result;
       }

       /* 
	* mirror all participating strings. The corresponding
	* pointer variables have the names of their originals backwards,
	* i.e. old -> dlo etc.
	*/

       dlo = mirror_string (old);
       wen = mirror_string (new);

       if (wen == NULL) {
	 wen = check_strdup ("");
       }

       tecjbus = mirror_string (subject);
       tluser = check_malloc (subject_len +1);

       p = tecjbus;
       s = dlo;
       j = 0;

       while (*p) {
	 i = 0;
	 while (*s && (*(p+i) == *s)) { s++; i++; }

	 if (j+new_len >= result_len) {
	   if ((tluser = (char *)realloc (tluser, j+new_len+subject_len) )
	       == (char *)NULL)
	     errexit (10, "realloc");
	   result_len = j+new_len+subject_len;
	 }
	 
	 if (*s) {
	   tluser[j++] = *p++;
	 }
	 else {
	   char *oldp;

	   p += old_len;
	   oldp = p; /* save position. the skipped text must be appended.. */

	   /* advance p to beginning of next token */
	   while (*p && !isspace(*p)) p++;
	   while (*p && isspace(*p)) p++;
	   tluser[j] = '\0';
	   strcat (tluser, wen);
	   j += new_len;
	   strncat (tluser, oldp, p - oldp);
	   j += p - oldp;
	 }
	 s = dlo;
       }
       tluser[j] = '\0';

       if (dlo) free (dlo);
       if (wen) free (wen);
       if (tecjbus) free (tecjbus);

       result = mirror_string (tluser);
       if (tluser) free (tluser);
       return result;
     }


LOCAL Bool chk_strsub (subject, old, new) 
     char *subject, **old, **new; {
       /*
	* Check if the string "subject" is of the form "str1=str2" where
	* str1 must be non-empty. If "subject" does have this form,
	* allocate space sufficient to hold "str1" and "str2" 
	* respectively. Copy str1 and str2 to these memory areas and
	* assign "old" to "str1" and "new" to "str2". Return TRUE.
	* If "subject doesn't match this form, leave the outbound 
	* parameters "old" and "new" untouched. Return FALSE.
	*/

       char *str1;
       register char *p;

       if (subject == (char *)NULL) return FALSE;

       p = subject;
       while (*p && (*p != '=')) p++;
       if (*p == '=') {
	 if (p == subject) return FALSE;

	 str1 = (char *)check_malloc ((p-subject)+1);
	 strncpy (str1, subject, p-subject);
	 *(str1+(p-subject)) = '\0';
	 p++;

	 *old = check_strdup (expandmacro (str1));
	 *new = check_strdup (expandmacro (p));
	 return TRUE;
       }
     return FALSE;
   }

EXPORT char *get_next_item(string)
     char *string;
{
  register char *p1;

  if ((p1 = strchr(string,' ')) != NIL)
    {
      *p1 = '\0';
      return string;
    }
  else
    return string;
}


LOCAL void get_macrodefs_from_env () {
  sb_ptr imports;
  char *imp_macro = expandmacro ("$(IMPORT)"), *thisdef, testmac[MACRONAM+3],
       *envval;
  int macl = imp_macro ? strlen (imp_macro) : 0;

  imports = sbnew (macl);
  imports = sbcpy (imports, imp_macro, macl);

  if (!(sbstr (imports) && *sbstr (imports))) return;
  thisdef = stStrtok (sbstr (imports));
  while (thisdef) {
    strcpy (testmac, "$(");
    strcat (testmac, thisdef);
    strcat (testmac, ")");
    if (!envflg && *expandmacro (testmac)) { /* macro defined in Shapefile */
      thisdef = stStrtok ((char *)NULL);
      continue;
    }
    envval = getenv (thisdef);
    define_macro (thisdef, envval, DYNAMIC);
    thisdef = stStrtok ((char *)NULL);
  }
}

EXPORT void define_macro (name, value, mtype)
     char *name, *value;
     int mtype;
{
  char *mt_val = "";
  register char *lptr;

  if (!(name && *name))
    return;

  if (!value) value = mt_val;

  while (*value && isspace (*value)) value++;
  lptr = value;
  while (*lptr) lptr++;
  if (lptr > value) lptr--;
  while (*lptr && isspace (*lptr)) lptr--;
  if (*lptr) 
    *(lptr+1) = '\0';

  if (!strcmp(name,"RECDEPTH")) {
    rec_do_depth = atoi(value);
  }
  addHash (name, value, mtype);
  if (!strcmp(name,"VPATH"))
    insertvpath(value);
  if (!strcmp(name,"IMPORT"))
    get_macrodefs_from_env();   /* macros are immediately defined */
}

#define	LEFT_PAREN	'('
#define	RIGHT_PAREN	')'
#define	LEFT_BRACE	'{'
#define RIGHT_BRACE	'}'


EXPORT char*
expandmacro(inpstring)
    char		*inpstring;	/* Input string to be command- and
					 * macro-expanded */
{
    char		*p;			/* Position in cmd_buf */
    char		*base;			/* Scan start in string */
    sb_ptr		cmd_buf	= sbnew(1024);	/* Cmd-expansion buffer */
    sb_ptr		mac_buf	= sbnew(1024);	/* Mac-expansion buffer */
    sb_ptr		name	= sbnew(64);	/* Macro name */
    static sb_ptr	fin_buf	= 0;		/* Final string-buffer */
    Bool                string_substitution = FALSE;
    char                *old, *new;             /* Pointers for str subst. */

    if (mac_buf == 0 ||
	cmd_buf == 0 ||
	name == 0)
	errexit(10, "sbnew");

    /*
     * Allocate final string-buffer if necessary.
     */
    if (fin_buf == 0)
	if ((fin_buf = sbnew(1024)) == 0)
	    errexit(10, "sbnew");

    /*
     * Terminate if macro nested too deeply.
     */
    if (++macdepth > 50)
	errexit(25, inpstring);

    if (no_comm_subst) {
	/*
	 * No command-expansion allowed, just copy input string to
	 * command-expansion buffer.
	 */
	if (sbcpy(cmd_buf, inpstring, strlen(inpstring)) == 0)
	    errexit(10, "sbcpy");

    } else {
	/*
	 * Command-expand input string into command-expansion buffer.
	 */
	char	*p1;		/* Points to initial '`' in input string */

	/*
	 * Loop through input string -- expanding each command-invocation
	 * in turn.
	 */
	for (base = inpstring; (p1 = strchr(base, '`')) != 0; ) {
	    char	*p2	= strchr(p1+1, '`');

	    if (p2 == 0) {
		int	len	= strlen(base);
		if (sbcat(cmd_buf, base, len) == 0)
		    errexit(10, "sbcat");
		base	+= len;
	    } else if ((p1 > base && p1[-1] == '\\') ||
			p2[-1] == '\\') {
		if (sbcat(cmd_buf, base, p2+1-base) == 0)
		    errexit(10, "sbcat");
		base	= p2 + 1;
	    } else {
		char	*expcomm;

		*p2 = '\0';
		expcomm = call_popen (p1+1);
		*p2 = '`';

		if (sbcat(cmd_buf, base, p1-base) == 0)
		    errexit(10, "sbcat");
		if (sbcat(cmd_buf, expcomm, strlen(expcomm)) == 0)
		    errexit(10, "sbcat");
		base	= p2 + 1;
	    }
	}					/* command-subst loop */

	if (sbcat(cmd_buf, base, strlen(base)) == 0)
	    errexit(10, "sbcat");
    }						/* command-subst enabled */

    /*
     * Loop through command-expanded string -- expanding each macro in turn.
     */
    for (base = sbstr(cmd_buf); (p = strchr(base, '$')) != 0; ) {

	/*
	 * Place head of string (up to current position) in macro-expansion
	 * buffer.
	 */
	if (sbcat(mac_buf, base, p-base) == 0)
	    errexit(10, "sbcat");

	if (p[1] == '@' || 			/* Special macro found */
	    p[1] == '#' ||
	    p[1] == '?' ||
	    p[1] == '<' ||
	    p[1] == '*' ||
	    p[1] == '$' ||
	    p[1] == '+') {

	    /*
	     * Special macro: just copy to buffer.
	     */
	    if (sbcat(mac_buf, p, 2) == 0)
		errexit(10, "sbcat");
	    base	= p + 2;

	} else {				/* single '$' found */

	    /*
	     * Extract name of non-special macro.
	     */
	    if (p[1] == LEFT_PAREN || p[1] == LEFT_BRACE) {
		/*
		 * ()- or {}-enclosed macro:
		 */
		char	*start	= p + 2;
		char    klauf   = p[1];
		char	klazu	= p[1] == LEFT_PAREN 
						? RIGHT_PAREN
						: RIGHT_BRACE;

		/*
		 * Macroname is terminated either by closing paren
		 * or by string susbstitution expression
		 */
		p = start;
		while (*p && (*p != klazu) && (*p != ':')) p++;

		if (sbcpy(name, start, p-start) == 0)
		    errexit(10, "sbcpy");
		if (strcmp(sbstr(name), "SHAPE") == 0 || 
		    strcmp(sbstr(name), "MAKE") == 0) {
		  shape_command	= TRUE;
		}

		if (*p == ':') {
		  register char *closer;
		  int level = 1;
		  base = p + 1;
		  while (*p && level) {
		    if (*p == klauf) level++;
		    if (*p == klazu) level--;
		    if (level) p++;
		  }
		  if (*p) {
		    *p = '\0';
		    string_substitution = chk_strsub (base, &old, &new);
		    *p = klazu;
		  }
		}

		if (*p == '\0') {
		    base	= p;
		} else {
		    base	= p + 1;
		}

	    } else {
		/*
		 * Single-character macro:
		 */
		sbcpy(name, p+1, 1);
		base	= p + 2;
	    }

	    /*
	     * Expand non-empty, non-special macro.
	     */
	    if (sblen(name) > 0) {
	      char	*variant_macro	= 0;

	      if (curvar[0] != 0) {
		char		*get_variant_macro();
		variant_macro	= get_variant_macro(sbstr(name));
		
		if (strcmp(variant_macro, "bLuMeNkOhL") != 0) {
		  char	*mist	= expandmacro(variant_macro);
		  
		  if (string_substitution) {
		    char *cooked_string;
		    
		    cooked_string = 
		      substitute_string (mist, old, new);
		    if (sbcat (mac_buf, cooked_string, 
			       strlen (cooked_string)) == 0)
		      errexit (10, "sbcat");
		    if (cooked_string) free (cooked_string);
		  }
		  else {
		    if (sbcat (mac_buf, mist, 
			       strlen (mist)) == 0)
		      errexit (10, "sbcat");
		  }
		}
	      }
	      if (variant_macro == 0 || !strcmp(variant_macro,"bLuMeNkOhL")){
		char *value, *expandedValue;
		  
		  if (!visitedHash (sbstr(name))) {
		    if ((value = getHash (sbstr(name)))) {
		      expandedValue = expandmacro (value);
		      clearHashVisited (sbstr(name));
		      
		      if (string_substitution) {
			char *cooked_string;

			cooked_string = 
			  substitute_string (expandedValue, old, new);
			if (sbcat (mac_buf, cooked_string, 
				      strlen (cooked_string)) == 0)
			  errexit (10, "sbcat");
			if (cooked_string) free (cooked_string);
		      }
		      else {
			if (sbcat(mac_buf, expandedValue, 
				strlen(expandedValue)) == 0)
			errexit(10, "sbcat");
		      }
		    }
		  }
		  else { /* recursive macro */
		    errexit (25, sbstr (name));
		  }
		}
		if (string_substitution) {
		  if (old) free (old);
		  if (new) free (new);
		  string_substitution = FALSE;
		}
	      }					/* non-empty macro name */
	  }					/* single '$' found */
      }						/* macro substitution loop */

    /*
     * Append remaining, trailing stuff to macro-expansion buffer.
     */
    if (sbcat(mac_buf, base, strlen(base)) == 0)
	errexit(10, "sbcat");

    /*
     * Copy macro-expansion buffer to final string-buffer.
     */
    if (sbcpy(fin_buf, sbstr(mac_buf), sblen(mac_buf)) == 0)
	errexit(10, "sbcpy");

    /*
     * Free temporary string-buffers.
     */
    sbfree(cmd_buf);
    sbfree(mac_buf);
    sbfree(name);

    macdepth--;

    return sbstr(fin_buf);
}

EXPORT void echo_macro(name)
     char *name;
{
  char macr[32];
  strcpy(&macr[0],"$(");
  strcat(&macr[0],name);
  strcat(&macr[0],")");
  printf("%s\n", expandmacro(macr));
}
