aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicholas Noll <nbnoll@eml.cc>2021-10-09 10:30:50 -0700
committerNicholas Noll <nbnoll@eml.cc>2021-10-09 10:30:50 -0700
commit6f2cac23a7e759c464ef52103fac929e1eeb6b10 (patch)
treed2412c7f82fc47bdb6dc47d82b685982e63cefe7
parent80f92a7109c0bce2f4220ff1ce04ec8fd6fb9f8c (diff)
feat(rc): added shell
-rw-r--r--sys/cmd/rc/code.c335
-rw-r--r--sys/cmd/rc/code.dep166
-rw-r--r--sys/cmd/rc/exec.c139
-rw-r--r--sys/cmd/rc/glob.c199
-rw-r--r--sys/cmd/rc/io.c446
-rw-r--r--sys/cmd/rc/lex.c417
-rw-r--r--sys/cmd/rc/main.c86
-rw-r--r--sys/cmd/rc/parse.c496
-rw-r--r--sys/cmd/rc/rc.h312
-rw-r--r--sys/cmd/rc/rules.mk22
-rw-r--r--sys/cmd/rc/simple.c13
-rw-r--r--sys/cmd/rc/tree.c144
-rw-r--r--sys/cmd/rc/util.c40
-rw-r--r--sys/cmd/rc/var.c129
-rw-r--r--sys/cmd/rc/word.c64
15 files changed, 3008 insertions, 0 deletions
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 <dirent.h>
+
+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, "<nil>");
+ 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 = "<null>";
+
+ 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 <u.h>
+#include <libn.h>
+
+#include <errno.h>
+#include <sys/wait.h>
+
+#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 <nil>\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);
+}