Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * 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(""));
}