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