#include <stdio.h>
#include <string.h>
/*#include <memory.h>*/
#include "define.h"
#include "lex.h"

#define bcmp memcmp

#define MAXDEFS         512
#define STRINGSTORESIZE 20000
#define MAXPARAMS       16

typedef struct {
  int namelen;
  int params;
  char *macro;
  char *def;
} MacroDef;

#define NOARGS  -1      /* for macros without () */

typedef struct {
  int cnt;
  MacroDef table[MAXPARAMS];
} LocalEnv;

#define EMPTYLOCALENV   (LocalEnv *)NULL
#define QSIZE 32000

extern int q[QSIZE];
extern int qin;
extern void parse_error(char *msg);

static MacroDef globdefs[MAXDEFS];
static int cnt=0;       /* no of used entries in globdefs */

static char stringstore[STRINGSTORESIZE];
static char *sfree=stringstore; /* points to first unused char in store */

static char *storestring(char *s)
{
  char *pos=sfree;
  strcpy(sfree,s);
  sfree+=strlen(s)+1;

  return pos;
}

static void freestring(char *s, int n)
{
  if(sfree==s+n+1) sfree=s;
        /* only the last string stored can be freed... */
  else 
    fprintf(stderr,"Unsuccessful freestring %d\n",sfree-s-strlen(s)-1); /* while debugging */
}

/* countparams counts the number of parameters of a macro definition
 * and as a side effect separates the parameter names with '\0'
 */
static int countparams(char *p)
{
  int cnt;

  if(!*p) return NOARGS;
  /* assert: *p=='(' */
  cnt=0;
  if(p[1]!=')')         /* special case for "()" (no parameters) */
    for(;*p!=')';cnt++) {
      *p++='\0';
      p+=strcspn(p,",)");
    }
  *p='\0';
  return cnt;
}

static void defmacro(char *macro, char *definition, int params, MacroDef *d)
{
  d->namelen=strlen(macro);
  d->macro=macro;
  d->params=params;
  d->def=definition;
}

void define(char *macro,char *definition)
{
  int n;
  char *s,*def;

  if(cnt<MAXDEFS) {
    n=strcspn(macro,"(");
    s=storestring(macro);
    def= definition ? storestring(definition) : "";
    defmacro(s,def,countparams(s+n),&globdefs[cnt]);
    if(isdefined(s)) fprintf(stderr,"Redefinition of macro %s\n",s);
    cnt++;
  }
  else parse_error("Macro def table full. ");
  /*
  fprintf(stderr,"DEF %s %s\n",macro,definition);
  */
}

#define UNDEFINED NULL

static MacroDef *lookupmacro(char *name, int len, MacroDef table[], int cnt)
{
  int i;

  /* search backwards to retrieve latest definition of a macro */
  for(i=cnt-1;i>=0;i--) {
    if(table[i].namelen==len && !bcmp(table[i].macro,name,len))
        return &table[i];
  }
  return UNDEFINED;
}

static MacroDef *lookup(char *name, int len, LocalEnv *localenv)
{
  MacroDef *d;

  return localenv!=NULL &&
         (d=lookupmacro(name,len,localenv->table,localenv->cnt))!=UNDEFINED
        ? d
        : lookupmacro(name,len,globdefs,cnt);
}

void undef(char * macro)
{
  parse_error("UNDEF macro ");
}

int isdefined(char *name)
{
  return lookupmacro(name,strlen(name),globdefs,cnt)!=UNDEFINED;
}

static int bindargs(MacroDef *d, char **p0, LocalEnv *localenv,LocalEnv *prevenv)
{
  char *par,*arg,*p,c;
  int params;

  p= *p0;
  params=d->params;
  localenv->cnt=0;
  if(params<0) return 1;
  p=skipspace(p);
  if(*p!='(') return 0;
  else if(params==0) {
    if(p[1]==')') {
      *p0=p+2;
      return 1;
    }
    else
      return 0;
  }
  else {
    par=d->macro;               /* par points to the macro name... */
    do {
      arg=p+1;
      p=skiparg(p,',',')');     /* find end of macro argument */
      c= *p;                    /* save terminating char */
      *p='\0';
      par+=strlen(par)+1;       /* find start of next parameter name */
      /*
      fprintf(stderr,"1 param %s=%s\n",par,arg);
      */
      { MacroDef *dt;
        dt=lookup(arg,strlen(arg),prevenv);
        if(dt!=UNDEFINED) arg = dt->def;
      }
      /*
      fprintf(stderr,"2 param %s=%s\n",par,arg);
      */
      defmacro(par,arg,NOARGS,&localenv->table[localenv->cnt++]);
    } while(--params>0 && c==',');
    /*
    fprintf(stderr,"exit %d %c\n",params,c);
    */
    *p0=p+1;
    /*
    fprintf(stderr,"return %d\n",params==0 && c==')');
    */
    return params==0 && c==')';
  }
}

static char *output(char *p, int n)
{
  int i;
  for ( i = 0; i < n ; i++)
       q[qin++] = (int) *(p++);
  return p;
}

static void expand(char *linebuf, LocalEnv *localenv);  /* forward */

static char *expandmacro(MacroDef *d, char *p0, LocalEnv *prevenv)
{
  char *p=p0;
  LocalEnv localenv;

  if(!bindargs(d,&p,&localenv,prevenv)) {
    fprintf(stderr,"Macro %s needs %d argument(s).\n",d->macro,d->params);
    p=output(d->macro,d->namelen);
        /* should leave the macro call unexpanded !! */
  }
  else {
    char *def=storestring(d->def);
    int len=strlen(def);
        /* expand destroys the string, so must make a copy of the macro def */
    expand(def,&localenv);
    freestring(def,len);
  }
  return p;
}

static void expand(char *linebuf, LocalEnv *localenv)
{
  static char idOrStringStart[]="\"#_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
  static char idInside[]="_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
  char *p;
  int n;
  MacroDef *d;

  for(p=linebuf;*p;) {
    n=strcspn(p,idOrStringStart);
    p=output(p,n);
    if(*p=='"') p=output(p,skipstring(p)-p);
    else 
    if(p[0] == '#') {
        if(p[1] == '#') p+=2;
        else p = output(p,1);
    } else {
      n=strspn(p,idInside);
      if(n>0) {
        d=lookup(p,n,localenv);
        if(d!=UNDEFINED) p=expandmacro(d,p+n,localenv);
        else p=output(p,n);
      }
    }
  }
}

void expandandputline(char *linebuf)
{
  expand(linebuf,EMPTYLOCALENV);
}

void printmemusage(void)
{
  int used=sfree-stringstore;

  fprintf(stderr,"Used %d of %d entries (%d%%) in macro def table.\n",
                 cnt,MAXDEFS,100*cnt/MAXDEFS);
  fprintf(stderr,"Used %d of %d bytes (%d%%) for macro defs.\n",
                 used,STRINGSTORESIZE,100*used/STRINGSTORESIZE);
}
