ref: 94916808dd9a53ac297ab537851791894b919828
dir: /sys/src/cmd/acid/builtin.c/
#include <u.h> #include <libc.h> #include <bio.h> #include <ctype.h> #include <mach.h> #include <regexp.h> #define Extern extern #include "acid.h" #include "y.tab.h" void cvtatof(Node*, Node*); void cvtatoi(Node*, Node*); void cvtitoa(Node*, Node*); void bprint(Node*, Node*); void funcbound(Node*, Node*); void printto(Node*, Node*); void getfile(Node*, Node*); void fmt(Node*, Node*); void pcfile(Node*, Node*); void pcline(Node*, Node*); void setproc(Node*, Node*); void strace(Node*, Node*); void follow(Node*, Node*); void reason(Node*, Node*); void newproc(Node*, Node*); void startstop(Node*, Node*); void match(Node*, Node*); void status(Node*, Node*); void kill(Node*,Node*); void waitstop(Node*, Node*); void stop(Node*, Node*); void start(Node*, Node*); void filepc(Node*, Node*); void doerror(Node*, Node*); void rc(Node*, Node*); void doaccess(Node*, Node*); void map(Node*, Node*); void readfile(Node*, Node*); void interpret(Node*, Node*); void include(Node*, Node*); void regexp(Node*, Node*); void dosysr1(Node*, Node*); void fmtof(Node*, Node*) ; void dofmtsize(Node*, Node*) ; void dogetfields(Node*, Node*); typedef struct Btab Btab; struct Btab { char *name; void (*fn)(Node*, Node*); } tab[] = { "atof", cvtatof, "atoi", cvtatoi, "error", doerror, "file", getfile, "readfile", readfile, "access", doaccess, "filepc", filepc, "fnbound", funcbound, "fmt", fmt, "follow", follow, "getfields", dogetfields, "itoa", cvtitoa, "kill", kill, "match", match, "newproc", newproc, "pcfile", pcfile, "pcline", pcline, "print", bprint, "printto", printto, "rc", rc, "reason", reason, "setproc", setproc, "start", start, "startstop", startstop, "status", status, "stop", stop, "strace", strace, "sysr1", dosysr1, "waitstop", waitstop, "map", map, "interpret", interpret, "include", include, "regexp", regexp, "fmtof", fmtof, "fmtsize", dofmtsize, 0 }; char vfmt[] = "aABbcCdDfFgGiIoOqQrRsSuUVWxXYZ38"; void mkprint(Lsym *s) { prnt = malloc(sizeof(Node)); memset(prnt, 0, sizeof(Node)); prnt->op = OCALL; prnt->left = malloc(sizeof(Node)); memset(prnt->left, 0, sizeof(Node)); prnt->left->sym = s; } void installbuiltin(void) { Btab *b; Lsym *s; b = tab; while(b->name) { s = look(b->name); if(s == 0) s = enter(b->name, Tid); s->builtin = b->fn; if(b->fn == bprint) mkprint(s); b++; } } void dosysr1(Node *r, Node*) { /* dummy argument for RARG spill */ extern int sysr1(void*); r->op = OCONST; r->type = TINT; r->fmt = 'D'; r->ival = sysr1(0); } void match(Node *r, Node *args) { int i; List *f; Node *av[Maxarg]; Node resi, resl; na = 0; flatten(av, args); if(na != 2) error("match(obj, list): arg count"); expr(av[1], &resl); if(resl.type != TLIST) error("match(obj, list): need list"); expr(av[0], &resi); r->op = OCONST; r->type = TINT; r->fmt = 'D'; r->ival = -1; i = 0; for(f = resl.l; f; f = f->next) { if(resi.type == f->type) { switch(resi.type) { case TINT: if(resi.ival == f->ival) { r->ival = i; return; } break; case TFLOAT: if(resi.fval == f->fval) { r->ival = i; return; } break; case TSTRING: if(scmp(resi.string, f->string)) { r->ival = i; return; } break; case TLIST: error("match(obj, list): not defined for list"); } } i++; } } void newproc(Node *r, Node *args) { int i; Node res; char *p, *e; char *argv[Maxarg], buf[Strsize]; i = 1; argv[0] = aout; if(args) { expr(args, &res); if(res.type != TSTRING) error("newproc(): arg not string"); if(res.string->len >= sizeof(buf)) error("newproc(): too many arguments"); memmove(buf, res.string->string, res.string->len); buf[res.string->len] = '\0'; p = buf; e = buf+res.string->len; for(;;) { while(p < e && (*p == '\t' || *p == ' ')) *p++ = '\0'; if(p >= e) break; argv[i++] = p; if(i >= Maxarg) error("newproc: too many arguments"); while(p < e && *p != '\t' && *p != ' ') p++; } } argv[i] = 0; r->op = OCONST; r->type = TINT; r->fmt = 'D'; r->ival = nproc(argv); } void startstop(Node *r, Node *args) { Node res; USED(r); if(args == 0) error("startstop(pid): no pid"); expr(args, &res); if(res.type != TINT) error("startstop(pid): arg type"); msg(res.ival, "startstop"); notes(res.ival); dostop(res.ival); } void waitstop(Node *r, Node *args) { Node res; USED(r); if(args == 0) error("waitstop(pid): no pid"); expr(args, &res); if(res.type != TINT) error("waitstop(pid): arg type"); Bflush(bout); msg(res.ival, "waitstop"); notes(res.ival); dostop(res.ival); } void start(Node *r, Node *args) { Node res; USED(r); if(args == 0) error("start(pid): no pid"); expr(args, &res); if(res.type != TINT) error("start(pid): arg type"); msg(res.ival, "start"); } void stop(Node *r, Node *args) { Node res; USED(r); if(args == 0) error("stop(pid): no pid"); expr(args, &res); if(res.type != TINT) error("stop(pid): arg type"); Bflush(bout); msg(res.ival, "stop"); notes(res.ival); dostop(res.ival); } void kill(Node *r, Node *args) { Node res; USED(r); if(args == 0) error("kill(pid): no pid"); expr(args, &res); if(res.type != TINT) error("kill(pid): arg type"); msg(res.ival, "kill"); deinstall(res.ival); } void status(Node *r, Node *args) { Node res; char *p; USED(r); if(args == 0) error("status(pid): no pid"); expr(args, &res); if(res.type != TINT) error("status(pid): arg type"); p = getstatus(res.ival); r->string = strnode(p); r->op = OCONST; r->fmt = 's'; r->type = TSTRING; } void reason(Node *r, Node *args) { Node res; if(args == 0) error("reason(cause): no cause"); expr(args, &res); if(res.type != TINT) error("reason(cause): arg type"); r->op = OCONST; r->type = TSTRING; r->fmt = 's'; r->string = strnode((*machdata->excep)(cormap, rget)); } void follow(Node *r, Node *args) { int n, i; Node res; uvlong f[10]; List **tail, *l; if(args == 0) error("follow(addr): no addr"); expr(args, &res); if(res.type != TINT) error("follow(addr): arg type"); n = (*machdata->foll)(cormap, res.ival, rget, f); if (n < 0) error("follow(addr): %r"); tail = &r->l; for(i = 0; i < n; i++) { l = al(TINT); l->ival = f[i]; l->fmt = 'A'; *tail = l; tail = &l->next; } } void funcbound(Node *r, Node *args) { int n; Node res; uvlong bounds[2]; List *l; if(args == 0) error("fnbound(addr): no addr"); expr(args, &res); if(res.type != TINT) error("fnbound(addr): arg type"); n = fnbound(res.ival, bounds); if (n != 0) { r->l = al(TINT); l = r->l; l->ival = bounds[0]; l->fmt = 'A'; l->next = al(TINT); l = l->next; l->ival = bounds[1]; l->fmt = 'A'; } } void setproc(Node *r, Node *args) { Node res; USED(r); if(args == 0) error("setproc(pid): no pid"); expr(args, &res); if(res.type != TINT) error("setproc(pid): arg type"); sproc(res.ival); } void filepc(Node *r, Node *args) { Node res; char *p, c; if(args == 0) error("filepc(filename:line): arg count"); expr(args, &res); if(res.type != TSTRING) error("filepc(filename:line): arg type"); p = strchr(res.string->string, ':'); if(p == 0) error("filepc(filename:line): bad arg format"); c = *p; *p++ = '\0'; r->ival = file2pc(res.string->string, strtol(p, 0, 0)); p[-1] = c; if(r->ival == ~0) error("filepc(filename:line): can't find address"); r->op = OCONST; r->type = TINT; r->fmt = 'V'; } void interpret(Node *r, Node *args) { Node res; int isave; if(args == 0) error("interpret(string): arg count"); expr(args, &res); if(res.type != TSTRING) error("interpret(string): arg type"); pushstr(&res); isave = interactive; interactive = 0; r->ival = yyparse(); interactive = isave; popio(); r->op = OCONST; r->type = TINT; r->fmt = 'D'; } void include(Node *r, Node *args) { Node res; int isave; if(args == 0) error("include(string): arg count"); expr(args, &res); if(res.type != TSTRING) error("include(string): arg type"); pushfile(res.string->string); isave = interactive; interactive = 0; r->ival = yyparse(); interactive = isave; popio(); r->op = OCONST; r->type = TINT; r->fmt = 'D'; } void rc(Node *r, Node *args) { Node res; int pid; char *p, *q, *argv[4]; Waitmsg *w; USED(r); if(args == 0) error("error(string): arg count"); expr(args, &res); if(res.type != TSTRING) error("error(string): arg type"); argv[0] = "/bin/rc"; argv[1] = "-c"; argv[2] = res.string->string; argv[3] = 0; pid = fork(); switch(pid) { case -1: error("fork %r"); case 0: exec("/bin/rc", argv); exits(0); default: w = waitfor(pid); break; } p = w->msg; q = strrchr(p, ':'); if (q) p = q+1; r->op = OCONST; r->type = TSTRING; r->string = strnode(p); free(w); r->fmt = 's'; } void doerror(Node *r, Node *args) { Node res; USED(r); if(args == 0) error("error(string): arg count"); expr(args, &res); if(res.type != TSTRING) error("error(string): arg type"); error(res.string->string); } void doaccess(Node *r, Node *args) { Node res; if(args == 0) error("access(filename): arg count"); expr(args, &res); if(res.type != TSTRING) error("access(filename): arg type"); r->op = OCONST; r->type = TINT; r->ival = 0; if(access(res.string->string, 4) == 0) r->ival = 1; } void readfile(Node *r, Node *args) { Node res; int n, fd; char *buf; Dir *db; if(args == 0) error("readfile(filename): arg count"); expr(args, &res); if(res.type != TSTRING) error("readfile(filename): arg type"); fd = open(res.string->string, OREAD); if(fd < 0) return; db = dirfstat(fd); if(db == nil || db->length == 0) n = 8192; else n = db->length; free(db); buf = malloc(n); n = read(fd, buf, n); if(n > 0) { r->op = OCONST; r->type = TSTRING; r->string = strnodlen(buf, n); r->fmt = 's'; } free(buf); close(fd); } void getfile(Node *r, Node *args) { int n; char *p; Node res; String *s; Biobuf *bp; List **l, *new; if(args == 0) error("file(filename): arg count"); expr(args, &res); if(res.type != TSTRING) error("file(filename): arg type"); r->op = OCONST; r->type = TLIST; r->l = 0; p = res.string->string; bp = Bopen(p, OREAD); if(bp == 0) return; l = &r->l; for(;;) { p = Brdline(bp, '\n'); n = Blinelen(bp); if(p == 0) { if(n == 0) break; s = strnodlen(0, n); Bread(bp, s->string, n); } else s = strnodlen(p, n-1); new = al(TSTRING); new->string = s; new->fmt = 's'; *l = new; l = &new->next; } Bterm(bp); } void cvtatof(Node *r, Node *args) { Node res; if(args == 0) error("atof(string): arg count"); expr(args, &res); if(res.type != TSTRING) error("atof(string): arg type"); r->op = OCONST; r->type = TFLOAT; r->fval = atof(res.string->string); r->fmt = 'f'; } void cvtatoi(Node *r, Node *args) { Node res; if(args == 0) error("atoi(string): arg count"); expr(args, &res); if(res.type != TSTRING) error("atoi(string): arg type"); r->op = OCONST; r->type = TINT; r->ival = strtoull(res.string->string, 0, 0); r->fmt = 'V'; } static char *fmtflags = "-0123456789. #,u"; static char *fmtverbs = "bdox"; static int acidfmt(char *fmt, char *buf, int blen) { char *r, *w, *e; w = buf; e = buf+blen; for(r=fmt; *r; r++){ if(w >= e) return -1; if(*r != '%'){ *w++ = *r; continue; } if(*r == '%'){ *w++ = *r++; if(*r == '%'){ if(w >= e) return -1; *w++ = *r; continue; } while(*r && strchr(fmtflags, *r)){ if(w >= e) return -1; *w++ = *r++; } if(*r == 0 || strchr(fmtverbs, *r) == nil) return -1; if(w+3 > e) return -1; *w++ = 'l'; *w++ = 'l'; *w++ = *r; } } if(w >= e) return -1; *w = 0; return 0; } void cvtitoa(Node *r, Node *args) { Node res; Node *av[Maxarg]; vlong ival; char buf[128], fmt[32]; if(args == 0) err: error("itoa(number [, fmt]): arg count"); na = 0; flatten(av, args); if(na == 0 || na > 2) goto err; expr(av[0], &res); if(res.type != TINT) error("itoa(number [, fmt]): arg type"); ival = res.ival; strncpy(fmt, "%lld", sizeof(fmt)); if(na == 2){ expr(av[1], &res); if(res.type != TSTRING) error("itoa(number [, fmt]): fmt type"); if(acidfmt(res.string->string, fmt, sizeof(fmt))) error("itoa(number [, fmt]): malformed fmt"); } snprint(buf, sizeof(buf), fmt, ival); r->op = OCONST; r->type = TSTRING; r->string = strnode(buf); r->fmt = 's'; } List* mapent(Map *m) { int i; List *l, *n, **t, *h; h = 0; t = &h; for(i = 0; i < m->nsegs; i++) { if(m->seg[i].inuse == 0) continue; l = al(TSTRING); n = al(TLIST); n->l = l; *t = n; t = &n->next; l->string = strnode(m->seg[i].name); l->fmt = 's'; l->next = al(TINT); l = l->next; l->ival = m->seg[i].b; l->fmt = 'W'; l->next = al(TINT); l = l->next; l->ival = m->seg[i].e; l->fmt = 'W'; l->next = al(TINT); l = l->next; l->ival = m->seg[i].f; l->fmt = 'W'; } return h; } void map(Node *r, Node *args) { int i; Map *m; List *l; char *ent; Node *av[Maxarg], res; na = 0; flatten(av, args); if(na != 0) { expr(av[0], &res); if(res.type != TLIST) error("map(list): map needs a list"); if(listlen(res.l) != 4) error("map(list): list must have 4 entries"); l = res.l; if(l->type != TSTRING) error("map name must be a string"); ent = l->string->string; m = symmap; i = findseg(m, ent); if(i < 0) { m = cormap; i = findseg(m, ent); } if(i < 0) error("%s is not a map entry", ent); l = l->next; if(l->type != TINT) error("map entry not int"); m->seg[i].b = l->ival; if (strcmp(ent, "text") == 0) textseg(l->ival, &fhdr); l = l->next; if(l->type != TINT) error("map entry not int"); m->seg[i].e = l->ival; l = l->next; if(l->type != TINT) error("map entry not int"); m->seg[i].f = l->ival; } r->type = TLIST; r->l = 0; if(symmap) r->l = mapent(symmap); if(cormap) { if(r->l == 0) r->l = mapent(cormap); else { for(l = r->l; l->next; l = l->next) ; l->next = mapent(cormap); } } } void flatten(Node **av, Node *n) { if(n == 0) return; switch(n->op) { case OLIST: flatten(av, n->left); flatten(av, n->right); break; default: av[na++] = n; if(na >= Maxarg) error("too many function arguments"); break; } } void strace(Node *r, Node *args) { Node *av[Maxarg], *n, res; uvlong pc, sp; na = 0; flatten(av, args); if(na != 3) error("strace(pc, sp, link): arg count"); n = av[0]; expr(n, &res); if(res.type != TINT) error("strace(pc, sp, link): pc bad type"); pc = res.ival; n = av[1]; expr(n, &res); if(res.type != TINT) error("strace(pc, sp, link): sp bad type"); sp = res.ival; n = av[2]; expr(n, &res); if(res.type != TINT) error("strace(pc, sp, link): link bad type"); tracelist = 0; if ((*machdata->ctrace)(cormap, pc, sp, res.ival, trlist) <= 0) error("no stack frame: %r"); r->type = TLIST; r->l = tracelist; } void regerror(char *msg) { error(msg); } void regexp(Node *r, Node *args) { Node res; Reprog *rp; Node *av[Maxarg]; na = 0; flatten(av, args); if(na != 2) error("regexp(pattern, string): arg count"); expr(av[0], &res); if(res.type != TSTRING) error("regexp(pattern, string): pattern must be string"); rp = regcomp(res.string->string); if(rp == 0) return; expr(av[1], &res); if(res.type != TSTRING) error("regexp(pattern, string): bad string"); r->fmt = 'D'; r->type = TINT; r->ival = regexec(rp, res.string->string, 0, 0); free(rp); } void fmt(Node *r, Node *args) { Node res; Node *av[Maxarg]; na = 0; flatten(av, args); if(na != 2) error("fmt(obj, fmt): arg count"); expr(av[1], &res); if(res.type != TINT || strchr(vfmt, res.ival) == 0) error("fmt(obj, fmt): bad format '%c'", (char)res.ival); expr(av[0], r); r->fmt = res.ival; } void patom(char type, Store *res) { int i; char fmt; char buf[512]; extern char *typenames[]; fmt = res->fmt; if(fmt == 'A') fmt = afmt; switch(fmt) { case 'c': Bprint(bout, "%c", (int)res->ival); break; case 'C': if(res->ival < ' ' || res->ival >= 0x7f) Bprint(bout, "%3d", (int)res->ival&0xff); else Bprint(bout, "%3c", (int)res->ival); break; case 'r': Bprint(bout, "%C", (int)res->ival); break; case 'B': memset(buf, '0', 34); buf[1] = 'b'; for(i = 0; i < 32; i++) { if(res->ival & (1<<i)) buf[33-i] = '1'; } buf[35] = '\0'; Bprint(bout, "%s", buf); break; case 'b': Bprint(bout, "%.2x", (int)res->ival&0xff); break; case 'X': Bprint(bout, "%.8lux", (ulong)res->ival); break; case 'x': Bprint(bout, "%.4lux", (ulong)res->ival&0xffff); break; case 'D': Bprint(bout, "%d", (int)res->ival); break; case 'd': Bprint(bout, "%hd", (short)res->ival); break; case 'u': Bprint(bout, "%hud", (ushort)res->ival); break; case 'U': Bprint(bout, "%lud", (ulong)res->ival); break; case 'Z': Bprint(bout, "%llud", res->ival); break; case 'V': Bprint(bout, "%lld", res->ival); break; case 'W': Bprint(bout, "%.8llux", res->ival); break; case 'Y': Bprint(bout, "%.16llux", res->ival); break; case 'o': Bprint(bout, "0%.11uo", (int)res->ival&0xffff); break; case 'O': Bprint(bout, "0%.6uo", (int)res->ival); break; case 'q': Bprint(bout, "0%.11o", (short)(res->ival&0xffff)); break; case 'Q': Bprint(bout, "0%.6o", (int)res->ival); break; case 'f': case 'F': case '3': case '8': if(type != TFLOAT) Bprint(bout, "*%c<%s>*", res->fmt, typenames[type]); else Bprint(bout, "%g", res->fval); break; case 's': case 'g': case 'G': if(type != TSTRING) Bprint(bout, "*%c<%s>*", res->fmt, typenames[type]); else Bwrite(bout, res->string->string, res->string->len); break; case 'R': if(type != TSTRING) Bprint(bout, "*%c<%s>*", res->fmt, typenames[type]); else Bprint(bout, "%S", (Rune*)res->string->string); break; case 'a': symoff(buf, sizeof(buf), res->ival, CANY); Bprint(bout, "%s", buf); break; case 'I': case 'i': if(type != TINT) Bprint(bout, "*%c<%s>*", res->fmt, typenames[type]); else { if (symmap == nil || (*machdata->das)(symmap, res->ival, res->fmt, buf, sizeof(buf)) < 0) Bprint(bout, "no instruction"); else Bprint(bout, "%s", buf); } break; } } void blprint(List *l) { Bprint(bout, "{"); while(l) { switch(l->type) { default: patom(l->type, &l->Store); break; case TSTRING: Bputc(bout, '"'); patom(l->type, &l->Store); Bputc(bout, '"'); break; case TLIST: blprint(l->l); break; case TCODE: pcode(l->cc, 0); break; } l = l->next; if(l) Bprint(bout, ", "); } Bprint(bout, "}"); } int comx(Node res) { Lsym *sl; Node *n, xx; if(res.fmt != 'a' || res.comt == 0 || res.comt->base == 0) return 0; sl = res.comt->base; if(sl->proc) { res.left = ZN; res.right = ZN; n = an(ONAME, ZN, ZN); n->sym = sl; n = an(OCALL, n, &res); n->left->sym = sl; expr(n, &xx); return 1; } print("(%s)", sl->name); return 0; } void bprint(Node *r, Node *args) { int i, nas; Node res, *av[Maxarg]; USED(r); na = 0; flatten(av, args); nas = na; for(i = 0; i < nas; i++) { expr(av[i], &res); switch(res.type) { default: if(comx(res)) break; patom(res.type, &res.Store); break; case TCODE: pcode(res.cc, 0); break; case TLIST: blprint(res.l); break; } } if(ret == 0) Bputc(bout, '\n'); } void printto(Node *r, Node *args) { int fd; Biobuf *b; int i, nas; Node res, *av[Maxarg]; USED(r); na = 0; flatten(av, args); nas = na; expr(av[0], &res); if(res.type != TSTRING) error("printto(string, ...): need string"); fd = create(res.string->string, OWRITE, 0666); if(fd < 0) fd = open(res.string->string, OWRITE); if(fd < 0) error("printto: open %s: %r", res.string->string); b = gmalloc(sizeof(Biobuf)); Binit(b, fd, OWRITE); Bflush(bout); io[iop++] = bout; bout = b; for(i = 1; i < nas; i++) { expr(av[i], &res); switch(res.type) { default: if(comx(res)) break; patom(res.type, &res.Store); break; case TLIST: blprint(res.l); break; } } if(ret == 0) Bputc(bout, '\n'); Bterm(b); close(fd); free(b); bout = io[--iop]; } void pcfile(Node *r, Node *args) { Node res; char *p, buf[128]; if(args == 0) error("pcfile(addr): arg count"); expr(args, &res); if(res.type != TINT) error("pcfile(addr): arg type"); r->type = TSTRING; r->fmt = 's'; if(fileline(buf, sizeof(buf), res.ival) == 0) { r->string = strnode("?file?"); return; } p = strrchr(buf, ':'); if(p == 0) error("pcfile(addr): funny file %s", buf); *p = '\0'; r->string = strnode(buf); } void pcline(Node *r, Node *args) { Node res; char *p, buf[128]; if(args == 0) error("pcline(addr): arg count"); expr(args, &res); if(res.type != TINT) error("pcline(addr): arg type"); r->type = TINT; r->fmt = 'D'; if(fileline(buf, sizeof(buf), res.ival) == 0) { r->ival = 0; return; } p = strrchr(buf, ':'); if(p == 0) error("pcline(addr): funny file %s", buf); r->ival = strtol(p+1, 0, 0); } void fmtof(Node *r, Node *args) { Node *av[Maxarg]; Node res; na = 0; flatten(av, args); if(na < 1) error("fmtof(obj): no argument"); if(na > 1) error("fmtof(obj): too many arguments") ; expr(av[0], &res); r->op = OCONST; r->type = TINT ; r->ival = res.fmt ; r->fmt = 'c'; } void dofmtsize(Node *r, Node *args) { Node *av[Maxarg]; Node res; Store * s ; Value v ; na = 0; flatten(av, args); if(na < 1) error("fmtsize(obj): no argument"); if(na > 1) error("fmtsize(obj): too many arguments") ; expr(av[0], &res); v.type = res.type ; s = &v.Store ; *s = res ; r->op = OCONST; r->type = TINT ; r->ival = fmtsize(&v) ; r->fmt = 'D'; } void dogetfields(Node *r, Node *args) { Node *av[Maxarg], nstr, ndelim, nmultif; char *buf; char *f[128]; int rc, i; List *l, **lp; na = 0; flatten(av, args); if(na != 3) error("getfields(str, delims, multiflag): arg count"); expr(av[0], &nstr); expr(av[1], &ndelim); expr(av[2], &nmultif); if(nstr.type != TSTRING || ndelim.type != TSTRING) error("getfields(str, delims, multiflag): arg type"); buf = strdup(nstr.string->string); if(buf == nil) fatal("out of memory"); rc = getfields(buf, f, nelem(f), bool(&nmultif), ndelim.string->string); lp = &r->l; for(i = 0; i < rc; i++){ l = al(TSTRING); l->fmt = 's'; l->string = strnode(f[i]); *lp = l; lp = &l->next; } r->op = OCONST; r->type = TLIST; free(buf); }