From 6f2cac23a7e759c464ef52103fac929e1eeb6b10 Mon Sep 17 00:00:00 2001 From: Nicholas Noll Date: Sat, 9 Oct 2021 10:30:50 -0700 Subject: feat(rc): added shell --- sys/cmd/rc/code.c | 335 +++++++++++++++++++++++++++++++++++ sys/cmd/rc/code.dep | 166 ++++++++++++++++++ sys/cmd/rc/exec.c | 139 +++++++++++++++ sys/cmd/rc/glob.c | 199 +++++++++++++++++++++ sys/cmd/rc/io.c | 446 ++++++++++++++++++++++++++++++++++++++++++++++ sys/cmd/rc/lex.c | 417 +++++++++++++++++++++++++++++++++++++++++++ sys/cmd/rc/main.c | 86 +++++++++ sys/cmd/rc/parse.c | 496 ++++++++++++++++++++++++++++++++++++++++++++++++++++ sys/cmd/rc/rc.h | 312 +++++++++++++++++++++++++++++++++ sys/cmd/rc/rules.mk | 22 +++ sys/cmd/rc/simple.c | 13 ++ sys/cmd/rc/tree.c | 144 +++++++++++++++ sys/cmd/rc/util.c | 40 +++++ sys/cmd/rc/var.c | 129 ++++++++++++++ sys/cmd/rc/word.c | 64 +++++++ 15 files changed, 3008 insertions(+) create mode 100644 sys/cmd/rc/code.c create mode 100644 sys/cmd/rc/code.dep create mode 100644 sys/cmd/rc/exec.c create mode 100644 sys/cmd/rc/glob.c create mode 100644 sys/cmd/rc/io.c create mode 100644 sys/cmd/rc/lex.c create mode 100644 sys/cmd/rc/main.c create mode 100644 sys/cmd/rc/parse.c create mode 100644 sys/cmd/rc/rc.h create mode 100644 sys/cmd/rc/rules.mk create mode 100644 sys/cmd/rc/simple.c create mode 100644 sys/cmd/rc/tree.c create mode 100644 sys/cmd/rc/util.c create mode 100644 sys/cmd/rc/var.c create mode 100644 sys/cmd/rc/word.c (limited to 'sys') diff --git a/sys/cmd/rc/code.c b/sys/cmd/rc/code.c new file mode 100644 index 0000000..edf47cf --- /dev/null +++ b/sys/cmd/rc/code.c @@ -0,0 +1,335 @@ +#include "rc.h" + +#define delcode 100 +#define c0 t->child[0] +#define c1 t->child[1] +#define c2 t->child[2] + +#define emitf(x) ((code.ip!=code.end || morecode()), code.ip++->f = (x), code.ip) +#define emiti(x) ((code.ip!=code.end || morecode()), code.ip++->i = (x), code.ip) +#define emits(x) ((code.ip!=code.end || morecode()), code.ip++->s = (x), code.ip) + +static struct +{ + int cap; + Code *buf, *ip, *end; +} code; + +static +int +morecode(void) +{ + code.cap += delcode; + code.buf = erealloc(code.buf, code.cap*sizeof(*code.buf)); + code.end = code.ip + delcode; + memset(code.ip, 0, delcode*sizeof(*code.buf)); + + return 0; +} + +static +void +stuffdot(Code *p) +{ + int a; + + a = p - code.buf; + if (code.ip <= p || p < code.buf) + panic("bad address %d in stuffdot", a); + code.buf[a].i = code.ip-code.buf; +} + +static +void +rcc(Tree *t, int eflag) +{ + Code *p, *q; + Tree *tt; + + if (!t) + return; + + switch(t->type) { + default: + pfmt(errio, "bad type %d in rc compiler\n", t->type); + break; + case Tdol: + emitf(Xmark); + rcc(c0, eflag); + emitf(Xdol); + break; + case Tquote: + emitf(Xmark); + rcc(c0, eflag); + emitf(Xflatten); + break; + case Tsub: + emitf(Xmark); + rcc(c0, eflag); + emitf(Xmark); + rcc(c1, eflag); + emitf(Xsub); + break; + case Tand: + emitf(Xasync); + p = emiti(0); + rcc(c0, eflag); + emitf(Xexit); + stuffdot(p); + break; + case Tsemi: + rcc(c0, eflag); + rcc(c1, eflag); + break; + case Tcarot: + emitf(Xmark); + rcc(c1, eflag); + emitf(Xmark); + rcc(c0, eflag); + emitf(Xcat); + break; + case Ttick: + emitf(Xcmdsub); + p = emiti(0); + rcc(c0, 0); + emitf(Xexit); + stuffdot(p); + break; + case Tandand: + rcc(c0, 0); + emitf(Xtrue); + p = emiti(0); + rcc(c1, eflag); + stuffdot(p); + break; + case Targs: + rcc(c1, eflag); + rcc(c0, eflag); + break; + case Tbang: + rcc(c0, eflag); + emitf(Xnegate); + break; + case Tparen: + case Tbrace: + rcc(c0, eflag); + break; + case Tcount: + emitf(Xmark); + rcc(c0, eflag); + emitf(Xcount); + break; + case Tfunc: + emitf(Xmark); + rcc(c0, eflag); + if(c1){ + emitf(Xfunc); + p = emiti(0); + emits(fnstr(c1)); + rcc(c1, eflag); + emitf(Xunlocal); /* get rid of $* */ + emitf(Xkill); + stuffdot(p); + } else + emitf(Xunfunc); + break; + case Tif: + rcc(c0, 0); + emitf(Xif); + p = emiti(0); + rcc(c1, eflag); + // emitf(Xwastrue); + stuffdot(p); + break; + // case Telse: + // if(!runq->iflast) + // rcerror("`else' does not follow `if(...)'"); + // emitf(Xelse); + // p = emiti(0); + // rcc(c0, eflag); + // stuffdot(p); + // break; + case Toror: + rcc(c0, 0); + emitf(Xfalse); + p = emiti(0); + rcc(c1, eflag); + stuffdot(p); + break; + case Tpcmd: + rcc(c0, eflag); + break; + case Tsimple: + emitf(Xmark); + rcc(c0, eflag); + emitf(Xsimple); + if(eflag) + emitf(Xeflag); + break; + case Tsubshell: + emitf(Xsubshell); + p = emiti(0); + rcc(c0, eflag); + emitf(Xexit); + stuffdot(p); + if(eflag) + emitf(Xeflag); + break; + case Tswitch: + codeswitch(t, eflag); + break; + case Ttwiddle: + emitf(Xmark); + rcc(c1, eflag); + emitf(Xmark); + rcc(c0, eflag); + emitf(Xmatch); + if(eflag) + emitf(Xeflag); + break; + case Twhile: + q = code.ip; + rcc(c0, 0); + if(q==code.ip) + emitf(Xsettrue); /* empty condition == while(true) */ + emitf(Xtrue); + p = emiti(0); + rcc(c1, eflag); + emitf(Xjump); + emiti(q-code.buf); + stuffdot(p); + break; + case Twords: + rcc(c1, eflag); + rcc(c0, eflag); + break; + case Tfor: + emitf(Xmark); + if(c1){ + rcc(c1, eflag); + emitf(Xglob); + } else{ + emitf(Xmark); + emitf(Xword); + emits(strdup("*")); + emitf(Xdol); + } + emitf(Xmark); /* dummy value for Xlocal */ + emitf(Xmark); + rcc(c0, eflag); + emitf(Xlocal); + p = emitf(Xfor); + q = emiti(0); + rcc(c2, eflag); + emitf(Xjump); + emiti(p-code.buf); + stuffdot(q); + emitf(Xunlocal); + break; + case Tword: + emitf(Xword); + emits(strdup(t->str)); + break; + case Tdup: + if(t->redir.type == Rdupfd){ + emitf(Xdup); + emiti(t->redir.fd[0]); + emiti(t->redir.fd[1]); + } else{ + emitf(Xclose); + emiti(t->redir.fd[0]); + } + rcc(c1, eflag); + emitf(Xpopredir); + break; + case Tpipefd: + emitf(Xpipefd); + emiti(t->redir.type); + p = emiti(0); + rcc(c0, eflag); + emitf(Xexit); + stuffdot(p); + break; + case Tredir: + emitf(Xmark); + rcc(c0, eflag); + emitf(Xglob); + switch(t->redir.type){ + case Rappend: + emitf(Xappend); + break; + case Rwrite: + emitf(Xwrite); + break; + case Rread: + case Rhere: + emitf(Xread); + break; + case Rrdwr: + emitf(Xrdwr); + break; + } + emiti(t->redir.fd[0]); + rcc(c1, eflag); + emitf(Xpopredir); + break; + case Teq: + tt = t; + for(;t && t->type==Teq;t = c2); + if(t){ + for(t = tt;t->type==Teq;t = c2){ + emitf(Xmark); + rcc(c1, eflag); + emitf(Xmark); + rcc(c0, eflag); + emitf(Xlocal); + } + rcc(t, eflag); + for(t = tt; t->type==Teq; t = c2) + emitf(Xunlocal); + } else{ + for(t = tt;t;t = c2){ + emitf(Xmark); + rcc(c1, eflag); + emitf(Xmark); + rcc(c0, eflag); + emitf(Xassign); + } + } + t = tt; /* so tests below will work */ + break; + case Tpipe: + emitf(Xpipe); + emiti(t->redir.fd[0]); + emiti(t->redir.fd[1]); + p = emiti(0); + q = emiti(0); + rcc(c0, eflag); + emitf(Xexit); + stuffdot(p); + rcc(c1, eflag); + emitf(Xkill); + stuffdot(q); + emitf(Xpipewait); + break; + } + if(t->type!=Telse && t->type!=Tsemi) + shell->iflast = t->type==Tif; + else if (c0) + shell->iflast = c0->type==Tif; +} + +Code* +compile(Tree *t) +{ + code.cap = delcode; + code.buf = code.ip = emalloc(code.cap*sizeof *code.buf); + code.end = code.ip + code.cap; + + emiti(0); + rcc(t, 0); + emitf(Xkill); + emitf(nil); + + return code.buf; +} diff --git a/sys/cmd/rc/code.dep b/sys/cmd/rc/code.dep new file mode 100644 index 0000000..7fdd4bc --- /dev/null +++ b/sys/cmd/rc/code.dep @@ -0,0 +1,166 @@ +#if 0 +// simple example code +error +cd(Args args) +{ + switch (args.len) { + case 0: + errorf("reached cd with no arguments!"); + return 1; + case 1: + one: + errorf("sh: expected argument to command 'cd'"); + return 1; + case 2: + if (args.a[1] == nil) + goto one; + break; + default: + errorf("sh: too many arguments to command 'cd'"); + return 1; + } + if (chdir(args.a[1])) + errorf("cd fail: %s", strerror(errno)); + + return 0; +} + +error +quit(Args args) +{ + exit(0); +} + +Builtin builtins[] = { + { "cd", cd }, + { "exit", quit }, +}; + +void +clear(Header *arr) +{ + arr->len = 0; +} + +int +readline(Code *code) +{ + int n, b; + + n = code->len; +getchar: + if (code->len >= code->cap) { + code->cap += 100; + code->s = realloc(code->s, code->cap); + } + /* TODO: unicode? */ + switch ((b = getchar())) { + case EOF: + n = -1; + goto null; + case '\n': + n = code->len - n; + null: + code->s[code->len] = '\0'; + break; + default: + code->s[code->len++] = b; + goto getchar; + } + + return n; +} + +/* TODO: unicode */ +int +readargs(Code code, Args *args) +{ + if (args->a) + clear(&args->hdr); + else { + args->cap += 20; + args->a = realloc(args->a, args->cap); + } + + args->a[args->len++] = code.s; + while (*code.s) { + if (!isspace(*code.s++)) + continue; + + code.s[-1] = '\0'; + /* consume all remaining space */ + while (isspace(*code.s)) + code.s++; + + if (args->len >= args->cap-1) { + args->cap += 20; + args->a = realloc(args->a, args->cap); + } + args->a[args->len++] = code.s; + } + /* nil acts as a sentinel value */ + args->a[args->len] = nil; + + return args->len; +} + +error +execute(Args args) +{ + int i, status; + pid_t cid, wid; + + for (i = 0; i < arrlen(builtins); i++) { + if (strcmp(args.a[0], builtins[i].cmd) == 0) + return builtins[i].func(args); + } + + if ((cid = fork()) == 0) { + if (execvp(args.a[0], args.a) == -1) + errorf("exec failed: %s", strerror(errno)); + exit(1); + } else if (cid > 0) + do + wid = waitpid(cid, &status, WUNTRACED); + while (!WIFEXITED(status) && !WIFSIGNALED(status)); + else + errorf("fork failed: %s", strerror(errno)); + + return status; +} + +static +void +flush(void) +{ + io·flush(stdout); +} + +static +void +prompt(void) +{ + printf(";"); + flush(); +} + +int +main(int argc, char *argv[]) +{ + int i, err, n; + Code code = {0}; + Args args = {0}; + + ARGBEGIN { + } ARGEND; + + do { + clear(&code.hdr); + prompt(); + + n = readline(&code); + readargs(code, &args); + err = execute(args); + } while (!err && n > 0); +} +#endif diff --git a/sys/cmd/rc/exec.c b/sys/cmd/rc/exec.c new file mode 100644 index 0000000..0155b22 --- /dev/null +++ b/sys/cmd/rc/exec.c @@ -0,0 +1,139 @@ +#include "rc.h" + +#define W0 shell->stack->words +// ----------------------------------------------------------------------- +// helper functions + +static +void +setstatus(char *s) +{ + setvar("status", newword(s, nil)); +} + +static +void +pushredir(int type, int from, int to) +{ + Redir *r; + + alloc(r); + r->type = type; + r->from = from; + r->to = to; + r->link = shell->redir, shell->redir = r; +} + +// ----------------------------------------------------------------------- +// interpreter functions + +void +Xerror(char *s) +{ + if(!strcmp(argv0, "rc")||!strcmp(argv0, "/bin/rc")) + pfmt(errio, "rc: %s: %r\n", s); + else + pfmt(errio, "rc (%s): %s: %r\n", argv0, s); + flush(&errio); + + setstatus("error"); + while(!shell->interactive) + Xkill(); +} + +void +Xappend(void) +{ + int fd; + char *path; + + switch(count(W0)) { + default: + Xerror(">> requires a singleton list"); + return; + case 0: + Xerror(">> requires one file"); + return; + case 1: + ; + } + + path = shell->stack->words->word; + if ((fd=open(path, 1))< 0 && (fd=creat(path, 0666L))<0) { + pfmt(errio, "%s: ", path); + Xerror("can't open"); + return; + } + lseek(fd, 0L, 2); + pushredir(Fopen, fd, shell->ip++->i); + poplist(); +} + +void +Xassign(void) +{ + Var *v; + if(count(W0)!=1) { + Xerror("variable name not singleton"); + return; + } + unglob(W0->word); + v = vlookup(W0->word); + poplist(); + globlist(); + freelist(v->val); + + v->val = W0; + if(v->update) + v->update(v); + W0 = nil; + poplist(); +} + +void +Xmark(void) +{ + pushlist(); +} + +void +Xword(void) +{ + pushword(shell->ip++->s); +} + +void Xasync(void); +void Xcat(void); +void Xclose(void); +void Xcmdsub(void); +void Xcount(void); +void Xdol(void); +void Xdup(void); +void Xexit(void); +void Xfalse(void); +void Xflatten(void); +void Xfor(void); +void Xfunc(void); +void Xglob(void); +void Xif(void); +void Xjump(void); +void Xkill(void); +void Xlocal(void); +void Xmark(void); +void Xmatch(void); +void Xnegate(void); +void Xpipe(void); +void Xpipefd(void); +void Xpipewait(void); +void Xpop(void); +void Xpopredir(void); +void Xrdwr(void); +void Xread(void); +void Xsub(void); +void Xsimple(void); +void Xsubshell(void); +void Xtrue(void); +void Xunfunc(void); +void Xunlocal(void); +void Xword(void); +void Xwrite(void); diff --git a/sys/cmd/rc/glob.c b/sys/cmd/rc/glob.c new file mode 100644 index 0000000..95b2ef3 --- /dev/null +++ b/sys/cmd/rc/glob.c @@ -0,0 +1,199 @@ +#include "rc.h" +#include + +static Word *matches; +static char buffer[6*1024]; + +// ----------------------------------------------------------------------- +// main exports + +void +unglob(char *s) +{ + char *t = s; + do { + if(*t==GLOB) + t++; + *s++ = *t; + } while(*t++); +} + +/* + * inspiration from rsc's blog post + * modified for utf8 sequences and character classes + * returns 1 if string matches pattern is found, 0 otherwise + */ +static +int +match(char *s, char *p) +{ + int c, ns, np; + rune sr, pr, lo, tr, hi; + char *sb = s, *ss = s, *pp = p; + while(*s || *p){ + if(*p){ + ns = utf8·bytetorune(&sr, s); + np = utf8·bytetorune(&pr, p); + + if(pr==GLOB){ + np = utf8·bytetorune(&pr, ++p); + switch(pr){ + case '?': /* single match */ + if(*s){ + p+=np, s+=ns; + continue; + } + case '[': /* class match */ + np = utf8·bytetorune(&pr, ++p); + if((c = (pr == '~'))) + np = utf8·bytetorune(&pr, ++p); + + lo = pr; + while(lo != ']' && *p){ + utf8·bytetorune(&tr, p+np); /* peek ahead */ + if(tr != '-') + hi = lo; + else { + p += np + 1, np = utf8·bytetorune(&hi, p); + if(!hi) /* we hit a syntax error */ + return 0; + if(hi < lo) + tr = hi, hi = lo, lo = tr; + } + if(c ^ (lo<=sr && sr<= hi)) + goto match; + p += np, np = utf8·bytetorune(&lo, p); + } + return 0; + match: + while (*p++ != ']' && *p); /* just iterate byte-wise */ + s += ns; + continue; + case '*': /* zero-or-more match */ + pp = p-1, ss = s+ns; + p++; + continue; + case GLOB: + if (sr != GLOB) + return 0; + s++, p++; + continue; + default: + panic("unrecognized glob operation", pr); + } + } + + if (sr==pr){ + s+=ns, p+=np; + continue; + } + } + /* hit end of pattern with no match, restart at last star */ + if (ss > sb) { + if (!*ss) /* hit end of string while matching a star */ + return 1; + + s = ss, p = pp; + continue; + } + /* mismatch */ + return 0; + } + return 1; +} + +static +void +globdir(char *p, char *path, int fd) +{ + DIR *d = nil; + char *g; /* pattern offset (base of new GLOB) */ + char *b; /* pointer into path */ + int i, j; + struct dirent *e; + + if(!*p) { + printf("making path %s\n", path); + matches = newword(buffer, matches); + return; + } + + if((fd = openat(fd, path[0]?path:".", O_RDONLY|O_CLOEXEC|O_DIRECTORY)) < 0) + return; + d = fdopendir(fd); + + for(g = p, b = path; *g; b++) { + if(*g==GLOB) + break; + *b=*g++; + if(*b == '/') { + *b = 0; + /* open new directory (close if we have opened another already */ + if ((fd = openat(fd, path, O_RDONLY|O_CLOEXEC|O_DIRECTORY)) < 0) + goto cleanup; + closedir(d); + d = fdopendir(fd); + *b = '/'; + path = b, p = g; + } + } + + /* if we are at the end of the pattern, check if name exists */ + if(!*g) { + *b = 0; + if(faccessat(fd, path, F_OK, AT_SYMLINK_NOFOLLOW) == 0) + matches = newword(buffer, matches); + goto cleanup; + } + + /* we have a non-trivial pattern to match */ + /* partition on the next directory */ + while(*g && *g!='/') + g++; + + if(*g){ + j = 1; + *g = 0; + } else + j = 0; + + while((e = readdir(d))) { + if (e->d_name[0] == '.') + if (e->d_name[1] == 0 || /* . */ + (e->d_name[1] == '.' && e->d_name[2] == 0)) /* .. */ + continue; + + for(i=0;e->d_name[i];i++) + b[i]=e->d_name[i]; + b[i]=0; + + if(match(path, p)) + globdir(g+j, b, fd); + } + + printf("successful\n"); +cleanup: + printf("cleaning up\n"); + /* NOTE: a successful closedir also closes the file descriptor */ + closedir(d); + return; +} + +void +glob(char *p) +{ + char *path = buffer; + + globdir(p, path, AT_FDCWD); +} + +#if 0 +int +main() +{ + errio = openfd(2); + glob("\x01*"); + pval(errio, matches); + flush(&errio); +} +#endif diff --git a/sys/cmd/rc/io.c b/sys/cmd/rc/io.c new file mode 100644 index 0000000..e06bfcc --- /dev/null +++ b/sys/cmd/rc/io.c @@ -0,0 +1,446 @@ +#include "rc.h" + +#define c0 t->child[0] +#define c1 t->child[1] +#define c2 t->child[2] + +#undef bufsize +#define bufsize 512 +#define strsize 100 + +//------------------------------------------------------------------------ +// buffer operations + +/* open file */ +Io* +openfd(int fd) +{ + Io *f; + + f = emalloc(sizeof *f + bufsize); + f->fd = fd; + f->b = f->e = f->buf; + return f; +} + +/* open string */ +Io* +openstr(void) +{ + Io *f; + + f = emalloc(sizeof *f + strsize + 1); + f->fd = -1; + f->b = f->buf; + f->e = f->buf+strsize; + memset(f->b, 0, strsize+1); + + return f; +} + +/* open core (not nil terminated) */ +Io* +opencore(int len, char *s) +{ + Io *f; + + f = emalloc(sizeof *f + len); + f->fd = -1; + f->b = f->buf; + f->e = f->buf+len; + memcpy(f->b, s, len); + + return f; +} + +void +rewindio(Io *f) +{ + if (f->fd < 0) + f->b = f->buf; + else { + f->b = f->e = f->buf; + lseek(f->fd, 0, 0); + } +} + +void +closeio(Io *f) +{ + if (f->fd >= 0) + close(f->fd); + + efree(f); +} + +/* has the chance to realloc */ +void +flush(Io **fp) +{ + int n; + char *s; + Io *f; + + f = *fp; + if (f->fd < 0) { + n = f->e - f->b; + f = erealloc(f, sizeof *f + n + strsize + 1); + if (!f) + panic("can't realloc %d bytes in flush", n+strsize+1); + f->b = f->buf+n; + f->e = f->buf+n+strsize; + memset(f->b, 0, strsize+1); + } else { + n = f->b - f->buf; + if (n && write(f->fd, f->buf, n) < 0) { + write(3, "write error\n", 12); + // if (ntrap) + // dotrap(); + } + f->b = f->buf; + f->e = f->buf + bufsize; + } + + *fp = f; +} + +//------------------------------------------------------------------------ +// read from io + +int +rchr(Io *f) +{ + int n; + if (f->b == f->e) { + if (f->fd < 0 || (n = read(f->fd, f->buf, bufsize)) <= 0) + return EOF; + + f->b = f->buf; + f->e = f->b + n; + } + + return *f->b++&0xFF; +} + +//------------------------------------------------------------------------ +// printf functionality + +/* character literal */ +int +pchr(Io *f, int c) +{ + if (f->b == f->e) + flush(&f); + + return *f->b++=c; +} + +/* quote */ +void +pquo(Io *f, char *s) +{ + pchr(f, '\''); + for (; *s; s++) + if (*s == '\'') + pfmt(f, "''"); + else + pchr(f, *s); + pchr(f, '\''); +} + +/* word */ +void +pwrd(Io *f, char *s) +{ + char *t; + for(t = s; *t; t++) + if(!wordchr(*t)) + break; + if(t == s || *t) + pquo(f, s); + else + pstr(f, s); +} + +/* pointer */ +void +pptr(Io *f, void *v) +{ + int n; + uintptr p; + + if (!v) { + pstr(f, ""); + return; + } + + p = (uintptr)v; + if ((sizeof(uintptr) == sizeof(uvlong)) && p >>32) + for (n = 60; n >= 32; n-=4) + pchr(f, "0123456789ABCDEF"[(p>>n)&0xF]); + for (n = 28; n >= 0; n-=4) + pchr(f, "0123456789ABCDEF"[(p>>n)&0xF]); +} + +/* string */ +void +pstr(Io *f, char *s) +{ + if(!s || !s[0]) + s = ""; + + while(*s) + pchr(f, *s++); +} + +/* decimal */ +void +pdec(Io *f, int n) +{ + if (n < 0) { + n = -n; + pchr(f, '-'); + if (n >= 0) { + pdec(f, n); + return; + } + n = 1 - n; + pdec(f, n/10); + pchr(f, n%10+'1'); + return; + } + + if (n > 9) + pdec(f, n/10); + pchr(f, n%10+'0'); +} + +/* octal */ +void +poct(Io *f, uint n) +{ + if (n > 7) + poct(f, n>>3); + pchr(f, (n&7)+'0'); +} + +/* value */ +void +pval(Io *f, Word *a) +{ + if(a) { + while(a->link && a->link->word) { + pwrd(f, a->word); + pchr(f, ' '); + a = a->link; + } + pwrd(f, a->word); + } +} + +/* tree */ +static +void +pdeglob(Io *f, char *s) +{ + while(*s){ + if(*s==GLOB) + s++; + pchr(f, *s++); + } +} + +void +pcmd(Io *f, Tree *t) +{ + if(!t) + return; + + switch(t->type){ + default: pfmt(f, "bad<%d> %p %p %p", t->type, c0, c1, c2); + break; + case Tdol: pfmt(f, "$%t", c0); + break; + case Tquote: pfmt(f, "$\"%t", c0); + break; + case Tand: pfmt(f, "%t&", c0); + break; + case Tcarot: pfmt(f, "%t^%t", c0, c1); + break; + case Ttick: pfmt(f, "`%t", c0); + break; + case Tandand: pfmt(f, "%t && %t", c0, c1); + break; + case Tbang: pfmt(f, "! %t", c0); + break; + case Tbrace: pfmt(f, "{%t}", c0); + break; + case Tcount: pfmt(f, "$#%t", c0); + break; + case Tfunc: pfmt(f, "func %t %t", c0, c1); + break; + case Tif: (c2) ? pfmt(f, "if%t%t else %t", c0, c1, c2): pfmt(f, "if%t%t", c0, c1); + break; + case Toror: pfmt(f, "%t || %t", c0, c1); + break; + case Tpcmd: /* fallthrough */ + case Tparen: pfmt(f, "(%t)", c0); + break; + case Tsub: pfmt(f, "$%t(%t)", c0, c1); + break; + case Tsimple: pfmt(f, "%t", c0); + break; + case Tsubshell: pfmt(f, "@ %t", c0); + break; + case Tswitch: pfmt(f, "switch %t %t", c0, c1); + break; + case Tcase: pfmt(f, "case %t:\n%t", c0, c1); + break; + case Ttwiddle: pfmt(f, "~ %t %t", c0, c1); + break; + case Twhile: pfmt(f, "while %t%t", c0, c1); + break; + case Targs: + if(c0==0) + pfmt(f, "%t", c1); + else if(c1==0) + pfmt(f, "%t", c0); + else + pfmt(f, "%t %t", c0, c1); + break; + case Tsemi: + if(c0) { + if(c1) + pfmt(f, "%t%c%t", c0, '\n', c1); + else + pfmt(f, "%t", c0); + } else + pfmt(f, "%t", c1); + break; + case Twords: + if(c0) + pfmt(f, "%t ", c0); + pfmt(f, "%t", c1); + break; + case Tfor: + pfmt(f, "for(%t", c0); + if(c1) + pfmt(f, " in %t", c1); + pfmt(f, ")%t", c2); + break; + case Tword: + if(t->quoted) + pfmt(f, "%Q", t->str); + else + pdeglob(f, t->str); + break; + case Tdup: + if(t->redir.type==Rdupfd) + pfmt(f, ">[%d=%d]", t->redir.fd[1], t->redir.fd[0]); /* yes, fd1, then fd0; read lex.c */ + else + pfmt(f, ">[%d=]", t->redir.fd[0]); + pfmt(f, "%t", c1); + break; + case Tpipefd: + case Tredir: + switch(t->redir.type){ + case Rhere: + pchr(f, '<'); + case Rread: + case Rrdwr: + pchr(f, '<'); + if(t->redir.type==Rrdwr) + pchr(f, '>'); + if(t->redir.fd[0]!=0) + pfmt(f, "[%d]", t->redir.fd[0]); + break; + case Rappend: + pchr(f, '>'); + case Rwrite: + pchr(f, '>'); + if(t->redir.fd[0]!=1) + pfmt(f, "[%d]", t->redir.fd[0]); + break; + } + pfmt(f, "%t", c0); + if(c1) + pfmt(f, " %t", c1); + break; + case Teq: + pfmt(f, "%t=%t", c0, c1); + if(c2) + pfmt(f, " %t", c2); + break; + case Tpipe: + pfmt(f, "%t|", c0); + if(t->redir.fd[1]==0){ + if(t->redir.fd[0]!=1) + pfmt(f, "[%d]", t->redir.fd[0]); + } + else pfmt(f, "[%d=%d]", t->redir.fd[0], t->redir.fd[1]); + pfmt(f, "%t", c1); + break; + } +} + +/* rc specific printf */ +static int pfmtlev; + +void +vpfmt(Io *f, char *fmt, va_list args) +{ + char err[124]; + + pfmtlev++; + for (; *fmt; fmt++) + if (*fmt!='%') + pchr(f, *fmt); + else switch(*++fmt) { + case '\0': + break; + case 'c': + pchr(f, va_arg(args, int)); + break; + case 'd': + pdec(f, va_arg(args, int)); + break; + case 'o': + poct(f, va_arg(args, uint)); + break; + case 'p': + pptr(f, va_arg(args, void*)); + break; + case 'Q': + pquo(f, va_arg(args, char*)); + break; + case 'q': + pwrd(f, va_arg(args, char*)); + break; + case 'r': + pstr(f, strerror(errno)); + break; + case 's': + pstr(f, va_arg(args, char*)); + break; + case 't': + pcmd(f, va_arg(args, Tree*)); + break; + case 'v': + pval(f, va_arg(args, Word*)); + break; + } + + if (--pfmtlev==0) + flush(&f); +} + +void +pfmt(Io *f, char *fmt, ...) +{ + va_list args; + char err[124]; + + va_start(args, fmt); + vpfmt(f, fmt, args); + va_end(args); +} diff --git a/sys/cmd/rc/lex.c b/sys/cmd/rc/lex.c new file mode 100644 index 0000000..f6e2b4e --- /dev/null +++ b/sys/cmd/rc/lex.c @@ -0,0 +1,417 @@ +#include "rc.h" + +#define onebyte(c) ((c&0x80)==0x00) +#define twobyte(c) ((c&0xe0)==0xc0) +#define threebyte(c) ((c&0xf0)==0xe0) +#define fourbyte(c) ((c&0xf8)==0xf0) + +// ----------------------------------------------------------------------- +// globals + +static int lastc, nextc=EOF, lastdol, lastword, doprompt = 1; +static char buf[8*1024]; + +// ----------------------------------------------------------------------- +// utilities + +static uchar nwordc[256] = +{ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +int +wordchr(int c) +{ + return !nwordc[c] && c!=EOF; +} + + +static uchar nquotec[256] = +{ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +int +quotechr(char c) +{ + return !nquotec[c] && c!=EOF; +} + +static uchar nvarc[256] = +{ + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + + +int +varchr(char c) +{ + return !nvarc[c] && c!=EOF; +} + +static +void +prompt(void) +{ + shell->cmd.line++; + doprompt = 0; +} + +/* lookahead one byte */ +static +int +lookahead(void) +{ + int c; + + if(nextc != EOF) + return nextc; + if(shell->cmd.eof) + return EOF; + + if(doprompt) + prompt(); + + c = rchr(shell->cmd.io); + doprompt = c == '\n' || c == EOF; + + if(c == EOF) + shell->cmd.eof++; + + return nextc = c; +} + +/* consumes the lookahead */ +static +int +advance(void) +{ + int c = lookahead(); + lastc = nextc, nextc = EOF; + + return c; +} + +/* + * advance until we no longer hit horizontal space + * consumes all comments + */ +static +void +skipws(void) +{ + int c; + for(;;) { + c = lookahead(); + if(c=='#'){ + for(;;){ + c = lookahead(); + if(c=='\n' || c==EOF) + break; + advance(); + } + } + if(c==' ' || c=='\t') + advance(); + else + return; + } +} + +/* advance until we no longer hit any space */ +void +skipnl(void) +{ + int c; + for(;;) { + skipws(); + if ((c = lookahead()) != '\n') + return; + advance(); + } +} + +/* advance if next char is equal to c */ +static +int +nextis(int c) +{ + if(lookahead()==c) { + advance(); + return 1; + } + return 0; +} + +/* functions to append to our write buffer */ +static +char* +putbyte(char *s, int c) +{ + if(!s) + return s; + if(s == arrend(buf)){ + *s = 0; + rcerror("out of buffer space"); + return nil; + } + *s++ = c; + return s; +} + +static +char* +putrune(char *s, int c) +{ + s = putbyte(s, c); + if (onebyte(c)) + return s; + if (twobyte(c)) + return putbyte(s, advance()); + if (threebyte(c)) { + putbyte(s, advance()); + return putbyte(s, advance()); + } + if (fourbyte(c)) { + putbyte(s, advance()); + putbyte(s, advance()); + return putbyte(s, advance()); + } + rcerror("malformed utf8 stream"); + return nil; +} + +// ----------------------------------------------------------------------- +// main exports + +void +rcerror(char *fmt, ...) +{ + va_list args; + + pfmt(errio, "rc:"); + if (shell->cmd.io) + pfmt(errio, "%s:%d ", shell->cmd.name, shell->cmd.line); + + va_start(args, fmt); + vpfmt(errio, fmt, args); + va_end(args); + + pfmt(errio, "\n"); + + flush(&errio); + lastword = lastdol = 0; + while (lastc != '\n' && lastc != EOF) + advance(); + /* for debugging only */ + abort(); +} + +/* word is only modified in the event of a lexed word */ +int +lex(Tree **node) +{ + int c; + char *w = buf; + /* + * NOTE: + * we inject tokens into the lexer based on context if last token = word: + * if we see a (, then we interpret that as a subscript + * otherwise, if the next character is the first char of a word, we return a ^ operator. + */ + if(lastword){ + lastword=0; + c = lookahead(); + if(c=='('){ + advance(); + return Tlparen; + } + if(quotechr(c)) + return Tcarot; + } + + skipws(); + switch(c=advance()) { + case EOF: + lastdol = 0; + return EOF; + case '$': + lastdol = 1; + if(nextis('#')) + return Tcount; + if (nextis('"')) + return Tquote; + return Tdol; + case '&': + lastdol = 0; + if(nextis('&')) + return Tandand; + return Tand; + + case '!': + return Tbang; + case '@': + return Tsubshell; + case '~': + return Ttwiddle; + + case '|': + lastdol = 0; + if(nextis('|')){ + skipnl(); + return Toror; + } + (*node) = newtree(); + (*node)->type = Tpipe; + (*node)->redir.fd[0] = 0; + (*node)->redir.fd[1] = 1; + goto redir; + case '>': + (*node) = newtree(); + (*node)->type = Tredir; + if (nextis(c)) + (*node)->redir.type = Rappend; + else + (*node)->redir.type = Rwrite; + (*node)->redir.fd[0] = 1; + goto redir; + case '<': + (*node) = newtree(); + (*node)->type = Tredir; + if(nextis(c)) + (*node)->redir.type = Rhere; + else if(nextis('>')) + (*node)->redir.type = Rrdwr; + else + (*node)->redir.type = Rread; + (*node)->redir.fd[0] = 0; + /* fallthrough */ + redir: + if(nextis('[')) { + c = advance(); + if(c < '0' || '9' < c) { + redirerr: + rcerror("incorrect redirection syntax"); + return EOF; + } + (*node)->redir.fd[0] = 0; + do { + (*node)->redir.fd[0] = 10*(*node)->redir.fd[0]+(c-'0'); + c = advance(); + } while('0'<=c && c<='9'); + + if(c == '=') { + if((*node)->type == Tredir) + (*node)->type = Tdup; + c = advance(); + if('0'<=c && c<='9') { + (*node)->redir.type = Rdupfd; + (*node)->redir.fd[1] = (*node)->redir.fd[0]; + (*node)->redir.fd[0] = 0; + do { + (*node)->redir.fd[0] = 10*(*node)->redir.fd[0]+(c-'0'); + c = advance(); + } while('0'<=c && c<='9'); + } else { + if((*node)->type == Tpipe) + goto redirerr; + (*node)->redir.type = Rclose; + } + } + if (c != ']' + ||(*node)->type==Tdup && ((*node)->redir.type==Rhere || (*node)->redir.type==Rappend)) + goto redirerr; + } + if ((c = ((*node)->type)) == Tpipe) + skipnl(); + return c; + + case '\'': + lastdol = 0; + lastword = 1; + for(;;){ + c = advance(); + if(c==EOF) + break; + if(c=='\''){ + if(lookahead()!='\'') + break; + advance(); + } + w = putrune(w, c); + } + *w = 0; + *node = wordnode(buf); + (*node)->quoted = 1; + return Tword; + } + if (!wordchr(c)) { + lastdol = 0; + return c; + } + for(;;){ + if(c=='*'||c=='['||c=='?'||c==GLOB) + w = putbyte(w, GLOB); + w = putrune(w, c); + c = lookahead(); + if(lastdol?!varchr(c):!wordchr(c)) + break; + advance(); + } + *w = 0; + + if ((c = kwlookup(buf)) == -1) { + (*node) = wordnode(buf); + (*node)->type = c = Tword; + (*node)->quoted = 0; + lastword = 1; + } + + lastdol = 0; + return c; +} diff --git a/sys/cmd/rc/main.c b/sys/cmd/rc/main.c new file mode 100644 index 0000000..b4a355e --- /dev/null +++ b/sys/cmd/rc/main.c @@ -0,0 +1,86 @@ +#include "rc.h" + +/* globals */ +Thread *shell = nil; +int ntrap = 0; +Io *errio; + +/* main execution */ + +void +dotrap(void) +{ + exit(1); +} + +void +bootup(Code *c, int off, Var *vars) +{ + Thread *sh; + + alloc(sh); + sh->code = c, c->i++; + sh->ip = sh->code + off; + sh->local = vars; + sh->stack = nil; + + sh->link = shell, shell = sh; +} + +int +main(int argc, char *argv[]) +{ + int i; + Code *ip, sh[32]; + + ARGBEGIN { + } ARGEND; + + errio = openfd(2); + + initkw(); + + ip = sh; + memset(sh, 0, sizeof(sh)); + /* + * NOTE: first element of code is a reference count + * bootup runs: + * 1. *=argv[1:] + * 2. . rcmain $* + */ +#if 0 + ip++->i = 1; + ip++->f = Xmark; + ip++->f = Xword; + ip++->s = "*"; + ip++->f = Xassign; + ip++->f = Xmark; + ip++->f = Xmark; + ip++->s = "*"; + ip++->f = Xdol; + ip++->s = "rcmain"; + ip++->f = Xword; + ip++->s = "."; + ip++->f = Xsimple; + ip++->f = Xexit; + ip++->i = 0; + + bootup(sh, 1, nil); + pushlist(); + for (i = argc-1; i != 0; i--) + pushword(argv[i]); + + for (;;) { + shell->ip++->f(); + if (ntrap) + dotrap(); + } +#else + bootup(sh, 1, nil); + shell->cmd.io = openfd(0); + while (parse()) + ; + +#endif + exit(0); +} diff --git a/sys/cmd/rc/parse.c b/sys/cmd/rc/parse.c new file mode 100644 index 0000000..b61ac3c --- /dev/null +++ b/sys/cmd/rc/parse.c @@ -0,0 +1,496 @@ +#include "rc.h" + +/* TODO: better error messages */ + +// ----------------------------------------------------------------------- +// global data + +static int lastt, nextt=EOF; +static Tree *node; /* if token was lexed as a tree node (redirs and words), its here */ + +/* anything that is not listed will automatically terminate parsing the given command */ +static uchar prectab[256] = { + [Tif] = 1, [Tfor] = 1, [Tswitch] = 1, /* NOTE: we give else lower precedence than if [Telse] = 1, */ + [Tandand] = 2, [Toror] = 2, + [Tbang] = 3, [Tsubshell] = 3, + [Tpipe] = 4, + [Tcarot] = 5, + [Tdol] = 6, [Tcount] = 6, [Tquote] = 6, + [Tsub] = 7, +}; + +// ----------------------------------------------------------------------- +// helpers + +static +int +lookahead(void) +{ + int tok; + + if (nextt != EOF) + return nextt; + + tok = lex(&node); + return nextt = tok; +} + +static +int +advance(void) +{ + int tok = lookahead(); + lastt = nextt, nextt = EOF; + node = nil; + + return tok; +} + +static +int +nextis(int tok) +{ + if (lookahead() == tok) { + advance(); + return 1; + } + return 0; +} + +// ----------------------------------------------------------------------- +// subparsers + +/* forward declarations */ +static Tree *word(void); +static Tree *words(void); +static Tree* body(int c); +static Tree *comword(void); +static Tree *cmd(int prec); + +/* implementations */ + +/* + * TODO: + * i don't like all this branching. + * think of a better way + */ + +static +Tree* +case_or_cmd(int c) +{ + Tree *t; + if (!c || !nextis(Tcase)) + return cmd(1); + + t = words(); + if (!nextis(';') && !nextis('\n')) + rcerror("case: missing terminator: recieved %d", nextt); + + t = tree2(Tcase, t, body(0)); + pfmt(errio, "%t\n", t); + + return t; +} + +static +Tree* +body(int c) +{ + int tok; + Tree *l, *r; + + skipnl(); + l = case_or_cmd(c); +loop: + switch((tok=lookahead())){ + case '&': + l = tree1('&', l); + /* fallthrough */ + case ';': case '\n': + advance(); + /* fallthrough */ + case Tcase: + if ((r = case_or_cmd(c))) { + l = tree2(';', l, r); + goto loop; + } + /* fallthrough */ + default: + ; + } + + return l; +} + +static +Tree* +brace(int c) +{ + Tree *t; + + if (!nextis('{')) + rcerror("brace: expected { found: %c", nextt); + t = tree1(Tbrace, body(c)); + if (!nextis('}')) + rcerror("brace: expected } found: %c", nextt); + + return t; +} + +static +Tree* +paren(void) +{ + Tree *t; + + if (!nextis('(')) + rcerror("not a paren"); + t = tree1(Tparen, body(0)); + if (!nextis(')')) + rcerror("unmatched paren"); + + return t; +} + +/* TODO: fill in */ +static +Tree* +heredoc(Tree* t) +{ + return t; +} + +static +Tree* +redir(void) +{ + int tok; + Tree *t; + + switch (tok = lookahead()) { + case Tdup: + t = node; + advance(); + break; + case Tredir: + t = node; + advance(); + t = hang1(t, (t->redir.type == Rhere) ? heredoc(word()) : word()); + break; + default: + t = nil; + } + + return t; +} + +static +Tree* +epilog(void) +{ + Tree *t, *tt; + + t = redir(); + while((tt = redir())) + t = hang2(t, t->child[0], tt); + + return t; +} + +static +Tree* +sword(void) +{ + int tok; + if (Kstart < (tok=lookahead()) && tok < Kend) + return node; + + return comword(); +} + +static +Tree* +word(void) +{ + int tok; + Tree *t; + + t = sword(); + while(nextis('^')) + t = tree2('^', t, sword()); + + return t; +} + + +static +Tree* +words(void) +{ + Tree *t, *tt; + t = word(); + while((tt=word())) + t = tree2(Twords, t, tt); + + return t; +} + +/* + * NOTE: we divergence from Duff's yacc grammar here. + * he has [dol|count|"]->word, we have [dol|count]->sword + * calling sword ensures we don't cat strings + * this was done in Tom's version by setting precedence + */ +static +Tree* +comword(void) +{ + int tok; + Tree *t, *tt; + + switch(tok=lookahead()){ + case Tdol: + advance(); + t = sword(); + if(nextis('(')) { + t = tree2(Tsub, t, words()); + if (!nextis(')')) + rcerror("malformed index expression"); + } + return tree1(Tdol, t); + case Tquote: + return tree1(Tquote, sword()); + case Tcount: + advance(); + return tree1(Tcount, sword()); + case Ttick: + advance(); + return tree1(Ttick, brace(0)); + case Tlparen: + return paren(); + case Tredir: + advance(); + t = hang1(node, brace(0)); + t->type = Tpipefd; + return t; + case Tword: + t = node; + advance(); + return t; + } + return nil; +} + +static +Tree* +first(void) +{ + int tok; + Tree *t; + + t = comword(); + while(nextis('^')) { + t = tree2('^', t, word()); + } + + return t; +} + +/* simple _or_ assignment */ +static +Tree* +simple_or_assign(void) +{ + int tok; + Tree *t, *tt; + + /* can't continue */ + if (!(t = first())) + return nil; + + /* is an assignment */ +assign: + if(nextis('=')) { + tt = word(); + return tree3(Teq, t, tt, cmd(prectab[Tbang])); + } + + /* is a 'simple' */ +simple: + switch ((tok=lookahead())) { + case Tredir: + case Tdup: + t = tree2(Targs, t, redir()); + goto simple; + default: + if ((tt = word())) { + t = tree2(Targs, t, tt); + goto simple; + } + /* fallthrough */ + } + + return simplehang(t); +} + +static +Tree* +opand(void) +{ + int tok; + Tree *t, *tt; + + switch(tok=lookahead()) { + case Tif: + advance(); + t = paren(); + skipnl(); + tt = cmd(prectab[Tif]); + if (nextis(Telse)) { + skipnl(); + t = tree3(Tif, t, tt, cmd(prectab[Tif])); + } else + t = tree3(Tif, t, tt, nil); + return t; + case Telse: + rcerror("invalid hanging else"); + break; + + case Tswitch: + advance(); + t = word(); + skipnl(); + tt = brace(1); + t = tree2(Tswitch, t, tt); + return t; + + case Tfor: + advance(); + + if (!nextis('(')) + rcerror("for: missing opening paren"); + t = word(); + if (nextis(Tin)) { + advance(); + tt = words(); + t = tree3(Tfor, t, tt, nil); + } else + t = tree3(Tfor, t, nil, nil); + if (!nextis(')')) + rcerror("for: missing closing paren"); + + skipnl(); + tt = cmd(prectab[Tfor]); + t->child[2] = tt; + return t; + + case Twhile: + advance(); + t = paren(); + skipnl(); + tt = cmd(1); + return tree2(Twhile, t, tt); + + case Tfunc: + advance(); + t = words(); + if ((tok=lookahead()) == '{') { + tt = brace(0); + t = tree2(Tfunc, t, tt); + } else + t = tree1(Tfunc, t); + return t; + + case Tsubshell: + advance(); + t = tree1(Tsubshell, cmd(prectab[Tsubshell])); + return t; + + case Tbang: + advance(); + t = tree1(Tbang, cmd(prectab[Tbang])); + return t; + + case Ttwiddle: + advance(); + tt = word(); + t = tree2(Ttwiddle, tt, words()); + return t; + + case Tlbrace: + t = brace(0); + tt = epilog(); + return epihang(t, tt); + + case Tredir: /* fallthrough */ + case Tdup: + t = redir(); + tt = cmd(prectab[Tbang]); + t = hang2(t, t->child[0], tt); + return t; + } + + return simple_or_assign(); +} + +static +Tree * +cmd(int prec) +{ + int np, tok; + Tree *l, *r, *p; + + if (!(l = opand())) + return nil; + + for(;;) { + tok = lookahead(); + np = prectab[tok]; + if (np < prec) + break; + p = node; + advance(); + r = cmd(np+1); + if (tok == Tpipe) + l = hang2(p, l, r); + else + l = tree2(tok, l, r); + } + + return l; +} + +// ----------------------------------------------------------------------- +// main function + +int +parse(void) +{ + int tok; + Tree *t, *tt; + + t = cmd(1); +loop: + switch(tok=lookahead()) { + case '&': + t = tree1('&', t); + /* fallthrough */ + case ';': + advance(); + tt = cmd(1); + t = tree2(';', t, tt); + goto loop; + case '\n': + advance(); + case EOF: + pfmt(errio, "%t\n", t); + break; + default: + if (tok > 0x20) + rcerror("unrecognized token: %c[%d]", tok, tok); + else + rcerror("unrecognized token: %d", tok, tok); + } + return tok != EOF; +} diff --git a/sys/cmd/rc/rc.h b/sys/cmd/rc/rc.h new file mode 100644 index 0000000..f32a737 --- /dev/null +++ b/sys/cmd/rc/rc.h @@ -0,0 +1,312 @@ +#pragma once + +#include +#include + +#include +#include + +#define alloc(ptr) ptr = emalloc(sizeof(*ptr)) + +// ----------------------------------------------------------------------- +// main enums + +#define GLOB 0x01 + +enum +{ + /* keywords */ + Kstart = 11, + Tfor, Tin, Twhile, Tif, Telse, + Tswitch, Tcase, Tfunc, Ttwiddle, + Tbang, Tsubshell, + Kend, + + /* tokens */ + Tword='w', Tredir='r', Tdup='d', Tsimple='s', + Targs='A', Twords='W', Tbrace='b', Tparen='p', Tsub='S', + Tpcmd='c', Tpipefd='-', Tandand='%', Toror='@', Tcount='#', + + Ttick='`', Tpipe = '|', Tdol='$', Tquote='"', Tand='&', + Tlparen = '(', Trparen = ')', Tlbrace='{', Trbrace='}', + Tsemi=';', Tcarot='^', Teq='=', +}; + +enum +{ + Rappend = 1, + Rwrite = 2, + Rread = 3, + Rhere = 4, + Rdupfd = 5, + Rclose = 6, + Rrdwr = 7, +}; + +enum +{ + Fopen = 1, + Fdup = 2, + Fclose = 3, +}; + +// ----------------------------------------------------------------------- +// main types + +typedef union Code Code; +typedef struct Word Word; +typedef struct List List; +typedef struct Var Var; +typedef struct Redir Redir; +typedef struct Tree Tree; +typedef struct Builtin Builtin; +typedef struct Thread Thread; +typedef struct Io Io; + +union Code +{ + int i; + char *s; + void (*f)(void); +}; + +struct Word +{ + char *word; + Word *link; +}; + +struct List +{ + Word *words; + List *link; +}; + +struct Redir +{ + uchar type; + short from, to; + Redir *link; +}; + +struct Var +{ + string name; + Word *val; + struct { + Code *func, *ip; + uint funcnew : 1; + }; + struct { + uint new : 1; + void (*update)(Var*); + }; + Var *link; +}; + +struct Tree +{ + ushort type; + uchar quoted : 1; + union { + char *str; + struct { + ushort type; + int fd[2]; + } redir; + }; + + Tree *child[3], *link; +}; + +struct Builtin +{ + char *cmd; + void (*func)(void); +}; + +struct Thread +{ + Code *code, *ip; + List *stack; + Redir *redir, *root; + Var *local; + uchar interactive : 1; + struct { + uchar eof : 1; + int line; + char *name; + Io *io; + } cmd; + + int pid; + Tree *nodes; + Thread *link; /* continuation */ +}; + +struct Io +{ + int fd; + uchar *b, *e, buf[]; +}; + +// ----------------------------------------------------------------------- +// global interpreter variables + +extern Thread *shell; +extern int ntrap; +extern int status; + +extern Io *errio; + +extern Builtin builtins[]; +extern Var *globals[1021]; /* for now must be prime */ + +// ----------------------------------------------------------------------- +// interpreter functions (defined in exec.c) + +/* + * notation: + * (var1, var2, ...) : items from stack + * [var1, var2, ...] : items from code stream + * {var1, var2, ...} : jump block from code stream + * -> moves value (stack) [code stream] + */ +extern void Xappend(void); /* Xappend(file)[fd]: open file to append */ +extern void Xassign(void); /* Xassign(name, val): assign name to val */ +extern void Xasync(void); /* Xasync(cmd): run command asynchronously */ +extern void Xcat(void); /* Xcat(list1, list2): concatenate strings */ +extern void Xclose(void); /* Xclose[fd]: close file descriptor */ +extern void Xcmdsub(void); /* Xcmdsub(cmd): use output of command as input to other */ +extern void Xcount(void); /* Xcount(name) -> (number): count items in list*/ +extern void Xdol(void); /* Xdol(name): get variable value */ +extern void Xdup(void); /* Xdup[i, j]: duplicate file descriptor */ +extern void Xexit(void); /* Xexit: exit with status */ +extern void Xfalse(void); /* Xfalse{...}: run only if $status=1 */ +extern void Xflatten(void); /* Xflatten(list) -> (string): flatten list */ +extern void Xfor(void); /* Xfor(list): flatten list */ +extern void Xfunc(void); /* Xfunc(name){... Xreturn}: define function */ +extern void Xglob(void); /* Xglob(list): globs value */ +extern void Xif(void); /* Xif: execute if $status */ +extern void Xjump(void); /* Xjump[addr]: jump to address */ +extern void Xkill(void); /* Xkill kill thread */ +extern void Xlocal(void); /* Xlocal(name, val): define local variable */ +extern void Xmark(void); /* Xmark: delimit stack with new list */ +extern void Xmatch(void); /* Xmatch(pat, str): sets status with result */ +extern void Xnegate(void); /* Xnegate: negate condition */ +extern void Xpipe(void); /* Xpipe[i j]{... Xkill}{... Xkill}: construct a pipe between 2 threads*/ +extern void Xpipefd(void); /* Xpipe[type]{... Xkill}: connect {} to a pipe */ +extern void Xpipewait(void); /* Xpipewait: wait on a pipe */ +extern void Xpop(void); /* Xpop(value): pops value from stack */ +extern void Xpopredir(void); /* Xpopredir(value): pops redir from redir stack */ +extern void Xrdwr(void); /* Xrdwr(file)[fd]: open file for reads/writes */ +extern void Xread(void); /* Xread(file)[fd]: open file for reads */ +extern void Xsub(void); /* Xsub(list, index): subscript list */ +extern void Xsimple(void); /* Xsimple(args): run command */ +extern void Xsubshell(void); /* Xsubshell(args): run command in a subshell */ +extern void Xtrue(void); /* Xtrue{...}: run only if $status=0 */ +extern void Xunfunc(void); /* Xunfunc(name) undefine function */ +extern void Xunlocal(void); /* Xunlocal(name) undefine local */ +extern void Xword(void); /* Xword[val] -> (val) */ +extern void Xwrite(void); /* Xwrite(file)[fd]: open file to write */ + +extern void Xerror(char *s); /* Xerror report an error */ + +// ----------------------------------------------------------------------- +// shell functions + +/* + * util.c + */ +void *emalloc(uintptr size); +void *erealloc(void *ptr, uintptr size); +void efree(void *); +void panic(char *msg, int n); + +/* + * io.c + */ +Io *openfd(int fd); +Io *openstr(void); +Io *opencore(int len, char *s); +void rewindio(Io *f); +void closeio(Io *f); +void flush(Io **fp); + +/* reads */ +int rchr(Io *f); + +/* writes */ +int pchr(Io *f, int c); +void pquo(Io *f, char *s); +void pwrd(Io *f, char *s); +void pptr(Io *f, void *v); +void pstr(Io *f, char *s); +void pdec(Io *f, int n); +void poct(Io *f, uint n); +void pval(Io *f, Word *a); +void pcmd(Io *f, Tree *t); +void pfmt(Io *f, char *fmt, ...); +void vpfmt(Io *f, char *fmt, va_list args); + +/* + * word.c + */ +void pushlist(void); +void freelist(Word *w); +void poplist(void); + +int count(Word *w); +Word *newword(char *w, Word *link); +void pushword(char *w); + +/* + * tree.c + */ + +Tree *newtree(void); +void freetree(Tree *t); +Tree *tree3(int type, Tree *c0, Tree *c1, Tree *c2); +Tree *tree2(int type, Tree *c0, Tree *c1); +Tree *tree1(int type, Tree *c0); + +Tree *hang1(Tree *p, Tree *c0); +Tree *hang2(Tree *p, Tree *c0, Tree *c1); +Tree *hang3(Tree *p, Tree *c0, Tree *c1, Tree *c2); +Tree *epihang(Tree *c, Tree *epi); +Tree *simplehang(Tree *t); +Tree *wordnode(char *w); + +/* + * var.c + */ + +Var *newvar(char *name, Var *link); +Var *gvlookup(char *name); +Var *vlookup(char *name); +void setvar(char *name, Word *val); + +int kwlookup(char *name); +void initkw(void); + +/* + * lex.c + */ + +void skipnl(void); +int wordchr(int c); + +void rcerror(char *msg, ...); +int lex(Tree **node); + +/* + * parse.c + */ + +int parse(void); + +/* + * main.c + */ + +void dotrap(void); diff --git a/sys/cmd/rc/rules.mk b/sys/cmd/rc/rules.mk new file mode 100644 index 0000000..654a44e --- /dev/null +++ b/sys/cmd/rc/rules.mk @@ -0,0 +1,22 @@ +include share/push.mk + +# Local sources +SRCS_$(d) := \ + $(d)/glob.c \ + $(d)/word.c \ + $(d)/util.c \ + $(d)/io.c \ + $(d)/var.c \ + $(d)/tree.c \ + $(d)/lex.c \ + $(d)/parse.c \ + $(d)/main.c +BINS_$(d) := $(d)/rc + +include share/paths.mk + +# Local rules +$(BINS_$(d)): $(OBJS_$(d)) $(OBJ_DIR)/libn/libn.a + $(COMPLINK) + +include share/pop.mk diff --git a/sys/cmd/rc/simple.c b/sys/cmd/rc/simple.c new file mode 100644 index 0000000..f934aa1 --- /dev/null +++ b/sys/cmd/rc/simple.c @@ -0,0 +1,13 @@ +void +Xsimple(void) +{ + Word *a; + Var *v; + + a = shell->stack->words; + if (!a) { + Xerror("empty argument list"); + return; + } + v = vlookup(a->word); +} diff --git a/sys/cmd/rc/tree.c b/sys/cmd/rc/tree.c new file mode 100644 index 0000000..14049e5 --- /dev/null +++ b/sys/cmd/rc/tree.c @@ -0,0 +1,144 @@ +#include "rc.h" + +// ----------------------------------------------------------------------- +// globals + +static Tree *nodes; + +// ----------------------------------------------------------------------- +// exported funcs + +Tree* +newtree(void) +{ + Tree *t; + + alloc(t); + t->str = nil; + t->child[0] = t->child[1] = t->child[2] = nil; + t->redir.fd[0] = t->redir.fd[1] = t->redir.type = 0; + + t->link = nodes, nodes = t; + return t; +} + +void +freetree(Tree *t) +{ + if (!t) + return; + + freetree(t->child[0]); + freetree(t->child[1]); + freetree(t->child[2]); + + if (t->str) + efree(t->str); + efree(t); +} + +void +freenodes(void) +{ + Tree *t, *u; + + for (t = nodes;t;t = u) { + u = t->link; + if (t->str) + efree(t->str); + efree(t); + } + nodes = nil; +} + +/* tree creation */ +Tree* +tree3(int type, Tree *c0, Tree *c1, Tree *c2) +{ + Tree *t; + + t = newtree(); + t->type = type; + t->child[0] = c0; + t->child[1] = c1; + t->child[2] = c2; + + return t; +} + +Tree* +tree2(int type, Tree *c0, Tree *c1) +{ + return tree3(type, c0, c1, nil); +} + +Tree* +tree1(int type, Tree *c0) +{ + return tree3(type, c0, nil, nil); +} + +/* tree hang */ +Tree* +hang1(Tree *p, Tree *c0) +{ + p->child[0] = c0; + return p; +} + +Tree* +hang2(Tree *p, Tree *c0, Tree *c1) +{ + p->child[0] = c0; + p->child[1] = c1; + return p; +} + +Tree* +hang3(Tree *p, Tree *c0, Tree *c1, Tree *c2) +{ + p->child[0] = c0; + p->child[1] = c1; + p->child[2] = c2; + return p; +} + +/* hangs the cmd underneath the epilogue */ +Tree* +epihang(Tree *c, Tree *epi) +{ + Tree *p; + if(!epi) + return c; + for(p=epi;p->child[1];p = p->child[1]) + ; + p->child[1] = c; + return epi; +} + +/* hangs tree t from a new simple node. percolates redirections to root */ +Tree* +simplehang(Tree *t) +{ + Tree *u; + t = tree1(Tsimple, t); + for(u = t->child[0];u->type==Targs;u=u->child[0]) { + if (u->child[1]->type==Tdup + || u->child[1]->type==Tredir){ + u->child[1]->child[1] = t; + t = u->child[1]; + u->child[1] = nil; + } + } + return t; +} + +Tree* +wordnode(char *w) +{ + Tree *t = newtree(); + t->type = Tword; + t->str = strdup(w); + + return t; +} diff --git a/sys/cmd/rc/util.c b/sys/cmd/rc/util.c new file mode 100644 index 0000000..02b3611 --- /dev/null +++ b/sys/cmd/rc/util.c @@ -0,0 +1,40 @@ +#include "rc.h" + +void * +emalloc(uintptr n) +{ + void *p = malloc(n); + if (!p) + panic("can't malloc %d bytes", n); + + return p; +} + +void * +erealloc(void *p, uintptr n) +{ + void *new = realloc(p, n); + if (!new) + panic("can't realloc %d bytes", n); + + return new; +} + +void +efree(void *p) +{ + if (p) + free(p); + else + pfmt(errio, "free \n"); +} + +void +panic(char *s, int n) +{ + pfmt(errio, "rc: "); + pfmt(errio, s, n); + pchr(errio, '\n'); + flush(&errio); + abort(); +} diff --git a/sys/cmd/rc/var.c b/sys/cmd/rc/var.c new file mode 100644 index 0000000..d442369 --- /dev/null +++ b/sys/cmd/rc/var.c @@ -0,0 +1,129 @@ +#include "rc.h" + +Var *globals[1021] = { 0 }; + +struct Keyword { + ushort type; + char *name; + struct Keyword *link; +} *keywords[41]; + +// ----------------------------------------------------------------------- +// utility + +static +int +hash(char *s, int n) +{ + int i = 1, h = 0; + while (*s) + h += *s++*i++; + h %= n; + return (h<0)?h+n:h; +} + +// ----------------------------------------------------------------------- +// keywords + +static +void +putkw(int type, char *name) +{ + struct Keyword *kw; + int h = hash(name, arrlen(keywords)); + + alloc(kw); + kw->type = type; + kw->name = name; + kw->link = keywords[h]; + + keywords[h] = kw; +} + +void +initkw(void) +{ + putkw(Tfor, "for"); + putkw(Tin, "in"); + putkw(Twhile, "while"); + putkw(Tif, "if"); + putkw(Telse, "else"); + putkw(Tswitch, "switch"); + putkw(Tcase, "case"); + putkw(Tfunc, "func"); +} + +int +kwlookup(char *name) +{ + int t; + struct Keyword *it; + for(t=-1,it = keywords[hash(name, arrlen(keywords))];it;it = it->link) + if(!strcmp(it->name, name)) + t = it->type; + return t; +} + +// ----------------------------------------------------------------------- +// variables + +Var * +newvar(char *name, Var *link) +{ + Var *v; + + alloc(v); + v->name = name; + v->val = 0; + v->func = nil; + v->funcnew = 0; + v->new = 0; + v->update = nil; + v->link = link; + + return v; +} + +/* only global lookup */ + +Var * +gvlookup(char *name) +{ + Var *v; + int h = hash(name, arrlen(globals)); + for (v = globals[h]; v; v = v->link) + if (!strcmp(v->name, name)) + return v; + + return globals[h] = newvar(strdup(name), globals[h]); +} + +/* local + global lookup */ +Var * +vlookup(char *name) +{ + Var *v; + if (shell) + for (v = shell->local; v; v = v->link) + if (!strcmp(v->name, name)) + return v; + return gvlookup(name); +} + +static +void +set(char *name, Word *val, int call) +{ + Var *v = vlookup(name); + freelist(v->val); + v->val = val; + v->new = 1; + if (call && v->update) + v->update(v); +} + +void +setvar(char *name, Word *val) +{ + set(name, val, 1); +} diff --git a/sys/cmd/rc/word.c b/sys/cmd/rc/word.c new file mode 100644 index 0000000..84ff40c --- /dev/null +++ b/sys/cmd/rc/word.c @@ -0,0 +1,64 @@ +#include "rc.h" + +void +pushlist(void) +{ + List *ls; + + alloc(ls); + ls->words = nil; + ls->link = shell->stack, shell->stack = ls; +} + +void +freelist(Word *w) +{ + Word *it; + while (w) { + it = w->link; + efree(w->word); + efree(w); + w = it; + } +} + +void +poplist(void) +{ + List *ls = shell->stack; + if (!ls) + panicf("shell stack underflow"); + + freelist(ls->words); + shell->stack = ls->link; + efree(ls); +} + +int +count(Word *w) +{ + int n; + for (n=0; w; n++) + w = w->link; + return n; +} + +Word* +newword(char *w, Word *link) +{ + Word *wd; + + alloc(wd); + wd->word = strdup(w); + wd->link = link; + + return wd; +} + +void +pushword(char *w) +{ + if (shell->stack == nil) + panicf("no active stack"); + shell->stack->words = newword(w, shell->stack->words); +} -- cgit v1.2.1