aboutsummaryrefslogtreecommitdiff
path: root/sys/cmd/rc
diff options
context:
space:
mode:
authorNicholas Noll <nbnoll@eml.cc>2020-06-18 19:45:40 -0700
committerNicholas Noll <nbnoll@eml.cc>2020-06-18 19:45:40 -0700
commit425ef692da7e74112f88f0b368f3286dba84f846 (patch)
treed45729e90010e8d8c539031c3b72165f6884575d /sys/cmd/rc
parent0522b4bf4e125b7ceb67f7177db692aed3a0ebf9 (diff)
feat: working parser for rc shell language
Diffstat (limited to 'sys/cmd/rc')
-rw-r--r--sys/cmd/rc/code.c356
-rw-r--r--sys/cmd/rc/code.dep166
-rw-r--r--sys/cmd/rc/exec.c19
-rw-r--r--sys/cmd/rc/io.c435
-rw-r--r--sys/cmd/rc/lex.c415
-rw-r--r--sys/cmd/rc/main.c86
-rw-r--r--sys/cmd/rc/parse.c430
-rw-r--r--sys/cmd/rc/rc.h254
-rw-r--r--sys/cmd/rc/rules.mk21
-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.c108
-rw-r--r--sys/cmd/rc/word.c64
14 files changed, 2551 insertions, 0 deletions
diff --git a/sys/cmd/rc/code.c b/sys/cmd/rc/code.c
new file mode 100644
index 0000000..f38dd43
--- /dev/null
+++ b/sys/cmd/rc/code.c
@@ -0,0 +1,356 @@
+#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))
+#define emiti(x) ((code.ip!=code.end || morecode()), code.ip++->i = (x))
+#define emits(x) ((code.ip!=code.end || morecode()), code.ip++->s = (x))
+
+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);
+ codebuf[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 '$':
+ emitf(Xmark);
+ rcc(c0, eflag);
+ emitf(Xdol);
+ break;
+ case '"':
+ emitf(Xmark);
+ rcc(c0, eflag);
+ emitf(Xqdol);
+ break;
+ case Asub:
+ emitf(Xmark);
+ rcc(c0, eflag);
+ emitf(Xmark);
+ rcc(c1, eflag);
+ emitf(Xsub);
+ break;
+ case '&':
+ emitf(Xasync);
+ if(havefork){
+ p = emiti(0);
+ rcc(c0, eflag);
+ emitf(Xexit);
+ stuffdot(p);
+ } else
+ emits(fnstr(c0));
+ break;
+ case ';':
+ rcc(c0, eflag);
+ rcc(c1, eflag);
+ break;
+ case '^':
+ emitf(Xmark);
+ rcc(c1, eflag);
+ emitf(Xmark);
+ rcc(c0, eflag);
+ emitf(Xconc);
+ break;
+ case '`':
+ emitf(Xbackq);
+ if(havefork){
+ p = emiti(0);
+ rcc(c0, 0);
+ emitf(Xexit);
+ stuffdot(p);
+ } else
+ emits(fnstr(c0));
+ break;
+ case Aandand:
+ rcc(c0, 0);
+ emitf(Xtrue);
+ p = emiti(0);
+ rcc(c1, eflag);
+ stuffdot(p);
+ break;
+ case Aargs:
+ rcc(c1, eflag);
+ rcc(c0, eflag);
+ break;
+ case Kbang:
+ rcc(c0, eflag);
+ emitf(Xbang);
+ break;
+ case Aparen:
+ case Abrace:
+ rcc(c0, eflag);
+ break;
+ case Acount:
+ emitf(Xmark);
+ rcc(c0, eflag);
+ emitf(Xcount);
+ break;
+ case Kfunc:
+ emitf(Xmark);
+ rcc(c0, eflag);
+ if(c1){
+ emitf(Xfn);
+ p = emiti(0);
+ emits(fnstr(c1));
+ rcc(c1, eflag);
+ emitf(Xunlocal); /* get rid of $* */
+ emitf(Xreturn);
+ stuffdot(p);
+ }
+ else
+ emitf(Xdelfn);
+ break;
+ case Kif:
+ rcc(c0, 0);
+ emitf(Xif);
+ p = emiti(0);
+ rcc(c1, eflag);
+ emitf(Xwastrue);
+ stuffdot(p);
+ break;
+ case Kelse:
+ if(!runq->iflast)
+ error("`else' does not follow `if(...)'");
+ emitf(Xelse);
+ p = emiti(0);
+ rcc(c0, eflag);
+ stuffdot(p);
+ break;
+ case Aoror:
+ rcc(c0, 0);
+ emitf(Xfalse);
+ p = emiti(0);
+ rcc(c1, eflag);
+ stuffdot(p);
+ break;
+ case Aparen:
+ rcc(c0, eflag);
+ break;
+ case Asimple:
+ emitf(Xmark);
+ rcc(c0, eflag);
+ emitf(Xsimple);
+ if(eflag)
+ emitf(Xeflag);
+ break;
+ case Ksubsh:
+ emitf(Xsubshell);
+ if(havefork){
+ p = emiti(0);
+ rcc(c0, eflag);
+ emitf(Xexit);
+ stuffdot(p);
+ } else
+ emits(fnstr(c0));
+ if(eflag)
+ emitf(Xeflag);
+ break;
+ case Kswitch:
+ codeswitch(t, eflag);
+ break;
+ case Ktwiddle:
+ emitf(Xmark);
+ rcc(c1, eflag);
+ emitf(Xmark);
+ rcc(c0, eflag);
+ emitf(Xmatch);
+ if(eflag)
+ emitf(Xeflag);
+ break;
+ case Kwhile:
+ 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);
+ stuffdot(p);
+ break;
+ case Awords:
+ rcc(c1, eflag);
+ rcc(c0, eflag);
+ break;
+ case Kfor:
+ 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 Aword:
+ emitf(Xword);
+ emits(strdup(t->str));
+ break;
+ case Adup:
+ if(t->rtype==Adupfd){
+ emitf(Xdup);
+ emiti(t->fd0);
+ emiti(t->fd1);
+ }
+ else{
+ emitf(Xclose);
+ emiti(t->fd0);
+ }
+ rcc(c1, eflag);
+ emitf(Xpopredir);
+ break;
+ case Apipefd:
+ emitf(Xpipefd);
+ emiti(t->rtype);
+ if(havefork){
+ p = emiti(0);
+ rcc(c0, eflag);
+ emitf(Xexit);
+ stuffdot(p);
+ } else {
+ emits(fnstr(c0));
+ }
+ break;
+ case Aredir:
+ emitf(Xmark);
+ rcc(c0, eflag);
+ emitf(Xglob);
+ switch(t->rtype){
+ case Rappend:
+ emitf(Xappend);
+ break;
+ case Rwrite:
+ emitf(Xwrite);
+ break;
+ case Rread:
+ case Rhere:
+ emitf(Xread);
+ break;
+ case Rrdwr:
+ emitf(Xrdwr);
+ break;
+ }
+ emiti(t->fd0);
+ rcc(c1, eflag);
+ emitf(Xpopredir);
+ break;
+ case '=':
+ tt = t;
+ for(;t && t->type=='=';t = c2);
+ if(t){
+ for(t = tt;t->type=='=';t = c2){
+ emitf(Xmark);
+ rcc(c1, eflag);
+ emitf(Xmark);
+ rcc(c0, eflag);
+ emitf(Xlocal);
+ }
+ rcc(t, eflag);
+ for(t = tt; t->type=='='; 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 Apipe:
+ emitf(Xpipe);
+ emiti(t->fd0);
+ emiti(t->fd1);
+ if(havefork){
+ p = emiti(0);
+ q = emiti(0);
+ rcc(c0, eflag);
+ emitf(Xexit);
+ stuffdot(p);
+ } else {
+ emits(fnstr(c0));
+ q = emiti(0);
+ }
+ rcc(c1, eflag);
+ emitf(Xreturn);
+ stuffdot(q);
+ emitf(Xpipewait);
+ break;
+ }
+ if(t->type!=Kelse && t->type!=';')
+ runq->iflast = t->type==IF;
+ else if(c0) runq->iflast = c0->type==IF;
+}
+
+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(Xreturn);
+ 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..2879678
--- /dev/null
+++ b/sys/cmd/rc/exec.c
@@ -0,0 +1,19 @@
+#include "rc.h"
+
+void
+Xerror(char *s)
+{
+
+}
+
+void
+Xmark(void)
+{
+ pushlist();
+}
+
+void
+Xword(void)
+{
+ pushword(shell->ip++->s);
+}
diff --git a/sys/cmd/rc/io.c b/sys/cmd/rc/io.c
new file mode 100644
index 0000000..8cc2f5b
--- /dev/null
+++ b/sys/cmd/rc/io.c
@@ -0,0 +1,435 @@
+#include "rc.h"
+
+#undef bufsize
+#define c0 t->child[0]
+#define c1 t->child[1]
+#define c2 t->child[2]
+
+#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;
+
+ 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 = "<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 '$': pfmt(f, "$%t", c0);
+ break;
+ case '"': pfmt(f, "$\"%t", c0);
+ break;
+ case '&': pfmt(f, "%t&", c0);
+ break;
+ case '^': pfmt(f, "%t^%t", c0, c1);
+ break;
+ case '`': pfmt(f, "`%t", c0);
+ break;
+ case Aandand: pfmt(f, "%t && %t", c0, c1);
+ break;
+ case Kbang: pfmt(f, "! %t", c0);
+ break;
+ case Abrace: pfmt(f, "{%t}", c0);
+ break;
+ case Acount: pfmt(f, "$#%t", c0);
+ break;
+ case Kfunc: pfmt(f, "fn %t %t", c0, c1);
+ break;
+ case Kif: pfmt(f, "if%t%t", c0, c1);
+ break;
+ case Kelse: pfmt(f, "else %t", c0);
+ break;
+ case Aoror: pfmt(f, "%t || %t", c0, c1);
+ break;
+ case Aparen: pfmt(f, "(%t)", c0);
+ break;
+ case Asub: pfmt(f, "$%t(%t)", c0, c1);
+ break;
+ case Asimple: pfmt(f, "%t", c0);
+ break;
+ case Ksubsh: pfmt(f, "@ %t", c0);
+ break;
+ case Kswitch: pfmt(f, "switch %t %t", c0, c1);
+ break;
+ case Ktwiddle: pfmt(f, "~ %t %t", c0, c1);
+ break;
+ case Kwhile: pfmt(f, "while %t%t", c0, c1);
+ break;
+ case Aargs:
+ if(c0==0)
+ pfmt(f, "%t", c1);
+ else if(c1==0)
+ pfmt(f, "%t", c0);
+ else
+ pfmt(f, "%t %t", c0, c1);
+ break;
+ case ';':
+ if(c0){
+ if(c1)
+ pfmt(f, "%t%c%t", c0, '\n', c1);
+ else pfmt(f, "%t", c0);
+ }
+ else pfmt(f, "%t", c1);
+ break;
+ case Awords:
+ if(c0)
+ pfmt(f, "%t ", c0);
+ pfmt(f, "%t", c1);
+ break;
+ case Kfor:
+ pfmt(f, "for(%t", c0);
+ if(c1)
+ pfmt(f, " in %t", c1);
+ pfmt(f, ")%t", c2);
+ break;
+ case Aword:
+ if(t->quoted)
+ pfmt(f, "%Q", t->str);
+ else
+ pdeglob(f, t->str);
+ break;
+ case Adup:
+ 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 Apipefd:
+ case Aredir:
+ 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 '=':
+ pfmt(f, "%t=%t", c0, c1);
+ if(c2)
+ pfmt(f, " %t", c2);
+ break;
+ case Apipe:
+ 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 '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..1415f5c
--- /dev/null
+++ b/sys/cmd/rc/lex.c
@@ -0,0 +1,415 @@
+#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(char 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();
+}
+
+/* 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 Alparen;
+ }
+ if(quotechr(c))
+ return Acarot;
+ }
+
+ skipws();
+ switch(c=advance()) {
+ case EOF:
+ lastdol = 0;
+ return EOF;
+ case '$':
+ lastdol = 1;
+ if(nextis('#'))
+ return Acount;
+ if (nextis('"'))
+ return Aquote;
+ return Adol;
+ case '&':
+ lastdol = 0;
+ if(nextis('&'))
+ return Aandand;
+ return Aand;
+
+ case '!':
+ return Kbang;
+ case '@':
+ return Ksubsh;
+ case '~':
+ return Ktwiddle;
+
+ case '|':
+ lastdol = 0;
+ if(nextis('|')){
+ skipnl();
+ return Aoror;
+ }
+ (*node) = newtree();
+ (*node)->type = Apipe;
+ (*node)->redir.fd[0] = 0;
+ (*node)->redir.fd[1] = 1;
+ goto redir;
+ case '>':
+ (*node) = newtree();
+ (*node)->type = Aredir;
+ if (nextis(c))
+ (*node)->redir.type = Rappend;
+ else
+ (*node)->redir.type = Rwrite;
+ (*node)->redir.fd[0] = 1;
+ goto redir;
+ case '<':
+ (*node) = newtree();
+ (*node)->type = Aredir;
+ 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 == Aredir)
+ (*node)->type = Adup;
+ 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 == Apipe)
+ goto redirerr;
+ (*node)->redir.type = Rclose;
+ }
+ }
+ if (c != ']'
+ ||(*node)->type==Adup && ((*node)->redir.type==Rhere || (*node)->redir.type==Rappend))
+ goto redirerr;
+ }
+ if ((c = ((*node)->type)) == Apipe)
+ 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 Aword;
+ }
+ 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 = Aword;
+ (*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..baaf6bc
--- /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);
+ parse();
+
+#endif
+ exit(0);
+}
diff --git a/sys/cmd/rc/parse.c b/sys/cmd/rc/parse.c
new file mode 100644
index 0000000..d963c12
--- /dev/null
+++ b/sys/cmd/rc/parse.c
@@ -0,0 +1,430 @@
+#include "rc.h"
+
+// -----------------------------------------------------------------------
+// global data
+
+static int lasta, nexta=EOF;
+static Tree *node; /* if token was lexed as a tree node (redirs and words), its here */
+
+static uchar prectab[256] = {
+ [Kif] = 1, [Kfor] = 1, [Kswitch] = 1, [Kelse] = 1,
+ [Aandand] = 2, [Aoror] = 2,
+ [Kbang] = 3, [Ksubsh] = 3,
+ [Apipe] = 4,
+ [Acarot] = 5,
+ [Adol] = 6, [Acount] = 6, [Aquote] = 6,
+ [Asub] = 7,
+};
+
+// -----------------------------------------------------------------------
+// helpers
+
+static
+int
+lookahead(void)
+{
+ int tok;
+
+ if (nexta != EOF)
+ return nexta;
+
+ tok = lex(&node);
+ return nexta = tok;
+}
+
+static
+int
+advance(void)
+{
+ int tok = lookahead();
+ lasta = nexta, nexta = EOF;
+ node = nil;
+
+ return tok;
+}
+
+static
+int
+nextis(int tok)
+{
+ if (lookahead() == tok) {
+ advance();
+ return 1;
+ }
+ return 0;
+}
+
+// -----------------------------------------------------------------------
+// subparsers
+
+static Tree *word(void);
+static Tree *comword(void);
+static Tree *cmd(int prec);
+
+static
+Tree*
+body(void)
+{
+ int tok;
+ Tree *l, *r;
+ l = cmd(1);
+loop:
+ switch((tok=lookahead())){
+ case '&':
+ l = tree1('&', l);
+ /* fallthrough */
+ case ';': case '\n':
+ advance();
+ r = cmd(1);
+ l = tree2(';', l, r);
+ goto loop;
+ default:
+ ;
+ }
+
+ return l;
+}
+
+static
+Tree*
+brace(void)
+{
+ Tree *t;
+
+ if (!nextis('{'))
+ rcerror("not a brace");
+ t = tree1(Abrace, body());
+ if (!nextis('}'))
+ rcerror("unmatched brace");
+
+ return t;
+}
+
+static
+Tree*
+paren(void)
+{
+ Tree *t;
+
+ if (!nextis('('))
+ rcerror("not a paren");
+ t = tree1(Aparen, body());
+ 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 Adup:
+ t = node;
+ advance();
+ break;
+ case Aredir:
+ advance();
+ t = hang1(node, (node->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(Awords, t, tt);
+
+ return t;
+}
+
+static
+Tree*
+comword(void)
+{
+ int tok;
+ Tree *t, *tt;
+
+ switch(tok=lookahead()){
+ case Adol:
+ advance();
+ t = word();
+ if(nextis('(')) {
+ t = tree2(Asub, t, words());
+ if (!nextis(')'))
+ rcerror("malformed index expression");
+ }
+ return tree1(Adol, t);
+ case Acount:
+ advance();
+ return tree1(Acount, word());
+ case Atick:
+ advance();
+ return tree1(Atick, brace());
+ case Alparen:
+ return paren();
+ case Aredir:
+ advance();
+ t = hang1(node, brace());
+ t->type = Apipefd;
+ return t;
+ case Aword:
+ 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('='))
+ return tree3(Aeq, t, word(), cmd(prectab[Kbang]));
+
+ /* is a 'simple' */
+simple:
+ switch ((tok=lookahead())) {
+ case Aredir:
+ case Adup:
+ t = tree2(Aargs, t, redir());
+ goto simple;
+ default:
+ if ((tt = word())) {
+ t = tree2(Aargs, t, tt);
+ goto simple;
+ }
+ /* fallthrough */
+ }
+
+ return simplehang(t);
+}
+
+static
+Tree*
+opand(void)
+{
+ int tok;
+ Tree *t, *tt;
+
+ switch(tok=lookahead()) {
+ case Kif:
+ advance();
+ t = paren();
+ skipnl();
+ tt = cmd(prectab[Kif]);
+ t = tree2(Kif, t, tt);
+ return t;
+
+ case Kelse:
+ advance();
+ skipnl();
+ t = tree1(Kelse, cmd(prectab[Kelse]));
+ return t;
+
+ case Kfor:
+ advance();
+ if (!nextis('('))
+ rcerror("malformed for statement");
+ t = word();
+ if (nextis(Kin)) {
+ advance();
+ tt = words();
+ t = tree3(Kin, t, tt, nil);
+ } else
+ t = tree3(Kin, t, nil, nil);
+ skipnl();
+ tt = cmd(prectab[Kfor]);
+ t->child[2] = tt;
+ return t;
+
+ case Kswitch:
+ advance();
+ t = word();
+ skipnl();
+ tt = brace();
+ t = tree2(Kswitch, t, tt);
+ return t;
+
+ case Kfunc:
+ advance();
+ t = words();
+ if ((tok=lookahead()) == '{') {
+ tt = brace();
+ t = tree2(Kfunc, t, tt);
+ } else
+ t = tree1(Kfunc, t);
+ return t;
+
+ case Ksubsh:
+ advance();
+ t = tree1(Ksubsh, cmd(prectab[Ksubsh]));
+ return t;
+
+ case Kbang:
+ advance();
+ t = tree1(Kbang, cmd(prectab[Kbang]));
+ return t;
+
+ case Ktwiddle:
+ advance();
+ tt = word();
+ t = tree2(Ktwiddle, tt, words());
+ return t;
+
+ case Albrace:
+ t = brace();
+ tt = epilog();
+ return epihang(t, tt);
+
+ case Aredir: /* fallthrough */
+ case Adup:
+ t = redir();
+ tt = cmd(prectab[Kbang]);
+ 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 == Apipe)
+ 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': case EOF:
+ pfmt(errio, "%t", t);
+ break;
+ default:
+ rcerror("unrecognized token: %d", tok);
+ }
+ return 0;
+}
diff --git a/sys/cmd/rc/rc.h b/sys/cmd/rc/rc.h
new file mode 100644
index 0000000..ad46a77
--- /dev/null
+++ b/sys/cmd/rc/rc.h
@@ -0,0 +1,254 @@
+#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
+
+/* TODO: make sure there are no collisions */
+enum
+{
+ /* keywords */
+ Kstart=11, Kfor, Kin, Kwhile, Kif, Kelse,
+ Kswitch, Kcase, Kfunc, Ktwiddle,
+ Kbang, Ksubsh, Kend,
+
+ /* tokens */
+ Aword='w', Aredir='r', Adup='d', Asimple='s',
+ Aargs='A', Awords='W', Abrace='b', Aparen='p', Asub='S',
+ Apcmd='c', Apipefd='-', Aandand='%', Aoror='@', Acount='#',
+
+ Atick='`', Apipe = '|', Adol='$', Aquote='"', Aand='&',
+ Alparen = '(', Arparen = ')', Albrace='{', Arbrace='}',
+ Asemi=';', Acarot='^', Aeq='=',
+};
+
+enum
+{
+ Rappend = 1,
+ Rwrite = 2,
+ Rread = 3,
+ Rhere = 4,
+ Rdupfd = 5,
+ Rclose = 6,
+ Rrdwr = 7,
+};
+
+// -----------------------------------------------------------------------
+// main types
+
+typedef union Code Code;
+typedef struct Word Word;
+typedef struct List List;
+typedef struct Var Var;
+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 Var
+{
+ string name;
+ Word *val;
+ Code *func;
+ 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;
+ Var *local;
+ 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
+ * -> moves value
+ */
+extern void Xmark(void); /* Xmark: delimit stack with new list */
+extern void Xword(void); /* Xword[val] -> (val) */
+extern void Xassign(void); /* Xassign(name, val): assign name to val */
+extern void Xdol(void); /* Xdol(name): get variable value */
+extern void Xsimple(void); /* Xsimple(args): run command */
+extern void Xexit(void); /* Xexit: exit with status */
+extern void Xerror(char *s); /* Xerror: report an error */
+extern void Xparse(void);
+
+// -----------------------------------------------------------------------
+// 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);
+
+int kwlookup(char *name);
+void initkw(void);
+
+/*
+ * lex.c
+ */
+
+void skipnl(void);
+int wordchr(char 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..33d6ba8
--- /dev/null
+++ b/sys/cmd/rc/rules.mk
@@ -0,0 +1,21 @@
+include share/push.mk
+
+# Local sources
+SRCS_$(d) := \
+ $(d)/io.c \
+ $(d)/util.c \
+ $(d)/var.c \
+ $(d)/word.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..8dca67f
--- /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(Asimple, t);
+ for(u = t->child[0];u->type==Aargs;u=u->child[0]) {
+ if (u->child[1]->type==Adup
+ || u->child[1]->type==Aredir){
+ 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 = Aword;
+ 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..dbe7c14
--- /dev/null
+++ b/sys/cmd/rc/var.c
@@ -0,0 +1,108 @@
+#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(Kfor, "for");
+ putkw(Kin, "in");
+ putkw(Kwhile, "while");
+ putkw(Kif, "if");
+ putkw(Kelse, "else");
+ putkw(Kswitch, "switch");
+ putkw(Kcase, "case");
+ putkw(Kfunc, "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->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);
+}
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);
+}