/*
* 7.  Macros, strings, diversion, and position traps.
*
*      macros can override builtins
*      builtins can be renamed or removed!
*/

#include "a.h"

enum
{
       MAXARG = 10,
       MAXMSTACK = 40
};

/* macro invocation frame */
typedef struct Mac Mac;
struct Mac
{
       int argc;
       Rune *argv[MAXARG];
};

Mac             mstack[MAXMSTACK];
int             nmstack;
void            emitdi(void);
void            flushdi(void);

/*
* Run a user-defined macro.
*/
void popmacro(void);
int
runmacro(int dot, int argc, Rune **argv)
{
       Rune *p;
       int i;
       Mac *m;

if(verbose && isupperrune(argv[0][0])) fprint(2, "run: %S\n", argv[0]);
       p = getds(argv[0]);
       if(p == nil){
               if(verbose)
                       warn("ignoring unknown request %C%S", dot, argv[0]);
               if(verbose > 1){
                       for(i=0; i<argc; i++)
                               fprint(2, " %S", argv[i]);
                       fprint(2, "\n");
               }
               return -1;
       }
       if(nmstack >= nelem(mstack)){
               fprint(2, "%L: macro stack overflow:");
               for(i=0; i<nmstack; i++)
                       fprint(2, " %S", mstack[i].argv[0]);
               fprint(2, "\n");
               return -1;
       }
       m = &mstack[nmstack++];
       m->argc = argc;
       for(i=0; i<argc; i++)
               m->argv[i] = erunestrdup(argv[i]);
       pushinputstring(p);
       nr(L(".$"), argc-1);
       inputnotify(popmacro);
       return 0;
}

void
popmacro(void)
{
       int i;
       Mac *m;

       if(--nmstack < 0){
               fprint(2, "%L: macro stack underflow\n");
               return;
       }
       m = &mstack[nmstack];
       for(i=0; i<m->argc; i++)
               free(m->argv[i]);
       if(nmstack > 0)
               nr(L(".$"), mstack[nmstack-1].argc-1);
       else
               nr(L(".$"), 0);
}

void popmacro1(void);
jmp_buf runjb[10];
int nrunjb;

void
runmacro1(Rune *name)
{
       Rune *argv[2];
       int obol;

if(verbose) fprint(2, "outcb %p\n", outcb);
       obol = bol;
       argv[0] = name;
       argv[1] = nil;
       bol = 1;
       if(runmacro('.', 1, argv) >= 0){
               inputnotify(popmacro1);
               if(!setjmp(runjb[nrunjb++]))
                       runinput();
               else
                       if(verbose) fprint(2, "finished %S\n", name);
       }
       bol = obol;
}

void
popmacro1(void)
{
       popmacro();
       if(nrunjb >= 0)
               longjmp(runjb[--nrunjb], 1);
}

/*
* macro arguments
*
*      "" means " inside " "
*      "" empty string
*      \newline can be done
*      argument separator is space (not tab)
*      number register .$ = number of arguments
*      no arguments outside macros or in strings
*
*      arguments copied in copy mode
*/

/*
* diversions
*
*      processed output diverted
*      dn dl registers vertical and horizontal size of last diversion
*      .z - current diversion name
*/

/*
* traps
*
*      skip most
*      .t register - distance to next trap
*/
static Rune *trap0;

void
outtrap(void)
{
       Rune *t;

       if(outcb)
               return;
       if(trap0){
if(verbose) fprint(2, "trap: %S\n", trap0);
               t = trap0;
               trap0 = nil;
               runmacro1(t);
               free(t);
       }
}

/* .wh - install trap */
void
r_wh(int argc, Rune **argv)
{
       int i;

       if(argc < 2)
               return;

       i = eval(argv[1]);
       if(argc == 2){
               if(i == 0){
                       free(trap0);
                       trap0 = nil;
               }else
                       if(verbose)
                               warn("not removing trap at %d", i);
       }
       if(argc > 2){
               if(i == 0){
                       free(trap0);
                       trap0 = erunestrdup(argv[2]);
               }else
                       if(verbose)
                               warn("not installing %S trap at %d", argv[2], i);
       }
}

void
r_ch(int argc, Rune **argv)
{
       int i;

       if(argc == 2){
               if(trap0 && runestrcmp(argv[1], trap0) == 0){
                       free(trap0);
                       trap0 = nil;
               }else
                       if(verbose)
                               warn("not removing %S trap", argv[1]);
               return;
       }
       if(argc >= 3){
               i = eval(argv[2]);
               if(i == 0){
                       free(trap0);
                       trap0 = erunestrdup(argv[1]);
               }else
                       if(verbose)
                               warn("not moving %S trap to %d", argv[1], i);
       }
}

void
r_dt(int argc, Rune **argv)
{
       USED(argc);
       USED(argv);
       warn("ignoring diversion trap");
}

/* define macro - .de, .am, .ig */
void
r_de(int argc, Rune **argv)
{
       Rune *end, *p;
       Fmt fmt;
       int ignore, len;

       delreq(argv[1]);
       delraw(argv[1]);
       ignore = runestrcmp(argv[0], L("ig")) == 0;
       if(!ignore)
               runefmtstrinit(&fmt);
       end = L("..");
       if(argc >= 3)
               end = argv[2];
       if(runestrcmp(argv[0], L("am")) == 0 && (p=getds(argv[1])) != nil)
               fmtrunestrcpy(&fmt, p);
       len = runestrlen(end);
       while((p = readline(CopyMode)) != nil){
               if(runestrncmp(p, end, len) == 0
               && (p[len]==' ' || p[len]==0 || p[len]=='\t'
                       || (p[len]=='\\' && p[len+1]=='}'))){
                       free(p);
                       goto done;
               }
               if(!ignore)
                       fmtprint(&fmt, "%S\n", p);
               free(p);
       }
       warn("eof in %C%S %S - looking for %#Q", dot, argv[0], argv[1], end);
done:
       if(ignore)
               return;
       p = runefmtstrflush(&fmt);
       if(p == nil)
               sysfatal("out of memory");
       ds(argv[1], p);
       free(p);
}

/* define string .ds .as */
void
r_ds(Rune *cmd)
{
       Rune *name, *line, *p;

       name = copyarg();
       line = readline(CopyMode);
       if(name == nil || line == nil){
               free(name);
               return;
       }
       p = line;
       if(*p == '"')
               p++;
       if(cmd[0] == 'd')
               ds(name, p);
       else
               as(name, p);
       free(name);
       free(line);
}

/* remove request, macro, or string */
void
r_rm(int argc, Rune **argv)
{
       int i;

       emitdi();
       for(i=1; i<argc; i++){
               delreq(argv[i]);
               delraw(argv[i]);
               ds(argv[i], nil);
       }
}

/* .rn - rename request, macro, or string */
void
r_rn(int argc, Rune **argv)
{
       USED(argc);
       renreq(argv[1], argv[2]);
       renraw(argv[1], argv[2]);
       ds(argv[2], getds(argv[1]));
       ds(argv[1], nil);
}

/* .di - divert output to macro xx */
/* .da - divert, appending to macro */
/* page offsetting is not done! */
Fmt difmt;
int difmtinit;
Rune di[20][100];
int ndi;

void
emitdi(void)
{
       flushdi();
       runefmtstrinit(&difmt);
       difmtinit = 1;
       fmtrune(&difmt, Uformatted);
}

void
flushdi(void)
{
       int n;
       Rune *p;

       if(ndi == 0 || difmtinit == 0)
               return;
       fmtrune(&difmt, Uunformatted);
       p = runefmtstrflush(&difmt);
       memset(&difmt, 0, sizeof difmt);
       difmtinit = 0;
       if(p == nil)
               warn("out of memory in diversion %C%S", dot, di[ndi-1]);
       else{
               n = runestrlen(p);
               if(n > 0 && p[n-1] != '\n'){
                       p = runerealloc(p, n+2);
                       p[n] = '\n';
                       p[n+1] = 0;
               }
       }
       as(di[ndi-1], p);
       free(p);
}

void
outdi(Rune r)
{
if(!difmtinit) abort();
       if(r == Uempty)
               return;
       fmtrune(&difmt, r);
}

/* .di, .da */
void
r_di(int argc, Rune **argv)
{
       br();
       if(argc > 2)
               warn("extra arguments to %C%S", dot, argv[0]);
       if(argc == 1){
               /* end diversion */
               if(ndi <= 0){
                       // warn("unmatched %C%S", dot, argv[0]);
                       return;
               }
               flushdi();
               if(--ndi == 0){
                       _nr(L(".z"), nil);
                       outcb = nil;
               }else{
                       _nr(L(".z"), di[ndi-1]);
                       runefmtstrinit(&difmt);
                       fmtrune(&difmt, Uformatted);
                       difmtinit = 1;
               }
               return;
       }
       /* start diversion */
       /* various register state should be saved, but it's all useless to us */
       flushdi();
       if(ndi >= nelem(di))
               sysfatal("%Cdi overflow", dot);
       if(argv[0][1] == 'i')
               ds(argv[1], nil);
       _nr(L(".z"), argv[1]);
       runestrcpy(di[ndi++], argv[1]);
       runefmtstrinit(&difmt);
       fmtrune(&difmt, Uformatted);
       difmtinit = 1;
       outcb = outdi;
}

/* .wh - install trap */
/* .ch - change trap */
/* .dt - install diversion trap */

/* set input-line count trap */
int itrapcount;
int itrapwaiting;
Rune *itrapname;

void
r_it(int argc, Rune **argv)
{
       if(argc < 3){
               itrapcount = 0;
               return;
       }
       itrapcount = eval(argv[1]);
       free(itrapname);
       itrapname = erunestrdup(argv[2]);
}

void
itrap(void)
{
       itrapset();
       if(itrapwaiting){
               itrapwaiting = 0;
               runmacro1(itrapname);
       }
}

void
itrapset(void)
{
       if(itrapcount > 0 && --itrapcount == 0)
               itrapwaiting = 1;
}

/* .em - invoke macro when all input is over */
void
r_em(int argc, Rune **argv)
{
       Rune buf[20];

       USED(argc);
       runesnprint(buf, nelem(buf), ".%S\n", argv[1]);
       as(L("eof"), buf);
}

int
e_star(void)
{
       Rune *p;

       p = getds(getname());
       if(p)
               pushinputstring(p);
       return 0;
}

int
e_t(void)
{
       if(inputmode&CopyMode)
               return '\t';
       return 0;
}

int
e_a(void)
{
       if(inputmode&CopyMode)
               return '\a';
       return 0;
}

int
e_backslash(void)
{
       if(inputmode&ArgMode)
               ungetrune('\\');
       return backslash;
}

int
e_dot(void)
{
       return '.';
}

int
e_dollar(void)
{
       int c;

       c = getnext();
       if(c < '1' || c > '9'){
               ungetnext(c);
               return 0;
       }
       c -= '0';
       if(nmstack <= 0 || mstack[nmstack-1].argc <= c)
               return 0;
       pushinputstring(mstack[nmstack-1].argv[c]);
       return 0;
}

void
t7init(void)
{
       addreq(L("de"), r_de, -1);
       addreq(L("am"), r_de, -1);
       addreq(L("ig"), r_de, -1);
       addraw(L("ds"), r_ds);
       addraw(L("as"), r_ds);
       addreq(L("rm"), r_rm, -1);
       addreq(L("rn"), r_rn, -1);
       addreq(L("di"), r_di, -1);
       addreq(L("da"), r_di, -1);
       addreq(L("it"), r_it, -1);
       addreq(L("em"), r_em, 1);
       addreq(L("wh"), r_wh, -1);
       addreq(L("ch"), r_ch, -1);
       addreq(L("dt"), r_dt, -1);

       addesc('$', e_dollar, CopyMode|ArgMode|HtmlMode);
       addesc('*', e_star, CopyMode|ArgMode|HtmlMode);
       addesc('t', e_t, CopyMode|ArgMode);
       addesc('a', e_a, CopyMode|ArgMode);
       addesc('\\', e_backslash, ArgMode|CopyMode);
       addesc('.', e_dot, CopyMode|ArgMode);

       ds(L("eof"), L(".sp 0.5i\n"));
       ds(L(".."), L(""));
}