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