summaryrefslogtreecommitdiff
path: root/appl/cmd/mash
diff options
context:
space:
mode:
Diffstat (limited to 'appl/cmd/mash')
-rw-r--r--appl/cmd/mash/builtins.b347
-rw-r--r--appl/cmd/mash/depends.b228
-rw-r--r--appl/cmd/mash/dump.b199
-rw-r--r--appl/cmd/mash/exec.b401
-rw-r--r--appl/cmd/mash/expr.b158
-rw-r--r--appl/cmd/mash/eyacc.b2785
-rw-r--r--appl/cmd/mash/eyaccpar223
-rw-r--r--appl/cmd/mash/history.b206
-rw-r--r--appl/cmd/mash/lex.b547
-rw-r--r--appl/cmd/mash/make.b723
-rw-r--r--appl/cmd/mash/mash.b154
-rw-r--r--appl/cmd/mash/mash.m372
-rw-r--r--appl/cmd/mash/mash.y269
-rw-r--r--appl/cmd/mash/mashfile36
-rw-r--r--appl/cmd/mash/mashlib.b60
-rw-r--r--appl/cmd/mash/mashparse.b662
-rw-r--r--appl/cmd/mash/mashparse.m56
-rw-r--r--appl/cmd/mash/misc.b313
-rw-r--r--appl/cmd/mash/mkfile78
-rw-r--r--appl/cmd/mash/serve.b154
-rw-r--r--appl/cmd/mash/symb.b265
-rw-r--r--appl/cmd/mash/tk.b603
-rw-r--r--appl/cmd/mash/xeq.b543
23 files changed, 9382 insertions, 0 deletions
diff --git a/appl/cmd/mash/builtins.b b/appl/cmd/mash/builtins.b
new file mode 100644
index 00000000..b4374581
--- /dev/null
+++ b/appl/cmd/mash/builtins.b
@@ -0,0 +1,347 @@
+implement Mashbuiltin;
+
+#
+# "builtins" builtin, defines:
+#
+# env - print environment or individual elements
+# eval - interpret arguments as mash input
+# exit - exit toplevel, eval or subshell
+# load - load a builtin
+# prompt - print or set prompt
+# quote - print arguments quoted as input for mash
+# run - interpret a file as mash input
+# status - report existence of error output
+# time - time the execution of a command
+# whatis - print variable, function and builtin
+#
+
+include "mash.m";
+include "mashparse.m";
+
+mashlib: Mashlib;
+
+Cmd, Env, Stab: import mashlib;
+sys, bufio: import mashlib;
+
+Iobuf: import bufio;
+
+#
+# Interface to catch the use as a command.
+#
+init(nil: ref Draw->Context, nil: list of string)
+{
+ ssys := load Sys Sys->PATH;
+ ssys->fprint(ssys->fildes(2), "builtins: cannot run as a command\n");
+ raise "fail: error";
+}
+
+#
+# Used by whatis.
+#
+name(): string
+{
+ return "builtins";
+}
+
+#
+# Install commands.
+#
+mashinit(nil: list of string, lib: Mashlib, this: Mashbuiltin, e: ref Env)
+{
+ mashlib = lib;
+ e.defbuiltin("env", this);
+ e.defbuiltin("eval", this);
+ e.defbuiltin("exit", this);
+ e.defbuiltin("load", this);
+ e.defbuiltin("prompt", this);
+ e.defbuiltin("quote", this);
+ e.defbuiltin("run", this);
+ e.defbuiltin("status", this);
+ e.defbuiltin("time", this);
+ e.defbuiltin("whatis", this);
+}
+
+#
+# Execute a builtin.
+#
+mashcmd(e: ref Env, l: list of string)
+{
+ case hd l {
+ "env" =>
+ l = tl l;
+ if (l == nil) {
+ out := e.outfile();
+ if (out == nil)
+ return;
+ prsymbs(out, e.global, "=");
+ prsymbs(out, e.local, ":=");
+ out.close();
+ } else
+ e.usage("env");
+ "eval" =>
+ eval(e, tl l);
+ "exit" =>
+ raise mashlib->EXIT;
+ "load" =>
+ l = tl l;
+ if (len l == 1)
+ e.doload(hd l);
+ else
+ e.usage("load file");
+ "prompt" =>
+ l = tl l;
+ case len l {
+ 0 =>
+ mashlib->prprompt(0);
+ 1 =>
+ mashlib->prompt = hd l;
+ 2 =>
+ mashlib->prompt = hd l;
+ mashlib->contin = hd tl l;
+ * =>
+ e.usage("prompt [string]");
+ }
+ "quote" =>
+ l = tl l;
+ if (l != nil) {
+ out := e.outfile();
+ if (out == nil)
+ return;
+ f := 0;
+ while (l != nil) {
+ if (f)
+ out.putc(' ');
+ else
+ f = 1;
+ out.puts(mashlib->quote(hd l));
+ l = tl l;
+ }
+ out.putc('\n');
+ out.close();
+ }
+ "run" =>
+ if (!run(e, tl l))
+ e.usage("run [-] [-denx] file [arg ...]");
+ "status" =>
+ l = tl l;
+ if (l != nil)
+ status(e, l);
+ else
+ e.usage("status cmd [arg ...]");
+ "time" =>
+ l = tl l;
+ if (l != nil)
+ time(e, l);
+ else
+ e.usage("time cmd [arg ...]");
+ "whatis" =>
+ l = tl l;
+ if (l != nil) {
+ out := e.outfile();
+ if (out == nil)
+ return;
+ while (l != nil) {
+ whatis(e, out, hd l);
+ l = tl l;
+ }
+ out.close();
+ }
+ }
+}
+
+#
+# Print a variable and its value.
+#
+prone(out: ref Iobuf, eq, s: string, v: list of string)
+{
+ out.puts(s);
+ out.putc(' ');
+ out.puts(eq);
+ if (v != mashlib->empty) {
+ do {
+ out.putc(' ');
+ out.puts(mashlib->quote(hd v));
+ v = tl v;
+ } while (v != nil);
+ }
+ out.puts(";\n");
+}
+
+#
+# Print the contents of a symbol table.
+#
+prsymbs(out: ref Iobuf, t: ref Stab, eq: string)
+{
+ if (t == nil)
+ return;
+ for (l := t.all(); l != nil; l = tl l) {
+ s := hd l;
+ v := s.value;
+ if (v != nil)
+ prone(out, eq, s.name, v);
+ }
+}
+
+#
+# Print variables, functions and builtins.
+#
+whatis(e: ref Env, out: ref Iobuf, s: string)
+{
+ f := 0;
+ v := e.global.find(s);
+ if (v != nil) {
+ if (v.value != nil)
+ prone(out, "=", s, v.value);
+ if (v.func != nil) {
+ out.puts("fn ");
+ out.puts(s);
+ out.puts(" { ");
+ out.puts(v.func.text());
+ out.puts(" };\n");
+ }
+ if (v.builtin != nil) {
+ out.puts("load ");
+ out.puts(v.builtin->name());
+ out.puts("; ");
+ out.puts(s);
+ out.puts(";\n");
+ }
+ f = 1;
+ }
+ if (e.local != nil) {
+ v = e.local.find(s);
+ if (v != nil) {
+ prone(out, ":=", s, v.value);
+ f = 1;
+ }
+ }
+ if (!f) {
+ out.puts(s);
+ out.puts(": not found\n");
+ }
+}
+
+#
+# Catenate arguments and interpret as mash input.
+#
+eval(e: ref Env, l: list of string)
+{
+ s: string;
+ while (l != nil) {
+ s = s + " " + hd l;
+ l = tl l;
+ }
+ e = e.copy();
+ e.flags &= ~mashlib->EInter;
+ e.sopen(s);
+ mashlib->parse->parse(e);
+}
+
+#
+# Interpret file as mash input.
+#
+run(e: ref Env, l: list of string): int
+{
+ f := 0;
+ if (l == nil)
+ return 0;
+ e = e.copy();
+ s := hd l;
+ while (s[0] == '-') {
+ if (s == "-")
+ f = 1;
+ else {
+ for (i := 1; i < len s; i++) {
+ case s[i] {
+ 'd' =>
+ e.flags |= mashlib->EDumping;
+ 'e' =>
+ e.flags |= mashlib->ERaise;
+ 'n' =>
+ e.flags |= mashlib->ENoxeq;
+ 'x' =>
+ e.flags |= mashlib->EEcho;
+ * =>
+ return 0;
+ }
+ }
+ }
+ l = tl l;
+ if (l == nil)
+ return 0;
+ s = hd l;
+ }
+ fd := sys->open(s, Sys->OREAD);
+ if (fd == nil) {
+ err := mashlib->errstr();
+ if (mashlib->nonexistent(err) && s[0] != '/' && s[0:2] != "./") {
+ fd = sys->open(mashlib->LIB + s, Sys->OREAD);
+ if (fd == nil)
+ err = mashlib->errstr();
+ else
+ s = mashlib->LIB + s;
+ }
+ if (fd == nil) {
+ if (!f)
+ e.report(s + ": " + err);
+ return 1;
+ }
+ }
+ e.local = Stab.new();
+ e.local.assign(mashlib->ARGS, tl l);
+ e.flags &= ~mashlib->EInter;
+ e.fopen(fd, s);
+ mashlib->parse->parse(e);
+ return 1;
+}
+
+#
+# Run a command and report true on no error output.
+#
+status(e: ref Env, l: list of string)
+{
+ in := child(e, l);
+ if (in == nil)
+ return;
+ b := array[256] of byte;
+ n := sys->read(in, b, len b);
+ if (n != 0) {
+ while (n > 0)
+ n = sys->read(in, b, len b);
+ if (n < 0)
+ e.couldnot("read", "pipe");
+ } else
+ e.output(Mashlib->TRUE);
+}
+
+#
+# Status env child.
+#
+child(e: ref Env, l: list of string): ref Sys->FD
+{
+ e = e.copy();
+ fds := e.pipe();
+ if (fds == nil)
+ return nil;
+ if (sys->dup(fds[0].fd, 2) < 0) {
+ e.couldnot("dup", "pipe");
+ return nil;
+ }
+ t := e.stderr;
+ e.stderr = fds[0];
+ e.runit(l, nil, nil, 0);
+ e.stderr = t;
+ sys->dup(t.fd, 2);
+ return fds[1];
+}
+
+#
+# Time the execution of a command.
+#
+time(e: ref Env, l: list of string)
+{
+ t1 := sys->millisec();
+ e.runit(l, nil, nil, 1);
+ t2 := sys->millisec();
+ sys->fprint(e.stderr, "%.4g\n", real (t2 - t1) / 1000.0);
+}
diff --git a/appl/cmd/mash/depends.b b/appl/cmd/mash/depends.b
new file mode 100644
index 00000000..dfb14ef5
--- /dev/null
+++ b/appl/cmd/mash/depends.b
@@ -0,0 +1,228 @@
+#
+# Dependency/rule routines.
+#
+
+DHASH: con 127; # dephash size
+
+#
+# Initialize. "make -clear" calls this.
+#
+initdep()
+{
+ dephash = array[DHASH] of list of ref Target;
+ rules = nil;
+}
+
+#
+# Lookup a target in dephash, maybe add it.
+#
+target(s: string, insert: int): ref Target
+{
+ h := hash->fun1(s, DHASH);
+ l := dephash[h];
+ while (l != nil) {
+ if ((hd l).target == s)
+ return hd l;
+ l = tl l;
+ }
+ if (!insert)
+ return nil;
+ t := ref Target(s, nil);
+ dephash[h] = t :: dephash[h];
+ return t;
+}
+
+adddep(s: string, d: ref Depend)
+{
+ t := target(s, 1);
+ t.depends = d :: t.depends;
+}
+
+#
+# Dependency (:) command.
+# Evaluate lhs and rhs, make dependency, and add to the targets.
+#
+Cmd.depend(c: self ref Cmd, e: ref Env)
+{
+ if ((e.flags & ETop) == 0) {
+ e.report("dependency not at top level");
+ return;
+ }
+ if (dephash == nil)
+ initdep();
+ w := pass1(e, c.words);
+ if (w == nil)
+ return;
+ l := pass2(e, w);
+ if (l == nil)
+ return;
+ r: list of string;
+ if (c.left.words != nil) {
+ w = pass1(e, c.left.words);
+ if (w == nil)
+ return;
+ r = pass2(e, w);
+ if (r == nil)
+ return;
+ }
+ d := ref Depend(l, r, c.left.op, c.left.left, 0);
+ while (l != nil) {
+ adddep(hd l, d);
+ l = tl l;
+ }
+}
+
+#
+# Evaluate rule lhs and break into path components.
+#
+rulelhs(e: ref Env, i: ref Item): ref Lhs
+{
+ i = i.ieval1(e);
+ if (i == nil)
+ return nil;
+ (s, l, nil) := i.ieval2(e);
+ if (l != nil) {
+ e.report("rule pattern evaluates to a list");
+ return nil;
+ }
+ if (s == nil) {
+ e.report("rule pattern evaluates to nil");
+ return nil;
+ }
+ (n, p) := sys->tokenize(s, "/");
+ return ref Lhs(s, p, n);
+}
+
+#
+# Rule (:~) command.
+# First pass of rhs evaluation is done here.
+#
+Cmd.rule(c: self ref Cmd, e: ref Env)
+{
+ if (e.flags & ETop) {
+ l := rulelhs(e, c.item);
+ if (l == nil)
+ return;
+ r := c.left.item.ieval1(e);
+ if (r == nil)
+ return;
+ rules = ref Rule(l, r, c.left.op, c.left.left) :: rules;
+ } else
+ e.report("rule not at top level");
+}
+
+Target.find(s: string): ref Target
+{
+ if (dephash == nil)
+ return nil;
+ return target(s, 0);
+}
+
+#
+# Match a path element.
+#
+matchelem(p, s: string): int
+{
+ m := len p;
+ n := len s;
+ if (m == n && p == s)
+ return 1;
+ for (i := 0; i < m; i++) {
+ if (p[i] == '*') {
+ j := i + 1;
+ if (j == m)
+ return 1;
+ q := p[j:];
+ do {
+ if (matchelem(q, s[i:]))
+ return 1;
+ } while (++i < n);
+ return 0;
+ } else if (i >= n || p[i] != s[i])
+ return 0;
+ }
+ return 0;
+}
+
+#
+# Match a path element and return a list of sub-matches.
+#
+matches(p, s: string): (int, list of string)
+{
+ m := len p;
+ n := len s;
+ for (i := 0; i < m; i++) {
+ if (p[i] == '*') {
+ j := i + 1;
+ if (j == m)
+ return (1, s[i:] :: nil);
+ q := p[j:];
+ do {
+ (r, l) := matches(q, s[i:]);
+ if (r)
+ return (1, s[j - 1: i] :: l);
+ } while (++i < n);
+ return (0, nil);
+ } else if (i >= n || p[i] != s[i])
+ return (0, nil);
+ }
+ return (m == n, nil);
+}
+
+#
+# Rule match.
+#
+Rule.match(r: self ref Rule, a, n: int, t: list of string): int
+{
+ l := r.lhs;
+ if (l.count != n || (l.text[0] == '/') != a)
+ return 0;
+ for (e := l.elems; e != nil; e = tl e) {
+ if (!matchelem(hd e, hd t))
+ return 0;
+ t = tl t;
+ }
+ return 1;
+}
+
+#
+# Rule match with array of sub-matches.
+#
+Rule.matches(r: self ref Rule, t: list of string): array of string
+{
+ m: list of list of string;
+ c := 1;
+ for (e := r.lhs.elems; e != nil; e = tl e) {
+ (x, l) := matches(hd e, hd t);
+ if (!x)
+ return nil;
+ if (l != nil) {
+ c += len l;
+ m = revstrs(l) :: m;
+ }
+ t = tl t;
+ }
+ a := array[c] of string;
+ while (m != nil) {
+ for (l := hd m; l != nil; l = tl l)
+ a[--c] = hd l;
+ m = tl m;
+ }
+ return a;
+}
+
+#
+# Return list of rules that match a string.
+#
+rulematch(s: string): list of ref Rule
+{
+ m: list of ref Rule;
+ a := s[0] == '/';
+ (n, t) := sys->tokenize(s, "/");
+ for (l := rules; l != nil; l = tl l) {
+ r := hd l;
+ if (r.match(a, n, t))
+ m = r :: m;
+ }
+ return m;
+}
diff --git a/appl/cmd/mash/dump.b b/appl/cmd/mash/dump.b
new file mode 100644
index 00000000..ed4b6309
--- /dev/null
+++ b/appl/cmd/mash/dump.b
@@ -0,0 +1,199 @@
+#
+# Output routines.
+#
+
+#
+# Echo list of strings.
+#
+echo(e: ref Env, s: list of string)
+{
+ out := e.outfile();
+ if (out == nil)
+ return;
+ out.putc('+');
+ for (t := s; t != nil; t = tl t) {
+ out.putc(' ');
+ out.puts(hd t);
+ }
+ out.putc('\n');
+ out.close();
+}
+
+#
+# Return text representation of Word/Item/Cmd.
+#
+
+Word.word(w: self ref Word, d: string): string
+{
+ if (w == nil)
+ return nil;
+ if (d != nil)
+ return d + w.text;
+ if (w.flags & Wquoted)
+ return enquote(w.text);
+ return w.text;
+}
+
+Item.text(i: self ref Item): string
+{
+ if (i == nil)
+ return nil;
+ case i.op {
+ Icaret =>
+ return i.left.text() + " ^ " + i.right.text();
+ Iicaret =>
+ return i.left.text() + i.right.text();
+ Idollarq =>
+ return i.word.word("$\"");
+ Idollar or Imatch =>
+ return i.word.word("$");
+ Iword =>
+ return i.word.word(nil);
+ Iexpr =>
+ return "(" + i.cmd.text() + ")";
+ Ibackq =>
+ return "`" + group(i.cmd);
+ Iquote =>
+ return "\"" + group(i.cmd);
+ Iinpipe =>
+ return "<" + group(i.cmd);
+ Ioutpipe =>
+ return ">" + group(i.cmd);
+ * =>
+ return "?" + string i.op;
+ }
+}
+
+words(l: list of ref Item): string
+{
+ s: string;
+ while (l != nil) {
+ if (s == nil)
+ s = (hd l).text();
+ else
+ s = s + " " + (hd l).text();
+ l = tl l;
+ }
+ return s;
+}
+
+redir(s: string, c: ref Cmd): string
+{
+ if (c == nil)
+ return s;
+ for (l := c.redirs; l != nil; l = tl l) {
+ r := hd l;
+ s = s + " " + rdsymbs[r.op] + " " + r.word.text();
+ }
+ return s;
+}
+
+cmd2in(c: ref Cmd, s: string): string
+{
+ return c.left.text() + " " + s + " " + c.right.text();
+}
+
+group(c: ref Cmd): string
+{
+ if (c == nil)
+ return "{ }";
+ return redir("{ " + c.text() + " }", c);
+}
+
+sequence(c: ref Cmd): string
+{
+ s: string;
+ do {
+ r := c.right;
+ t := ";";
+ if (r.op == Casync) {
+ r = r.left;
+ t = "&";
+ }
+ if (s == nil)
+ s = r.text() + t;
+ else
+ s = r.text() + t + " " + s;
+ c = c.left;
+ } while (c != nil);
+ return s;
+}
+
+Cmd.text(c: self ref Cmd): string
+{
+ if (c == nil)
+ return nil;
+ case c.op {
+ Csimple =>
+ return redir(words(c.words), c);
+ Cseq =>
+ return sequence(c);
+ Cfor =>
+ return "for (" + c.item.text() + " in " + words(c.words) + ") " + c.left.text();
+ Cif =>
+ return "if (" + c.left.text() +") " + c.right.text();
+ Celse =>
+ return c.left.text() +" else " + c.right.text();
+ Cwhile =>
+ return "while (" + c.left.text() +") " + c.right.text();
+ Ccase =>
+ return redir("case " + c.left.text() + " { " + c.right.text() + "}", c);
+ Ccases =>
+ s := c.left.text();
+ if (s[len s - 1] != '&')
+ return s + "; " + c.right.text();
+ return s + " " + c.right.text();
+ Cmatched =>
+ return cmd2in(c, "=>");
+ Cdefeq =>
+ return c.item.text() + " := " + words(c.words);
+ Ceq =>
+ return c.item.text() + " = " + words(c.words);
+ Cfn =>
+ return "fn " + c.item.text() + " " + group(c.left);
+ Crescue =>
+ return "rescue " + c.item.text() + " " + group(c.left);
+ Casync =>
+ return c.left.text() + "&";
+ Cgroup =>
+ return group(c.left);
+ Clistgroup =>
+ return ":" + group(c.left);
+ Csubgroup =>
+ return "@" + group(c.left);
+ Cnop =>
+ return nil;
+ Cword =>
+ return c.item.text();
+ Ccaret =>
+ return cmd2in(c, "^");
+ Chd =>
+ return "hd " + c.left.text();
+ Clen =>
+ return "len " + c.left.text();
+ Cnot =>
+ return "!" + c.left.text();
+ Ctl =>
+ return "tl " + c.left.text();
+ Ccons =>
+ return cmd2in(c, "::");
+ Ceqeq =>
+ return cmd2in(c, "==");
+ Cnoteq =>
+ return cmd2in(c, "!=");
+ Cmatch =>
+ return cmd2in(c, "~");
+ Cpipe =>
+ return cmd2in(c, "|");
+ Cdepend =>
+ return words(c.words) + " : " + words(c.left.words) + " " + c.left.text();
+ Crule =>
+ return c.item.text() + " :~ " + c.left.item.text() + " " + c.left.text();
+ * =>
+ if (c.op >= Cprivate)
+ return "Priv+" + string (c.op - Cprivate);
+ else
+ return "?" + string c.op;
+ }
+ return nil;
+}
diff --git a/appl/cmd/mash/exec.b b/appl/cmd/mash/exec.b
new file mode 100644
index 00000000..faa003b3
--- /dev/null
+++ b/appl/cmd/mash/exec.b
@@ -0,0 +1,401 @@
+#
+# Manage the execution of a command.
+#
+
+srv: string; # srv file proto
+nsrv: int = 0; # srv file unique id
+
+#
+# Return error string.
+#
+errstr(): string
+{
+ return sys->sprint("%r");
+}
+
+#
+# Server thread for servefd.
+#
+server(c: ref Sys->FileIO, fd: ref Sys->FD, write: int)
+{
+ a: array of byte;
+ if (!write)
+ a = array[Sys->ATOMICIO] of byte;
+ for (;;) {
+ alt {
+ (nil, b, nil, wc) := <- c.write =>
+ if (wc == nil)
+ return;
+ if (!write) {
+ wc <- = (0, EPIPE);
+ return;
+ }
+ r := sys->write(fd, b, len b);
+ if (r < 0) {
+ wc <- = (0, errstr());
+ return;
+ }
+ wc <- = (r, nil);
+ (nil, n, nil, rc) := <- c.read =>
+ if (rc == nil)
+ return;
+ if (write) {
+ rc <- = (array[0] of byte, nil);
+ return;
+ }
+ if (n > Sys->ATOMICIO)
+ n = Sys->ATOMICIO;
+ r := sys->read(fd, a, n);
+ if (r < 0) {
+ rc <- = (nil, errstr());
+ return;
+ }
+ rc <- = (a[0:r], nil);
+ }
+ }
+}
+
+#
+# Serve FD as a #s file. Used to implement generators.
+#
+Env.servefd(e: self ref Env, fd: ref Sys->FD, write: int): string
+{
+ (s, c) := e.servefile(nil);
+ spawn server(c, fd, write);
+ return s;
+}
+
+#
+# Generate name and FileIO adt for a served filed.
+#
+Env.servefile(e: self ref Env, n: string): (string, ref Sys->FileIO)
+{
+ c: ref Sys->FileIO;
+ s: string;
+ if (srv == nil) {
+ (ok, d) := sys->stat(CHAN);
+ if (ok < 0)
+ e.couldnot("stat", CHAN);
+ if (d.dtype != 's') {
+ if (sys->bind("#s", CHAN, Sys->MBEFORE) < 0)
+ e.couldnot("bind", CHAN);
+ }
+ srv = "mash." + string sys->pctl(0, nil);
+ }
+ retry := 0;
+ for (;;) {
+ if (retry || n == nil)
+ s = srv + "." + string nsrv++;
+ else
+ s = n;
+ c = sys->file2chan(CHAN, s);
+ s = CHAN + "/" + s;
+ if (c == nil) {
+ if (retry || n == nil || errstr() != EEXISTS)
+ e.couldnot("file2chan", s);
+ retry = 1;
+ continue;
+ }
+ break;
+ }
+ if (n != nil)
+ n = CHAN + "/" + n;
+ else
+ n = s;
+ if (retry && sys->bind(s, n, Sys->MREPL) < 0)
+ e.couldnot("bind", n);
+ return (n, c);
+}
+
+#
+# Shorthand for string output.
+#
+Env.output(e: self ref Env, s: string)
+{
+ if (s == nil)
+ return;
+ out := e.outfile();
+ if (out == nil)
+ return;
+ out.puts(s);
+ out.close();
+}
+
+#
+# Return Iobuf for stdout.
+#
+Env.outfile(e: self ref Env): ref Bufio->Iobuf
+{
+ fd := e.out;
+ if (fd == nil)
+ fd = sys->fildes(1);
+ out := bufio->fopen(fd, Bufio->OWRITE);
+ if (out == nil)
+ e.report(sys->sprint("fopen failed: %r"));
+ return out;
+}
+
+#
+# Return FD for /dev/null.
+#
+Env.devnull(e: self ref Env): ref Sys->FD
+{
+ fd := sys->open(DEVNULL, Sys->OREAD);
+ if (fd == nil)
+ e.couldnot("open", DEVNULL);
+ return fd;
+}
+
+#
+# Make a pipe.
+#
+Env.pipe(e: self ref Env): array of ref Sys->FD
+{
+ fds := array[2] of ref Sys->FD;
+ if (sys->pipe(fds) < 0) {
+ e.report(sys->sprint("pipe failed: %r"));
+ return nil;
+ }
+ return fds;
+}
+
+#
+# Open wait file for an env.
+#
+waitfd(e: ref Env)
+{
+ w := "#p/" + string sys->pctl(0, nil) + "/wait";
+ fd := sys->open(w, sys->OREAD);
+ if (fd == nil)
+ e.couldnot("open", w);
+ e.wait = fd;
+}
+
+#
+# Wait for a thread. Perhaps propagate exception or exit.
+#
+waitfor(e: ref Env, pid: int, wc: chan of int, ec, xc: chan of string)
+{
+ if (ec != nil || xc != nil) {
+ spawn waiter(e, pid, wc);
+ if (ec == nil)
+ ec = chan of string;
+ if (xc == nil)
+ xc = chan of string;
+ alt {
+ <-wc =>
+ return;
+ x := <-ec =>
+ <-wc;
+ exitmash();
+ x := <-xc =>
+ <-wc;
+ s := x;
+ if (len s < FAILLEN || s[0:FAILLEN] != FAIL)
+ s = FAIL + s;
+ raise s;
+ }
+ } else
+ waiter(e, pid, nil);
+}
+
+#
+# Wait for a specific pid.
+#
+waiter(e: ref Env, pid: int, wc: chan of int)
+{
+ buf := array[sys->WAITLEN] of byte;
+ for(;;) {
+ n := sys->read(e.wait, buf, len buf);
+ if (n < 0) {
+ e.report(sys->sprint("read wait: %r\n"));
+ break;
+ }
+ status := string buf[0:n];
+ if (status[len status - 1] != ':')
+ sys->fprint(e.stderr, "%s\n", status);
+ who := int status;
+ if (who != 0 && who == pid)
+ break;
+ }
+ if (wc != nil)
+ wc <-= 0;
+}
+
+#
+# Preparse IO for a new thread.
+# Make a new FD group and redirect stdin/stdout.
+#
+prepareio(in, out: ref sys->FD): (int, ref Sys->FD)
+{
+ fds := list of { 0, 1, 2};
+ if (in != nil)
+ fds = in.fd :: fds;
+ if (out != nil)
+ fds = out.fd :: fds;
+ pid := sys->pctl(sys->NEWFD, fds);
+ console := sys->fildes(2);
+ if (in != nil) {
+ sys->dup(in.fd, 0);
+ in = nil;
+ }
+ if (out != nil) {
+ sys->dup(out.fd, 1);
+ out = nil;
+ }
+ return (pid, console);
+}
+
+#
+# Add ".dis" to a command if missing.
+#
+dis(s: string): string
+{
+ if (len s < 4 || s[len s - 4:] != ".dis")
+ return s + ".dis";
+ return s;
+}
+
+#
+# Load a builtin.
+#
+Env.doload(e: self ref Env, s: string)
+{
+ file := dis(s);
+ l := load Mashbuiltin file;
+ if (l == nil) {
+ err := errstr();
+ if (nonexistent(err) && file[0] != '/' && file[0:2] != "./") {
+ l = load Mashbuiltin LIB + file;
+ if (l == nil)
+ err = errstr();
+ }
+ if (l == nil) {
+ e.report(s + ": " + err);
+ return;
+ }
+ }
+ l->mashinit("load" :: s :: nil, lib, l, e);
+}
+
+#
+# Execute a spawned thread (dis module or builtin).
+#
+mkprog(args: list of string, e: ref Env, in, out: ref Sys->FD, wc: chan of int, ec, xc: chan of string)
+{
+ (pid, console) := prepareio(in, out);
+ wc <-= pid;
+ if (pid < 0)
+ return;
+ cmd := hd args;
+ {
+ b := e.builtin(cmd);
+ if (b != nil) {
+ e = e.copy();
+ e.in = in;
+ e.out = out;
+ e.stderr = console;
+ e.wait = nil;
+ b->mashcmd(e, args);
+ } else {
+ file := dis(cmd);
+ c := load Command file;
+ if (c == nil) {
+ err := errstr();
+ if (nonexistent(err) && file[0] != '/' && file[0:2] != "./") {
+ c = load Command "/dis/" + file;
+ if (c == nil)
+ err = errstr();
+ }
+ if (c == nil) {
+ sys->fprint(console, "%s: %s\n", file, err);
+ return;
+ }
+ }
+ c->init(gctxt, args);
+ }
+ }exception x{
+ FAILPAT =>
+ if (xc != nil)
+ xc <-= x;
+ # the command failure should be propagated silently to
+ # a higher level, where $status can be set.. - wrtp.
+ #else
+ # sys->fprint(console, "%s: %s\n", cmd, x.name);
+ exit;
+ EPIPE =>
+ if (xc != nil)
+ xc <-= x;
+ #else
+ # sys->fprint(console, "%s: %s\n", cmd, x.name);
+ exit;
+ EXIT =>
+ if (ec != nil)
+ ec <-= x;
+ exit;
+ }
+}
+
+#
+# Open/create files for redirection.
+#
+redirect(e: ref Env, f: array of string, in, out: ref Sys->FD): (int, ref Sys->FD, ref Sys->FD)
+{
+ s: string;
+ err := 0;
+ if (f[Rinout] != nil) {
+ s = f[Rinout];
+ in = sys->open(s, Sys->ORDWR);
+ if (in == nil) {
+ sys->fprint(e.stderr, "%s: %r\n", s);
+ err = 1;
+ }
+ out = in;
+ } else if (f[Rin] != nil) {
+ s = f[Rin];
+ in = sys->open(s, Sys->OREAD);
+ if (in == nil) {
+ sys->fprint(e.stderr, "%s: %r\n", s);
+ err = 1;
+ }
+ }
+ if (f[Rout] != nil || f[Rappend] != nil) {
+ if (f[Rappend] != nil) {
+ s = f[Rappend];
+ out = sys->open(s, Sys->OWRITE);
+ if (out != nil)
+ sys->seek(out, big 0, Sys->SEEKEND);
+ } else {
+ s = f[Rout];
+ out = nil;
+ }
+ if (out == nil) {
+ out = sys->create(s, Sys->OWRITE, 8r666);
+ if (out == nil) {
+ sys->fprint(e.stderr, "%s: %r\n", s);
+ err = 1;
+ }
+ }
+ }
+ if (err)
+ return (0, nil, nil);
+ return (1, in, out);
+}
+
+#
+# Spawn a command and maybe wait for it.
+#
+exec(a: list of string, e: ref Env, infd, outfd: ref Sys->FD, wait: int)
+{
+ if (wait && e.wait == nil)
+ waitfd(e);
+ wc := chan of int;
+ if (wait && (e.flags & ERaise))
+ xc := chan of string;
+ if (wait && (e.flags & ETop))
+ ec := chan of string;
+ spawn mkprog(a, e, infd, outfd, wc, ec, xc);
+ pid := <-wc;
+ if (wait)
+ waitfor(e, pid, wc, ec, xc);
+}
diff --git a/appl/cmd/mash/expr.b b/appl/cmd/mash/expr.b
new file mode 100644
index 00000000..00e45069
--- /dev/null
+++ b/appl/cmd/mash/expr.b
@@ -0,0 +1,158 @@
+#
+# Expression evaluation.
+#
+
+#
+# Filename pattern matching.
+#
+glob(e: ref Env, s: string): (string, list of string)
+{
+ if (filepat == nil) {
+ filepat = load Filepat Filepat->PATH;
+ if (filepat == nil)
+ e.couldnot("load", Filepat->PATH);
+ }
+ l := filepat->expand(s);
+ if (l != nil)
+ return (nil, l);
+ return (s, nil);
+}
+
+#
+# RE pattern matching.
+#
+match(s1, s2: string): int
+{
+ (re, nil) := regex->compile(s2, 0);
+ return regex->execute(re, s1) != nil;
+}
+
+#
+# RE match of two lists. Two non-singleton lists never match.
+#
+match2(e: ref Env, s1: string, l1: list of string, s2: string, l2: list of string): int
+{
+ if (regex == nil) {
+ regex = load Regex Regex->PATH;
+ if (regex == nil)
+ e.couldnot("load", Regex->PATH);
+ }
+ if (s1 != nil) {
+ if (s2 != nil)
+ return match(s1, s2);
+ while (l2 != nil) {
+ if (match(s1, hd l2))
+ return 1;
+ l2 = tl l2;
+ }
+ } else if (l1 != nil) {
+ if (s2 == nil)
+ return 0;
+ while (l1 != nil) {
+ if (match(hd l1, s2))
+ return 1;
+ l1 = tl l1;
+ }
+ } else if (s2 != nil)
+ return match(nil, s2);
+ else if (l2 != nil) {
+ while (l2 != nil) {
+ if (match(nil, hd l2))
+ return 1;
+ l2 = tl l2;
+ }
+ } else
+ return 1;
+ return 0;
+}
+
+#
+# Test list equality. Same length and identical members.
+#
+eqlist(l1, l2: list of string): int
+{
+ while (l1 != nil && l2 != nil) {
+ if (hd l1 != hd l2)
+ return 0;
+ l1 = tl l1;
+ l2 = tl l2;
+ }
+ return l1 == nil && l2 == nil;
+}
+
+#
+# Equality operator.
+#
+Cmd.evaleq(c: self ref Cmd, e: ref Env): int
+{
+ (s1, l1, nil) := c.left.eeval2(e);
+ (s2, l2, nil) := c.right.eeval2(e);
+ if (s1 != nil)
+ return s1 == s2;
+ if (l1 != nil)
+ return eqlist(l1, l2);
+ return s2 == nil && l2 == nil;
+}
+
+#
+# Match operator.
+#
+Cmd.evalmatch(c: self ref Cmd, e: ref Env): int
+{
+ (s1, l1, nil) := c.left.eeval2(e);
+ (s2, l2, nil) := c.right.eeval2(e);
+ return match2(e, s1, l1, s2, l2);
+}
+
+#
+# Catenation operator.
+#
+Item.caret(i: self ref Item, e: ref Env): (string, list of string, int)
+{
+ (s1, l1, x1) := i.left.ieval2(e);
+ (s2, l2, x2) := i.right.ieval2(e);
+ return caret(s1, l1, x1, s2, l2, x2);
+}
+
+#
+# Caret of lists. A singleton distributes. Otherwise pairwise, padded with nils.
+#
+caret(s1: string, l1: list of string, x1: int, s2: string, l2: list of string, x2: int): (string, list of string, int)
+{
+ l: list of string;
+ if (s1 != nil) {
+ if (s2 != nil)
+ return (s1 + s2, nil, x1 | x2);
+ if (l2 == nil)
+ return (s1, nil, x1);
+ while (l2 != nil) {
+ l = (s1 + hd l2) :: l;
+ l2 = tl l2;
+ }
+ } else if (s2 != nil) {
+ if (l1 == nil)
+ return (s2, nil, x2);
+ while (l1 != nil) {
+ l = (hd l1 + s2) :: l;
+ l1 = tl l1;
+ }
+ } else if (l1 != nil) {
+ if (l2 == nil)
+ return (nil, l1, 0);
+ while (l1 != nil || l2 != nil) {
+ if (l1 != nil) {
+ s1 = hd l1;
+ l1 = tl l1;
+ } else
+ s1 = nil;
+ if (l2 != nil) {
+ s2 = hd l2;
+ l2 = tl l2;
+ } else
+ s2 = nil;
+ l = (s1 + s2) :: l;
+ }
+ } else if (l2 != nil)
+ return (nil, l2, 0);
+ return (nil, revstrs(l), 0);
+}
diff --git a/appl/cmd/mash/eyacc.b b/appl/cmd/mash/eyacc.b
new file mode 100644
index 00000000..96b6e412
--- /dev/null
+++ b/appl/cmd/mash/eyacc.b
@@ -0,0 +1,2785 @@
+implement Yacc;
+
+include "sys.m";
+ sys: Sys;
+ print, fprint, sprint: import sys;
+ UTFmax: import Sys;
+
+include "bufio.m";
+ bufio: Bufio;
+ Iobuf: import bufio;
+
+include "draw.m";
+
+Yacc: module
+{
+ init: fn(ctxt: ref Draw->Context, argv: list of string);
+};
+
+Arg: adt
+{
+ argv: list of string;
+ c: int;
+ opts: string;
+
+ init: fn(argv: list of string): ref Arg;
+ opt: fn(arg: self ref Arg): int;
+ arg: fn(arg: self ref Arg): string;
+};
+
+PARSER: con "./eyaccpar";
+OFILE: con "tab.b";
+FILEU: con "output";
+FILED: con "tab.m";
+FILEDEBUG: con "debug";
+
+# the following are adjustable
+# according to memory size
+ACTSIZE: con 30000;
+NSTATES: con 2000;
+TEMPSIZE: con 2000;
+
+SYMINC: con 50; # increase for non-term or term
+RULEINC: con 50; # increase for max rule length prodptr[i]
+PRODINC: con 100; # increase for productions prodptr
+WSETINC: con 50; # increase for working sets wsets
+STATEINC: con 200; # increase for states statemem
+
+NAMESIZE: con 50;
+NTYPES: con 63;
+ISIZE: con 400;
+
+PRIVATE: con 16rE000; # unicode private use
+
+# relationships which must hold:
+# TEMPSIZE >= NTERMS + NNONTERM + 1
+# TEMPSIZE >= NSTATES
+#
+
+NTBASE: con 8r10000;
+ERRCODE: con 8190;
+ACCEPTCODE: con 8191;
+YYLEXUNK: con 3;
+TOKSTART: con 4; #index of first defined token
+
+# no, left, right, binary assoc.
+NOASC, LASC, RASC, BASC: con iota;
+
+# flags for state generation
+DONE, MUSTDO, MUSTLOOKAHEAD: con iota;
+
+# flags for a rule having an action, and being reduced
+ACTFLAG: con 16r4;
+REDFLAG: con 16r8;
+
+# output parser flags
+YYFLAG1: con -1000;
+
+# parse tokens
+IDENTIFIER, MARK, TERM, LEFT, RIGHT, BINARY, PREC, LCURLY, IDENTCOLON, NUMBER, START, TYPEDEF, TYPENAME, MODULE: con PRIVATE+iota;
+
+ENDFILE: con 0;
+
+EMPTY: con 1;
+WHOKNOWS: con 0;
+OK: con 1;
+NOMORE: con -1000;
+
+# macros for getting associativity and precedence levels
+ASSOC(i: int): int
+{
+ return i & 3;
+}
+
+PLEVEL(i: int): int
+{
+ return (i >> 4) & 16r3f;
+}
+
+TYPE(i: int): int
+{
+ return (i >> 10) & 16r3f;
+}
+
+# macros for setting associativity and precedence levels
+SETASC(i, j: int): int
+{
+ return i | j;
+}
+
+SETPLEV(i, j: int): int
+{
+ return i | (j << 4);
+}
+
+SETTYPE(i, j: int): int
+{
+ return i | (j << 10);
+}
+
+# I/O descriptors
+stderr: ref Sys->FD;
+fdefine: ref Iobuf; # file for module definition
+fdebug: ref Iobuf; # y.debug for strings for debugging
+ftable: ref Iobuf; # y.tab.c file
+finput: ref Iobuf; # input file
+foutput: ref Iobuf; # y.output file
+
+CodeData, CodeMod, CodeAct: con iota;
+NCode: con 8192;
+
+Code: adt
+{
+ kind: int;
+ data: array of byte;
+ ndata: int;
+ next: cyclic ref Code;
+};
+
+codehead: ref Code;
+codetail: ref Code;
+
+modname: string; # name of module
+
+# communication variables between various I/O routines
+infile: string; # input file name
+numbval: int; # value of an input number
+tokname: string; # input token name, slop for runes and 0
+
+# structure declarations
+Lkset: type array of int;
+
+Pitem: adt
+{
+ prod: array of int;
+ off: int; # offset within the production
+ first: int; # first term or non-term in item
+ prodno: int; # production number for sorting
+};
+
+Item: adt
+{
+ pitem: Pitem;
+ look: Lkset;
+};
+
+Symb: adt
+{
+ name: string;
+ value: int;
+};
+
+Wset: adt
+{
+ pitem: Pitem;
+ flag: int;
+ ws: Lkset;
+};
+
+ # storage of names
+
+parser := PARSER;
+yydebug: string;
+
+ # storage of types
+ntypes: int; # number of types defined
+typeset := array[NTYPES] of string; # pointers to type tags
+
+ # token information
+
+ntokens := 0; # number of tokens
+tokset: array of Symb;
+toklev: array of int; # vector with the precedence of the terminals
+
+ # nonterminal information
+
+nnonter := -1; # the number of nonterminals
+nontrst: array of Symb;
+start: int; # start symbol
+
+ # state information
+
+nstate := 0; # number of states
+pstate := array[NSTATES+2] of int; # index into statemem to the descriptions of the states
+statemem : array of Item;
+tystate := array[NSTATES] of int; # contains type information about the states
+tstates : array of int; # states generated by terminal gotos
+ntstates : array of int; # states generated by nonterminal gotos
+mstates := array[NSTATES] of {* => 0}; # chain of overflows of term/nonterm generation lists
+lastred: int; # number of last reduction of a state
+defact := array[NSTATES] of int; # default actions of states
+
+ # lookahead set information
+
+lkst: array of Lkset;
+nolook := 0; # flag to turn off lookahead computations
+tbitset := 0; # size of lookahead sets
+clset: Lkset; # temporary storage for lookahead computations
+
+ # working set information
+
+wsets: array of Wset;
+cwp: int;
+
+ # storage for action table
+
+amem: array of int; # action table storage
+memp: int; # next free action table position
+indgo := array[NSTATES] of int; # index to the stored goto table
+
+ # temporary vector, indexable by states, terms, or ntokens
+
+temp1 := array[TEMPSIZE] of int; # temporary storage, indexed by terms + ntokens or states
+lineno := 1; # current input line number
+fatfl := 1; # if on, error is fatal
+nerrors := 0; # number of errors
+
+ # assigned token type values
+extval := 0;
+
+ytabc := OFILE; # name of y.tab.c
+
+ # grammar rule information
+
+nprod := 1; # number of productions
+prdptr: array of array of int; # pointers to descriptions of productions
+levprd: array of int; # precedence levels for the productions
+rlines: array of int; # line number for this rule
+
+
+ # statistics collection variables
+
+zzgoent := 0;
+zzgobest := 0;
+zzacent := 0;
+zzexcp := 0;
+zzclose := 0;
+zzrrconf := 0;
+zzsrconf := 0;
+zzstate := 0;
+
+ # optimizer arrays
+yypgo: array of array of int;
+optst: array of array of int;
+ggreed: array of int;
+pgo: array of int;
+
+maxspr: int; # maximum spread of any entry
+maxoff: int; # maximum offset into a array
+maxa: int;
+
+ # storage for information about the nonterminals
+
+pres: array of array of array of int; # vector of pointers to productions yielding each nonterminal
+pfirst: array of Lkset;
+pempty: array of int; # vector of nonterminals nontrivially deriving e
+ # random stuff picked out from between functions
+
+indebug := 0; # debugging flag for cpfir
+pidebug := 0; # debugging flag for putitem
+gsdebug := 0; # debugging flag for stagen
+cldebug := 0; # debugging flag for closure
+pkdebug := 0; # debugging flag for apack
+g2debug := 0; # debugging for go2gen
+adb := 0; # debugging for callopt
+
+Resrv : adt
+{
+ name: string;
+ value: int;
+};
+
+resrv := array[] of {
+ Resrv("binary", BINARY),
+ Resrv("module", MODULE),
+ Resrv("left", LEFT),
+ Resrv("nonassoc", BINARY),
+ Resrv("prec", PREC),
+ Resrv("right", RIGHT),
+ Resrv("start", START),
+ Resrv("term", TERM),
+ Resrv("token", TERM),
+ Resrv("type", TYPEDEF),};
+
+zznewstate := 0;
+
+init(nil: ref Draw->Context, argv: list of string)
+{
+ sys = load Sys Sys->PATH;
+ bufio = load Bufio Bufio->PATH;
+
+ stderr = sys->fildes(2);
+
+ setup(argv); # initialize and read productions
+
+ tbitset = (ntokens+32)/32;
+ cpres(); # make table of which productions yield a given nonterminal
+ cempty(); # make a table of which nonterminals can match the empty string
+ cpfir(); # make a table of firsts of nonterminals
+
+ stagen(); # generate the states
+
+ yypgo = array[nnonter+1] of array of int;
+ optst = array[nstate] of array of int;
+ output(); # write the states and the tables
+ go2out();
+
+ hideprod();
+ summary();
+
+ callopt();
+
+ others();
+
+ bufio->flush();
+}
+
+setup(argv: list of string)
+{
+ j, ty: int;
+
+ ytab := 0;
+ vflag := 0;
+ dflag := 0;
+ stem := 0;
+ stemc := "y";
+ foutput = nil;
+ fdefine = nil;
+ fdebug = nil;
+ arg := Arg.init(argv);
+ while(c := arg.opt()){
+ case c{
+ 'v' or 'V' =>
+ vflag++;
+ 'D' =>
+ yydebug = arg.arg();
+ 'd' =>
+ dflag++;
+ 'o' =>
+ ytab++;
+ ytabc = arg.arg();
+ 's' =>
+ stem++;
+ stemc = arg.arg();
+ * =>
+ usage();
+ }
+ }
+ argv = arg.argv;
+ if(len argv != 1)
+ usage();
+ infile = hd argv;
+ finput = bufio->open(infile, Bufio->OREAD);
+ if(finput == nil)
+ error("cannot open '"+infile+"'");
+
+ openup(stemc, dflag, vflag, ytab, ytabc);
+
+ defin(0, "$end");
+ extval = PRIVATE; # tokens start in unicode 'private use'
+ defin(0, "error");
+ defin(1, "$accept");
+ defin(0, "$unk");
+ i := 0;
+
+ for(t := gettok(); t != MARK && t != ENDFILE; )
+ case t {
+ ';' =>
+ t = gettok();
+
+ START =>
+ if(gettok() != IDENTIFIER)
+ error("bad %%start construction");
+ start = chfind(1, tokname);
+ t = gettok();
+
+ TYPEDEF =>
+ if(gettok() != TYPENAME)
+ error("bad syntax in %%type");
+ ty = numbval;
+ for(;;) {
+ t = gettok();
+ case t {
+ IDENTIFIER =>
+ if((t=chfind(1, tokname)) < NTBASE) {
+ j = TYPE(toklev[t]);
+ if(j != 0 && j != ty)
+ error("type redeclaration of token "+
+ tokset[t].name);
+ else
+ toklev[t] = SETTYPE(toklev[t], ty);
+ } else {
+ j = nontrst[t-NTBASE].value;
+ if(j != 0 && j != ty)
+ error("type redeclaration of nonterminal "+
+ nontrst[t-NTBASE].name);
+ else
+ nontrst[t-NTBASE].value = ty;
+ }
+ continue;
+ ',' =>
+ continue;
+ ';' =>
+ t = gettok();
+ }
+ break;
+ }
+
+ MODULE =>
+ cpymodule();
+ t = gettok();
+
+ LEFT or BINARY or RIGHT or TERM =>
+ # nonzero means new prec. and assoc.
+ lev := t-TERM;
+ if(lev)
+ i++;
+ ty = 0;
+
+ # get identifiers so defined
+ t = gettok();
+
+ # there is a type defined
+ if(t == TYPENAME) {
+ ty = numbval;
+ t = gettok();
+ }
+ for(;;) {
+ case t {
+ ',' =>
+ t = gettok();
+ continue;
+
+ ';' =>
+ break;
+
+ IDENTIFIER =>
+ j = chfind(0, tokname);
+ if(j >= NTBASE)
+ error(tokname+" defined earlier as nonterminal");
+ if(lev) {
+ if(ASSOC(toklev[j]))
+ error("redeclaration of precedence of "+tokname);
+ toklev[j] = SETASC(toklev[j], lev);
+ toklev[j] = SETPLEV(toklev[j], i);
+ }
+ if(ty) {
+ if(TYPE(toklev[j]))
+ error("redeclaration of type of "+tokname);
+ toklev[j] = SETTYPE(toklev[j],ty);
+ }
+ t = gettok();
+ if(t == NUMBER) {
+ tokset[j].value = numbval;
+ t = gettok();
+ }
+ continue;
+ }
+ break;
+ }
+
+ LCURLY =>
+ cpycode();
+ t = gettok();
+
+ * =>
+ error("syntax error");
+ }
+ if(t == ENDFILE)
+ error("unexpected EOF before %%");
+ if(modname == nil)
+ error("missing %module specification");
+
+ moreprod();
+ prdptr[0] = array[4] of {
+ NTBASE, # added production
+ start, # if start is 0, we will overwrite with the lhs of the first rule
+ 1,
+ 0
+ };
+ nprod = 1;
+ curprod := array[RULEINC] of int;
+ t = gettok();
+ if(t != IDENTCOLON)
+ error("bad syntax on first rule");
+
+ if(!start)
+ prdptr[0][1] = chfind(1, tokname);
+
+ # read rules
+ # put into prdptr array in the format
+ # target
+ # followed by id's of terminals and non-terminals
+ # followd by -nprod
+ while(t != MARK && t != ENDFILE) {
+ mem := 0;
+ # process a rule
+ rlines[nprod] = lineno;
+ if(t == '|')
+ curprod[mem++] = prdptr[nprod-1][0];
+ else if(t == IDENTCOLON) {
+ curprod[mem] = chfind(1, tokname);
+ if(curprod[mem] < NTBASE)
+ error("token illegal on LHS of grammar rule");
+ mem++;
+ } else
+ error("illegal rule: missing semicolon or | ?");
+
+ # read rule body
+ t = gettok();
+
+ for(;;){
+ while(t == IDENTIFIER) {
+ curprod[mem] = chfind(1, tokname);
+ if(curprod[mem] < NTBASE)
+ levprd[nprod] = toklev[curprod[mem]];
+ mem++;
+ if(mem >= len curprod){
+ ncurprod := array[mem+RULEINC] of int;
+ ncurprod[0:] = curprod;
+ curprod = ncurprod;
+ }
+ t = gettok();
+ }
+ if(t == PREC) {
+ if(gettok() != IDENTIFIER)
+ error("illegal %%prec syntax");
+ j = chfind(2, tokname);
+ if(j >= NTBASE)
+ error("nonterminal "+nontrst[j-NTBASE].name+" illegal after %%prec");
+ levprd[nprod] = toklev[j];
+ t = gettok();
+ }
+ if(t != '=')
+ break;
+ levprd[nprod] |= ACTFLAG;
+ addcode(CodeAct, "\n"+string nprod+"=>");
+ cpyact(curprod, mem);
+
+ # action within rule...
+ if((t=gettok()) == IDENTIFIER) {
+ # make it a nonterminal
+ j = chfind(1, "$$"+string nprod);
+
+ #
+ # the current rule will become rule number nprod+1
+ # enter null production for action
+ #
+ prdptr[nprod] = array[2] of {j, -nprod};
+
+ # update the production information
+ nprod++;
+ moreprod();
+ levprd[nprod] = levprd[nprod-1] & ~ACTFLAG;
+ levprd[nprod-1] = ACTFLAG;
+ rlines[nprod] = lineno;
+
+ # make the action appear in the original rule
+ curprod[mem++] = j;
+ if(mem >= len curprod){
+ ncurprod := array[mem+RULEINC] of int;
+ ncurprod[0:] = curprod;
+ curprod = ncurprod;
+ }
+ }
+ }
+
+ while(t == ';')
+ t = gettok();
+ curprod[mem++] = -nprod;
+
+ # check that default action is reasonable
+ if(ntypes && !(levprd[nprod]&ACTFLAG) && nontrst[curprod[0]-NTBASE].value) {
+ # no explicit action, LHS has value
+
+ tempty := curprod[1];
+ if(tempty < 0)
+ error("must return a value, since LHS has a type");
+ else
+ if(tempty >= NTBASE)
+ tempty = nontrst[tempty-NTBASE].value;
+ else
+ tempty = TYPE(toklev[tempty]);
+ if(tempty != nontrst[curprod[0]-NTBASE].value)
+ error("default action causes potential type clash");
+ else{
+ addcodec(CodeAct, '\n');
+ addcode(CodeAct, string nprod);
+ addcode(CodeAct, "=>\ne.yyval.");
+ addcode(CodeAct, typeset[tempty]);
+ addcode(CodeAct, " = yys[yyp+1].yyv.");
+ addcode(CodeAct, typeset[tempty]);
+ addcodec(CodeAct, ';');
+ }
+ }
+ moreprod();
+ prdptr[nprod] = array[mem] of int;
+ prdptr[nprod][0:] = curprod[:mem];
+ nprod++;
+ moreprod();
+ levprd[nprod] = 0;
+ }
+
+ #
+ # end of all rules
+ # dump out the prefix code
+ #
+ ftable.puts("implement ");
+ ftable.puts(modname);
+ ftable.puts(";\n");
+
+ dumpcode(CodeMod);
+ dumpmod();
+ dumpcode(CodeAct);
+
+ ftable.puts("YYEOFCODE: con 1;\n");
+ ftable.puts("YYERRCODE: con 2;\n");
+ ftable.puts("YYMAXDEPTH: con 200;\n"); # was 150
+# ftable.puts("yyval: YYSTYPE;\n");
+
+ #
+ # copy any postfix code
+ #
+ if(t == MARK) {
+ ftable.puts("\n#line\t");
+ ftable.puts(string lineno);
+ ftable.puts("\t\"");
+ ftable.puts(infile);
+ ftable.puts("\"\n");
+ while((c=finput.getc()) != Bufio->EOF)
+ ftable.putc(c);
+ }
+ finput.close();
+}
+
+#
+# allocate enough room to hold another production
+#
+moreprod()
+{
+ n := len prdptr;
+ if(nprod < n)
+ return;
+ n += PRODINC;
+ aprod := array[n] of array of int;
+ aprod[0:] = prdptr;
+ prdptr = aprod;
+
+ alevprd := array[n] of int;
+ alevprd[0:] = levprd;
+ levprd = alevprd;
+
+ arlines := array[n] of int;
+ arlines[0:] = rlines;
+ rlines = arlines;
+}
+
+#
+# define s to be a terminal if t=0
+# or a nonterminal if t=1
+#
+defin(nt: int, s: string): int
+{
+ val := 0;
+ if(nt) {
+ nnonter++;
+ if(nnonter >= len nontrst){
+ anontrst := array[nnonter + SYMINC] of Symb;
+ anontrst[0:] = nontrst;
+ nontrst = anontrst;
+ }
+ nontrst[nnonter] = Symb(s, 0);
+ return NTBASE + nnonter;
+ }
+
+ # must be a token
+ ntokens++;
+ if(ntokens >= len tokset){
+ atokset := array[ntokens + SYMINC] of Symb;
+ atokset[0:] = tokset;
+ tokset = atokset;
+
+ atoklev := array[ntokens + SYMINC] of int;
+ atoklev[0:] = toklev;
+ toklev = atoklev;
+ }
+ tokset[ntokens].name = s;
+ toklev[ntokens] = 0;
+
+ # establish value for token
+ # single character literal
+ if(s[0] == ' ' && len s == 1+1){
+ val = s[1];
+ }else if(s[0] == ' ' && s[1] == '\\') { # escape sequence
+ if(len s == 2+1) {
+ # single character escape sequence
+ case s[2] {
+ '\'' => val = '\'';
+ '"' => val = '"';
+ '\\' => val = '\\';
+ 'a' => val = '\a';
+ 'b' => val = '\b';
+ 'n' => val = '\n';
+ 'r' => val = '\r';
+ 't' => val = '\t';
+ 'v' => val = '\v';
+ * =>
+ error("invalid escape "+s[1:3]);
+ }
+ }else if(s[2] == 'u' && len s == 2+1+4) { # \unnnn sequence
+ val = 0;
+ s = s[3:];
+ while(s != ""){
+ c := s[0];
+ if(c >= '0' && c <= '9')
+ c -= '0';
+ else if(c >= 'a' && c <= 'f')
+ c -= 'a' - 10;
+ else if(c >= 'A' && c <= 'F')
+ c -= 'A' - 10;
+ else
+ error("illegal \\unnnn construction");
+ val = val * 16 + c;
+ s = s[1:];
+ }
+ if(val == 0)
+ error("'\\u0000' is illegal");
+ }else
+ error("unknown escape");
+ }else
+ val = extval++;
+
+ tokset[ntokens].value = val;
+ return ntokens;
+}
+
+peekline := 0;
+gettok(): int
+{
+ i, match, c: int;
+
+ tokname = "";
+ for(;;){
+ reserve := 0;
+ lineno += peekline;
+ peekline = 0;
+ c = finput.getc();
+ while(c == ' ' || c == '\n' || c == '\t' || c == '\v' || c == '\r') {
+ if(c == '\n')
+ lineno++;
+ c = finput.getc();
+ }
+
+ # skip comment
+ if(c != '#')
+ break;
+ lineno += skipcom();
+ }
+ case c {
+ Bufio->EOF =>
+ return ENDFILE;
+
+ '{' =>
+ finput.ungetc();
+ return '=';
+
+ '<' =>
+ # get, and look up, a type name (union member name)
+ i = 0;
+ while((c=finput.getc()) != '>' && c != Bufio->EOF && c != '\n')
+ tokname[i++] = c;
+ if(c != '>')
+ error("unterminated < ... > clause");
+ for(i=1; i<=ntypes; i++)
+ if(typeset[i] == tokname) {
+ numbval = i;
+ return TYPENAME;
+ }
+ ntypes++;
+ numbval = ntypes;
+ typeset[numbval] = tokname;
+ return TYPENAME;
+
+ '"' or '\'' =>
+ match = c;
+ tokname[0] = ' ';
+ i = 1;
+ for(;;) {
+ c = finput.getc();
+ if(c == '\n' || c == Bufio->EOF)
+ error("illegal or missing ' or \"" );
+ if(c == '\\') {
+ tokname[i++] = '\\';
+ c = finput.getc();
+ } else if(c == match)
+ return IDENTIFIER;
+ tokname[i++] = c;
+ }
+
+ '%' =>
+ case c = finput.getc(){
+ '%' => return MARK;
+ '=' => return PREC;
+ '{' => return LCURLY;
+ }
+
+ getword(c);
+ # find a reserved word
+ for(c=0; c < len resrv; c++)
+ if(tokname == resrv[c].name)
+ return resrv[c].value;
+ error("invalid escape, or illegal reserved word: "+tokname);
+
+ '0' to '9' =>
+ numbval = c - '0';
+ while(isdigit(c = finput.getc()))
+ numbval = numbval*10 + c-'0';
+ finput.ungetc();
+ return NUMBER;
+
+ * =>
+ if(isword(c) || c=='.' || c=='$')
+ getword(c);
+ else
+ return c;
+ }
+
+ # look ahead to distinguish IDENTIFIER from IDENTCOLON
+ c = finput.getc();
+ while(c == ' ' || c == '\t'|| c == '\n' || c == '\v' || c == '\r' || c == '#') {
+ if(c == '\n')
+ peekline++;
+ # look for comments
+ if(c == '#')
+ peekline += skipcom();
+ c = finput.getc();
+ }
+ if(c == ':')
+ return IDENTCOLON;
+ finput.ungetc();
+ return IDENTIFIER;
+}
+
+getword(c: int)
+{
+ i := 0;
+ while(isword(c) || isdigit(c) || c == '_' || c=='.' || c=='$') {
+ tokname[i++] = c;
+ c = finput.getc();
+ }
+ finput.ungetc();
+}
+
+#
+# determine the type of a symbol
+#
+fdtype(t: int): int
+{
+ v : int;
+ s: string;
+
+ if(t >= NTBASE) {
+ v = nontrst[t-NTBASE].value;
+ s = nontrst[t-NTBASE].name;
+ } else {
+ v = TYPE(toklev[t]);
+ s = tokset[t].name;
+ }
+ if(v <= 0)
+ error("must specify type for "+s);
+ return v;
+}
+
+chfind(t: int, s: string): int
+{
+ if(s[0] == ' ')
+ t = 0;
+ for(i:=0; i<=ntokens; i++)
+ if(s == tokset[i].name)
+ return i;
+ for(i=0; i<=nnonter; i++)
+ if(s == nontrst[i].name)
+ return NTBASE+i;
+
+ # cannot find name
+ if(t > 1)
+ error(s+" should have been defined earlier");
+ return defin(t, s);
+}
+
+#
+# saves module definition in Code
+#
+cpymodule()
+{
+ if(gettok() != IDENTIFIER)
+ error("bad %%module construction");
+ if(modname != nil)
+ error("duplicate %%module construction");
+ modname = tokname;
+
+ level := 0;
+ for(;;) {
+ if((c:=finput.getc()) == Bufio->EOF)
+ error("EOF encountered while processing %%module");
+ case c {
+ '\n' =>
+ lineno++;
+ '{' =>
+ level++;
+ if(level == 1)
+ continue;
+ '}' =>
+ level--;
+
+ # we are finished copying
+ if(level == 0)
+ return;
+ }
+ addcodec(CodeMod, c);
+ }
+}
+
+#
+# saves code between %{ and %}
+#
+cpycode()
+{
+ c := finput.getc();
+ if(c == '\n') {
+ c = finput.getc();
+ lineno++;
+ }
+ addcode(CodeData, "\n#line\t" + string lineno + "\t\"" + infile + "\"\n");
+ while(c != Bufio->EOF) {
+ if(c == '%') {
+ if((c=finput.getc()) == '}')
+ return;
+ addcodec(CodeData, '%');
+ }
+ addcodec(CodeData, c);
+ if(c == '\n')
+ lineno++;
+ c = finput.getc();
+ }
+ error("eof before %%}");
+}
+
+addcode(k: int, s: string)
+{
+ for(i := 0; i < len s; i++)
+ addcodec(k, s[i]);
+}
+
+addcodec(k, c: int)
+{
+ if(codehead == nil
+ || k != codetail.kind
+ || codetail.ndata >= NCode){
+ cd := ref Code(k, array[NCode+UTFmax] of byte, 0, nil);
+ if(codehead == nil)
+ codehead = cd;
+ else
+ codetail.next = cd;
+ codetail = cd;
+ }
+
+ codetail.ndata += sys->char2byte(c, codetail.data, codetail.ndata);
+}
+
+dumpcode(til: int)
+{
+ for(; codehead != nil; codehead = codehead.next){
+ if(codehead.kind == til)
+ return;
+ if(ftable.write(codehead.data, codehead.ndata) != codehead.ndata)
+ error("can't write output file");
+ }
+}
+
+#
+# write out the module declaration and any token info
+#
+dumpmod()
+{
+ if(fdefine != nil) {
+ fdefine.puts(modname);
+ fdefine.puts(": module {\n");
+ }
+ ftable.puts(modname);
+ ftable.puts(": module {\n");
+
+ for(; codehead != nil; codehead = codehead.next){
+ if(codehead.kind != CodeMod)
+ break;
+ if(ftable.write(codehead.data, codehead.ndata) != codehead.ndata)
+ error("can't write output file");
+ if(fdefine != nil && fdefine.write(codehead.data, codehead.ndata) != codehead.ndata)
+ error("can't write define file");
+ }
+
+ for(i:=TOKSTART; i<=ntokens; i++) {
+ # non-literals
+ c := tokset[i].name[0];
+ if(c != ' ' && c != '$') {
+ s := tokset[i].name+": con "+string tokset[i].value+";\n";
+ ftable.puts(s);
+ if(fdefine != nil)
+ fdefine.puts(s);
+ }
+ }
+
+ if(fdefine != nil)
+ fdefine.puts("};\n");
+ ftable.puts("\n};\n");
+
+ if(fdebug != nil) {
+ fdebug.puts("yytoknames = array[] of {\n");
+ for(i=1; i<=ntokens; i++) {
+ if(tokset[i].name != nil)
+ fdebug.puts("\t\""+chcopy(tokset[i].name)+"\",\n");
+ else
+ fdebug.puts("\t\"\",\n");
+ }
+ fdebug.puts("};\n");
+ }
+}
+
+#
+# skip over comments
+# skipcom is called after reading a '#'
+#
+skipcom(): int
+{
+ c := finput.getc();
+ while(c != Bufio->EOF) {
+ if(c == '\n')
+ return 1;
+ c = finput.getc();
+ }
+ error("EOF inside comment");
+ return 0;
+}
+
+#
+# copy limbo action to the next ; or closing }
+#
+cpyact(curprod: array of int, max: int)
+{
+ addcode(CodeAct, "\n#line\t");
+ addcode(CodeAct, string lineno);
+ addcode(CodeAct, "\t\"");
+ addcode(CodeAct, infile);
+ addcode(CodeAct, "\"\n");
+
+ brac := 0;
+
+loop: for(;;){
+ c := finput.getc();
+ swt: case c {
+ ';' =>
+ if(brac == 0) {
+ addcodec(CodeAct, c);
+ return;
+ }
+
+ '{' =>
+ brac++;
+
+ '$' =>
+ s := 1;
+ tok := -1;
+ c = finput.getc();
+
+ # type description
+ if(c == '<') {
+ finput.ungetc();
+ if(gettok() != TYPENAME)
+ error("bad syntax on $<ident> clause");
+ tok = numbval;
+ c = finput.getc();
+ }
+ if(c == '$') {
+ addcode(CodeAct, "e.yyval");
+
+ # put out the proper tag...
+ if(ntypes) {
+ if(tok < 0)
+ tok = fdtype(curprod[0]);
+ addcode(CodeAct, "."+typeset[tok]);
+ }
+ continue loop;
+ }
+ if(c == '-') {
+ s = -s;
+ c = finput.getc();
+ }
+ j := 0;
+ if(isdigit(c)) {
+ while(isdigit(c)) {
+ j = j*10 + c-'0';
+ c = finput.getc();
+ }
+ finput.ungetc();
+ j = j*s;
+ if(j >= max)
+ error("Illegal use of $" + string j);
+ }else if(isword(c) || c == '_' || c == '.') {
+ # look for $name
+ finput.ungetc();
+ if(gettok() != IDENTIFIER)
+ error("$ must be followed by an identifier");
+ tokn := chfind(2, tokname);
+ fnd := -1;
+ if((c = finput.getc()) != '@')
+ finput.ungetc();
+ else if(gettok() != NUMBER)
+ error("@ must be followed by number");
+ else
+ fnd = numbval;
+ for(j=1; j<max; j++){
+ if(tokn == curprod[j]) {
+ fnd--;
+ if(fnd <= 0)
+ break;
+ }
+ }
+ if(j >= max)
+ error("$name or $name@number not found");
+ }else{
+ addcodec(CodeAct, '$');
+ if(s < 0)
+ addcodec(CodeAct, '-');
+ finput.ungetc();
+ continue loop;
+ }
+ addcode(CodeAct, "yys[yypt-" + string(max-j-1) + "].yyv");
+
+ # put out the proper tag
+ if(ntypes) {
+ if(j <= 0 && tok < 0)
+ error("must specify type of $" + string j);
+ if(tok < 0)
+ tok = fdtype(curprod[j]);
+ addcodec(CodeAct, '.');
+ addcode(CodeAct, typeset[tok]);
+ }
+ continue loop;
+
+ '}' =>
+ brac--;
+ if(brac)
+ break;
+ addcodec(CodeAct, c);
+ return;
+
+ '#' =>
+ # a comment
+ addcodec(CodeAct, c);
+ c = finput.getc();
+ while(c != Bufio->EOF) {
+ if(c == '\n') {
+ lineno++;
+ break swt;
+ }
+ addcodec(CodeAct, c);
+ c = finput.getc();
+ }
+ error("EOF inside comment");
+
+ '\''or '"' =>
+ # character string or constant
+ match := c;
+ addcodec(CodeAct, c);
+ while(c = finput.getc()) {
+ if(c == '\\') {
+ addcodec(CodeAct, c);
+ c = finput.getc();
+ if(c == '\n')
+ lineno++;
+ } else if(c == match)
+ break swt;
+ if(c == '\n')
+ error("newline in string or char const.");
+ addcodec(CodeAct, c);
+ }
+ error("EOF in string or character constant");
+
+ Bufio->EOF =>
+ error("action does not terminate");
+
+ '\n' =>
+ lineno++;
+ }
+
+ addcodec(CodeAct, c);
+ }
+}
+
+openup(stem: string, dflag, vflag, ytab: int, ytabc: string)
+{
+ buf: string;
+ if(vflag) {
+ buf = stem + "." + FILEU;
+ foutput = bufio->create(buf, Bufio->OWRITE, 8r666);
+ if(foutput == nil)
+ error("can't create " + buf);
+ }
+ if(yydebug != nil) {
+ buf = stem + "." + FILEDEBUG;
+ fdebug = bufio->create(buf, Bufio->OWRITE, 8r666);
+ if(fdebug == nil)
+ error("can't create " + buf);
+ }
+ if(dflag) {
+ buf = stem + "." + FILED;
+ fdefine = bufio->create(buf, Bufio->OWRITE, 8r666);
+ if(fdefine == nil)
+ error("can't create " + buf);
+ }
+ if(ytab == 0)
+ buf = stem + "." + OFILE;
+ else
+ buf = ytabc;
+ ftable = bufio->create(buf, Bufio->OWRITE, 8r666);
+ if(ftable == nil)
+ error("can't create file " + buf);
+}
+
+#
+# return a pointer to the name of symbol i
+#
+symnam(i: int): string
+{
+ s: string;
+ if(i >= NTBASE)
+ s = nontrst[i-NTBASE].name;
+ else
+ s = tokset[i].name;
+ if(s[0] == ' ')
+ s = s[1:];
+ return s;
+}
+
+#
+# write out error comment
+#
+error(s: string)
+{
+ nerrors++;
+ fprint(stderr, "\n fatal error: %s, %s:%d\n", s, infile, lineno);
+ if(!fatfl)
+ return;
+ summary();
+ exit;
+# exits("error");
+}
+
+#
+# set elements 0 through n-1 to c
+#
+aryfil(v: array of int, n, c: int)
+{
+ for(i:=0; i<n; i++)
+ v[i] = c;
+}
+
+#
+# compute an array with the beginnings of productions yielding given nonterminals
+# The array pres points to these lists
+# the array pyield has the lists: the total size is only NPROD+1
+#
+cpres()
+{
+ pres = array[nnonter+1] of array of array of int;
+ curres := array[nprod] of array of int;
+ for(i:=0; i<=nnonter; i++) {
+ n := 0;
+ c := i+NTBASE;
+ fatfl = 0; # make undefined symbols nonfatal
+ for(j:=0; j<nprod; j++)
+ if(prdptr[j][0] == c)
+ curres[n++] = prdptr[j][1:];
+ if(n == 0)
+ error("nonterminal " + nontrst[i].name + " not defined!");
+ else{
+ pres[i] = array[n] of array of int;
+ pres[i][0:] = curres[:n];
+ }
+ }
+ fatfl = 1;
+ if(nerrors) {
+ summary();
+ exit; #exits("error");
+ }
+}
+
+dumppres()
+{
+ for(i := 0; i <= nnonter; i++){
+ print("nonterm %d\n", i);
+ curres := pres[i];
+ for(j := 0; j < len curres; j++){
+ print("\tproduction %d:", j);
+ prd := curres[j];
+ for(k := 0; k < len prd; k++)
+ print(" %d", prd[k]);
+ print("\n");
+ }
+ }
+}
+
+#
+# mark nonterminals which derive the empty string
+# also, look for nonterminals which don't derive any token strings
+#
+cempty()
+{
+ i, p, np: int;
+ prd: array of int;
+
+ pempty = array[nnonter+1] of int;
+
+ # first, use the array pempty to detect productions that can never be reduced
+ # set pempty to WHONOWS
+ aryfil(pempty, nnonter+1, WHOKNOWS);
+
+ # now, look at productions, marking nonterminals which derive something
+more: for(;;){
+ for(i=0; i<nprod; i++) {
+ prd = prdptr[i];
+ if(pempty[prd[0] - NTBASE])
+ continue;
+ np = len prd - 1;
+ for(p = 1; p < np; p++)
+ if(prd[p] >= NTBASE && pempty[prd[p]-NTBASE] == WHOKNOWS)
+ break;
+ # production can be derived
+ if(p == np) {
+ pempty[prd[0]-NTBASE] = OK;
+ continue more;
+ }
+ }
+ break;
+ }
+
+ # now, look at the nonterminals, to see if they are all OK
+ for(i=0; i<=nnonter; i++) {
+ # the added production rises or falls as the start symbol ...
+ if(i == 0)
+ continue;
+ if(pempty[i] != OK) {
+ fatfl = 0;
+ error("nonterminal " + nontrst[i].name + " never derives any token string");
+ }
+ }
+
+ if(nerrors) {
+ summary();
+ exit; #exits("error");
+ }
+
+ # now, compute the pempty array, to see which nonterminals derive the empty string
+ # set pempty to WHOKNOWS
+ aryfil(pempty, nnonter+1, WHOKNOWS);
+
+ # loop as long as we keep finding empty nonterminals
+
+again: for(;;){
+ next: for(i=1; i<nprod; i++) {
+ # not known to be empty
+ prd = prdptr[i];
+ if(pempty[prd[0]-NTBASE] != WHOKNOWS)
+ continue;
+ np = len prd - 1;
+ for(p = 1; p < np; p++)
+ if(prd[p] < NTBASE || pempty[prd[p]-NTBASE] != EMPTY)
+ continue next;
+
+ # we have a nontrivially empty nonterminal
+ pempty[prd[0]-NTBASE] = EMPTY;
+ # got one ... try for another
+ continue again;
+ }
+ return;
+ }
+}
+
+dumpempty()
+{
+ for(i := 0; i <= nnonter; i++)
+ if(pempty[i] == EMPTY)
+ print("non-term %d %s matches empty\n", i, symnam(i+NTBASE));
+}
+
+#
+# compute an array with the first of nonterminals
+#
+cpfir()
+{
+ s, n, p, np, ch: int;
+ curres: array of array of int;
+ prd: array of int;
+
+ wsets = array[nnonter+WSETINC] of Wset;
+ pfirst = array[nnonter+1] of Lkset;
+ for(i:=0; i<=nnonter; i++) {
+ wsets[i].ws = mkset();
+ pfirst[i] = mkset();
+ curres = pres[i];
+ n = len curres;
+ # initially fill the sets
+ for(s = 0; s < n; s++) {
+ prd = curres[s];
+ np = len prd - 1;
+ for(p = 0; p < np; p++) {
+ ch = prd[p];
+ if(ch < NTBASE) {
+ setbit(pfirst[i], ch);
+ break;
+ }
+ if(!pempty[ch-NTBASE])
+ break;
+ }
+ }
+ }
+
+ # now, reflect transitivity
+ changes := 1;
+ while(changes) {
+ changes = 0;
+ for(i=0; i<=nnonter; i++) {
+ curres = pres[i];
+ n = len curres;
+ for(s = 0; s < n; s++) {
+ prd = curres[s];
+ np = len prd - 1;
+ for(p = 0; p < np; p++) {
+ ch = prd[p] - NTBASE;
+ if(ch < 0)
+ break;
+ changes |= setunion(pfirst[i], pfirst[ch]);
+ if(!pempty[ch])
+ break;
+ }
+ }
+ }
+ }
+
+ if(!indebug)
+ return;
+ if(foutput != nil){
+ for(i=0; i<=nnonter; i++) {
+ foutput.putc('\n');
+ foutput.puts(nontrst[i].name);
+ foutput.puts(": ");
+ prlook(pfirst[i]);
+ foutput.putc(' ');
+ foutput.puts(string pempty[i]);
+ foutput.putc('\n');
+ }
+ }
+}
+
+#
+# generate the states
+#
+stagen()
+{
+ # initialize
+ nstate = 0;
+ tstates = array[ntokens+1] of {* => 0}; # states generated by terminal gotos
+ ntstates = array[nnonter+1] of {* => 0};# states generated by nonterminal gotos
+ amem = array[ACTSIZE] of {* => 0};
+ memp = 0;
+
+ clset = mkset();
+ pstate[0] = pstate[1] = 0;
+ aryfil(clset, tbitset, 0);
+ putitem(Pitem(prdptr[0], 0, 0, 0), clset);
+ tystate[0] = MUSTDO;
+ nstate = 1;
+ pstate[2] = pstate[1];
+
+ #
+ # now, the main state generation loop
+ # first pass generates all of the states
+ # later passes fix up lookahead
+ # could be sped up a lot by remembering
+ # results of the first pass rather than recomputing
+ #
+ first := 1;
+ for(more := 1; more; first = 0){
+ more = 0;
+ for(i:=0; i<nstate; i++) {
+ if(tystate[i] != MUSTDO)
+ continue;
+
+ tystate[i] = DONE;
+ aryfil(temp1, nnonter+1, 0);
+
+ # take state i, close it, and do gotos
+ closure(i);
+
+ # generate goto's
+ for(p:=0; p<cwp; p++) {
+ pi := wsets[p];
+ if(pi.flag)
+ continue;
+ wsets[p].flag = 1;
+ c := pi.pitem.first;
+ if(c <= 1) {
+ if(pstate[i+1]-pstate[i] <= p)
+ tystate[i] = MUSTLOOKAHEAD;
+ continue;
+ }
+ # do a goto on c
+ putitem(wsets[p].pitem, wsets[p].ws);
+ for(q:=p+1; q<cwp; q++) {
+ # this item contributes to the goto
+ if(c == wsets[q].pitem.first) {
+ putitem(wsets[q].pitem, wsets[q].ws);
+ wsets[q].flag = 1;
+ }
+ }
+
+ if(c < NTBASE)
+ state(c); # register new state
+ else
+ temp1[c-NTBASE] = state(c);
+ }
+
+ if(gsdebug && foutput != nil) {
+ foutput.puts(string i + ": ");
+ for(j:=0; j<=nnonter; j++)
+ if(temp1[j])
+ foutput.puts(nontrst[j].name + " " + string temp1[j] + ", ");
+ foutput.putc('\n');
+ }
+
+ if(first)
+ indgo[i] = apack(temp1[1:], nnonter-1) - 1;
+
+ more++;
+ }
+ }
+}
+
+#
+# generate the closure of state i
+#
+closure(i: int)
+{
+ zzclose++;
+
+ # first, copy kernel of state i to wsets
+ cwp = 0;
+ q := pstate[i+1];
+ for(p:=pstate[i]; p<q; p++) {
+ wsets[cwp].pitem = statemem[p].pitem;
+ wsets[cwp].flag = 1; # this item must get closed
+ wsets[cwp].ws[0:] = statemem[p].look;
+ cwp++;
+ }
+
+ # now, go through the loop, closing each item
+ work := 1;
+ while(work) {
+ work = 0;
+ for(u:=0; u<cwp; u++) {
+ if(wsets[u].flag == 0)
+ continue;
+ # dot is before c
+ c := wsets[u].pitem.first;
+ if(c < NTBASE) {
+ wsets[u].flag = 0;
+ # only interesting case is where . is before nonterminal
+ continue;
+ }
+
+ # compute the lookahead
+ aryfil(clset, tbitset, 0);
+
+ # find items involving c
+ for(v:=u; v<cwp; v++) {
+ if(wsets[v].flag != 1
+ || wsets[v].pitem.first != c)
+ continue;
+ pi := wsets[v].pitem.prod;
+ ipi := wsets[v].pitem.off + 1;
+
+ wsets[v].flag = 0;
+ if(nolook)
+ continue;
+ while((ch := pi[ipi++]) > 0) {
+ # terminal symbol
+ if(ch < NTBASE) {
+ setbit(clset, ch);
+ break;
+ }
+ # nonterminal symbol
+ setunion(clset, pfirst[ch-NTBASE]);
+ if(!pempty[ch-NTBASE])
+ break;
+ }
+ if(ch <= 0)
+ setunion(clset, wsets[v].ws);
+ }
+
+ #
+ # now loop over productions derived from c
+ #
+ curres := pres[c - NTBASE];
+ n := len curres;
+ # initially fill the sets
+ nexts: for(s := 0; s < n; s++) {
+ prd := curres[s];
+ #
+ # put these items into the closure
+ # is the item there
+ #
+ for(v=0; v<cwp; v++) {
+ # yes, it is there
+ if(wsets[v].pitem.off == 0
+ && wsets[v].pitem.prod == prd) {
+ if(!nolook && setunion(wsets[v].ws, clset))
+ wsets[v].flag = work = 1;
+ continue nexts;
+ }
+ }
+
+ # not there; make a new entry
+ if(cwp >= len wsets){
+ awsets := array[cwp + WSETINC] of Wset;
+ awsets[0:] = wsets;
+ wsets = awsets;
+ }
+ wsets[cwp].pitem = Pitem(prd, 0, prd[0], -prd[len prd-1]);
+ wsets[cwp].flag = 1;
+ wsets[cwp].ws = mkset();
+ if(!nolook) {
+ work = 1;
+ wsets[cwp].ws[0:] = clset;
+ }
+ cwp++;
+ }
+ }
+ }
+
+ # have computed closure; flags are reset; return
+ if(cldebug && foutput != nil) {
+ foutput.puts("\nState " + string i + ", nolook = " + string nolook + "\n");
+ for(u:=0; u<cwp; u++) {
+ if(wsets[u].flag)
+ foutput.puts("flag set!\n");
+ wsets[u].flag = 0;
+ foutput.putc('\t');
+ foutput.puts(writem(wsets[u].pitem));
+ prlook(wsets[u].ws);
+ foutput.putc('\n');
+ }
+ }
+}
+
+#
+# sorts last state,and sees if it equals earlier ones. returns state number
+#
+state(c: int): int
+{
+ zzstate++;
+ p1 := pstate[nstate];
+ p2 := pstate[nstate+1];
+ if(p1 == p2)
+ return 0; # null state
+ # sort the items
+ k, l: int;
+ for(k = p1+1; k < p2; k++) { # make k the biggest
+ for(l = k; l > p1; l--) {
+ if(statemem[l].pitem.prodno < statemem[l-1].pitem.prodno
+ || statemem[l].pitem.prodno == statemem[l-1].pitem.prodno
+ && statemem[l].pitem.off < statemem[l-1].pitem.off) {
+ s := statemem[l];
+ statemem[l] = statemem[l-1];
+ statemem[l-1] = s;
+ }else
+ break;
+ }
+ }
+
+ size1 := p2 - p1; # size of state
+
+ if(c >= NTBASE)
+ i := ntstates[c-NTBASE];
+ else
+ i = tstates[c];
+
+look: for(; i != 0; i = mstates[i]) {
+ # get ith state
+ q1 := pstate[i];
+ q2 := pstate[i+1];
+ size2 := q2 - q1;
+ if(size1 != size2)
+ continue;
+ k = p1;
+ for(l = q1; l < q2; l++) {
+ if(statemem[l].pitem.prod != statemem[k].pitem.prod
+ || statemem[l].pitem.off != statemem[k].pitem.off)
+ continue look;
+ k++;
+ }
+
+ # found it
+ pstate[nstate+1] = pstate[nstate]; # delete last state
+ # fix up lookaheads
+ if(nolook)
+ return i;
+ k = p1;
+ for(l = q1; l < q2; l++) {
+ if(setunion(statemem[l].look, statemem[k].look))
+ tystate[i] = MUSTDO;
+ k++;
+ }
+ return i;
+ }
+ # state is new
+ zznewstate++;
+ if(nolook)
+ error("yacc state/nolook error");
+ pstate[nstate+2] = p2;
+ if(nstate+1 >= NSTATES)
+ error("too many states");
+ if(c >= NTBASE) {
+ mstates[nstate] = ntstates[c-NTBASE];
+ ntstates[c-NTBASE] = nstate;
+ } else {
+ mstates[nstate] = tstates[c];
+ tstates[c] = nstate;
+ }
+ tystate[nstate] = MUSTDO;
+ return nstate++;
+}
+
+putitem(p: Pitem, set: Lkset)
+{
+ p.off++;
+ p.first = p.prod[p.off];
+
+ if(pidebug && foutput != nil)
+ foutput.puts("putitem(" + writem(p) + "), state " + string nstate + "\n");
+ j := pstate[nstate+1];
+ if(j >= len statemem){
+ asm := array[j + STATEINC] of Item;
+ asm[0:] = statemem;
+ statemem = asm;
+ }
+ statemem[j].pitem = p;
+ if(!nolook){
+ s := mkset();
+ s[0:] = set;
+ statemem[j].look = s;
+ }
+ j++;
+ pstate[nstate+1] = j;
+}
+
+#
+# creates output string for item pointed to by pp
+#
+writem(pp: Pitem): string
+{
+ i: int;
+ p := pp.prod;
+ q := chcopy(nontrst[prdptr[pp.prodno][0]-NTBASE].name) + ": ";
+ npi := pp.off;
+ pi := p == prdptr[pp.prodno];
+ for(;;){
+ c := ' ';
+ if(pi == npi)
+ c = '.';
+ q[len q] = c;
+ i = p[pi++];
+ if(i <= 0)
+ break;
+ q += chcopy(symnam(i));
+ }
+
+ # an item calling for a reduction
+ i = p[npi];
+ if(i < 0)
+ q += " (" + string -i + ")";
+ return q;
+}
+
+#
+# pack state i from temp1 into amem
+#
+apack(p: array of int, n: int): int
+{
+ #
+ # we don't need to worry about checking because
+ # we will only look at entries known to be there...
+ # eliminate leading and trailing 0's
+ #
+ off := 0;
+ for(pp := 0; pp <= n && p[pp] == 0; pp++)
+ off--;
+ # no actions
+ if(pp > n)
+ return 0;
+ for(; n > pp && p[n] == 0; n--)
+ ;
+ p = p[pp:n+1];
+
+ # now, find a place for the elements from p to q, inclusive
+ r := len amem - len p;
+nextk: for(rr := 0; rr <= r; rr++) {
+ qq := rr;
+ for(pp = 0; pp < len p; pp++) {
+ if(p[pp] != 0)
+ if(p[pp] != amem[qq] && amem[qq] != 0)
+ continue nextk;
+ qq++;
+ }
+
+ # we have found an acceptable k
+ if(pkdebug && foutput != nil)
+ foutput.puts("off = " + string(off+rr) + ", k = " + string rr + "\n");
+ qq = rr;
+ for(pp = 0; pp < len p; pp++) {
+ if(p[pp]) {
+ if(qq > memp)
+ memp = qq;
+ amem[qq] = p[pp];
+ }
+ qq++;
+ }
+ if(pkdebug && foutput != nil) {
+ for(pp = 0; pp <= memp; pp += 10) {
+ foutput.putc('\t');
+ for(qq = pp; qq <= pp+9; qq++)
+ foutput.puts(string amem[qq] + " ");
+ foutput.putc('\n');
+ }
+ }
+ return off + rr;
+ }
+ error("no space in action table");
+ return 0;
+}
+
+#
+# print the output for the states
+#
+output()
+{
+ c, u, v: int;
+
+ ftable.puts("yyexca := array[] of {");
+ if(fdebug != nil)
+ fdebug.puts("yystates = array [] of {\n");
+
+ noset := mkset();
+
+ # output the stuff for state i
+ for(i:=0; i<nstate; i++) {
+ nolook = tystate[i]!=MUSTLOOKAHEAD;
+ closure(i);
+
+ # output actions
+ nolook = 1;
+ aryfil(temp1, ntokens+nnonter+1, 0);
+ for(u=0; u<cwp; u++) {
+ c = wsets[u].pitem.first;
+ if(c > 1 && c < NTBASE && temp1[c] == 0) {
+ for(v=u; v<cwp; v++)
+ if(c == wsets[v].pitem.first)
+ putitem(wsets[v].pitem, noset);
+ temp1[c] = state(c);
+ } else
+ if(c > NTBASE && temp1[(c -= NTBASE) + ntokens] == 0)
+ temp1[c+ntokens] = amem[indgo[i]+c];
+ }
+ if(i == 1)
+ temp1[1] = ACCEPTCODE;
+
+ # now, we have the shifts; look at the reductions
+ lastred = 0;
+ for(u=0; u<cwp; u++) {
+ c = wsets[u].pitem.first;
+
+ # reduction
+ if(c > 0)
+ continue;
+ lastred = -c;
+ us := wsets[u].ws;
+ for(k:=0; k<=ntokens; k++) {
+ if(!bitset(us, k))
+ continue;
+ if(temp1[k] == 0)
+ temp1[k] = c;
+ else
+ if(temp1[k] < 0) { # reduce/reduce conflict
+ if(foutput != nil)
+ foutput.puts(
+ "\n" + string i + ": reduce/reduce conflict (red'ns "
+ + string -temp1[k] + " and " + string lastred + " ) on " + symnam(k));
+ if(-temp1[k] > lastred)
+ temp1[k] = -lastred;
+ zzrrconf++;
+ } else
+ # potential shift/reduce conflict
+ precftn(lastred, k, i);
+ }
+ }
+ wract(i);
+ }
+
+ if(fdebug != nil)
+ fdebug.puts("};\n");
+ ftable.puts("};\n");
+ ftable.puts("YYNPROD: con " + string nprod + ";\n");
+ ftable.puts("YYPRIVATE: con " + string PRIVATE + ";\n");
+ ftable.puts("yytoknames: array of string;\n");
+ ftable.puts("yystates: array of string;\n");
+ if(yydebug != nil){
+ ftable.puts("include \"y.debug\";\n");
+ ftable.puts("yydebug: con " + yydebug + ";\n");
+ }else{
+ ftable.puts("yydebug: con 0;\n");
+ }
+}
+
+#
+# decide a shift/reduce conflict by precedence.
+# r is a rule number, t a token number
+# the conflict is in state s
+# temp1[t] is changed to reflect the action
+#
+precftn(r, t, s: int)
+{
+ action: int;
+
+ lp := levprd[r];
+ lt := toklev[t];
+ if(PLEVEL(lt) == 0 || PLEVEL(lp) == 0) {
+
+ # conflict
+ if(foutput != nil)
+ foutput.puts(
+ "\n" + string s + ": shift/reduce conflict (shift "
+ + string temp1[t] + "(" + string PLEVEL(lt) + "), red'n "
+ + string r + "(" + string PLEVEL(lp) + ")) on " + symnam(t));
+ zzsrconf++;
+ return;
+ }
+ if(PLEVEL(lt) == PLEVEL(lp))
+ action = ASSOC(lt);
+ else if(PLEVEL(lt) > PLEVEL(lp))
+ action = RASC; # shift
+ else
+ action = LASC; # reduce
+ case action{
+ BASC => # error action
+ temp1[t] = ERRCODE;
+ LASC => # reduce
+ temp1[t] = -r;
+ }
+}
+
+#
+# output state i
+# temp1 has the actions, lastred the default
+#
+wract(i: int)
+{
+ p, p1: int;
+
+ # find the best choice for lastred
+ lastred = 0;
+ ntimes := 0;
+ for(j:=0; j<=ntokens; j++) {
+ if(temp1[j] >= 0)
+ continue;
+ if(temp1[j]+lastred == 0)
+ continue;
+ # count the number of appearances of temp1[j]
+ count := 0;
+ tred := -temp1[j];
+ levprd[tred] |= REDFLAG;
+ for(p=0; p<=ntokens; p++)
+ if(temp1[p]+tred == 0)
+ count++;
+ if(count > ntimes) {
+ lastred = tred;
+ ntimes = count;
+ }
+ }
+
+ #
+ # for error recovery, arrange that, if there is a shift on the
+ # error recovery token, `error', that the default be the error action
+ #
+ if(temp1[2] > 0)
+ lastred = 0;
+
+ # clear out entries in temp1 which equal lastred
+ # count entries in optst table
+ n := 0;
+ for(p=0; p<=ntokens; p++) {
+ p1 = temp1[p];
+ if(p1+lastred == 0)
+ temp1[p] = p1 = 0;
+ if(p1 > 0 && p1 != ACCEPTCODE && p1 != ERRCODE)
+ n++;
+ }
+
+ wrstate(i);
+ defact[i] = lastred;
+ flag := 0;
+ os := array[n*2] of int;
+ n = 0;
+ for(p=0; p<=ntokens; p++) {
+ if((p1=temp1[p]) != 0) {
+ if(p1 < 0) {
+ p1 = -p1;
+ } else if(p1 == ACCEPTCODE) {
+ p1 = -1;
+ } else if(p1 == ERRCODE) {
+ p1 = 0;
+ } else {
+ os[n++] = p;
+ os[n++] = p1;
+ zzacent++;
+ continue;
+ }
+ if(flag++ == 0)
+ ftable.puts("-1, " + string i + ",\n");
+ ftable.puts("\t" + string p + ", " + string p1 + ",\n");
+ zzexcp++;
+ }
+ }
+ if(flag) {
+ defact[i] = -2;
+ ftable.puts("\t-2, " + string lastred + ",\n");
+ }
+ optst[i] = os;
+}
+
+#
+# writes state i
+#
+wrstate(i: int)
+{
+ j0, j1, u: int;
+ pp, qq: int;
+
+ if(fdebug != nil) {
+ if(lastred) {
+ fdebug.puts(" nil, #" + string i + "\n");
+ } else {
+ fdebug.puts(" \"");
+ qq = pstate[i+1];
+ for(pp=pstate[i]; pp<qq; pp++){
+ fdebug.puts(writem(statemem[pp].pitem));
+ fdebug.puts("\\n");
+ }
+ if(tystate[i] == MUSTLOOKAHEAD)
+ for(u = pstate[i+1] - pstate[i]; u < cwp; u++)
+ if(wsets[u].pitem.first < 0){
+ fdebug.puts(writem(wsets[u].pitem));
+ fdebug.puts("\\n");
+ }
+ fdebug.puts("\", #" + string i + "/\n");
+ }
+ }
+ if(foutput == nil)
+ return;
+ foutput.puts("\nstate " + string i + "\n");
+ qq = pstate[i+1];
+ for(pp=pstate[i]; pp<qq; pp++){
+ foutput.putc('\t');
+ foutput.puts(writem(statemem[pp].pitem));
+ foutput.putc('\n');
+ }
+ if(tystate[i] == MUSTLOOKAHEAD) {
+ # print out empty productions in closure
+ for(u = pstate[i+1] - pstate[i]; u < cwp; u++) {
+ if(wsets[u].pitem.first < 0) {
+ foutput.putc('\t');
+ foutput.puts(writem(wsets[u].pitem));
+ foutput.putc('\n');
+ }
+ }
+ }
+
+ # check for state equal to another
+ for(j0=0; j0<=ntokens; j0++)
+ if((j1=temp1[j0]) != 0) {
+ foutput.puts("\n\t" + symnam(j0) + " ");
+ # shift, error, or accept
+ if(j1 > 0) {
+ if(j1 == ACCEPTCODE)
+ foutput.puts("accept");
+ else if(j1 == ERRCODE)
+ foutput.puts("error");
+ else
+ foutput.puts("shift "+string j1);
+ } else
+ foutput.puts("reduce " + string -j1 + " (src line " + string rlines[-j1] + ")");
+ }
+
+ # output the final production
+ if(lastred)
+ foutput.puts("\n\t. reduce " + string lastred + " (src line " + string rlines[lastred] + ")\n\n");
+ else
+ foutput.puts("\n\t. error\n\n");
+
+ # now, output nonterminal actions
+ j1 = ntokens;
+ for(j0 = 1; j0 <= nnonter; j0++) {
+ j1++;
+ if(temp1[j1])
+ foutput.puts("\t" + symnam(j0+NTBASE) + " goto " + string temp1[j1] + "\n");
+ }
+}
+
+#
+# output the gotos for the nontermninals
+#
+go2out()
+{
+ for(i := 1; i <= nnonter; i++) {
+ go2gen(i);
+
+ # find the best one to make default
+ best := -1;
+ times := 0;
+
+ # is j the most frequent
+ for(j := 0; j < nstate; j++) {
+ if(tystate[j] == 0)
+ continue;
+ if(tystate[j] == best)
+ continue;
+
+ # is tystate[j] the most frequent
+ count := 0;
+ cbest := tystate[j];
+ for(k := j; k < nstate; k++)
+ if(tystate[k] == cbest)
+ count++;
+ if(count > times) {
+ best = cbest;
+ times = count;
+ }
+ }
+
+ # best is now the default entry
+ zzgobest += times-1;
+ n := 0;
+ for(j = 0; j < nstate; j++)
+ if(tystate[j] != 0 && tystate[j] != best)
+ n++;
+ goent := array[2*n+1] of int;
+ n = 0;
+ for(j = 0; j < nstate; j++)
+ if(tystate[j] != 0 && tystate[j] != best) {
+ goent[n++] = j;
+ goent[n++] = tystate[j];
+ zzgoent++;
+ }
+
+ # now, the default
+ if(best == -1)
+ best = 0;
+ zzgoent++;
+ goent[n] = best;
+ yypgo[i] = goent;
+ }
+}
+
+#
+# output the gotos for nonterminal c
+#
+go2gen(c: int)
+{
+ i, cc, p, q: int;
+
+ # first, find nonterminals with gotos on c
+ aryfil(temp1, nnonter+1, 0);
+ temp1[c] = 1;
+ work := 1;
+ while(work) {
+ work = 0;
+ for(i=0; i<nprod; i++) {
+ # cc is a nonterminal with a goto on c
+ cc = prdptr[i][1]-NTBASE;
+ if(cc >= 0 && temp1[cc] != 0) {
+ # thus, the left side of production i does too
+ cc = prdptr[i][0]-NTBASE;
+ if(temp1[cc] == 0) {
+ work = 1;
+ temp1[cc] = 1;
+ }
+ }
+ }
+ }
+
+ # now, we have temp1[c] = 1 if a goto on c in closure of cc
+ if(g2debug && foutput != nil) {
+ foutput.puts(nontrst[c].name);
+ foutput.puts(": gotos on ");
+ for(i=0; i<=nnonter; i++)
+ if(temp1[i]){
+ foutput.puts(nontrst[i].name);
+ foutput.putc(' ');
+ }
+ foutput.putc('\n');
+ }
+
+ # now, go through and put gotos into tystate
+ aryfil(tystate, nstate, 0);
+ for(i=0; i<nstate; i++) {
+ q = pstate[i+1];
+ for(p=pstate[i]; p<q; p++) {
+ if((cc = statemem[p].pitem.first) >= NTBASE) {
+ # goto on c is possible
+ if(temp1[cc-NTBASE]) {
+ tystate[i] = amem[indgo[i]+c];
+ break;
+ }
+ }
+ }
+ }
+}
+
+#
+# in order to free up the mem and amem arrays for the optimizer,
+# and still be able to output yyr1, etc., after the sizes of
+# the action array is known, we hide the nonterminals
+# derived by productions in levprd.
+#
+hideprod()
+{
+ j := 0;
+ levprd[0] = 0;
+ for(i:=1; i<nprod; i++) {
+ if(!(levprd[i] & REDFLAG)) {
+ j++;
+ if(foutput != nil) {
+ foutput.puts("Rule not reduced: ");
+ foutput.puts(writem(Pitem(prdptr[i], 0, 0, i)));
+ foutput.putc('\n');
+ }
+ }
+ levprd[i] = prdptr[i][0] - NTBASE;
+ }
+ if(j)
+ print("%d rules never reduced\n", j);
+}
+
+callopt()
+{
+ j, k, p, q: int;
+ v: array of int;
+
+ pgo = array[nnonter+1] of int;
+ pgo[0] = 0;
+ maxoff = 0;
+ maxspr = 0;
+ for(i := 0; i < nstate; i++) {
+ k = 32000;
+ j = 0;
+ v = optst[i];
+ q = len v;
+ for(p = 0; p < q; p += 2) {
+ if(v[p] > j)
+ j = v[p];
+ if(v[p] < k)
+ k = v[p];
+ }
+ # nontrivial situation
+ if(k <= j) {
+ # j is now the range
+# j -= k; # call scj
+ if(k > maxoff)
+ maxoff = k;
+ }
+ tystate[i] = q + 2*j;
+ if(j > maxspr)
+ maxspr = j;
+ }
+
+ # initialize ggreed table
+ ggreed = array[nnonter+1] of int;
+ for(i = 1; i <= nnonter; i++) {
+ ggreed[i] = 1;
+ j = 0;
+
+ # minimum entry index is always 0
+ v = yypgo[i];
+ q = len v - 1;
+ for(p = 0; p < q ; p += 2) {
+ ggreed[i] += 2;
+ if(v[p] > j)
+ j = v[p];
+ }
+ ggreed[i] = ggreed[i] + 2*j;
+ if(j > maxoff)
+ maxoff = j;
+ }
+
+ # now, prepare to put the shift actions into the amem array
+ for(i = 0; i < ACTSIZE; i++)
+ amem[i] = 0;
+ maxa = 0;
+ for(i = 0; i < nstate; i++) {
+ if(tystate[i] == 0 && adb > 1)
+ ftable.puts("State " + string i + ": null\n");
+ indgo[i] = YYFLAG1;
+ }
+ while((i = nxti()) != NOMORE)
+ if(i >= 0)
+ stin(i);
+ else
+ gin(-i);
+
+ # print amem array
+ if(adb > 2)
+ for(p = 0; p <= maxa; p += 10) {
+ ftable.puts(string p + " ");
+ for(i = 0; i < 10; i++)
+ ftable.puts(string amem[p+i] + " ");
+ ftable.putc('\n');
+ }
+
+ aoutput();
+ osummary();
+}
+
+#
+# finds the next i
+#
+nxti(): int
+{
+ max := 0;
+ maxi := 0;
+ for(i := 1; i <= nnonter; i++)
+ if(ggreed[i] >= max) {
+ max = ggreed[i];
+ maxi = -i;
+ }
+ for(i = 0; i < nstate; i++)
+ if(tystate[i] >= max) {
+ max = tystate[i];
+ maxi = i;
+ }
+ if(max == 0)
+ return NOMORE;
+ return maxi;
+}
+
+gin(i: int)
+{
+ s: int;
+
+ # enter gotos on nonterminal i into array amem
+ ggreed[i] = 0;
+
+ q := yypgo[i];
+ nq := len q - 1;
+ # now, find amem place for it
+nextgp: for(p := 0; p < ACTSIZE; p++) {
+ if(amem[p])
+ continue;
+ for(r := 0; r < nq; r += 2) {
+ s = p + q[r] + 1;
+ if(s > maxa){
+ maxa = s;
+ if(maxa >= ACTSIZE)
+ error("a array overflow");
+ }
+ if(amem[s])
+ continue nextgp;
+ }
+ # we have found amem spot
+ amem[p] = q[nq];
+ if(p > maxa)
+ maxa = p;
+ for(r = 0; r < nq; r += 2) {
+ s = p + q[r] + 1;
+ amem[s] = q[r+1];
+ }
+ pgo[i] = p;
+ if(adb > 1)
+ ftable.puts("Nonterminal " + string i + ", entry at " + string pgo[i] + "\n");
+ return;
+ }
+ error("cannot place goto " + string i + "\n");
+}
+
+stin(i: int)
+{
+ s: int;
+
+ tystate[i] = 0;
+
+ # enter state i into the amem array
+ q := optst[i];
+ nq := len q;
+ # find an acceptable place
+nextn: for(n := -maxoff; n < ACTSIZE; n++) {
+ flag := 0;
+ for(r := 0; r < nq; r += 2) {
+ s = q[r] + n;
+ if(s < 0 || s > ACTSIZE)
+ continue nextn;
+ if(amem[s] == 0)
+ flag++;
+ else if(amem[s] != q[r+1])
+ continue nextn;
+ }
+
+ # check the position equals another only if the states are identical
+ for(j:=0; j<nstate; j++) {
+ if(indgo[j] == n) {
+
+ # we have some disagreement
+ if(flag)
+ continue nextn;
+ if(nq == len optst[j]) {
+
+ # states are equal
+ indgo[i] = n;
+ if(adb > 1)
+ ftable.puts("State " + string i + ": entry at "
+ + string n + " equals state " + string j + "\n");
+ return;
+ }
+
+ # we have some disagreement
+ continue nextn;
+ }
+ }
+
+ for(r = 0; r < nq; r += 2) {
+ s = q[r] + n;
+ if(s > maxa)
+ maxa = s;
+ if(amem[s] != 0 && amem[s] != q[r+1])
+ error("clobber of a array, pos'n " + string s + ", by " + string q[r+1] + "");
+ amem[s] = q[r+1];
+ }
+ indgo[i] = n;
+ if(adb > 1)
+ ftable.puts("State " + string i + ": entry at " + string indgo[i] + "\n");
+ return;
+ }
+ error("Error; failure to place state " + string i + "\n");
+}
+
+#
+# this version is for limbo
+# write out the optimized parser
+#
+aoutput()
+{
+ ftable.puts("YYLAST:\tcon "+string (maxa+1)+";\n");
+ arout("yyact", amem, maxa+1);
+ arout("yypact", indgo, nstate);
+ arout("yypgo", pgo, nnonter+1);
+}
+
+#
+# put out other arrays, copy the parsers
+#
+others()
+{
+ finput = bufio->open(parser, Bufio->OREAD);
+ if(finput == nil)
+ error("cannot find parser " + parser);
+ arout("yyr1", levprd, nprod);
+ aryfil(temp1, nprod, 0);
+
+ #
+ #yyr2 is the number of rules for each production
+ #
+ for(i:=1; i<nprod; i++)
+ temp1[i] = len prdptr[i] - 2;
+ arout("yyr2", temp1, nprod);
+
+ aryfil(temp1, nstate, -1000);
+ for(i=0; i<=ntokens; i++)
+ for(j:=tstates[i]; j!=0; j=mstates[j])
+ temp1[j] = i;
+ for(i=0; i<=nnonter; i++)
+ for(j=ntstates[i]; j!=0; j=mstates[j])
+ temp1[j] = -i;
+ arout("yychk", temp1, nstate);
+ arout("yydef", defact, nstate);
+
+ # put out token translation tables
+ # table 1 has 0-256
+ aryfil(temp1, 256, 0);
+ c := 0;
+ for(i=1; i<=ntokens; i++) {
+ j = tokset[i].value;
+ if(j >= 0 && j < 256) {
+ if(temp1[j]) {
+ print("yacc bug -- cant have 2 different Ts with same value\n");
+ print(" %s and %s\n", tokset[i].name, tokset[temp1[j]].name);
+ nerrors++;
+ }
+ temp1[j] = i;
+ if(j > c)
+ c = j;
+ }
+ }
+ for(i = 0; i <= c; i++)
+ if(temp1[i] == 0)
+ temp1[i] = YYLEXUNK;
+ arout("yytok1", temp1, c+1);
+
+ # table 2 has PRIVATE-PRIVATE+256
+ aryfil(temp1, 256, 0);
+ c = 0;
+ for(i=1; i<=ntokens; i++) {
+ j = tokset[i].value - PRIVATE;
+ if(j >= 0 && j < 256) {
+ if(temp1[j]) {
+ print("yacc bug -- cant have 2 different Ts with same value\n");
+ print(" %s and %s\n", tokset[i].name, tokset[temp1[j]].name);
+ nerrors++;
+ }
+ temp1[j] = i;
+ if(j > c)
+ c = j;
+ }
+ }
+ arout("yytok2", temp1, c+1);
+
+ # table 3 has everything else
+ ftable.puts("yytok3 := array[] of {\n");
+ c = 0;
+ for(i=1; i<=ntokens; i++) {
+ j = tokset[i].value;
+ if(j >= 0 && j < 256)
+ continue;
+ if(j >= PRIVATE && j < 256+PRIVATE)
+ continue;
+
+ ftable.puts(sprint("%4d,%4d,", j, i));
+ c++;
+ if(c%5 == 0)
+ ftable.putc('\n');
+ }
+ ftable.puts(sprint("%4d\n};\n", 0));
+
+ # copy parser text
+ while((c=finput.getc()) != Bufio->EOF) {
+ if(c == '$') {
+ if((c = finput.getc()) != 'A')
+ ftable.putc('$');
+ else { # copy actions
+ if(codehead == nil)
+ ftable.puts("* => ;");
+ else
+ dumpcode(-1);
+ c = finput.getc();
+ }
+ }
+ ftable.putc(c);
+ }
+ ftable.close();
+}
+
+arout(s: string, v: array of int, n: int)
+{
+ ftable.puts(s+" := array[] of {");
+ for(i := 0; i < n; i++) {
+ if(i%10 == 0)
+ ftable.putc('\n');
+ ftable.puts(sprint("%4d", v[i]));
+ ftable.putc(',');
+ }
+ ftable.puts("\n};\n");
+}
+
+#
+# output the summary on y.output
+#
+summary()
+{
+ if(foutput != nil) {
+ foutput.puts("\n" + string ntokens + " terminals, " + string(nnonter + 1) + " nonterminals\n");
+ foutput.puts("" + string nprod + " grammar rules, " + string nstate + "/" + string NSTATES + " states\n");
+ foutput.puts("" + string zzsrconf + " shift/reduce, " + string zzrrconf + " reduce/reduce conflicts reported\n");
+ foutput.puts("" + string len wsets + " working sets used\n");
+ foutput.puts("memory: parser " + string memp + "/" + string ACTSIZE + "\n");
+ foutput.puts(string (zzclose - 2*nstate) + " extra closures\n");
+ foutput.puts(string zzacent + " shift entries, " + string zzexcp + " exceptions\n");
+ foutput.puts(string zzgoent + " goto entries\n");
+ foutput.puts(string zzgobest + " entries saved by goto default\n");
+ }
+ if(zzsrconf != 0 || zzrrconf != 0) {
+ print("\nconflicts: ");
+ if(zzsrconf)
+ print("%d shift/reduce", zzsrconf);
+ if(zzsrconf && zzrrconf)
+ print(", ");
+ if(zzrrconf)
+ print("%d reduce/reduce", zzrrconf);
+ print("\n");
+ }
+ if(fdefine != nil)
+ fdefine.close();
+}
+
+#
+# write optimizer summary
+#
+osummary()
+{
+ if(foutput == nil)
+ return;
+ i := 0;
+ for(p := maxa; p >= 0; p--)
+ if(amem[p] == 0)
+ i++;
+
+ foutput.puts("Optimizer space used: output " + string (maxa+1) + "/" + string ACTSIZE + "\n");
+ foutput.puts(string(maxa+1) + " table entries, " + string i + " zero\n");
+ foutput.puts("maximum spread: " + string maxspr + ", maximum offset: " + string maxoff + "\n");
+}
+
+#
+# copies and protects "'s in q
+#
+chcopy(q: string): string
+{
+ s := "";
+ j := 0;
+ for(i := 0; i < len q; i++) {
+ if(q[i] == '"') {
+ s += q[j:i] + "\\";
+ j = i;
+ }
+ }
+ return s + q[j:i];
+}
+
+usage()
+{
+ fprint(stderr, "usage: yacc [-vd] [-Dn] [-o output] [-s stem] file\n");
+ exit;
+}
+
+bitset(set: Lkset, bit: int): int
+{
+ return set[bit>>5] & (1<<(bit&31));
+}
+
+setbit(set: Lkset, bit: int): int
+{
+ return set[bit>>5] |= (1<<(bit&31));
+}
+
+mkset(): Lkset
+{
+ return array[tbitset] of {* => 0};
+}
+
+#
+# set a to the union of a and b
+# return 1 if b is not a subset of a, 0 otherwise
+#
+setunion(a, b: array of int): int
+{
+ sub := 0;
+ for(i:=0; i<tbitset; i++) {
+ x := a[i];
+ y := x | b[i];
+ a[i] = y;
+ if(y != x)
+ sub = 1;
+ }
+ return sub;
+}
+
+prlook(p: Lkset)
+{
+ if(p == nil){
+ foutput.puts("\tNULL");
+ return;
+ }
+ foutput.puts(" { ");
+ for(j:=0; j<=ntokens; j++){
+ if(bitset(p, j)){
+ foutput.puts(symnam(j));
+ foutput.putc(' ');
+ }
+ }
+ foutput.putc('}');
+}
+
+#
+# utility routines
+#
+isdigit(c: int): int
+{
+ return c >= '0' && c <= '9';
+}
+
+isword(c: int): int
+{
+ return c >= 16ra0 || c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z';
+}
+
+mktemp(t: string): string
+{
+ return t;
+}
+
+#
+# arg processing
+#
+Arg.init(argv: list of string): ref Arg
+{
+ if(argv != nil)
+ argv = tl argv;
+ return ref Arg(argv, 0, "");
+}
+
+Arg.opt(arg: self ref Arg): int
+{
+ opts := arg.opts;
+ if(opts != ""){
+ arg.c = opts[0];
+ arg.opts = opts[1:];
+ return arg.c;
+ }
+ argv := arg.argv;
+ if(argv == nil)
+ return arg.c = 0;
+ opts = hd argv;
+ if(len opts < 2 || opts[0] != '-')
+ return arg.c = 0;
+ arg.argv = tl argv;
+ if(opts == "--")
+ return arg.c = 0;
+ arg.opts = opts[2:];
+ return arg.c = opts[1];
+}
+
+Arg.arg(arg: self ref Arg): string
+{
+ s := arg.opts;
+ arg.opts = "";
+ if(s != "")
+ return s;
+ argv := arg.argv;
+ if(argv == nil)
+ return "";
+ arg.argv = tl argv;
+ return hd argv;
+}
diff --git a/appl/cmd/mash/eyaccpar b/appl/cmd/mash/eyaccpar
new file mode 100644
index 00000000..2bbb0355
--- /dev/null
+++ b/appl/cmd/mash/eyaccpar
@@ -0,0 +1,223 @@
+YYFLAG: con -1000;
+
+# parser for yacc output
+YYENV: adt
+{
+ yylval: ref YYSTYPE; # lexical value
+ yyval: YYSTYPE; # goto value
+ yyenv: YYETYPE; # useer environment
+ yynerrs: int; # number of errors
+ yyerrflag: int; # error recovery flag
+ yysys: Sys;
+ yystderr: ref Sys->FD;
+};
+
+yytokname(yyc: int): string
+{
+ if(yyc > 0 && yyc <= len yytoknames && yytoknames[yyc-1] != nil)
+ return yytoknames[yyc-1];
+ return "<"+string yyc+">";
+}
+
+yystatname(yys: int): string
+{
+ if(yys >= 0 && yys < len yystates && yystates[yys] != nil)
+ return yystates[yys];
+ return "<"+string yys+">\n";
+}
+
+yylex1(e: ref YYENV): int
+{
+ c, yychar : int;
+ yychar = yyelex(e);
+ if(yychar <= 0)
+ c = yytok1[0];
+ else if(yychar < len yytok1)
+ c = yytok1[yychar];
+ else if(yychar >= YYPRIVATE && yychar < YYPRIVATE+len yytok2)
+ c = yytok2[yychar-YYPRIVATE];
+ else{
+ n := len yytok3;
+ c = 0;
+ for(i := 0; i < n; i+=2) {
+ if(yytok3[i+0] == yychar) {
+ c = yytok3[i+1];
+ break;
+ }
+ }
+ if(c == 0)
+ c = yytok2[1]; # unknown char
+ }
+ if(yydebug >= 3)
+ e.yysys->fprint(e.yystderr, "lex %.4ux %s\n", yychar, yytokname(c));
+ return c;
+}
+
+YYS: adt
+{
+ yyv: YYSTYPE;
+ yys: int;
+};
+
+yyparse(): int
+{
+ return yyeparse(nil);
+}
+
+yyeparse(e: ref YYENV): int
+{
+ if(e == nil)
+ e = ref YYENV;
+ if(e.yylval == nil)
+ e.yylval = ref YYSTYPE;
+ if(e.yysys == nil) {
+ e.yysys = load Sys "$Sys";
+ e.yystderr = e.yysys->fildes(2);
+ }
+
+ yys := array[YYMAXDEPTH] of YYS;
+
+ yystate := 0;
+ yychar := -1;
+ e.yynerrs = 0;
+ e.yyerrflag = 0;
+ yyp := -1;
+ yyn := 0;
+
+yystack:
+ for(;;){
+ # put a state and value onto the stack
+ if(yydebug >= 4)
+ e.yysys->fprint(e.yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate));
+
+ yyp++;
+ if(yyp >= YYMAXDEPTH) {
+ yyerror(e, "yacc stack overflow");
+ yyn = 1;
+ break yystack;
+ }
+ yys[yyp].yys = yystate;
+ yys[yyp].yyv = e.yyval;
+
+ for(;;){
+ yyn = yypact[yystate];
+ if(yyn > YYFLAG) { # simple state
+ if(yychar < 0)
+ yychar = yylex1(e);
+ yyn += yychar;
+ if(yyn >= 0 && yyn < YYLAST) {
+ yyn = yyact[yyn];
+ if(yychk[yyn] == yychar) { # valid shift
+ yychar = -1;
+ yyp++;
+ if(yyp >= YYMAXDEPTH) {
+ yyerror(e, "yacc stack overflow");
+ yyn = 1;
+ break yystack;
+ }
+ yystate = yyn;
+ yys[yyp].yys = yystate;
+ yys[yyp].yyv = *e.yylval;
+ if(e.yyerrflag > 0)
+ e.yyerrflag--;
+ if(yydebug >= 4)
+ e.yysys->fprint(e.yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate));
+ continue;
+ }
+ }
+ }
+
+ # default state action
+ yyn = yydef[yystate];
+ if(yyn == -2) {
+ if(yychar < 0)
+ yychar = yylex1(e);
+
+ # look through exception table
+ for(yyxi:=0;; yyxi+=2)
+ if(yyexca[yyxi] == -1 && yyexca[yyxi+1] == yystate)
+ break;
+ for(yyxi += 2;; yyxi += 2) {
+ yyn = yyexca[yyxi];
+ if(yyn < 0 || yyn == yychar)
+ break;
+ }
+ yyn = yyexca[yyxi+1];
+ if(yyn < 0){
+ yyn = 0;
+ break yystack;
+ }
+ }
+
+ if(yyn != 0)
+ break;
+
+ # error ... attempt to resume parsing
+ if(e.yyerrflag == 0) { # brand new error
+ yyerror(e, "syntax error");
+ e.yynerrs++;
+ if(yydebug >= 1) {
+ e.yysys->fprint(e.yystderr, "%s", yystatname(yystate));
+ e.yysys->fprint(e.yystderr, "saw %s\n", yytokname(yychar));
+ }
+ }
+
+ if(e.yyerrflag != 3) { # incompletely recovered error ... try again
+ e.yyerrflag = 3;
+
+ # find a state where "error" is a legal shift action
+ while(yyp >= 0) {
+ yyn = yypact[yys[yyp].yys] + YYERRCODE;
+ if(yyn >= 0 && yyn < YYLAST) {
+ yystate = yyact[yyn]; # simulate a shift of "error"
+ if(yychk[yystate] == YYERRCODE) {
+ yychar = -1;
+ continue yystack;
+ }
+ }
+
+ # the current yyp has no shift on "error", pop stack
+ if(yydebug >= 2)
+ e.yysys->fprint(e.yystderr, "error recovery pops state %d, uncovers %d\n",
+ yys[yyp].yys, yys[yyp-1].yys );
+ yyp--;
+ }
+ # there is no state on the stack with an error shift ... abort
+ yyn = 1;
+ break yystack;
+ }
+
+ # no shift yet; clobber input char
+ if(yydebug >= 2)
+ e.yysys->fprint(e.yystderr, "error recovery discards %s\n", yytokname(yychar));
+ if(yychar == YYEOFCODE) {
+ yyn = 1;
+ break yystack;
+ }
+ yychar = -1;
+ # try again in the same state
+ }
+
+ # reduction by production yyn
+ if(yydebug >= 2)
+ e.yysys->fprint(e.yystderr, "reduce %d in:\n\t%s", yyn, yystatname(yystate));
+
+ yypt := yyp;
+ yyp -= yyr2[yyn];
+# yyval = yys[yyp+1].yyv;
+ yym := yyn;
+
+ # consult goto table to find next state
+ yyn = yyr1[yyn];
+ yyg := yypgo[yyn];
+ yyj := yyg + yys[yyp].yys + 1;
+
+ if(yyj >= YYLAST || yychk[yystate=yyact[yyj]] != -yyn)
+ yystate = yyact[yyg];
+ case yym {
+ $A
+ }
+ }
+
+ return yyn;
+}
diff --git a/appl/cmd/mash/history.b b/appl/cmd/mash/history.b
new file mode 100644
index 00000000..7f7cf9b6
--- /dev/null
+++ b/appl/cmd/mash/history.b
@@ -0,0 +1,206 @@
+implement Mashbuiltin;
+
+#
+# "history" builtin, defines:
+#
+
+include "mash.m";
+include "mashparse.m";
+
+mashlib: Mashlib;
+chanfill: ChanFill;
+
+Env: import mashlib;
+sys, bufio: import mashlib;
+
+Iobuf: import bufio;
+
+Hcmd: adt
+{
+ seek: int;
+ text: array of byte;
+};
+
+Reader: adt
+{
+ fid: int;
+ offset: int;
+ hint: int;
+ next: cyclic ref Reader;
+};
+
+history: array of ref Hcmd;
+lhist: int;
+nhist: int;
+seek: int;
+readers: ref Reader;
+eof := array[0] of byte;
+
+#
+# Interface to catch the use as a command.
+#
+init(nil: ref Draw->Context, args: list of string)
+{
+ raise "fail: " + hd args + " not loaded";
+}
+
+#
+# Used by whatis.
+#
+name(): string
+{
+ return "history";
+}
+
+#
+# Install commands.
+#
+mashinit(nil: list of string, lib: Mashlib, nil: Mashbuiltin, e: ref Env)
+{
+ mashlib = lib;
+ if (mashlib->histchan != nil)
+ return;
+ mashlib->startserve = 1;
+ nhist = 0;
+ lhist = 256;
+ history = array[lhist] of ref Hcmd;
+ seek = 0;
+ (f, c) := e.servefile(mashlib->HISTF);
+ spawn servehist(f, c);
+ (f, c) = e.servefile(mashlib->MASHF);
+ spawn servemash(f, c);
+}
+
+mashcmd(nil: ref Env, nil: list of string)
+{
+}
+
+addhist(b: array of byte)
+{
+ if (nhist == lhist) {
+ n := 3 * nhist / 4;
+ part := history[:n];
+ part[:] = history[nhist - n:];
+ nhist = n;
+ }
+ history[nhist] = ref Hcmd(seek, b);
+ nhist++;
+ seek += len b;
+}
+
+getfid(fid: int, del: int): ref Reader
+{
+ prev: ref Reader;
+ for (r := readers; r != nil; r = r.next) {
+ if (r.fid == fid) {
+ if (del) {
+ if (prev == nil)
+ readers = r.next;
+ else
+ prev.next = r.next;
+ return nil;
+ }
+ return r;
+ }
+ prev = r;
+ }
+ o := 0;
+ if (nhist > 0)
+ o = history[0].seek;
+ return readers = ref Reader(fid, o, 0, readers);
+}
+
+readhist(off, count, fid: int): (array of byte, string)
+{
+ r := getfid(fid, 0);
+ off += r.offset;
+ if (nhist == 0 || off >= seek)
+ return (eof, nil);
+ i := r.hint;
+ if (i >= nhist)
+ i = nhist - 1;
+ s := history[i].seek;
+ if (off == s) {
+ r.hint = i + 1;
+ return (history[i].text, nil);
+ }
+ if (off > s) {
+ do {
+ if (++i == nhist)
+ break;
+ s = history[i].seek;
+ } while (off >= s);
+ i--;
+ } else {
+ do {
+ if (--i < 0)
+ return (eof, "data truncated");
+ s = history[i].seek;
+ } while (off < s);
+ }
+ r.hint = i + 1;
+ b := history[i].text;
+ if (off != s)
+ b = b[off - s:];
+ return (b, nil);
+}
+
+loadhist(data: array of byte, fid: int, wc: Sys->Rwrite, c: ref Sys->FileIO)
+{
+ in: ref Iobuf;
+ if (chanfill == nil)
+ chanfill = load ChanFill ChanFill->PATH;
+ if (chanfill != nil)
+ in = chanfill->init(data, fid, wc, c, mashlib->bufio);
+ if (in == nil) {
+ in = bufio->sopen(string data);
+ if (in == nil) {
+ wc <-= (0, mashlib->errstr());
+ return;
+ }
+ wc <-= (len data, nil);
+ }
+ while ((s := in.gets('\n')) != nil)
+ addhist(array of byte s);
+ in.close();
+}
+
+servehist(f: string, c: ref Sys->FileIO)
+{
+ mashlib->reap();
+ h := chan of array of byte;
+ mashlib->histchan = h;
+ for (;;) {
+ alt {
+ b := <-h =>
+ addhist(b);
+ (off, count, fid, rc) := <-c.read =>
+ if (rc == nil) {
+ getfid(fid, 1);
+ continue;
+ }
+ rc <-= readhist(off, count, fid);
+ (off, data, fid, wc) := <-c.write =>
+ if (wc != nil)
+ loadhist(data, fid, wc, c);
+ }
+ }
+}
+
+servemash(f: string, c: ref Sys->FileIO)
+{
+ mashlib->reap();
+ for (;;) {
+ alt {
+ (off, count, fid, rc) := <-c.read =>
+ if (rc != nil)
+ rc <-= (nil, "not supported");
+ (off, data, fid, wc) := <-c.write =>
+ if (wc != nil) {
+ wc <-= (len data, nil);
+ if (mashlib->servechan != nil && len data > 0)
+ mashlib->servechan <-= data;
+ }
+ }
+ }
+}
diff --git a/appl/cmd/mash/lex.b b/appl/cmd/mash/lex.b
new file mode 100644
index 00000000..c9c3789b
--- /dev/null
+++ b/appl/cmd/mash/lex.b
@@ -0,0 +1,547 @@
+#
+# Lexical analyzer.
+#
+
+lexdebug : con 0;
+
+#
+# Import tokens from parser.
+#
+Land,
+Lat,
+Lbackq,
+Lcaret,
+Lcase,
+Lcolon,
+Lcolonmatch,
+Lcons,
+Ldefeq,
+Lelse,
+Leof,
+Leq,
+Leqeq,
+Lerror,
+Lfn,
+Lfor,
+Lgreat,
+Lgreatgreat,
+Lhd,
+Lif,
+Lin,
+Llen,
+Lless,
+Llessgreat,
+Lmatch,
+Lmatched,
+Lnot,
+Lnoteq,
+Loffcurly,
+Loffparen,
+Loncurly,
+Lonparen,
+Lpipe,
+Lquote,
+Lrescue,
+Lsemi,
+Ltl,
+Lwhile,
+Lword
+ : import Mashparse;
+
+KWSIZE: con 31; # keyword hashtable size
+NCTYPE: con 128; # character class array size
+
+ALPHA,
+NUMERIC,
+ONE,
+WS,
+META
+ : con 1 << iota;
+
+keywords := array[] of
+{
+ ("case", Lcase),
+ ("else", Lelse),
+ ("fn", Lfn),
+ ("for", Lfor),
+ ("hd", Lhd),
+ ("if", Lif),
+ ("in", Lin),
+ ("len", Llen),
+ ("rescue", Lrescue),
+ ("tl", Ltl),
+ ("while", Lwhile)
+};
+
+ctype := array[NCTYPE] of
+{
+ 0 or ' ' or '\t' or '\n' or '\r' or '\v' => WS,
+ ':' or '#' or ';' or '&' or '|' or '^' or '$' or '=' or '@'
+ or '~' or '`'or '{' or '}' or '(' or ')' or '<' or '>' => ONE,
+ 'a' to 'z' or 'A' to 'Z' or '_' => ALPHA,
+ '0' to '9' => NUMERIC,
+ '*' or '[' or ']' or '?' => META,
+ * => 0
+};
+
+keytab: ref HashTable;
+
+#
+# Initialize hashtable.
+#
+initlex()
+{
+ keytab = hash->new(KWSIZE);
+ for (i := 0; i < len keywords; i++) {
+ (s, v) := keywords[i];
+ keytab.insert(s, HashVal(v, 0.0, nil));
+ }
+}
+
+#
+# Keyword value, or -1.
+#
+keyval(i: ref Item): int
+{
+ if (i.op != Iword)
+ return -1;
+ w := i.word;
+ if (w.flags & Wquoted)
+ return -1;
+ v := keytab.find(w.text);
+ if (v == nil)
+ return -1;
+ return v.i;
+}
+
+#
+# Attach a source file to an environment.
+#
+Env.fopen(e: self ref Env, fd: ref Sys->FD, s: string)
+{
+ in := bufio->fopen(fd, Bufio->OREAD);
+ if (in == nil)
+ e.error(sys->sprint("could not fopen %s: %r\n", s));
+ e.file = ref File(in, s, 1, 0);
+}
+
+#
+# Attach a source string to an environment.
+#
+Env.sopen(e: self ref Env, s: string)
+{
+ in := bufio->sopen(s);
+ if (in == nil)
+ e.error(sys->sprint("Bufio->sopen failed: %r\n"));
+ e.file = ref File(in, "<string>", 1, 0);
+}
+
+#
+# Close source file.
+#
+fclose(e: ref Env, c: int)
+{
+ if (c == Bufio->ERROR)
+ readerror(e, e.file);
+ e.file.in.close();
+ e.file = nil;
+}
+
+#
+# Character class routines.
+#
+
+isalpha(c: int): int
+{
+ return c >= NCTYPE || (c >= 0 && (ctype[c] & ALPHA) != 0);
+}
+
+isalnum(c: int): int
+{
+ return c >= NCTYPE || (c >= 0 && (ctype[c] & (ALPHA | NUMERIC)) != 0);
+}
+
+isdigit(c: int): int
+{
+ return c >= 0 && c < NCTYPE && (ctype[c] & NUMERIC) != 0;
+}
+
+isquote(c: int): int
+{
+ return c < NCTYPE && (c < 0 || (ctype[c] & (ONE | WS | META)) != 0);
+}
+
+isspace(c: int): int
+{
+ return c >= 0 && c < NCTYPE && (ctype[c] & WS) != 0;
+}
+
+isterm(c: int): int
+{
+ return c < NCTYPE && (c < 0 || (ctype[c] & (ONE | WS)) != 0);
+}
+
+#
+# Test for an identifier.
+#
+ident(s: string): int
+{
+ if (s == nil || !isalpha(s[0]))
+ return 0;
+ n := len s;
+ for (x := 1; x < n; x++) {
+ if (!isalnum(s[x]))
+ return 0;
+ }
+ return 1;
+}
+
+#
+# Quote text.
+#
+enquote(s: string): string
+{
+ r := "'";
+ j := 1;
+ n := len s;
+ for (i := 0; i < n; i++) {
+ c := s[i];
+ if (c == '\'' || c == '\\')
+ r[j++] = '\\';
+ r[j++] = c;
+ }
+ r[j] = '\'';
+ return r;
+}
+
+#
+# Quote text if needed.
+#
+quote(s: string): string
+{
+ n := len s;
+ for (i := 0; i < n; i++) {
+ if (isquote(s[i]))
+ return enquote(s);
+ }
+ return s;
+}
+
+#
+# Test for single word and identifier.
+#
+Item.sword(i: self ref Item, e: ref Env): ref Item
+{
+ if (i.op == Iword && ident(i.word.text))
+ return i;
+ e.report("malformed identifier: " + i.text());
+ return nil;
+}
+
+readerror(e: ref Env, f: ref File)
+{
+ sys->fprint(e.stderr, "error reading %s: %r\n", f.name);
+}
+
+where(e: ref Env): string
+{
+ if ((e.flags & EInter) || e.file == nil)
+ return nil;
+ return e.file.name + ":" + string e.file.line + ": ";
+}
+
+#
+# Suck input (on error).
+#
+Env.suck(e: self ref Env)
+{
+ if (e.file == nil)
+ return;
+ in := e.file.in;
+ while ((c := in.getc()) >= 0 && c != '\n')
+ ;
+}
+
+#
+# Lexical analyzer.
+#
+Env.lex(e: self ref Env, yylval: ref Mashparse->YYSTYPE): int
+{
+ i, r: ref Item;
+reader:
+ for (;;) {
+ if (e.file == nil)
+ return -1;
+ f := e.file;
+ in := f.in;
+ while (isspace(c := in.getc())) {
+ if (c == '\n')
+ f.line++;
+ }
+ if (c < 0) {
+ fclose(e, c);
+ return Leof;
+ }
+ case c {
+ ':' =>
+ if ((d := in.getc()) == ':')
+ return Lcons;
+ if (d == '=')
+ return Ldefeq;
+ if (d == '~')
+ return Lcolonmatch;
+ if (d >= 0)
+ in.ungetc();
+ return Lcolon;
+ '#' =>
+ for (;;) {
+ if ((c = in.getc()) < 0) {
+ fclose(e, c);
+ return Leof;
+ }
+ if (c == '\n') {
+ f.line++;
+ continue reader;
+ }
+ }
+ ';' =>
+ return Lsemi;
+ '&' =>
+ return Land;
+ '|' =>
+ return Lpipe;
+ '^' =>
+ return Lcaret;
+ '@' =>
+ return Lat;
+ '!' =>
+ if ((d := in.getc()) == '=')
+ return Lnoteq;
+ if (d >= 0)
+ in.ungetc();
+ return Lnot;
+ '~' =>
+ return Lmatch;
+ '=' =>
+ if ((d := in.getc()) == '>')
+ return Lmatched;
+ if (d == '=')
+ return Leqeq;
+ if (d >= 0)
+ in.ungetc();
+ return Leq;
+ '`' =>
+ return Lbackq;
+ '"' =>
+ return Lquote;
+ '{' =>
+ return Loncurly;
+ '}' =>
+ return Loffcurly;
+ '(' =>
+ return Lonparen;
+ ')' =>
+ return Loffparen;
+ '<' =>
+ if ((d := in.getc()) == '>')
+ return Llessgreat;
+ if (d >= 0)
+ in.ungetc();
+ return Lless;
+ '>' =>
+ if ((d := in.getc()) == '>')
+ return Lgreatgreat;
+ if (d >= 0)
+ in.ungetc();
+ return Lgreat;
+ '\\' =>
+ if ((d := in.getc()) == '\n') {
+ f.line++;
+ continue reader;
+ }
+ if (d >= 0)
+ in.ungetc();
+ }
+ # Loop over "carets for free".
+ for (;;) {
+ if (c == '$')
+ (i, c) = getdollar(f);
+ else
+ (i, c) = getword(e, f, c);
+ if (i == nil)
+ return Lerror;
+ if (isterm(c) && c != '$')
+ break;
+ if (r != nil)
+ r = ref Item(Iicaret, nil, r, i, nil, nil);
+ else
+ r = i;
+ }
+ if (c >= 0)
+ in.ungetc();
+ if (r != nil)
+ yylval.item = ref Item(Iicaret, nil, r, i, nil, nil);
+ else if ((c = keyval(i)) >= 0)
+ return c;
+ else
+ yylval.item = i;
+ return Lword;
+ }
+}
+
+#
+# Get $n or $word.
+#
+getdollar(f: ref File): (ref Item, int)
+{
+ s: string;
+ in := f.in;
+ l := f.line;
+ o := Idollar;
+ if (isdigit(c := in.getc())) {
+ s[0] = c;
+ n := 1;
+ while (isdigit(c = in.getc()))
+ s[n++] = c;
+ o = Imatch;
+ } else {
+ if (c == '"') {
+ o = Idollarq;
+ c = in.getc();
+ }
+ if (isalpha(c)) {
+ s[0] = c;
+ n := 1;
+ while (isalnum(c = in.getc()))
+ s[n++] = c;
+ } else {
+ if (o == Idollar)
+ s = "$";
+ else
+ s = "$\"";
+ o = Iword;
+ }
+ }
+ return (ref Item(o, ref Word(s, 0, Src(l, f.name)), nil, nil, nil, nil), c);
+}
+
+#
+# Get word with quoting.
+#
+getword(e: ref Env, f: ref File, c: int): (ref Item, int)
+{
+ s: string;
+ in := f.in;
+ l := f.line;
+ wf := 0;
+ n := 0;
+ if (c == '\'') {
+ wf = Wquoted;
+ collect:
+ while ((c = in.getc()) >= 0) {
+ case c {
+ '\'' =>
+ c = in.getc();
+ break collect;
+ '\\' =>
+ c = in.getc();
+ if (c != '\'' && c != '\\') {
+ if (c == '\n')
+ continue collect;
+ if (c >= 0)
+ in.ungetc();
+ c = '\\';
+ }
+ '\n' =>
+ f.line++;
+ e.report("newline in quoted word");
+ return (nil, 0);
+ }
+ s[n++] = c;
+ }
+ } else {
+ do {
+ case c {
+ '*' or '[' or '?' =>
+ wf |= Wexpand;
+ }
+ s[n++] = c;
+ } while (!isterm(c = in.getc()) && c != '\'');
+ }
+ if (lexdebug && s == "exit")
+ exit;
+ return (ref Item(Iword, ref Word(s, wf, Src(l, f.name)), nil, nil, nil, nil), c);
+}
+
+#
+# Get a line, mapping escape newline to space newline.
+#
+getline(in: ref Bufio->Iobuf): string
+{
+ if (inchan != nil) {
+ alt {
+ b := <-inchan =>
+ if (inchan == nil)
+ return nil;
+ s := string b;
+ n := len s;
+ if (n > 1) {
+ while (s[n - 2] == '\\' && s[n - 1] == '\n') {
+ s[n - 2] = ' ';
+ s[n - 1] = ' ';
+ prprompt(1);
+ b = <-inchan;
+ if (b == nil)
+ break;
+ s += string b;
+ n = len s;
+ }
+ }
+ return s;
+ b := <-servechan =>
+ s := string b;
+ sys->print("%s", s);
+ return s;
+ }
+ } else {
+ s := in.gets('\n');
+ if (s == nil)
+ return nil;
+ n := len s;
+ if (n > 1) {
+ while (s[n - 2] == '\\' && s[n - 1] == '\n') {
+ s[n - 2] = ' ';
+ s[n - 1] = ' ';
+ prprompt(1);
+ t := in.gets('\n');
+ if (t == nil)
+ break;
+ s += t;
+ n = len s;
+ }
+ }
+ return s;
+ }
+}
+
+#
+# Interactive shell loop.
+#
+Env.interactive(e: self ref Env, fd: ref Sys->FD)
+{
+ in := bufio->fopen(fd, Sys->OREAD);
+ if (in == nil)
+ e.error(sys->sprint("could not fopen stdin: %r\n"));
+ e.flags |= EInter;
+ for (;;) {
+ prprompt(0);
+ if (startserve)
+ e.serve();
+ if ((s := getline(in)) == nil)
+ exitmash();
+ e.sopen(s);
+ parse->parse(e);
+ if (histchan != nil)
+ histchan <-= array of byte s;
+ }
+}
diff --git a/appl/cmd/mash/make.b b/appl/cmd/mash/make.b
new file mode 100644
index 00000000..4d566c00
--- /dev/null
+++ b/appl/cmd/mash/make.b
@@ -0,0 +1,723 @@
+implement Mashbuiltin;
+
+#
+# "make" builtin, defines:
+#
+# depends - print dependencies
+# make - make-like command
+# match - print details of rule matches
+# rules - print rules
+#
+
+include "mash.m";
+include "mashparse.m";
+
+verbose: con 0; # debug output
+
+mashlib: Mashlib;
+
+Cmd, Env, Item, Stab: import mashlib;
+Depend, Rule, Target: import mashlib;
+sys, bufio, hash: import mashlib;
+
+Iobuf: import bufio;
+
+#
+# Interface to catch the use as a command.
+#
+init(nil: ref Draw->Context, args: list of string)
+{
+ raise "fail: " + hd args + " not loaded";
+}
+
+#
+# Used by whatis.
+#
+name(): string
+{
+ return "make";
+}
+
+#
+# Install commands.
+#
+mashinit(nil: list of string, lib: Mashlib, this: Mashbuiltin, e: ref Env)
+{
+ mashlib = lib;
+ e.defbuiltin("depends", this);
+ e.defbuiltin("make", this);
+ e.defbuiltin("match", this);
+ e.defbuiltin("rules", this);
+}
+
+#
+# Execute a builtin.
+#
+mashcmd(e: ref Env, l: list of string)
+{
+ s := hd l;
+ l = tl l;
+ case s {
+ "depends" =>
+ out := e.outfile();
+ if (out == nil)
+ return;
+ if (l == nil)
+ alldeps(out);
+ else
+ depends(out, l);
+ out.close();
+ "make" =>
+ domake(e, l);
+ "match" =>
+ domatch(e, l);
+ "rules" =>
+ out := e.outfile();
+ if (out == nil)
+ return;
+ if (l == nil)
+ allrules(out);
+ else
+ rules(out, l);
+ out.close();
+ }
+}
+
+#
+# Node states.
+#
+SUnknown, SNoexist, SExist, SStale, SMade, SDir, SDirload
+ : con iota;
+
+#
+# Node flags.
+#
+# FMark - marked as in progress
+#
+FMark
+ : con 1 << iota;
+
+Node: adt
+{
+ name: string;
+ state: int;
+ flags: int;
+ mtime: int;
+};
+
+#
+# Step in implicit chain.
+#
+Step: type (ref Rule, array of string, ref Node);
+
+#
+# Implicit match.
+#
+Match: adt
+{
+ node: ref Node;
+ path: list of Step;
+};
+
+NSIZE: con 127; # node hash size
+DSIZE: con 32; # number of dir entries for read
+
+ntab: array of list of ref Node; # node hash table
+
+initnodes()
+{
+ ntab = array[NSIZE] of list of ref Node;
+}
+
+#
+# Find node for a pathname.
+#
+getnode(s: string): ref Node
+{
+ h := hash->fun1(s, NSIZE);
+ for (l := ntab[h]; l != nil; l = tl l) {
+ n := hd l;
+ if (n.name == s)
+ return n;
+ }
+ r := ref Node(s, SUnknown, 0, 0);
+ ntab[h] = r :: ntab[h];
+ return r;
+}
+
+#
+# Make a pathname from a dir and an entry.
+#
+mkpath(d, s: string): string
+{
+ if (d == ".")
+ return s;
+ else if (d == "/")
+ return "/" + s;
+ else
+ return d + "/" + s;
+}
+
+#
+# Load a directory.
+#
+loaddir(s: string)
+{
+ if (verbose)
+ sys->print("loaddir %s\n", s);
+ fd := sys->open(s, Sys->OREAD);
+ if (fd == nil)
+ return;
+ for (;;) {
+ (c, dbuf) := sys->dirread(fd);
+ if(c <= 0)
+ break;
+ for (i := 0; i < c; i++) {
+ n := getnode(mkpath(s, dbuf[i].name));
+ if (dbuf[i].mode & Sys->DMDIR)
+ n.state = SDir;
+ else
+ n.state = SExist;
+ n.mtime = dbuf[i].mtime;
+ }
+ }
+}
+
+#
+# Load a file. Get its node, maybe stat it or loaddir.
+#
+loadfile(s: string): ref Node
+{
+ n := getnode(s);
+ if (n.state == SUnknown) {
+ if (verbose)
+ sys->print("stat %s\n", s);
+ (ok, d) := sys->stat(s);
+ if (ok >= 0) {
+ n.mtime = d.mtime;
+ if (d.mode & Sys->DMDIR) {
+ loaddir(s);
+ n.state = SDirload;
+ } else
+ n.state = SExist;
+ } else
+ n.state = SNoexist;
+ } else if (n.state == SDir) {
+ loaddir(s);
+ n.state = SDirload;
+ }
+ return n;
+}
+
+#
+# Get the node for a file and load the directories in its path.
+#
+getfile(s: string): ref Node
+{
+ d: string;
+ n := len s;
+ while (n >= 2 && s[0:2] == "./") {
+ n -= 2;
+ s = s[2:];
+ }
+ if (n > 0 && s[0] == '/') {
+ d = "/";
+ s = s[1:];
+ } else
+ d = ".";
+ (nil, l) := sys->tokenize(s, "/");
+ for (;;) {
+ w := loadfile(d);
+ if (l == nil)
+ return w;
+ s = hd l;
+ l = tl l;
+ d = mkpath(d, s);
+ }
+}
+
+#
+# If a dependency rule makes more than one target propogate SMade.
+#
+propagate(l: list of string)
+{
+ if (tl l == nil)
+ return ;
+ while (l != nil) {
+ s := hd l;
+ if (verbose)
+ sys->print("propogate to %s\n", s);
+ getfile(s).state = SMade;
+ l = tl l;
+ }
+}
+
+#
+# Try to make a node, or mark it as stale.
+# Return -1 on (reported) error, 0 on fail, 1 on success.
+#
+explicit(e: ref Env, t: ref Target, n: ref Node): int
+{
+ d: ref Depend;
+ for (l := t.depends; l != nil ; l = tl l) {
+ if ((hd l).op != Cnop) {
+ if (d != nil) {
+ e.report(sys->sprint("make: too many rules for %s", t.target));
+ return -1;
+ }
+ d = hd l;
+ }
+ }
+ for (l = t.depends; l != nil ; l = tl l) {
+ for (u := (hd l).depends; u != nil; u = tl u) {
+ s := hd u;
+ m := getfile(s);
+ x := make(e, m, s);
+ if (x < 0) {
+ sys->print("don't know how to make %s\n", s);
+ return x;
+ }
+ if (m.state == SMade || m.mtime > n.mtime) {
+ if (verbose)
+ sys->print("%s makes %s stale\n", s, t.target);
+ n.state = SStale;
+ }
+ }
+ }
+ if (d != nil) {
+ if (n.state == SNoexist || n.state == SStale) {
+ if (verbose)
+ sys->print("build %s with explicit rule\n", t.target);
+ e = e.copy();
+ e.flags |= mashlib->EEcho | Mashlib->ERaise;
+ e.flags &= ~mashlib->EInter;
+ d.cmd.xeq(e);
+ propagate(d.targets);
+ n.state = SMade;
+ } else if (verbose)
+ sys->print("%s up to date\n", t.target);
+ return 1;
+ }
+ return 0;
+}
+
+#
+# Report multiple implicit chains of equal length.
+#
+multimatch(e: ref Env, n: ref Node, l: list of Match)
+{
+ e.report(sys->sprint("%d rules match for %s", len l, n.name));
+ f := e.stderr;
+ while (l != nil) {
+ m := hd l;
+ sys->fprint(f, "%s", m.node.name);
+ for (p := m.path; p != nil; p = tl p) {
+ (nil, nil, t) := hd p;
+ sys->fprint(f, " -> %s", t.name);
+ }
+ sys->fprint(f, "\n");
+ l = tl l;
+ }
+}
+
+cycle(e: ref Env, n: ref Node)
+{
+ e.report(sys->sprint("make: cycle in dependencies for target %s", n.name));
+}
+
+#
+# Mark the nodes in an implicit chain.
+#
+markchain(e: ref Env, l: list of Step): int
+{
+ while (tl l != nil) {
+ (nil, nil, n) := hd l;
+ if (n.flags & FMark) {
+ cycle(e, n);
+ return 0;
+ }
+ n.flags |= FMark;
+ l = tl l;
+ }
+ return 1;
+}
+
+#
+# Unmark the nodes in an implicit chain.
+#
+unmarkchain(l: list of Step): int
+{
+ while (tl l != nil) {
+ (nil, nil, n) := hd l;
+ n.flags &= ~FMark;
+ l = tl l;
+ }
+ return 1;
+}
+
+#
+# Execute an implicit rule chain.
+#
+xeqmatch(e: ref Env, b, n: ref Node, l: list of Step): int
+{
+ if (!markchain(e, l))
+ return -1;
+ if (verbose)
+ sys->print("making %s for implicit rule chain\n", n.name);
+ e.args = nil;
+ x := make(e, n, n.name);
+ if (x < 0) {
+ sys->print("don't know how to make %s\n", n.name);
+ return x;
+ }
+ if (n.state == SMade || n.mtime > b.mtime || b.state == SStale) {
+ e = e.copy();
+ e.flags |= mashlib->EEcho | Mashlib->ERaise;
+ e.flags &= ~mashlib->EInter;
+ for (;;) {
+ (r, a, t) := hd l;
+ if (verbose)
+ sys->print("making %s with implicit rule\n", t.name);
+ e.args = a;
+ r.cmd.xeq(e);
+ t.state = SMade;
+ l = tl l;
+ if (l == nil)
+ break;
+ t.flags &= ~FMark;
+ }
+ } else
+ unmarkchain(l);
+ return 1;
+}
+
+#
+# Find the shortest implicit rule chain.
+#
+implicit(e: ref Env, base: ref Node): int
+{
+ win, lose: list of Match;
+ l: list of ref Rule;
+ cand := Match(base, nil) :: nil;
+ do {
+ # cand - list of candidate chains
+ # lose - list of extended chains that lose
+ # win - list of extended chains that win
+ lose = nil;
+ match:
+ # for each candidate
+ for (c := cand; c != nil; c = tl c) {
+ (b, x) := hd c;
+ s := b.name;
+ # find rules that match end of chain
+ m := mashlib->rulematch(s);
+ l = nil;
+ # exclude rules already in the chain
+ exclude:
+ for (n := m; n != nil; n = tl n) {
+ r := hd n;
+ for (y := x; y != nil; y = tl y) {
+ (u, nil, nil) := hd y;
+ if (u == r)
+ continue exclude;
+ }
+ l = r :: l;
+ }
+ if (l == nil)
+ continue match;
+ (nil, t) := sys->tokenize(s, "/");
+ # for each new rule that matched
+ for (n = l; n != nil; n = tl n) {
+ r := hd n;
+ a := r.matches(t);
+ if (a == nil) {
+ e.report("rule match cock up");
+ return -1;
+ }
+ a[0] = s;
+ e.args = a;
+ # eval rhs
+ (v, nil, nil) := r.rhs.ieval2(e);
+ if (v == nil)
+ continue;
+ y := (r, a, b) :: x;
+ z := getfile(v);
+ # winner or loser
+ if (z.state != SNoexist || Target.find(v) != nil)
+ win = (z, y) :: win;
+ else
+ lose = (z, y) :: lose;
+ }
+ }
+ # winner should be unique
+ if (win != nil) {
+ if (tl win != nil) {
+ multimatch(e, base, win);
+ return -1;
+ } else {
+ (a, p) := hd win;
+ return xeqmatch(e, base, a, p);
+ }
+ }
+ # losers are candidates in next round
+ cand = lose;
+ } while (cand != nil);
+ return 0;
+}
+
+#
+# Make a node (recursive).
+# Return -1 on (reported) error, 0 on fail, 1 on success.
+#
+make(e: ref Env, n: ref Node, s: string): int
+{
+ if (n == nil)
+ n = getfile(s);
+ if (verbose)
+ sys->print("making %s\n", n.name);
+ if (n.state == SMade)
+ return 1;
+ if (n.flags & FMark) {
+ cycle(e, n);
+ return -1;
+ }
+ n.flags |= FMark;
+ t := Target.find(s);
+ if (t != nil) {
+ x := explicit(e, t, n);
+ if (x != 0) {
+ n.flags &= ~FMark;
+ return x;
+ }
+ }
+ x := implicit(e, n);
+ n.flags &= ~FMark;
+ if (x != 0)
+ return x;
+ if (n.state == SExist)
+ return 0;
+ return -1;
+}
+
+makelevel: int = 0; # count recursion
+
+#
+# Make driver routine. Maybe initialize and handle exceptions.
+#
+domake(e: ref Env, l: list of string)
+{
+ if ((e.flags & mashlib->ETop) == 0) {
+ e.report("make not at top level");
+ return;
+ }
+ inited := 0;
+ if (makelevel > 0)
+ inited = 1;
+ makelevel++;
+ if (l == nil)
+ l = "default" :: nil;
+ while (l != nil) {
+ s := hd l;
+ l = tl l;
+ if (s[0] == '-') {
+ case s {
+ "-clear" =>
+ mashlib->initdep();
+ * =>
+ e.report("make: unknown option: " + s);
+ }
+ } else {
+ if (!inited) {
+ initnodes();
+ inited = 1;
+ }
+ {
+ if (make(e, nil, s) < 0) {
+ sys->print("don't know how to make %s\n", s);
+ raise "fail: make error";
+ }
+ }exception x{
+ mashlib->FAILPAT =>
+ makelevel--;
+ raise x;
+ }
+ }
+ }
+ makelevel--;
+}
+
+#
+# Print dependency/rule command.
+#
+prcmd(out: ref Iobuf, op: int, c: ref Cmd)
+{
+ if (op == Clistgroup)
+ out.putc(':');
+ if (c != nil) {
+ out.puts("{ ");
+ out.puts(c.text());
+ out.puts(" }");
+ } else
+ out.puts("{}");
+}
+
+#
+# Print details of rule matches.
+#
+domatch(e: ref Env, l: list of string)
+{
+ out := e.outfile();
+ if (out == nil)
+ return;
+ e = e.copy();
+ while (l != nil) {
+ s := hd l;
+ out.puts(sys->sprint("%s:\n", s));
+ m := mashlib->rulematch(s);
+ (nil, t) := sys->tokenize(s, "/");
+ while (m != nil) {
+ r := hd m;
+ out.puts(sys->sprint("\tlhs %s\n", r.lhs.text));
+ a := r.matches(t);
+ if (a != nil) {
+ a[0] = s;
+ n := len a;
+ for (i := 0; i < n; i++)
+ out.puts(sys->sprint("\t$%d '%s'\n", i, a[i]));
+ e.args = a;
+ (v, w, nil) := r.rhs.ieval2(e);
+ if (v != nil)
+ out.puts(sys->sprint("\trhs '%s'\n", v));
+ else
+ out.puts(sys->sprint("\trhs list %d\n", len w));
+ if (r.cmd != nil) {
+ out.putc('\t');
+ prcmd(out, r.op, r.cmd);
+ out.puts(";\n");
+ }
+ } else
+ out.puts("\tcock up\n");
+ m = tl m;
+ }
+ l = tl l;
+ }
+ out.close();
+}
+
+#
+# Print word list.
+#
+prwords(out: ref Iobuf, l: list of string, pre: int)
+{
+ while (l != nil) {
+ if (pre)
+ out.putc(' ');
+ out.puts(mashlib->quote(hd l));
+ if (!pre)
+ out.putc(' ');
+ l = tl l;
+ }
+}
+
+#
+# Print dependency.
+#
+prdep(out: ref Iobuf, d: ref Depend)
+{
+ prwords(out, d.targets, 0);
+ out.putc(':');
+ prwords(out, d.depends, 1);
+ if (d.op != Cnop) {
+ out.putc(' ');
+ prcmd(out, d.op, d.cmd);
+ }
+ out.puts(";\n");
+}
+
+#
+# Print all dependencies, avoiding duplicates.
+#
+alldep(out: ref Iobuf, d: ref Depend, pass: int)
+{
+ case pass {
+ 0 =>
+ d.mark = 0;
+ 1 =>
+ if (!d.mark) {
+ prdep(out, d);
+ d.mark = 1;
+ }
+ }
+}
+
+#
+# Print all dependencies.
+#
+alldeps(out: ref Iobuf)
+{
+ a := mashlib->dephash;
+ n := len a;
+ for (p := 0; p < 2; p++)
+ for (i := 0; i < n; i++)
+ for (l := a[i]; l != nil; l = tl l)
+ for (d := (hd l).depends; d != nil; d = tl d)
+ alldep(out, hd d, p);
+}
+
+#
+# Print dependencies.
+#
+depends(out: ref Iobuf, l: list of string)
+{
+ while (l != nil) {
+ s := hd l;
+ out.puts(s);
+ out.puts(":\n");
+ t := Target.find(s);
+ if (t != nil) {
+ for (d := t.depends; d != nil; d = tl d)
+ prdep(out, hd d);
+ }
+ l = tl l;
+ }
+}
+
+#
+# Print rule.
+#
+prrule(out: ref Iobuf, r: ref Rule)
+{
+ out.puts(r.lhs.text);
+ out.puts(" :~ ");
+ out.puts(r.rhs.text());
+ out.putc(' ');
+ prcmd(out, r.op, r.cmd);
+ out.puts(";\n");
+}
+
+#
+# Print all rules.
+#
+allrules(out: ref Iobuf)
+{
+ for (l := mashlib->rules; l != nil; l = tl l)
+ prrule(out, hd l);
+}
+
+#
+# Print matching rules.
+#
+rules(out: ref Iobuf, l: list of string)
+{
+ while (l != nil) {
+ s := hd l;
+ out.puts(s);
+ out.puts(":\n");
+ r := mashlib->rulematch(s);
+ while (r != nil) {
+ prrule(out, hd r);
+ r = tl r;
+ }
+ l = tl l;
+ }
+}
diff --git a/appl/cmd/mash/mash.b b/appl/cmd/mash/mash.b
new file mode 100644
index 00000000..4e2f2ded
--- /dev/null
+++ b/appl/cmd/mash/mash.b
@@ -0,0 +1,154 @@
+implement Mash;
+
+#
+# mash - Inferno make/shell
+#
+# Bruce Ellis - 1Q 98
+#
+
+include "mash.m";
+include "mashparse.m";
+
+#
+# mash consists of three modules plus library modules and loadable builtins.
+#
+# This module, Mash, loads the other two (Mashparse and Mashlib), loads
+# the builtin "builtins", initializes things and calls the parser.
+#
+# It has two entry points. One is the traditional init() function and the other,
+# tkinit, is an interface to WmMash that allows the "tk" builtin to cooperate
+# with the command window.
+#
+
+Mash: module
+{
+ tkinit: fn(ctxt: ref Draw->Context, top: ref Tk->Toplevel, args: list of string);
+ init: fn(ctxt: ref Draw->Context, args: list of string);
+};
+
+Iobuf: import Bufio;
+
+sys: Sys;
+lib: Mashlib;
+parse: Mashparse;
+
+Env, Stab: import lib;
+
+cmd: string;
+
+#
+# Check for /dev/console.
+#
+isconsole(fd: ref Sys->FD): int
+{
+ (ok1, d1) := sys->fstat(fd);
+ (ok2, d2) := sys->stat(lib->CONSOLE);
+ if (ok1 < 0 || ok2 < 0)
+ return 0;
+ return d1.dtype == d2.dtype && d1.qid.path == d2.qid.path;
+}
+
+usage(e: ref Env)
+{
+ sys->fprint(e.stderr, "usage: mash [-denx] [-c command] [src [args]]\n");
+ lib->exits("usage");
+}
+
+flags(e: ref Env, l: list of string): list of string
+{
+ while (l != nil && len hd l && (s := hd l)[0] == '-') {
+ l = tl l;
+ if (s == "--")
+ break;
+ n := len s;
+ for (i := 1; i < n; i++) {
+ case s[i] {
+ 'c' =>
+ if (++i < n) {
+ if (l != nil)
+ usage(e);
+ cmd = s[i:];
+ } else {
+ if (len l != 1)
+ usage(e);
+ cmd = hd l;
+ }
+ return nil;
+ 'd' =>
+ e.flags |= lib->EDumping;
+ 'e' =>
+ e.flags |= lib->ERaise;
+ 'n' =>
+ e.flags |= lib->ENoxeq;
+ 'x' =>
+ e.flags |= lib->EEcho;
+ * =>
+ usage(e);
+ }
+ }
+ }
+ return l;
+}
+
+tkinit(ctxt: ref Draw->Context, top: ref Tk->Toplevel, args: list of string)
+{
+ fd: ref Sys->FD;
+ sys = load Sys Sys->PATH;
+ stderr := sys->fildes(2);
+ lib = load Mashlib Mashlib->PATH;
+ if (lib == nil) {
+ sys->fprint(stderr, "could not load %s: %r\n", Mashlib->PATH);
+ exit;
+ }
+ parse = load Mashparse Mashparse->PATH;
+ if (parse == nil) {
+ sys->fprint(stderr, "could not load %s: %r\n", Mashparse->PATH);
+ exit;
+ }
+ e := Env.new();
+ e.stderr = stderr;
+ stderr = nil;
+ lib->initmash(ctxt, top, sys, e, lib, parse);
+ parse->init(lib);
+ boot := args == nil;
+ if (!boot)
+ args = flags(e, tl args);
+ e.doload(lib->LIB + lib->BUILTINS);
+ lib->prompt = "mash% ";
+ lib->contin = "\t";
+ if (cmd == nil && args == nil && !boot) {
+ e.global.assign(lib->MASHINIT, "true" :: nil);
+ fd = sys->open(lib->PROFILE, Sys->OREAD);
+ if (fd != nil) {
+ e.fopen(fd, lib->PROFILE);
+ parse->parse(e);
+ fd = nil;
+ }
+ }
+ e.global.assign(lib->MASHINIT, nil);
+ if (cmd == nil) {
+ if (args != nil) {
+ s := hd args;
+ args = tl args;
+ fd = sys->open(s, Sys->OREAD);
+ if (fd == nil)
+ e.couldnot("open", s);
+ e.fopen(fd, s);
+ e.global.assign(lib->ARGS, args);
+ }
+ if (fd == nil) {
+ fd = sys->fildes(0);
+ if (isconsole(fd))
+ e.interactive(fd);
+ e.fopen(fd, "<stdin>");
+ fd = nil;
+ }
+ } else
+ e.sopen(cmd);
+ parse->parse(e);
+}
+
+init(ctxt: ref Draw->Context, args: list of string)
+{
+ tkinit(ctxt, nil, args);
+}
diff --git a/appl/cmd/mash/mash.m b/appl/cmd/mash/mash.m
new file mode 100644
index 00000000..ae16fee6
--- /dev/null
+++ b/appl/cmd/mash/mash.m
@@ -0,0 +1,372 @@
+include "sys.m";
+include "bufio.m";
+include "draw.m";
+include "hash.m";
+include "filepat.m";
+include "regex.m";
+include "sh.m";
+include "string.m";
+include "tk.m";
+
+#
+# mash - Inferno make/shell
+#
+# Bruce Ellis - 1Q 98
+#
+
+ Rin,
+ Rout,
+ Rappend,
+ Rinout,
+ Rcount
+ : con iota; # Redirections
+
+ Icaret,
+ Iicaret,
+ Idollar,
+ Idollarq,
+ Imatch,
+ Iword,
+ Iexpr,
+ Ibackq,
+ Iquote,
+ Iinpipe,
+ Ioutpipe,
+ Iredir
+ : con iota; # Items
+
+ Csimple,
+ Cseq,
+ Cfor,
+ Cif,
+ Celse,
+ Cwhile,
+ Ccase,
+ Ccases,
+ Cmatched,
+ Cdefeq,
+ Ceq,
+ Cfn,
+ Crescue,
+ Casync,
+ Cgroup,
+ Clistgroup,
+ Csubgroup,
+ Cnop,
+ Cword,
+ Clist,
+ Ccaret,
+ Chd,
+ Clen,
+ Cnot,
+ Ctl,
+ Ccons,
+ Ceqeq,
+ Cnoteq,
+ Cmatch,
+ Cpipe,
+ Cdepend,
+ Crule,
+ Cprivate
+ : con iota; # Commands
+
+ Svalue,
+ Sfunc,
+ Sbuiltin
+ : con iota; # Symbol types
+
+Mashlib: module
+{
+ PATH: con "/dis/lib/mashlib.dis";
+
+ File: adt
+ {
+ in: ref Bufio->Iobuf;
+ name: string;
+ line: int;
+ eof: int;
+ };
+
+ Src: adt
+ {
+ line: int;
+ file: string;
+ };
+
+ Wquoted,
+ Wexpand
+ : con 1 << iota;
+
+ Word: adt
+ {
+ text: string;
+ flags: int;
+ where: Src;
+
+ word: fn(w: self ref Word, d: string): string;
+ };
+
+ Item: adt
+ {
+ op: int;
+ word: ref Word;
+ left, right: ref Item;
+ cmd: ref Cmd;
+ redir: ref Redir;
+
+ item1: fn(op: int, l: ref Item): ref Item;
+ item2: fn(op: int, l, r: ref Item): ref Item;
+ itemc: fn(op: int, c: ref Cmd): ref Item;
+ iteml: fn(l: list of string): ref Item;
+ itemr: fn(op: int, i: ref Item): ref Item;
+ itemw: fn(s: string): ref Item;
+
+ caret: fn(i: self ref Item, e: ref Env): (string, list of string, int);
+ ieval: fn(i: self ref Item, e: ref Env): (string, list of string, int);
+ ieval1: fn(i: self ref Item, e: ref Env): ref Item;
+ ieval2: fn(i: self ref Item, e: ref Env): (string, list of string, int);
+ reval: fn(i: self ref Item, e: ref Env): (int, string);
+ sword: fn(i: self ref Item, e: ref Env): ref Item;
+ text: fn(i: self ref Item): string;
+ };
+
+ Redir: adt
+ {
+ op: int;
+ word: ref Item;
+ };
+
+ Cmd: adt
+ {
+ op: int;
+ words: cyclic list of ref Item;
+ left, right: cyclic ref Cmd;
+ item: cyclic ref Item;
+ redirs: cyclic list of ref Redir;
+ value: list of string;
+ error: int;
+
+ cmd1: fn(op: int, l: ref Cmd): ref Cmd;
+ cmd2: fn(op: int, l, r: ref Cmd): ref Cmd;
+ cmd1i: fn(op: int, l: ref Cmd, i: ref Item): ref Cmd;
+ cmd1w: fn(op: int, l: ref Cmd, w: list of ref Item): ref Cmd;
+ cmde: fn(c: self ref Cmd, op: int, l, r: ref Cmd): ref Cmd;
+ cmdiw: fn(op: int, i: ref Item, w: list of ref Item): ref Cmd;
+
+ assign: fn(c: self ref Cmd, e: ref Env, def: int);
+ checkpipe: fn(c: self ref Cmd, e: ref Env, f: int): int;
+ cmdio: fn(c: self ref Cmd, e: ref Env, i: ref Item);
+ depend: fn(c: self ref Cmd, e: ref Env);
+ eeval: fn(c: self ref Cmd, e: ref Env): (string, list of string);
+ eeval1: fn(c: self ref Cmd, e: ref Env): ref Cmd;
+ eeval2: fn(c: self ref Cmd, e: ref Env): (string, list of string, int);
+ evaleq: fn(c: self ref Cmd, e: ref Env): int;
+ evalmatch: fn(c: self ref Cmd, e: ref Env): int;
+ mkcmd: fn(c: self ref Cmd, e: ref Env, async: int): ref Cmd;
+ quote: fn(c: self ref Cmd, e: ref Env, back: int): ref Item;
+ rotcases: fn(c: self ref Cmd): ref Cmd;
+ rule: fn(c: self ref Cmd, e: ref Env);
+ serve: fn(c: self ref Cmd, e: ref Env, write: int): ref Item;
+ simple: fn(c: self ref Cmd, e: ref Env, wait: int);
+ text: fn(c: self ref Cmd): string;
+ truth: fn(c: self ref Cmd, e: ref Env): int;
+ xeq: fn(c: self ref Cmd, e: ref Env);
+ xeqit: fn(c: self ref Cmd, e: ref Env, wait: int);
+ };
+
+ Depend: adt
+ {
+ targets: list of string;
+ depends: list of string;
+ op: int;
+ cmd: ref Cmd;
+ mark: int;
+ };
+
+ Target: adt
+ {
+ target: string;
+ depends: list of ref Depend;
+
+ find: fn(s: string): ref Target;
+ };
+
+ Lhs: adt
+ {
+ text: string;
+ elems: list of string;
+ count: int;
+ };
+
+ Rule: adt
+ {
+ lhs: ref Lhs;
+ rhs: ref Item;
+ op: int;
+ cmd: ref Cmd;
+
+ match: fn(r: self ref Rule, a, n: int, t: list of string): int;
+ matches: fn(r: self ref Rule, t: list of string): array of string;
+ };
+
+ SHASH: con 31; # Symbol table hash size
+ SMASK: con 16r7FFFFFFF; # Mask for SHASH bits
+
+ Symb: adt
+ {
+ name: string;
+ value: list of string;
+ func: ref Cmd;
+ builtin: Mashbuiltin;
+ tag: int;
+ };
+
+ Stab: adt
+ {
+ tab: array of list of ref Symb;
+ wmask: int;
+ copy: int;
+
+ new: fn(): ref Stab;
+ clone: fn(t: self ref Stab): ref Stab;
+ all: fn(t: self ref Stab): list of ref Symb;
+ assign: fn(t: self ref Stab, s: string, v: list of string);
+ defbuiltin: fn(t: self ref Stab, s: string, b: Mashbuiltin);
+ define: fn(t: self ref Stab, s: string, f: ref Cmd);
+ find: fn(t: self ref Stab, s: string): ref Symb;
+ func: fn(t: self ref Stab, s: string): ref Cmd;
+ update: fn(t: self ref Stab, s: string, tag: int, v: list of string, f: ref Cmd, b: Mashbuiltin): ref Symb;
+ };
+
+ ETop, EInter, EEcho, ERaise, EDumping, ENoxeq:
+ con 1 << iota;
+
+ Env: adt
+ {
+ global: ref Stab;
+ local: ref Stab;
+ flags: int;
+ in, out: ref Sys->FD;
+ stderr: ref Sys->FD;
+ wait: ref Sys->FD;
+ file: ref File;
+ args: array of string;
+ level: int;
+
+ new: fn(): ref Env;
+ clone: fn(e: self ref Env): ref Env;
+ copy: fn(e: self ref Env): ref Env;
+
+ interactive: fn(e: self ref Env, fd: ref Sys->FD);
+
+ arg: fn(e: self ref Env, s: string): string;
+ builtin: fn(e: self ref Env, s: string): Mashbuiltin;
+ defbuiltin: fn(e: self ref Env, s: string, b: Mashbuiltin);
+ define: fn(e: self ref Env, s: string, f: ref Cmd);
+ dollar: fn(e: self ref Env, s: string): ref Symb;
+ func: fn(e: self ref Env, s: string): ref Cmd;
+ let: fn(e: self ref Env, s: string, v: list of string);
+ set: fn(e: self ref Env, s: string, v: list of string);
+
+ couldnot: fn(e: self ref Env, what, who: string);
+ diag: fn(e: self ref Env, s: string): string;
+ error: fn(e: self ref Env, s: string);
+ report: fn(e: self ref Env, s: string);
+ sopen: fn(e: self ref Env, s: string);
+ suck: fn(e: self ref Env);
+ undefined: fn(e: self ref Env, s: string);
+ usage: fn(e: self ref Env, s: string);
+
+ devnull: fn(e: self ref Env): ref Sys->FD;
+ fopen: fn(e: self ref Env, fd: ref Sys->FD, s: string);
+ outfile: fn(e: self ref Env): ref Bufio->Iobuf;
+ output: fn(e: self ref Env, s: string);
+ pipe: fn(e: self ref Env): array of ref Sys->FD;
+ runit: fn(e: self ref Env, s: list of string, in, out: ref Sys->FD, wait: int);
+ serve: fn(e: self ref Env);
+ servefd: fn(e: self ref Env, fd: ref Sys->FD, write: int): string;
+ servefile: fn(e: self ref Env, n: string): (string, ref Sys->FileIO);
+
+ doload: fn(e: self ref Env, s: string);
+ lex: fn(e: self ref Env, y: ref Mashparse->YYSTYPE): int;
+ mklist: fn(e: self ref Env, l: list of ref Item): list of ref Item;
+ mksimple: fn(e: self ref Env, l: list of ref Item): ref Cmd;
+ };
+
+ initmash: fn(ctxt: ref Draw->Context, top: ref Tk->Toplevel, s: Sys, e: ref Env, l: Mashlib, p: Mashparse);
+ nonexistent: fn(s: string): int;
+
+ errstr: fn(): string;
+ exits: fn(s: string);
+ ident: fn(s: string): int;
+ initdep: fn();
+ prepareio: fn(in, out: ref sys->FD): (int, ref Sys->FD);
+ prprompt: fn(n: int);
+ quote: fn(s: string): string;
+ reap: fn();
+ revitems: fn(l: list of ref Item): list of ref Item;
+ revstrs: fn(l: list of string): list of string;
+ rulematch: fn(s: string): list of ref Rule;
+
+ ARGS: con "args";
+ BUILTINS: con "builtins.dis";
+ CHAN: con "/chan";
+ CONSOLE: con "/dev/cons";
+ DEVNULL: con "/dev/null";
+ EEXISTS: con "file exists";
+ EPIPE: con "write on closed pipe";
+ EXIT: con "exit";
+ FAILPAT: con "fail:*";
+ FAIL: con "fail:";
+ FAILLEN: con len FAIL;
+ HISTF: con "history";
+ LIB: con "/dis/lib/mash/";
+ MASHF: con "mash";
+ MASHINIT: con "mashinit";
+ PROFILE: con "/lib/mashinit";
+ TRUE: con "true";
+ MAXELEV: con 256;
+
+ sys: Sys;
+ bufio: Bufio;
+ filepat: Filepat;
+ hash: Hash;
+ regex: Regex;
+ str: String;
+ tk: Tk;
+
+ gctxt: ref Draw->Context;
+ gtop: ref Tk->Toplevel;
+
+ prompt: string;
+ contin: string;
+
+ empty: list of string;
+
+ PIDEXIT: con 0;
+
+ histchan: chan of array of byte;
+ inchan: chan of array of byte;
+ pidchan: chan of int;
+ servechan: chan of array of byte;
+ startserve: int;
+
+ rules: list of ref Rule;
+ dephash: array of list of ref Target;
+
+ parse: Mashparse;
+};
+
+#
+# Interface to loadable builtin modules. mashinit is called when a module
+# is loaded. mashcmd is called for a builtin as defined by Env.defbuiltin().
+# init() is in the interface to catch the use of builtin modules as commands.
+# name() is used by whatis.
+#
+Mashbuiltin: module
+{
+ mashinit: fn(l: list of string, lib: Mashlib, this: Mashbuiltin, e: ref Mashlib->Env);
+ mashcmd: fn(e: ref Mashlib->Env, l: list of string);
+ init: fn(ctxt: ref Draw->Context, args: list of string);
+ name: fn(): string;
+};
diff --git a/appl/cmd/mash/mash.y b/appl/cmd/mash/mash.y
new file mode 100644
index 00000000..2417ef51
--- /dev/null
+++ b/appl/cmd/mash/mash.y
@@ -0,0 +1,269 @@
+%{
+include "mash.m";
+
+#
+# mash parser. Thread safe.
+#
+%}
+
+%module Mashparse
+{
+ PATH: con "/dis/lib/mashparse.dis";
+
+ init: fn(l: Mashlib);
+ parse: fn(e: ref Mashlib->Env);
+
+ YYSTYPE: adt
+ {
+ cmd: ref Mashlib->Cmd;
+ item: ref Mashlib->Item;
+ items: list of ref Mashlib->Item;
+ flag: int;
+ };
+
+ YYETYPE: type ref Mashlib->Env;
+}
+
+%{
+ lib: Mashlib;
+
+ Cmd, Item, Stab, Env: import lib;
+%}
+
+%left Lcase Lfor Lif Lwhile Loffparen # low prec
+%left Lelse
+%left Lpipe
+%left Leqeq Lmatch Lnoteq
+%right Lcons
+%left Lcaret
+%left Lnot Lhd Ltl Llen
+%type <flag> term
+%type <item> item wgen witem word redir sword
+%type <items> asimple list
+%type <cmd> case cases cmd cmda cmds cmdt complex
+%type <cmd> epilog expr cbrace cobrace obrace simple
+%token <item> Lword
+%token Lbackq Lcolon Lcolonmatch Ldefeq Leq Lmatched Lquote
+%token Loncurly Lonparen Loffcurly Loffparen Lat
+%token Lgreat Lgreatgreat Lless Llessgreat
+%token Lfn Lin Lrescue
+%token Land Leof Lsemi
+%token Lerror
+
+%%
+
+script : tcmds
+ ;
+
+tcmds : # empty
+ | tcmds xeq
+ ;
+
+xeq : cmda
+ { $1.xeq(e.yyenv); }
+ | Leof
+ | error
+ ;
+
+cmdt : # empty
+ { $$ = nil; }
+ | cmdt cmda
+ { $$ = Cmd.cmd2(Cseq, $1, $2); }
+ ;
+
+cmda : cmd term
+ { $$ = $1.mkcmd(e.yyenv, $2); }
+ ;
+
+cmds : cmdt
+ | cmdt cmd
+ { $$ = Cmd.cmd2(Cseq, $1, $2.mkcmd(e.yyenv, 0)); }
+ ;
+
+cmd : simple
+ | complex
+ | cmd Lpipe cmd
+ { $$ = Cmd.cmd2(Cpipe, $1, $3); }
+ ;
+
+simple : asimple
+ { $$ = e.yyenv.mksimple($1); }
+ | asimple Lcolon list cobrace
+ {
+ $4.words = e.yyenv.mklist($3);
+ $$ = Cmd.cmd1w(Cdepend, $4, e.yyenv.mklist($1));
+ }
+ ;
+
+complex : Loncurly cmds Loffcurly epilog
+ { $$ = $4.cmde(Cgroup, $2, nil); }
+ | Lat Loncurly cmds Loffcurly epilog
+ { $$ = $5.cmde(Csubgroup, $3, nil); }
+ | Lfor Lonparen sword Lin list Loffparen cmd
+ { $$ = Cmd.cmd1i(Cfor, $7, $3); $$.words = lib->revitems($5); }
+ | Lif Lonparen expr Loffparen cmd
+ { $$ = Cmd.cmd2(Cif, $3, $5); }
+ | Lif Lonparen expr Loffparen cmd Lelse cmd
+ { $$ = Cmd.cmd2(Cif, $3, Cmd.cmd2(Celse, $5, $7)); }
+ | Lwhile Lonparen expr Loffparen cmd
+ { $$ = Cmd.cmd2(Cwhile, $3, $5); }
+ | Lcase expr Loncurly cases Loffcurly
+ { $$ = Cmd.cmd2(Ccase, $2, $4.rotcases()); }
+ | sword Leq list
+ { $$ = Cmd.cmdiw(Ceq, $1, $3); }
+ | sword Ldefeq list
+ { $$ = Cmd.cmdiw(Cdefeq, $1, $3); }
+ | Lfn word obrace
+ { $$ = Cmd.cmd1i(Cfn, $3, $2); }
+ | Lrescue word obrace
+ { $$ = Cmd.cmd1i(Crescue, $3, $2); }
+ | word Lcolonmatch word cbrace
+ {
+ $4.item = $3;
+ $$ = Cmd.cmd1i(Crule, $4, $1);
+ }
+ ;
+
+cbrace : Lcolon Loncurly cmds Loffcurly
+ { $$ = Cmd.cmd1(Clistgroup, $3); }
+ | Loncurly cmds Loffcurly
+ { $$ = Cmd.cmd1(Cgroup, $2); }
+ ;
+
+cobrace : # empty
+ { $$ = Cmd.cmd1(Cnop, nil); }
+ | cbrace
+ ;
+
+obrace : # empty
+ { $$ = nil; }
+ | Loncurly cmds Loffcurly
+ { $$ = $2; }
+ ;
+
+cases : # empty
+ { $$ = nil; }
+ | cases case
+ { $$ = Cmd.cmd2(Ccases, $1, $2); }
+ ;
+
+case : expr Lmatched cmda
+ { $$ = Cmd.cmd2(Cmatched, $1, $3); }
+ ;
+
+asimple : word
+ { $$ = $1 :: nil; }
+ | asimple item
+ { $$ = $2 :: $1; }
+ ;
+
+item : witem
+ | redir
+ ;
+
+witem : word
+ | wgen
+ ;
+
+wgen : Lbackq Loncurly cmds Loffcurly
+ { $$ = Item.itemc(Ibackq, $3); }
+ | Lquote Loncurly cmds Loffcurly
+ { $$ = Item.itemc(Iquote, $3); }
+ | Lless Loncurly cmds Loffcurly
+ { $$ = Item.itemc(Iinpipe, $3); }
+ | Lgreat Loncurly cmds Loffcurly
+ { $$ = Item.itemc(Ioutpipe, $3); }
+ ;
+
+word : Lword
+ | word Lcaret word
+ { $$ = Item.item2(Icaret, $1, $3); }
+ | Lonparen expr Loffparen
+ { $$ = Item.itemc(Iexpr, $2); }
+ ;
+
+sword : Lword
+ { $$ = $1.sword(e.yyenv); }
+ ;
+
+list : # empty
+ { $$ = nil; }
+ | list witem
+ { $$ = $2 :: $1; }
+ ;
+
+epilog : # empty
+ { $$ = ref Cmd; $$.error = 0; }
+ | epilog redir
+ { $$ = $1; $1.cmdio(e.yyenv, $2); }
+ ;
+
+redir : Lless word
+ { $$ = Item.itemr(Rin, $2); }
+ | Lgreat word
+ { $$ = Item.itemr(Rout, $2); }
+ | Lgreatgreat word
+ { $$ = Item.itemr(Rappend, $2); }
+ | Llessgreat word
+ { $$ = Item.itemr(Rinout, $2); }
+ ;
+
+term : Lsemi
+ { $$ = 0; }
+ | Leof
+ { $$ = 0; }
+ | Land
+ { $$ = 1; }
+ ;
+
+expr : Lword
+ { $$ = Cmd.cmd1i(Cword, nil, $1); }
+ | wgen
+ { $$ = Cmd.cmd1i(Cword, nil, $1); }
+ | Lonparen expr Loffparen
+ { $$ = $2; }
+ | expr Lcaret expr
+ { $$ = Cmd.cmd2(Ccaret, $1, $3); }
+ | Lhd expr
+ { $$ = Cmd.cmd1(Chd, $2); }
+ | Ltl expr
+ { $$ = Cmd.cmd1(Ctl, $2); }
+ | Llen expr
+ { $$ = Cmd.cmd1(Clen, $2); }
+ | Lnot expr
+ { $$ = Cmd.cmd1(Cnot, $2); }
+ | expr Lcons expr
+ { $$ = Cmd.cmd2(Ccons, $1, $3); }
+ | expr Leqeq expr
+ { $$ = Cmd.cmd2(Ceqeq, $1, $3); }
+ | expr Lnoteq expr
+ { $$ = Cmd.cmd2(Cnoteq, $1, $3); }
+ | expr Lmatch expr
+ { $$ = Cmd.cmd2(Cmatch, $1, $3); }
+ ;
+%%
+
+init(l: Mashlib)
+{
+ lib = l;
+}
+
+parse(e: ref Env)
+{
+ y := ref YYENV;
+ y.yyenv = e;
+ y.yysys = lib->sys;
+ y.yystderr = e.stderr;
+ yyeparse(y);
+}
+
+yyerror(e: ref YYENV, s: string)
+{
+ e.yyenv.report(s);
+ e.yyenv.suck();
+}
+
+yyelex(e: ref YYENV): int
+{
+ return e.yyenv.lex(e.yylval);
+}
diff --git a/appl/cmd/mash/mashfile b/appl/cmd/mash/mashfile
new file mode 100644
index 00000000..0357c3dc
--- /dev/null
+++ b/appl/cmd/mash/mashfile
@@ -0,0 +1,36 @@
+make -clear;
+lflags = -wg;
+
+fn lc {
+ limbo $lflags $args;
+};
+
+libsrc = depends.b dump.b exec.b expr.b lex.b misc.b serve.b symb.b xeq.b;
+bus = builtins.dis tk.dis make.dis history.dis;
+core = mash.dis mashlib.dis mashparse.dis;
+
+bulib = /dis/lib/mash;
+bulibs = $bulib/$bus;
+
+mashparse.b mashparse.m : mash.y
+{
+ eyacc -vd mash.y;
+ mv y.tab.m mashparse.m;
+ mv y.tab.b mashparse.b;
+};
+
+*.dis :~ $1.b { lc $1.b };
+$bulib/*.dis :~ $1.dis { cp $1.dis $bulib };
+/dis/*.dis :~ $1.dis { cp $1.dis /dis };
+/dis/lib/*.dis :~ $1.dis { cp $1.dis /dis/lib };
+
+$core $bus : mash.m mashparse.m;
+mashlib.dis : $libsrc;
+
+insbu : $bulibs {};
+insdis : /dis/mash.dis /dis/lib/mashlib.dis /dis/lib/mashparse.dis {};
+
+all : eyacc.dis mash.dis mashlib.dis mashparse.dis $bus {};
+install : insbu insdis {};
+
+clean : { rm mashparse.b mashparse.m *.dis };
diff --git a/appl/cmd/mash/mashlib.b b/appl/cmd/mash/mashlib.b
new file mode 100644
index 00000000..c7ac7a29
--- /dev/null
+++ b/appl/cmd/mash/mashlib.b
@@ -0,0 +1,60 @@
+implement Mashlib;
+
+#
+# Mashlib - All of the real work except for the parsing.
+#
+
+include "mash.m";
+include "mashparse.m";
+
+Iobuf: import bufio;
+HashTable, HashVal: import hash;
+
+include "depends.b";
+include "dump.b";
+include "exec.b";
+include "expr.b";
+include "lex.b";
+include "misc.b";
+include "serve.b";
+include "symb.b";
+include "xeq.b";
+
+lib: Mashlib;
+
+initmash(ctxt: ref Draw->Context, top: ref Tk->Toplevel, s: Sys, e: ref Env, l: Mashlib, p: Mashparse)
+{
+ gctxt = ctxt;
+ gtop = top;
+ sys = s;
+ lib = l;
+ parse = p;
+ if (top != nil) {
+ tk = load Tk Tk->PATH;
+ if (tk == nil)
+ e.couldnot("load", Tk->PATH);
+ }
+ bufio = load Bufio Bufio->PATH;
+ if (bufio == nil)
+ e.couldnot("load", Bufio->PATH);
+ hash = load Hash Hash->PATH;
+ if (hash == nil)
+ e.couldnot("load", Hash->PATH);
+ str = load String String->PATH;
+ if (str == nil)
+ e.couldnot("load", String->PATH);
+ initlex();
+ empty = "no" :: "value" :: nil;
+ startserve = 0;
+}
+
+nonexistent(e: string): int
+{
+ errs := array[] of {"does not exist", "directory entry not found"};
+ for (i := 0; i < len errs; i++){
+ j := len errs[i];
+ if (j <= len e && e[len e-j:] == errs[i])
+ return 1;
+ }
+ return 0;
+}
diff --git a/appl/cmd/mash/mashparse.b b/appl/cmd/mash/mashparse.b
new file mode 100644
index 00000000..b154f12c
--- /dev/null
+++ b/appl/cmd/mash/mashparse.b
@@ -0,0 +1,662 @@
+implement Mashparse;
+
+#line 2 "mash.y"
+include "mash.m";
+
+#
+# mash parser. Thread safe.
+#
+Mashparse: module {
+
+ PATH: con "/dis/lib/mashparse.dis";
+
+ init: fn(l: Mashlib);
+ parse: fn(e: ref Mashlib->Env);
+
+ YYSTYPE: adt
+ {
+ cmd: ref Mashlib->Cmd;
+ item: ref Mashlib->Item;
+ items: list of ref Mashlib->Item;
+ flag: int;
+ };
+
+ YYETYPE: type ref Mashlib->Env;
+Lcase: con 57346;
+Lfor: con 57347;
+Lif: con 57348;
+Lwhile: con 57349;
+Loffparen: con 57350;
+Lelse: con 57351;
+Lpipe: con 57352;
+Leqeq: con 57353;
+Lmatch: con 57354;
+Lnoteq: con 57355;
+Lcons: con 57356;
+Lcaret: con 57357;
+Lnot: con 57358;
+Lhd: con 57359;
+Ltl: con 57360;
+Llen: con 57361;
+Lword: con 57362;
+Lbackq: con 57363;
+Lcolon: con 57364;
+Lcolonmatch: con 57365;
+Ldefeq: con 57366;
+Leq: con 57367;
+Lmatched: con 57368;
+Lquote: con 57369;
+Loncurly: con 57370;
+Lonparen: con 57371;
+Loffcurly: con 57372;
+Lat: con 57373;
+Lgreat: con 57374;
+Lgreatgreat: con 57375;
+Lless: con 57376;
+Llessgreat: con 57377;
+Lfn: con 57378;
+Lin: con 57379;
+Lrescue: con 57380;
+Land: con 57381;
+Leof: con 57382;
+Lsemi: con 57383;
+Lerror: con 57384;
+
+};
+
+#line 28 "mash.y"
+ lib: Mashlib;
+
+ Cmd, Item, Stab, Env: import lib;
+YYEOFCODE: con 1;
+YYERRCODE: con 2;
+YYMAXDEPTH: con 150;
+
+#line 244 "mash.y"
+
+
+init(l: Mashlib)
+{
+ lib = l;
+}
+
+parse(e: ref Env)
+{
+ y := ref YYENV;
+ y.yyenv = e;
+ y.yysys = lib->sys;
+ y.yystderr = e.stderr;
+ yyeparse(y);
+}
+
+yyerror(e: ref YYENV, s: string)
+{
+ e.yyenv.report(s);
+ e.yyenv.suck();
+}
+
+yyelex(e: ref YYENV): int
+{
+ return e.yyenv.lex(e.yylval);
+}
+yyexca := array[] of {-1, 1,
+ 1, -1,
+ -2, 0,
+-1, 2,
+ 1, 1,
+ -2, 0,
+-1, 21,
+ 24, 51,
+ 25, 51,
+ -2, 48,
+};
+YYNPROD: con 75;
+YYPRIVATE: con 57344;
+yytoknames: array of string;
+yystates: array of string;
+yydebug: con 0;
+YYLAST: con 249;
+yyact := array[] of {
+ 7, 20, 4, 49, 41, 47, 110, 65, 103, 95,
+ 17, 24, 32, 112, 33, 146, 142, 38, 39, 28,
+ 59, 60, 140, 129, 40, 64, 22, 46, 63, 35,
+ 36, 34, 37, 128, 127, 38, 67, 69, 70, 71,
+ 27, 26, 25, 76, 22, 75, 126, 111, 77, 74,
+ 45, 80, 81, 38, 44, 78, 88, 89, 90, 91,
+ 92, 68, 22, 98, 99, 93, 94, 32, 124, 33,
+ 97, 106, 62, 107, 38, 39, 104, 108, 109, 104,
+ 68, 40, 105, 22, 66, 105, 56, 143, 55, 116,
+ 117, 118, 119, 120, 73, 32, 32, 33, 33, 38,
+ 39, 122, 132, 36, 131, 37, 40, 123, 22, 72,
+ 125, 56, 43, 55, 135, 136, 58, 57, 133, 62,
+ 134, 139, 38, 6, 62, 16, 13, 14, 15, 141,
+ 66, 22, 96, 67, 69, 62, 32, 79, 33, 84,
+ 83, 21, 24, 61, 147, 148, 144, 24, 149, 11,
+ 22, 3, 12, 16, 13, 14, 15, 18, 2, 19,
+ 1, 5, 85, 87, 86, 84, 83, 8, 101, 21,
+ 54, 51, 52, 53, 48, 39, 9, 11, 22, 82,
+ 12, 40, 42, 50, 137, 18, 56, 19, 55, 54,
+ 51, 52, 53, 48, 39, 115, 138, 38, 39, 130,
+ 40, 10, 50, 29, 40, 56, 22, 55, 102, 56,
+ 31, 55, 85, 87, 86, 84, 83, 121, 23, 30,
+ 85, 87, 86, 84, 83, 114, 0, 145, 85, 87,
+ 86, 84, 83, 113, 0, 0, 85, 87, 86, 84,
+ 83, 100, 0, 0, 85, 87, 86, 84, 83,
+};
+yypact := array[] of {
+-1000,-1000, 121,-1000,-1000,-1000,-1000, 1,-1000,-1000,
+ -3,-1000, 84, 25, 21, -2, 173, 92, 15, 15,
+ 120,-1000, 173,-1000, 149,-1000,-1000,-1000,-1000,-1000,
+-1000,-1000, 109,-1000, 102, 33, 15, 15,-1000, 81,
+ 66, 19, 149,-1000, 117, 173, 173, 151,-1000,-1000,
+ 173, 173, 173, 173, 173, 56, 52,-1000,-1000, 104,
+ 104, 15, 15, 233,-1000, 54,-1000, 109,-1000, 109,
+ 109, 109,-1000,-1000,-1000,-1000, 1, 17, -24,-1000,
+ 225, 217,-1000, 173, 173, 173, 173, 173, 209,-1000,
+-1000,-1000,-1000, 177, 177,-1000,-1000,-1000, 57,-1000,
+-1000,-1000,-1000,-1000, 40,-1000, 16, 4, 3, -7,
+ 70,-1000,-1000, 149, 149, 154,-1000, 125, 125, 125,
+ 125,-1000, -8,-1000,-1000, -14,-1000,-1000,-1000,-1000,
+-1000, 15, 15, 70, 79, 137, 132,-1000,-1000, 201,
+-1000, -15,-1000, 149, 149, 149,-1000, 132, 132,-1000,
+};
+yypgo := array[] of {
+ 0, 218, 203, 3, 208, 1, 199, 10, 201, 7,
+ 196, 195, 0, 2, 4, 182, 176, 6, 5, 8,
+ 168, 9, 167, 160, 158, 151,
+};
+yyr1 := array[] of {
+ 0, 23, 24, 24, 25, 25, 25, 15, 15, 13,
+ 14, 14, 12, 12, 12, 22, 22, 16, 16, 16,
+ 16, 16, 16, 16, 16, 16, 16, 16, 16, 19,
+ 19, 20, 20, 21, 21, 11, 11, 10, 8, 8,
+ 2, 2, 4, 4, 3, 3, 3, 3, 5, 5,
+ 5, 7, 9, 9, 17, 17, 6, 6, 6, 6,
+ 1, 1, 1, 18, 18, 18, 18, 18, 18, 18,
+ 18, 18, 18, 18, 18,
+};
+yyr2 := array[] of {
+ 0, 1, 0, 2, 1, 1, 1, 0, 2, 2,
+ 1, 2, 1, 1, 3, 1, 4, 4, 5, 7,
+ 5, 7, 5, 5, 3, 3, 3, 3, 4, 4,
+ 3, 0, 1, 0, 3, 0, 2, 3, 1, 2,
+ 1, 1, 1, 1, 4, 4, 4, 4, 1, 3,
+ 3, 1, 0, 2, 0, 2, 2, 2, 2, 2,
+ 1, 1, 1, 1, 1, 3, 3, 2, 2, 2,
+ 2, 3, 3, 3, 3,
+};
+yychk := array[] of {
+-1000, -23, -24, -25, -13, 40, 2, -12, -22, -16,
+ -8, 28, 31, 5, 6, 7, 4, -7, 36, 38,
+ -5, 20, 29, -1, 10, 41, 40, 39, 22, -2,
+ -4, -6, -5, -3, 34, 32, 33, 35, 20, 21,
+ 27, -14, -15, 28, 29, 29, 29, -18, 20, -3,
+ 29, 17, 18, 19, 16, 34, 32, 25, 24, -5,
+ -5, 23, 15, -18, -12, -9, 28, -5, 28, -5,
+ -5, -5, 28, 28, 30, -13, -12, -14, -7, 20,
+ -18, -18, 28, 15, 14, 11, 13, 12, -18, -18,
+ -18, -18, -18, -9, -9, -21, 28, -21, -5, -5,
+ 8, -20, -4, -19, 22, 28, -14, -14, -14, -14,
+ -17, 30, 37, 8, 8, -11, -18, -18, -18, -18,
+ -18, 8, -14, -19, 28, -14, 30, 30, 30, 30,
+ -6, 34, 32, -17, -9, -12, -12, 30, -10, -18,
+ 30, -14, 30, 8, 9, 26, 30, -12, -12, -13,
+};
+yydef := array[] of {
+ 2, -2, -2, 3, 4, 5, 6, 0, 12, 13,
+ 15, 7, 0, 0, 0, 0, 0, 0, 0, 0,
+ 38, -2, 0, 9, 0, 60, 61, 62, 52, 39,
+ 40, 41, 42, 43, 0, 0, 0, 0, 48, 0,
+ 0, 0, 10, 7, 0, 0, 0, 0, 63, 64,
+ 0, 0, 0, 0, 0, 0, 0, 52, 52, 33,
+ 33, 0, 0, 0, 14, 31, 7, 56, 7, 57,
+ 58, 59, 7, 7, 54, 8, 11, 0, 0, 51,
+ 0, 0, 35, 0, 0, 0, 0, 0, 0, 67,
+ 68, 69, 70, 24, 25, 26, 7, 27, 0, 49,
+ 50, 16, 53, 32, 0, 7, 0, 0, 0, 0,
+ 17, 54, 52, 0, 0, 0, 66, 71, 72, 73,
+ 74, 65, 0, 28, 7, 0, 46, 47, 44, 45,
+ 55, 0, 0, 18, 0, 20, 22, 23, 36, 0,
+ 34, 0, 30, 0, 0, 0, 29, 19, 21, 37,
+};
+yytok1 := array[] of {
+ 1,
+};
+yytok2 := array[] of {
+ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
+ 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
+ 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+ 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,
+ 42,
+};
+yytok3 := array[] of {
+ 0
+};
+
+YYFLAG: con -1000;
+
+# parser for yacc output
+YYENV: adt
+{
+ yylval: ref YYSTYPE; # lexical value
+ yyval: YYSTYPE; # goto value
+ yyenv: YYETYPE; # useer environment
+ yynerrs: int; # number of errors
+ yyerrflag: int; # error recovery flag
+ yysys: Sys;
+ yystderr: ref Sys->FD;
+};
+
+yytokname(yyc: int): string
+{
+ if(yyc > 0 && yyc <= len yytoknames && yytoknames[yyc-1] != nil)
+ return yytoknames[yyc-1];
+ return "<"+string yyc+">";
+}
+
+yystatname(yys: int): string
+{
+ if(yys >= 0 && yys < len yystates && yystates[yys] != nil)
+ return yystates[yys];
+ return "<"+string yys+">\n";
+}
+
+yylex1(e: ref YYENV): int
+{
+ c, yychar : int;
+ yychar = yyelex(e);
+ if(yychar <= 0)
+ c = yytok1[0];
+ else if(yychar < len yytok1)
+ c = yytok1[yychar];
+ else if(yychar >= YYPRIVATE && yychar < YYPRIVATE+len yytok2)
+ c = yytok2[yychar-YYPRIVATE];
+ else{
+ n := len yytok3;
+ c = 0;
+ for(i := 0; i < n; i+=2) {
+ if(yytok3[i+0] == yychar) {
+ c = yytok3[i+1];
+ break;
+ }
+ }
+ if(c == 0)
+ c = yytok2[1]; # unknown char
+ }
+ if(yydebug >= 3)
+ e.yysys->fprint(e.yystderr, "lex %.4ux %s\n", yychar, yytokname(c));
+ return c;
+}
+
+YYS: adt
+{
+ yyv: YYSTYPE;
+ yys: int;
+};
+
+yyparse(): int
+{
+ return yyeparse(nil);
+}
+
+yyeparse(e: ref YYENV): int
+{
+ if(e == nil)
+ e = ref YYENV;
+ if(e.yylval == nil)
+ e.yylval = ref YYSTYPE;
+ if(e.yysys == nil) {
+ e.yysys = load Sys "$Sys";
+ e.yystderr = e.yysys->fildes(2);
+ }
+
+ yys := array[YYMAXDEPTH] of YYS;
+
+ yystate := 0;
+ yychar := -1;
+ e.yynerrs = 0;
+ e.yyerrflag = 0;
+ yyp := -1;
+ yyn := 0;
+
+yystack:
+ for(;;){
+ # put a state and value onto the stack
+ if(yydebug >= 4)
+ e.yysys->fprint(e.yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate));
+
+ yyp++;
+ if(yyp >= YYMAXDEPTH) {
+ yyerror(e, "yacc stack overflow");
+ yyn = 1;
+ break yystack;
+ }
+ yys[yyp].yys = yystate;
+ yys[yyp].yyv = e.yyval;
+
+ for(;;){
+ yyn = yypact[yystate];
+ if(yyn > YYFLAG) { # simple state
+ if(yychar < 0)
+ yychar = yylex1(e);
+ yyn += yychar;
+ if(yyn >= 0 && yyn < YYLAST) {
+ yyn = yyact[yyn];
+ if(yychk[yyn] == yychar) { # valid shift
+ yychar = -1;
+ yyp++;
+ if(yyp >= YYMAXDEPTH) {
+ yyerror(e, "yacc stack overflow");
+ yyn = 1;
+ break yystack;
+ }
+ yystate = yyn;
+ yys[yyp].yys = yystate;
+ yys[yyp].yyv = *e.yylval;
+ if(e.yyerrflag > 0)
+ e.yyerrflag--;
+ if(yydebug >= 4)
+ e.yysys->fprint(e.yystderr, "char %s in %s", yytokname(yychar), yystatname(yystate));
+ continue;
+ }
+ }
+ }
+
+ # default state action
+ yyn = yydef[yystate];
+ if(yyn == -2) {
+ if(yychar < 0)
+ yychar = yylex1(e);
+
+ # look through exception table
+ for(yyxi:=0;; yyxi+=2)
+ if(yyexca[yyxi] == -1 && yyexca[yyxi+1] == yystate)
+ break;
+ for(yyxi += 2;; yyxi += 2) {
+ yyn = yyexca[yyxi];
+ if(yyn < 0 || yyn == yychar)
+ break;
+ }
+ yyn = yyexca[yyxi+1];
+ if(yyn < 0){
+ yyn = 0;
+ break yystack;
+ }
+ }
+
+ if(yyn != 0)
+ break;
+
+ # error ... attempt to resume parsing
+ if(e.yyerrflag == 0) { # brand new error
+ yyerror(e, "syntax error");
+ e.yynerrs++;
+ if(yydebug >= 1) {
+ e.yysys->fprint(e.yystderr, "%s", yystatname(yystate));
+ e.yysys->fprint(e.yystderr, "saw %s\n", yytokname(yychar));
+ }
+ }
+
+ if(e.yyerrflag != 3) { # incompletely recovered error ... try again
+ e.yyerrflag = 3;
+
+ # find a state where "error" is a legal shift action
+ while(yyp >= 0) {
+ yyn = yypact[yys[yyp].yys] + YYERRCODE;
+ if(yyn >= 0 && yyn < YYLAST) {
+ yystate = yyact[yyn]; # simulate a shift of "error"
+ if(yychk[yystate] == YYERRCODE) {
+ yychar = -1;
+ continue yystack;
+ }
+ }
+
+ # the current yyp has no shift on "error", pop stack
+ if(yydebug >= 2)
+ e.yysys->fprint(e.yystderr, "error recovery pops state %d, uncovers %d\n",
+ yys[yyp].yys, yys[yyp-1].yys );
+ yyp--;
+ }
+ # there is no state on the stack with an error shift ... abort
+ yyn = 1;
+ break yystack;
+ }
+
+ # no shift yet; clobber input char
+ if(yydebug >= 2)
+ e.yysys->fprint(e.yystderr, "error recovery discards %s\n", yytokname(yychar));
+ if(yychar == YYEOFCODE) {
+ yyn = 1;
+ break yystack;
+ }
+ yychar = -1;
+ # try again in the same state
+ }
+
+ # reduction by production yyn
+ if(yydebug >= 2)
+ e.yysys->fprint(e.yystderr, "reduce %d in:\n\t%s", yyn, yystatname(yystate));
+
+ yypt := yyp;
+ yyp -= yyr2[yyn];
+# yyval = yys[yyp+1].yyv;
+ yym := yyn;
+
+ # consult goto table to find next state
+ yyn = yyr1[yyn];
+ yyg := yypgo[yyn];
+ yyj := yyg + yys[yyp].yys + 1;
+
+ if(yyj >= YYLAST || yychk[yystate=yyact[yyj]] != -yyn)
+ yystate = yyact[yyg];
+ case yym {
+
+4=>
+#line 63 "mash.y"
+{ yys[yypt-0].yyv.cmd.xeq(e.yyenv); }
+7=>
+#line 69 "mash.y"
+{ e.yyval.cmd = nil; }
+8=>
+#line 71 "mash.y"
+{ e.yyval.cmd = Cmd.cmd2(Cseq, yys[yypt-1].yyv.cmd, yys[yypt-0].yyv.cmd); }
+9=>
+#line 75 "mash.y"
+{ e.yyval.cmd = yys[yypt-1].yyv.cmd.mkcmd(e.yyenv, yys[yypt-0].yyv.flag); }
+10=>
+e.yyval.cmd = yys[yyp+1].yyv.cmd;
+11=>
+#line 80 "mash.y"
+{ e.yyval.cmd = Cmd.cmd2(Cseq, yys[yypt-1].yyv.cmd, yys[yypt-0].yyv.cmd.mkcmd(e.yyenv, 0)); }
+12=>
+e.yyval.cmd = yys[yyp+1].yyv.cmd;
+13=>
+e.yyval.cmd = yys[yyp+1].yyv.cmd;
+14=>
+#line 86 "mash.y"
+{ e.yyval.cmd = Cmd.cmd2(Cpipe, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd); }
+15=>
+#line 90 "mash.y"
+{ e.yyval.cmd = e.yyenv.mksimple(yys[yypt-0].yyv.items); }
+16=>
+#line 92 "mash.y"
+{
+ yys[yypt-0].yyv.cmd.words = e.yyenv.mklist(yys[yypt-1].yyv.items);
+ e.yyval.cmd = Cmd.cmd1w(Cdepend, yys[yypt-0].yyv.cmd, e.yyenv.mklist(yys[yypt-3].yyv.items));
+ }
+17=>
+#line 99 "mash.y"
+{ e.yyval.cmd = yys[yypt-0].yyv.cmd.cmde(Cgroup, yys[yypt-2].yyv.cmd, nil); }
+18=>
+#line 101 "mash.y"
+{ e.yyval.cmd = yys[yypt-0].yyv.cmd.cmde(Csubgroup, yys[yypt-2].yyv.cmd, nil); }
+19=>
+#line 103 "mash.y"
+{ e.yyval.cmd = Cmd.cmd1i(Cfor, yys[yypt-0].yyv.cmd, yys[yypt-4].yyv.item); e.yyval.cmd.words = lib->revitems(yys[yypt-2].yyv.items); }
+20=>
+#line 105 "mash.y"
+{ e.yyval.cmd = Cmd.cmd2(Cif, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd); }
+21=>
+#line 107 "mash.y"
+{ e.yyval.cmd = Cmd.cmd2(Cif, yys[yypt-4].yyv.cmd, Cmd.cmd2(Celse, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd)); }
+22=>
+#line 109 "mash.y"
+{ e.yyval.cmd = Cmd.cmd2(Cwhile, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd); }
+23=>
+#line 111 "mash.y"
+{ e.yyval.cmd = Cmd.cmd2(Ccase, yys[yypt-3].yyv.cmd, yys[yypt-1].yyv.cmd.rotcases()); }
+24=>
+#line 113 "mash.y"
+{ e.yyval.cmd = Cmd.cmdiw(Ceq, yys[yypt-2].yyv.item, yys[yypt-0].yyv.items); }
+25=>
+#line 115 "mash.y"
+{ e.yyval.cmd = Cmd.cmdiw(Cdefeq, yys[yypt-2].yyv.item, yys[yypt-0].yyv.items); }
+26=>
+#line 117 "mash.y"
+{ e.yyval.cmd = Cmd.cmd1i(Cfn, yys[yypt-0].yyv.cmd, yys[yypt-1].yyv.item); }
+27=>
+#line 119 "mash.y"
+{ e.yyval.cmd = Cmd.cmd1i(Crescue, yys[yypt-0].yyv.cmd, yys[yypt-1].yyv.item); }
+28=>
+#line 121 "mash.y"
+{
+ yys[yypt-0].yyv.cmd.item = yys[yypt-1].yyv.item;
+ e.yyval.cmd = Cmd.cmd1i(Crule, yys[yypt-0].yyv.cmd, yys[yypt-3].yyv.item);
+ }
+29=>
+#line 128 "mash.y"
+{ e.yyval.cmd = Cmd.cmd1(Clistgroup, yys[yypt-1].yyv.cmd); }
+30=>
+#line 130 "mash.y"
+{ e.yyval.cmd = Cmd.cmd1(Cgroup, yys[yypt-1].yyv.cmd); }
+31=>
+#line 134 "mash.y"
+{ e.yyval.cmd = Cmd.cmd1(Cnop, nil); }
+32=>
+e.yyval.cmd = yys[yyp+1].yyv.cmd;
+33=>
+#line 139 "mash.y"
+{ e.yyval.cmd = nil; }
+34=>
+#line 141 "mash.y"
+{ e.yyval.cmd = yys[yypt-1].yyv.cmd; }
+35=>
+#line 145 "mash.y"
+{ e.yyval.cmd = nil; }
+36=>
+#line 147 "mash.y"
+{ e.yyval.cmd = Cmd.cmd2(Ccases, yys[yypt-1].yyv.cmd, yys[yypt-0].yyv.cmd); }
+37=>
+#line 151 "mash.y"
+{ e.yyval.cmd = Cmd.cmd2(Cmatched, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd); }
+38=>
+#line 155 "mash.y"
+{ e.yyval.items = yys[yypt-0].yyv.item :: nil; }
+39=>
+#line 157 "mash.y"
+{ e.yyval.items = yys[yypt-0].yyv.item :: yys[yypt-1].yyv.items; }
+40=>
+e.yyval.item = yys[yyp+1].yyv.item;
+41=>
+e.yyval.item = yys[yyp+1].yyv.item;
+42=>
+e.yyval.item = yys[yyp+1].yyv.item;
+43=>
+e.yyval.item = yys[yyp+1].yyv.item;
+44=>
+#line 169 "mash.y"
+{ e.yyval.item = Item.itemc(Ibackq, yys[yypt-1].yyv.cmd); }
+45=>
+#line 171 "mash.y"
+{ e.yyval.item = Item.itemc(Iquote, yys[yypt-1].yyv.cmd); }
+46=>
+#line 173 "mash.y"
+{ e.yyval.item = Item.itemc(Iinpipe, yys[yypt-1].yyv.cmd); }
+47=>
+#line 175 "mash.y"
+{ e.yyval.item = Item.itemc(Ioutpipe, yys[yypt-1].yyv.cmd); }
+48=>
+e.yyval.item = yys[yyp+1].yyv.item;
+49=>
+#line 180 "mash.y"
+{ e.yyval.item = Item.item2(Icaret, yys[yypt-2].yyv.item, yys[yypt-0].yyv.item); }
+50=>
+#line 182 "mash.y"
+{ e.yyval.item = Item.itemc(Iexpr, yys[yypt-1].yyv.cmd); }
+51=>
+#line 186 "mash.y"
+{ e.yyval.item = yys[yypt-0].yyv.item.sword(e.yyenv); }
+52=>
+#line 190 "mash.y"
+{ e.yyval.items = nil; }
+53=>
+#line 192 "mash.y"
+{ e.yyval.items = yys[yypt-0].yyv.item :: yys[yypt-1].yyv.items; }
+54=>
+#line 196 "mash.y"
+{ e.yyval.cmd = ref Cmd; e.yyval.cmd.error = 0; }
+55=>
+#line 198 "mash.y"
+{ e.yyval.cmd = yys[yypt-1].yyv.cmd; yys[yypt-1].yyv.cmd.cmdio(e.yyenv, yys[yypt-0].yyv.item); }
+56=>
+#line 202 "mash.y"
+{ e.yyval.item = Item.itemr(Rin, yys[yypt-0].yyv.item); }
+57=>
+#line 204 "mash.y"
+{ e.yyval.item = Item.itemr(Rout, yys[yypt-0].yyv.item); }
+58=>
+#line 206 "mash.y"
+{ e.yyval.item = Item.itemr(Rappend, yys[yypt-0].yyv.item); }
+59=>
+#line 208 "mash.y"
+{ e.yyval.item = Item.itemr(Rinout, yys[yypt-0].yyv.item); }
+60=>
+#line 212 "mash.y"
+{ e.yyval.flag = 0; }
+61=>
+#line 214 "mash.y"
+{ e.yyval.flag = 0; }
+62=>
+#line 216 "mash.y"
+{ e.yyval.flag = 1; }
+63=>
+#line 220 "mash.y"
+{ e.yyval.cmd = Cmd.cmd1i(Cword, nil, yys[yypt-0].yyv.item); }
+64=>
+#line 222 "mash.y"
+{ e.yyval.cmd = Cmd.cmd1i(Cword, nil, yys[yypt-0].yyv.item); }
+65=>
+#line 224 "mash.y"
+{ e.yyval.cmd = yys[yypt-1].yyv.cmd; }
+66=>
+#line 226 "mash.y"
+{ e.yyval.cmd = Cmd.cmd2(Ccaret, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd); }
+67=>
+#line 228 "mash.y"
+{ e.yyval.cmd = Cmd.cmd1(Chd, yys[yypt-0].yyv.cmd); }
+68=>
+#line 230 "mash.y"
+{ e.yyval.cmd = Cmd.cmd1(Ctl, yys[yypt-0].yyv.cmd); }
+69=>
+#line 232 "mash.y"
+{ e.yyval.cmd = Cmd.cmd1(Clen, yys[yypt-0].yyv.cmd); }
+70=>
+#line 234 "mash.y"
+{ e.yyval.cmd = Cmd.cmd1(Cnot, yys[yypt-0].yyv.cmd); }
+71=>
+#line 236 "mash.y"
+{ e.yyval.cmd = Cmd.cmd2(Ccons, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd); }
+72=>
+#line 238 "mash.y"
+{ e.yyval.cmd = Cmd.cmd2(Ceqeq, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd); }
+73=>
+#line 240 "mash.y"
+{ e.yyval.cmd = Cmd.cmd2(Cnoteq, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd); }
+74=>
+#line 242 "mash.y"
+{ e.yyval.cmd = Cmd.cmd2(Cmatch, yys[yypt-2].yyv.cmd, yys[yypt-0].yyv.cmd); }
+ }
+ }
+
+ return yyn;
+}
diff --git a/appl/cmd/mash/mashparse.m b/appl/cmd/mash/mashparse.m
new file mode 100644
index 00000000..157c2f54
--- /dev/null
+++ b/appl/cmd/mash/mashparse.m
@@ -0,0 +1,56 @@
+Mashparse: module {
+
+ PATH: con "/dis/lib/mashparse.dis";
+
+ init: fn(l: Mashlib);
+ parse: fn(e: ref Mashlib->Env);
+
+ YYSTYPE: adt
+ {
+ cmd: ref Mashlib->Cmd;
+ item: ref Mashlib->Item;
+ items: list of ref Mashlib->Item;
+ flag: int;
+ };
+
+ YYETYPE: type ref Mashlib->Env;
+Lcase: con 57346;
+Lfor: con 57347;
+Lif: con 57348;
+Lwhile: con 57349;
+Loffparen: con 57350;
+Lelse: con 57351;
+Lpipe: con 57352;
+Leqeq: con 57353;
+Lmatch: con 57354;
+Lnoteq: con 57355;
+Lcons: con 57356;
+Lcaret: con 57357;
+Lnot: con 57358;
+Lhd: con 57359;
+Ltl: con 57360;
+Llen: con 57361;
+Lword: con 57362;
+Lbackq: con 57363;
+Lcolon: con 57364;
+Lcolonmatch: con 57365;
+Ldefeq: con 57366;
+Leq: con 57367;
+Lmatched: con 57368;
+Lquote: con 57369;
+Loncurly: con 57370;
+Lonparen: con 57371;
+Loffcurly: con 57372;
+Lat: con 57373;
+Lgreat: con 57374;
+Lgreatgreat: con 57375;
+Lless: con 57376;
+Llessgreat: con 57377;
+Lfn: con 57378;
+Lin: con 57379;
+Lrescue: con 57380;
+Land: con 57381;
+Leof: con 57382;
+Lsemi: con 57383;
+Lerror: con 57384;
+};
diff --git a/appl/cmd/mash/misc.b b/appl/cmd/mash/misc.b
new file mode 100644
index 00000000..749f8be2
--- /dev/null
+++ b/appl/cmd/mash/misc.b
@@ -0,0 +1,313 @@
+#
+# Miscellaneous routines.
+#
+
+Cmd.cmd1(op: int, l: ref Cmd): ref Cmd
+{
+ return ref Cmd(op, nil, l, nil, nil, nil, nil, 0);
+}
+
+Cmd.cmd2(op: int, l, r: ref Cmd): ref Cmd
+{
+ return ref Cmd(op, nil, l, r, nil, nil, nil, 0);
+}
+
+Cmd.cmd1i(op: int, l: ref Cmd, i: ref Item): ref Cmd
+{
+ return ref Cmd(op, nil, l, nil, i, nil, nil, 0);
+}
+
+Cmd.cmd1w(op: int, l: ref Cmd, w: list of ref Item): ref Cmd
+{
+ return ref Cmd(op, w, l, nil, nil, nil, nil, 0);
+}
+
+Cmd.cmde(c: self ref Cmd, op: int, l, r: ref Cmd): ref Cmd
+{
+ c.op = op;
+ c.left = l;
+ c.right = r;
+ return c;
+}
+
+Cmd.cmdiw(op: int, i: ref Item, w: list of ref Item): ref Cmd
+{
+ return ref Cmd(op, revitems(w), nil, nil, i, nil, nil, 0);
+}
+
+Pin, Pout: con 1 << iota;
+
+rdmap := array[] of
+{
+ Rin => Pin,
+ Rout or Rappend => Pout,
+ Rinout => Pin | Pout,
+};
+
+rdsymbs := array[] of
+{
+ Rin => "<",
+ Rout => ">",
+ Rappend => ">>",
+ Rinout => "<>",
+};
+
+ionames := array[] of
+{
+ Pin => "input",
+ Pout => "ouput",
+ Pin | Pout => "input/output",
+};
+
+#
+# Check a pipeline for ambiguities.
+#
+Cmd.checkpipe(c: self ref Cmd, e: ref Env, f: int): int
+{
+ if (c.error)
+ return 0;
+ if (c.op == Cpipe) {
+ if (!c.left.checkpipe(e, f | Pout))
+ return 0;
+ if (!c.right.checkpipe(e, f | Pin))
+ return 0;
+ }
+ if (f) {
+ t := 0;
+ for (l := c.redirs; l != nil; l = tl l)
+ t |= rdmap[(hd l).op];
+ f &= t;
+ if (f) {
+ e.report(sys->sprint("%s redirection conflicts with pipe", ionames[f]));
+ return 0;
+ }
+ }
+ return 1;
+}
+
+#
+# Update a command with another redirection.
+#
+Cmd.cmdio(c: self ref Cmd, e: ref Env, i: ref Item)
+{
+ f := 0;
+ for (l := c.redirs; l != nil; l = tl l)
+ f |= rdmap[(hd l).op];
+ r := i.redir;
+ f &= rdmap[r.op];
+ if (f != 0) {
+ e.report(sys->sprint("repeat %s redirection", ionames[f]));
+ c.error = 1;
+ }
+ c.redirs = r :: c.redirs;
+}
+
+#
+# Make a basic command.
+#
+Cmd.mkcmd(c: self ref Cmd, e: ref Env, async: int): ref Cmd
+{
+ if (!c.checkpipe(e, 0))
+ return nil;
+ if (async)
+ return ref Cmd(Casync, nil, c, nil, nil, nil, nil, 0);
+ else
+ return c;
+}
+
+#
+# Rotate parse tree of cases.
+#
+Cmd.rotcases(c: self ref Cmd): ref Cmd
+{
+ l := c;
+ c = nil;
+ while (l != nil) {
+ t := l.right;
+ l.right = c;
+ c = l;
+ l = l.left;
+ c.left = t;
+ }
+ return c;
+}
+
+Item.item1(op: int, l: ref Item): ref Item
+{
+ return ref Item(op, nil, l, nil, nil, nil);
+}
+
+Item.item2(op: int, l, r: ref Item): ref Item
+{
+ return ref Item(op, nil, l, r, nil, nil);
+}
+
+Item.itemc(op: int, c: ref Cmd): ref Item
+{
+ return ref Item(op, nil, nil, nil, c, nil);
+}
+
+#
+# Make an item from a list of strings.
+#
+Item.iteml(l: list of string): ref Item
+{
+ if (l != nil && tl l == nil)
+ return Item.itemw(hd l);
+ r: list of string;
+ while (l != nil) {
+ r = (hd l) :: r;
+ l = tl l;
+ }
+ c := ref Cmd;
+ c.op = Clist;
+ c.value = revstrs(r);
+ return Item.itemc(Iexpr, c);
+}
+
+Item.itemr(op: int, i: ref Item): ref Item
+{
+ return ref Item(Iredir, nil, nil, nil, nil, ref Redir(op, i));
+}
+
+qword: Word = (nil, Wquoted, (0, nil));
+
+Item.itemw(s: string): ref Item
+{
+ w := ref qword;
+ w.text = s;
+ return ref Item(Iword, w, nil, nil, nil, nil);
+}
+
+revitems(l: list of ref Item): list of ref Item
+{
+ r: list of ref Item;
+ while (l != nil) {
+ r = (hd l) :: r;
+ l = tl l;
+ }
+ return r;
+}
+
+revstrs(l: list of string): list of string
+{
+ r: list of string;
+ while (l != nil) {
+ r = (hd l) :: r;
+ l = tl l;
+ }
+ return r;
+}
+
+prepend(l: list of string, r: list of string): list of string
+{
+ while (r != nil) {
+ l = (hd r) :: l;
+ r = tl r;
+ }
+ return l;
+}
+
+concat(l: list of string): string
+{
+ s := hd l;
+ for (;;) {
+ l = tl l;
+ if (l == nil)
+ return s;
+ s += " ";
+ s += hd l;
+ }
+}
+
+#
+# Make an item list, no redirections allowed.
+#
+Env.mklist(e: self ref Env, l: list of ref Item): list of ref Item
+{
+ r: list of ref Item;
+ while (l != nil) {
+ i := hd l;
+ if (i.op == Iredir)
+ e.report("redirection in list");
+ else
+ r = i :: r;
+ l = tl l;
+ }
+ return r;
+}
+
+#
+# Make a simple command.
+#
+Env.mksimple(e: self ref Env, l: list of ref Item): ref Cmd
+{
+ r: list of ref Item;
+ c := ref Cmd;
+ c.op = Csimple;
+ c.error = 0;
+ while (l != nil) {
+ i := hd l;
+ if (i.op == Iredir)
+ c.cmdio(e, i);
+ else
+ r = i :: r;
+ l = tl l;
+ }
+ c.words = r;
+ return c;
+}
+
+Env.diag(e: self ref Env, s: string): string
+{
+ return where(e) + s;
+}
+
+Env.usage(e: self ref Env, s: string)
+{
+ e.report("usage: " + s);
+}
+
+Env.report(e: self ref Env, s: string)
+{
+ sys->fprint(e.stderr, "%s\n", e.diag(s));
+ if (e.flags & ERaise)
+ exits("error");
+}
+
+Env.error(e: self ref Env, s: string)
+{
+ e.report(s);
+ cleanup();
+}
+
+panic(s: string)
+{
+ raise "panic: " + s;
+}
+
+prprompt(n: int)
+{
+ case n {
+ 0 =>
+ sys->print("%s", prompt);
+ 1 =>
+ sys->print("%s", contin);
+ }
+}
+
+Env.couldnot(e: self ref Env, what, who: string)
+{
+ sys->fprint(e.stderr, "could not %s %s: %r\n", what, who);
+ exits("system error");
+}
+
+cleanup()
+{
+ exit;
+}
+
+exits(s: string)
+{
+ raise "fail: mash " + s;
+}
diff --git a/appl/cmd/mash/mkfile b/appl/cmd/mash/mkfile
new file mode 100644
index 00000000..942f7b38
--- /dev/null
+++ b/appl/cmd/mash/mkfile
@@ -0,0 +1,78 @@
+<../../../mkconfig
+
+TARG= mash.dis\
+ mashlib.dis\
+ mashparse.dis\
+ builtins.dis\
+ history.dis\
+ make.dis\
+
+INS= $ROOT/dis/mash.dis\
+ $ROOT/dis/lib/mashlib.dis\
+ $ROOT/dis/lib/mashparse.dis\
+ $ROOT/dis/lib/mash/builtins.dis\
+ $ROOT/dis/lib/mash/history.dis\
+ $ROOT/dis/lib/mash/make.dis\
+
+MODULES=\
+ mash.m\
+ mashparse.m\
+
+SYSMODULES=\
+ bufio.m\
+ draw.m\
+ filepat.m\
+ hash.m\
+ regex.m\
+ sh.m\
+ string.m\
+ sys.m\
+
+LIBSRC=\
+ depends.b\
+ dump.b\
+ exec.b\
+ expr.b\
+ lex.b\
+ misc.b\
+ serve.b\
+ symb.b\
+ xeq.b\
+
+all:V: $TARG
+
+install:V: $INS
+
+nuke:V: clean
+ rm -f $INS
+
+clean:V:
+ rm -f *.dis *.sbl
+
+uninstall:V:
+ rm -f $INS
+
+MODDIR=$ROOT/module
+SYS_MODULE=${SYSMODULES:%=$MODDIR/%}
+LIMBOFLAGS=-I$MODDIR
+
+$ROOT/dis/mash.dis: mash.dis
+ rm -f $ROOT/dis/mash.dis && cp mash.dis $ROOT/dis/mash.dis
+
+$ROOT/dis/lib/mashlib.dis: mashlib.dis
+ rm -f $ROOT/dis/mashlib.dis && cp mashlib.dis $ROOT/dis/lib/mashlib.dis
+
+$ROOT/dis/lib/mashparse.dis: mashparse.dis
+ rm -f $ROOT/dis/mashparse.dis && cp mashparse.dis $ROOT/dis/lib/mashparse.dis
+
+$ROOT/dis/lib/mash/%.dis: %.dis
+ rm -f $ROOT/dis/$stem.dis && cp $stem.dis $ROOT/dis/lib/mash/$stem.dis
+
+%.dis: $MODULES $SYS_MODULE
+mashlib.dis: $LIBSRC
+
+%.dis: %.b
+ limbo $LIMBOFLAGS -gw $stem.b
+
+%.s: %.b
+ limbo $LIMBOFLAGS -w -G -S $stem.b
diff --git a/appl/cmd/mash/serve.b b/appl/cmd/mash/serve.b
new file mode 100644
index 00000000..e293a8f4
--- /dev/null
+++ b/appl/cmd/mash/serve.b
@@ -0,0 +1,154 @@
+#
+# This should be called by spawned (persistent) threads.
+# It arranges for them to be killed at the end of the day.
+#
+reap()
+{
+ if (pidchan == nil) {
+ pidchan = chan of int;
+ spawn zombie();
+ }
+ pidchan <-= sys->pctl(0, nil);
+}
+
+#
+# This thread records spawned threads and kills them.
+#
+zombie()
+{
+ pids := array[10] of int;
+ pidx := 0;
+ for (;;) {
+ pid := <- pidchan;
+ if (pid == PIDEXIT) {
+ for (i := 0; i < pidx; i++)
+ kill(pids[i]);
+ exit;
+ }
+ if (pidx == len pids) {
+ n := pidx * 3 / 2;
+ a := array[n] of int;
+ a[:] = pids;
+ pids = a;
+ }
+ pids[pidx++] = pid;
+ }
+}
+
+#
+# Kill a thread.
+#
+kill(pid: int)
+{
+ fd := sys->open("#p/" + string pid + "/ctl", sys->OWRITE);
+ if (fd != nil)
+ sys->fprint(fd, "kill");
+}
+
+#
+# Exit top level, killing spawned threads.
+#
+exitmash()
+{
+ if (pidchan != nil)
+ pidchan <-= PIDEXIT;
+ exit;
+}
+
+#
+# Slice a buffer if needed.
+#
+restrict(buff: array of byte, count: int): array of byte
+{
+ if (count < len buff)
+ return buff[:count];
+ else
+ return buff;
+}
+
+#
+# Serve mash console reads. Favours other programs
+# ahead of the input loop.
+#
+serve_read(c: ref Sys->FileIO, sync: chan of int)
+{
+ s: string;
+ in := sys->fildes(0);
+ sys->pctl(Sys->NEWFD, in.fd :: nil);
+ sync <-= 0;
+ reap();
+ buff := array[Sys->ATOMICIO] of byte;
+outer: for (;;) {
+ n := sys->read(in, buff, len buff);
+ if (n < 0) {
+ n = 0;
+ s = errstr();
+ } else
+ s = nil;
+ b := buff[:n];
+ alt {
+ (off, count, fid, rc) := <-c.read =>
+ if (rc == nil)
+ break;
+ rc <-= (restrict(b, count), s);
+ continue outer;
+ * =>
+ ;
+ }
+ inner: for (;;) {
+ alt {
+ (off, count, fid, rc) := <-c.read =>
+ if (rc == nil)
+ continue inner;
+ rc <-= (restrict(b, count), s);
+ inchan <-= b =>
+ ;
+ }
+ break;
+ }
+ }
+}
+
+#
+# Serve mash console writes.
+#
+serve_write(c: ref Sys->FileIO, sync: chan of int)
+{
+ out := sys->fildes(1);
+ sys->pctl(Sys->NEWFD, out.fd :: nil);
+ sync <-= 0;
+ reap();
+ for (;;) {
+ (off, data, fid, wc) := <-c.write;
+ if (wc == nil)
+ continue;
+ if (sys->write(out, data, len data) < 0)
+ wc <-= (0, errstr());
+ else
+ wc <-= (len data, nil);
+ }
+}
+
+#
+# Begin serving the mash console.
+#
+Env.serve(e: self ref Env)
+{
+ if (servechan != nil)
+ return;
+ (s, c) := e.servefile(nil);
+ inchan = chan of array of byte;
+ servechan = chan of array of byte;
+ sync := chan of int;
+ spawn serve_read(c, sync);
+ spawn serve_write(c, sync);
+ <-sync;
+ <-sync;
+ if (sys->bind(s, CONSOLE, Sys->MREPL) < 0)
+ e.couldnot("bind", CONSOLE);
+ sys->pctl(Sys->NEWFD, nil);
+ e.in = sys->open(CONSOLE, sys->OREAD | sys->ORCLOSE);
+ e.out = sys->open(CONSOLE, sys->OWRITE);
+ e.stderr = sys->open(CONSOLE, sys->OWRITE);
+ e.wait = nil;
+}
diff --git a/appl/cmd/mash/symb.b b/appl/cmd/mash/symb.b
new file mode 100644
index 00000000..8d317b37
--- /dev/null
+++ b/appl/cmd/mash/symb.b
@@ -0,0 +1,265 @@
+#
+# Symbol table routines. A symbol table becomes copy-on-write
+# when it is cloned. The first modification will copy the hash table.
+# Every list is then copied on first modification.
+#
+
+#
+# Copy a hash list.
+#
+cpsymbs(l: list of ref Symb): list of ref Symb
+{
+ r: list of ref Symb;
+ while (l != nil) {
+ r = (ref *hd l) :: r;
+ l = tl l;
+ }
+ return r;
+}
+
+#
+# New symbol table.
+#
+Stab.new(): ref Stab
+{
+ return ref Stab(array[SHASH] of list of ref Symb, 0, 0);
+}
+
+#
+# Clone a symbol table. Copy Stab and mark contents copy-on-write.
+#
+Stab.clone(t: self ref Stab): ref Stab
+{
+ t.copy = 1;
+ t.wmask = SMASK;
+ return ref *t;
+}
+
+#
+# Update symbol table entry, or add new entry.
+#
+Stab.update(t: self ref Stab, s: string, tag: int, v: list of string, f: ref Cmd, b: Mashbuiltin): ref Symb
+{
+ if (t.copy) {
+ a := array[SHASH] of list of ref Symb;
+ a[:] = t.tab[:];
+ t.tab = a;
+ t.copy = 0;
+ }
+ x := hash->fun1(s, SHASH);
+ l := t.tab[x];
+ if (t.wmask & (1 << x)) {
+ l = cpsymbs(l);
+ t.tab[x] = l;
+ t.wmask &= ~(1 << x);
+ }
+ r := l;
+ while (r != nil) {
+ h := hd r;
+ if (h.name == s) {
+ case tag {
+ Svalue =>
+ h.value = v;
+ Sfunc =>
+ h.func = f;
+ Sbuiltin =>
+ h.builtin = b;
+ }
+ return h;
+ }
+ r = tl r;
+ }
+ n := ref Symb(s, v, f, b, 0);
+ t.tab[x] = n :: l;
+ return n;
+}
+
+#
+# Make a list of a symbol table's contents.
+#
+Stab.all(t: self ref Stab): list of ref Symb
+{
+ r: list of ref Symb;
+ for (i := 0; i < SHASH; i++) {
+ for (l := t.tab[i]; l != nil; l = tl l)
+ r = (ref *hd l) :: r;
+ }
+ return r;
+}
+
+#
+# Assign a list of strings to a variable. The distinguished value
+# "empty" is used to distinguish nil value from undefined.
+#
+Stab.assign(t: self ref Stab, s: string, v: list of string)
+{
+ if (v == nil)
+ v = empty;
+ t.update(s, Svalue, v, nil, nil);
+}
+
+#
+# Define a builtin.
+#
+Stab.defbuiltin(t: self ref Stab, s: string, b: Mashbuiltin)
+{
+ t.update(s, Sbuiltin, nil, nil, b);
+}
+
+#
+# Define a function.
+#
+Stab.define(t: self ref Stab, s: string, f: ref Cmd)
+{
+ t.update(s, Sfunc, nil, f, nil);
+}
+
+#
+# Symbol table lookup.
+#
+Stab.find(t: self ref Stab, s: string): ref Symb
+{
+ l := t.tab[hash->fun1(s, SHASH)];
+ while (l != nil) {
+ h := hd l;
+ if (h.name == s)
+ return h;
+ l = tl l;
+ }
+ return nil;
+}
+
+#
+# Function lookup.
+#
+Stab.func(t: self ref Stab, s: string): ref Cmd
+{
+ v := t.find(s);
+ if (v == nil)
+ return nil;
+ return v.func;
+}
+
+#
+# New environment.
+#
+Env.new(): ref Env
+{
+ return ref Env(Stab.new(), nil, ETop, nil, nil, nil, nil, nil, nil, 0);
+}
+
+#
+# Clone environment. No longer top-level or interactive.
+#
+Env.clone(e: self ref Env): ref Env
+{
+ e = e.copy();
+ e.flags &= ~(ETop | EInter);
+ e.global = e.global.clone();
+ if (e.local != nil)
+ e.local = e.local.clone();
+ return e;
+}
+
+#
+# Copy environment.
+#
+Env.copy(e: self ref Env): ref Env
+{
+ return ref *e;
+}
+
+#
+# Fetch $n argument.
+#
+Env.arg(e: self ref Env, s: string): string
+{
+ n := int s;
+ if (e.args == nil || n >= len e.args)
+ return "$" + s;
+ else
+ return e.args[n];
+}
+
+#
+# Lookup builtin.
+#
+Env.builtin(e: self ref Env, s: string): Mashbuiltin
+{
+ v := e.global.find(s);
+ if (v == nil)
+ return nil;
+ return v.builtin;
+}
+
+#
+# Define a builtin.
+#
+Env.defbuiltin(e: self ref Env, s: string, b: Mashbuiltin)
+{
+ e.global.defbuiltin(s, b);
+}
+
+#
+# Define a function.
+#
+Env.define(e: self ref Env, s: string, f: ref Cmd)
+{
+ e.global.define(s, f);
+}
+
+#
+# Value of a shell variable (check locals then globals).
+#
+Env.dollar(e: self ref Env, s: string): ref Symb
+{
+ if (e.local != nil) {
+ l := e.local.find(s);
+ if (l != nil && l.value != nil)
+ return l;
+ }
+ g := e.global.find(s);
+ if (g != nil && g.value != nil)
+ return g;
+ return nil;
+}
+
+#
+# Lookup a function.
+#
+Env.func(e: self ref Env, s: string): ref Cmd
+{
+ v := e.global.find(s);
+ if (v == nil)
+ return nil;
+ return v.func;
+}
+
+#
+# Local assignment.
+#
+Env.let(e: self ref Env, s: string, v: list of string)
+{
+ if (e.local == nil)
+ e.local = Stab.new();
+ e.local.assign(s, v);
+}
+
+#
+# Assignment. Update local or define global.
+#
+Env.set(e: self ref Env, s: string, v: list of string)
+{
+ if (e.local != nil && e.local.find(s) != nil)
+ e.local.assign(s, v);
+ else
+ e.global.assign(s, v);
+}
+
+#
+# Report undefined.
+#
+Env.undefined(e: self ref Env, s: string)
+{
+ e.report(s + ": undefined");
+}
diff --git a/appl/cmd/mash/tk.b b/appl/cmd/mash/tk.b
new file mode 100644
index 00000000..8b0f4f1a
--- /dev/null
+++ b/appl/cmd/mash/tk.b
@@ -0,0 +1,603 @@
+implement Mashbuiltin;
+
+#
+# "tk" builtin.
+#
+# tk clear - clears the text frame
+# tk def button name value
+# tk def ibutton name value image
+# tk def menu name
+# tk def item menu name value
+# tk dialog title mesg default label ...
+# tk dump - print commands to reconstruct toolbar
+# tk dump name ...
+# tk env - update tk execution env
+# tk file title dir pattern ...
+# tk geom
+# tk layout name ...
+# tk notice message
+# tk sel - print selection
+# tk sget - print snarf
+# tk sput string - put snarf
+# tk string mesg - get string
+# tk taskbar string
+# tk text - print window text
+#
+
+include "mash.m";
+include "mashparse.m";
+include "wmlib.m";
+include "dialog.m";
+include "selectfile.m";
+
+mashlib: Mashlib;
+wmlib: Wmlib;
+dialog: Dialog;
+selectfile: Selectfile;
+
+Env, Stab, Symb: import mashlib;
+sys, bufio, tk: import mashlib;
+gtop, gctxt, ident: import mashlib;
+
+Iobuf: import bufio;
+
+tkitems: ref Stab;
+tklayout: list of string;
+tkenv: ref Env;
+tkserving: int = 0;
+
+Cbutton, Cibutton, Cmenu: con Cprivate + iota;
+
+Cmark: con 3;
+BUTT: con ".b.";
+
+#
+# Interface to catch the use as a command.
+#
+init(nil: ref Draw->Context, args: list of string)
+{
+ raise "fail: " + hd args + " not loaded";
+}
+
+#
+# Used by whatis.
+#
+name(): string
+{
+ return "tk";
+}
+
+#
+# Install command and initialize state.
+#
+mashinit(nil: list of string, lib: Mashlib, this: Mashbuiltin, e: ref Env)
+{
+ mashlib = lib;
+ if (gctxt == nil) {
+ e.report("tk: no graphics context");
+ return;
+ }
+ if (gtop == nil) {
+ e.report("tk: not run from wmsh");
+ return;
+ }
+ wmlib = load Wmlib Wmlib->PATH;
+ if (wmlib == nil) {
+ e.report(sys->sprint("tk: could not load %s: %r", Wmlib->PATH));
+ return;
+ }
+ dialog = load Dialog Dialog->PATH;
+ if (dialog == nil) {
+ e.report(sys->sprint("tk: could not load %s: %r", Dialog->PATH));
+ return;
+ }
+ selectfile = load Selectfile Selectfile->PATH;
+ if (selectfile == nil) {
+ e.report(sys->sprint("tk: could not load %s: %r", Selectfile->PATH));
+ return;
+ }
+ wmlib->init();
+ dialog->init();
+ selectfile->init();
+ e.defbuiltin("tk", this);
+ tkitems = Stab.new();
+}
+
+#
+# Execute the "tk" builtin.
+#
+mashcmd(e: ref Env, l: list of string)
+{
+ # must lock
+ l = tl l;
+ if (l == nil)
+ return;
+ s := hd l;
+ l = tl l;
+ case s {
+ "clear" =>
+ if (l != nil) {
+ e.usage("tk clear");
+ return;
+ }
+ clear(e);
+ "def" =>
+ define(e, l);
+ "dialog" =>
+ if (len l < 4) {
+ e.usage("tk dialog title mesg default label ...");
+ return;
+ }
+ dodialog(e, l);
+ "dump" =>
+ dump(e, l);
+ "env" =>
+ if (l != nil) {
+ e.usage("tk env");
+ return;
+ }
+ tkenv = e.clone();
+ tkenv.flags |= mashlib->ETop;
+ "file" =>
+ if (len l < 3) {
+ e.usage("tk file title dir pattern ...");
+ return;
+ }
+ dofile(e, hd l, hd tl l, tl tl l);
+ "geom" =>
+ if (l != nil) {
+ e.usage("tk geom");
+ return;
+ }
+ e.output(wmlib->geom(gtop));
+ "layout" =>
+ layout(e, l);
+ "notice" =>
+ if (len l != 1) {
+ e.usage("tk notice message");
+ return;
+ }
+ notice(hd l);
+ "sel" =>
+ if (l != nil) {
+ e.usage("tk sel");
+ return;
+ }
+ sel(e);
+ "sget" =>
+ if (l != nil) {
+ e.usage("tk sget");
+ return;
+ }
+ e.output(wmlib->snarfget());
+ "sput" =>
+ if (len l != 1) {
+ e.usage("tk sput string");
+ return;
+ }
+ wmlib->snarfput(hd l);
+ "string" =>
+ if (len l != 1) {
+ e.usage("tk string mesg");
+ return;
+ }
+ e.output(dialog->getstring(gctxt, gtop.image, hd l));
+ focus(e);
+ "taskbar" =>
+ if (len l != 1) {
+ e.usage("tk taskbar string");
+ return;
+ }
+ e.output(wmlib->taskbar(gtop, hd l));
+ "text" =>
+ if (l != nil) {
+ e.usage("tk text");
+ return;
+ }
+ text(e);
+ * =>
+ e.report(sys->sprint("tk: unknown command: %s", s));
+ }
+}
+
+#
+# Execute tk command and check for error.
+#
+tkcmd(e: ref Env, s: string): string
+{
+ if (e != nil && (e.flags & mashlib->EDumping))
+ sys->fprint(e.stderr, "+ %s\n", s);
+ r := tk->cmd(gtop, s);
+ if (r != nil && r[0] == '!' && e != nil)
+ sys->fprint(e.stderr, "tk: %s\n\tcommand was %s\n", r[1:], s);
+ return r;
+}
+
+focus(e: ref Env)
+{
+ tkcmd(e, "focus .ft.t");
+}
+
+#
+# Serve loop.
+#
+tkserve(mash: chan of string)
+{
+ mashlib->reap();
+ for (;;) {
+ cmd := <-mash;
+ if (mashlib->servechan != nil && len cmd > 1) {
+ cmd[len cmd - 1] = '\n';
+ mashlib->servechan <-= array of byte cmd[1:];
+ }
+ }
+}
+
+notname(e: ref Env, s: string)
+{
+ e.report(sys->sprint("tk: %s: malformed name", s));
+}
+
+#
+# Define a button, menu or item.
+#
+define(e: ref Env, l: list of string)
+{
+ if (l == nil) {
+ e.usage("tk def definition");
+ return;
+ }
+ s := hd l;
+ l = tl l;
+ case s {
+ "button" =>
+ if (len l != 2) {
+ e.usage("tk def button name value");
+ return;
+ }
+ s = hd l;
+ if (!ident(s)) {
+ notname(e, s);
+ return;
+ }
+ i := tkitems.update(s, Svalue, tl l, nil, nil);
+ i.tag = Cbutton;
+ "ibutton" =>
+ if (len l != 3) {
+ e.usage("tk def ibutton name value path");
+ return;
+ }
+ s = hd l;
+ if (!ident(s)) {
+ notname(e, s);
+ return;
+ }
+ i := tkitems.update(s, Svalue, tl l, nil, nil);
+ i.tag = Cibutton;
+ "menu" =>
+ if (len l != 1) {
+ e.usage("tk def menu name");
+ return;
+ }
+ s = hd l;
+ if (!ident(s)) {
+ notname(e, s);
+ return;
+ }
+ i := tkitems.update(s, Svalue, nil, nil, nil);
+ i.tag = Cmenu;
+ "item" =>
+ if (len l != 3) {
+ e.usage("tk def item menu name value");
+ return;
+ }
+ s = hd l;
+ i := tkitems.find(s);
+ if (i == nil || i.tag != Cmenu) {
+ e.report(s + ": not a menu");
+ return;
+ }
+ l = tl l;
+ i.value = updateitem(i.value, hd l, hd tl l);
+ * =>
+ e.report("tk: " + s + ": unknown command");
+ }
+}
+
+#
+# Update a menu item.
+#
+updateitem(l: list of string, c, v: string): list of string
+{
+ r: list of string;
+ while (l != nil) {
+ w := hd l;
+ l = tl l;
+ d := hd l;
+ l = tl l;
+ if (d == c) {
+ r = c :: v :: r;
+ c = nil;
+ } else
+ r = d :: w :: r;
+ }
+ if (c != nil)
+ r = c :: v :: r;
+ return mashlib->revstrs(r);
+}
+
+items(e: ref Env, l: list of string): list of ref Symb
+{
+ r: list of ref Symb;
+ while (l != nil) {
+ i := tkitems.find(hd l);
+ if (i == nil) {
+ e.report(hd l + ": not an item");
+ return nil;
+ }
+ r = i :: r;
+ l = tl l;
+ }
+ return r;
+}
+
+deleteall(e: ref Env, l: list of string)
+{
+ while (l != nil) {
+ tkcmd(e, "destroy " + BUTT + hd l);
+ l = tl l;
+ }
+}
+
+sendcmd(c: string): string
+{
+ return tk->quote("send mash " + tk->quote(c));
+}
+
+addbutton(e: ref Env, w, t, c: string)
+{
+ tkcmd(e, sys->sprint("button %s%s -%s %s -command %s", BUTT, t, w, t, sendcmd(c)));
+}
+
+addimage(e: ref Env, t, f: string)
+{
+ r := tkcmd(nil, sys->sprint("image create bitmap %s -file %s.bit -maskfile %s.mask", t, f, f));
+ if (r != nil && r[0] == '!')
+ tkcmd(e, sys->sprint("image create bitmap %s -file %s.bit", t, f));
+}
+
+additem(e: ref Env, s: ref Symb)
+{
+ case s.tag {
+ Cbutton =>
+ addbutton(e, "text", s.name, hd s.value);
+ Cibutton =>
+ addimage(e, s.name, hd tl s.value);
+ addbutton(e, "image", s.name, hd s.value);
+ Cmenu =>
+ t := s.name;
+ tkcmd(e, sys->sprint("menubutton %s%s -text %s -menu %s%s.menu -underline -1", BUTT, t, t, BUTT,t));
+ t += ".menu";
+ tkcmd(e, "menu " + BUTT + t);
+ t = BUTT + t;
+ l := s.value;
+ while (l != nil) {
+ v := sendcmd(hd l);
+ l = tl l;
+ c := tk->quote(hd l);
+ l = tl l;
+ tkcmd(e, sys->sprint("%s add command -label %s -command %s", t, c, v));
+ }
+ }
+}
+
+pack(e: ref Env, l: list of string)
+{
+ s := "pack";
+ while (l != nil) {
+ s += sys->sprint(" %s%s", BUTT, hd l);
+ l = tl l;
+ }
+ s += " -side left";
+ tkcmd(e, s);
+}
+
+propagate(e: ref Env)
+{
+ tkcmd(e, "pack propagate . 0");
+ tkcmd(e, "update");
+}
+
+unmark(r: list of ref Symb)
+{
+ while (r != nil) {
+ s := hd r;
+ case s.tag {
+ Cbutton + Cmark or Cibutton + Cmark or Cmenu + Cmark =>
+ s.tag -= Cmark;
+ }
+ r = tl r;
+ }
+}
+
+#
+# Check that the layout tags are unique.
+#
+unique(e: ref Env, r: list of ref Symb): int
+{
+ u := 1;
+loop:
+ for (l := r; l != nil; l = tl l) {
+ s := hd l;
+ case s.tag {
+ Cbutton + Cmark or Cibutton + Cmark or Cmenu + Cmark =>
+ e.report(sys->sprint("layout: tag %s repeated", s.name));
+ u = 0;
+ break loop;
+ Cbutton or Cibutton or Cmenu =>
+ s.tag += Cmark;
+ }
+ }
+ unmark(r);
+ return u;
+}
+
+#
+# Update the button bar layout and the environment.
+# Maybe spawn the server.
+#
+layout(e: ref Env, l: list of string)
+{
+ r := items(e, l);
+ if (r == nil && l != nil)
+ return;
+ if (!unique(e, r))
+ return;
+ if (tklayout != nil)
+ deleteall(e, tklayout);
+ n := len r;
+ a := array[n] of ref Symb;
+ while (--n >= 0) {
+ a[n] = hd r;
+ r = tl r;
+ }
+ n = len a;
+ for (i := 0; i < n; i++)
+ additem(e, a[i]);
+ pack(e, l);
+ propagate(e);
+ tklayout = l;
+ tkenv = e.clone();
+ tkenv.flags |= mashlib->ETop;
+ if (!tkserving) {
+ tkserving = 1;
+ mash := chan of string;
+ tk->namechan(gtop, mash, "mash");
+ spawn tkserve(mash);
+ mashlib->startserve = 1;
+ }
+}
+
+dumpbutton(out: ref Iobuf, w: string, s: ref Symb)
+{
+ out.puts(sys->sprint("tk def %s %s %s", w, s.name, mashlib->quote(hd s.value)));
+ if (s.tag == Cibutton)
+ out.puts(sys->sprint(" %s", mashlib->quote(hd tl s.value)));
+ out.puts(";\n");
+}
+
+#
+# Print commands to reconstruct toolbar.
+#
+dump(e: ref Env, l: list of string)
+{
+ r: list of ref Symb;
+ if (l != nil)
+ r = items(e, l);
+ else
+ r = tkitems.all();
+ out := e.outfile();
+ if (out == nil)
+ return;
+ while (r != nil) {
+ s := hd r;
+ case s.tag {
+ Cbutton =>
+ dumpbutton(out, "button", s);
+ Cibutton =>
+ dumpbutton(out, "ibutton", s);
+ Cmenu =>
+ t := s.name;
+ out.puts(sys->sprint("tk def menu %s;\n", t));
+ i := s.value;
+ while (i != nil) {
+ v := hd i;
+ i = tl i;
+ c := hd i;
+ i = tl i;
+ out.puts(sys->sprint("tk def item %s %s %s;\n", t, c, mashlib->quote(v)));
+ }
+ }
+ r = tl r;
+ }
+ if (l == nil) {
+ out.puts("tk layout");
+ for (l = tklayout; l != nil; l = tl l) {
+ out.putc(' ');
+ out.puts(hd l);
+ }
+ out.puts(";\n");
+ }
+ out.close();
+}
+
+clear(e: ref Env)
+{
+ tkcmd(e, ".ft.t delete 1.0 end; update");
+}
+
+dofile(e: ref Env, title, dir: string, pats: list of string)
+{
+ e.output(selectfile->filename(gctxt, gtop.image, title, pats, dir));
+}
+
+sel(e: ref Env)
+{
+ sel := tkcmd(e, ".ft.t tag ranges sel");
+ if (sel != nil) {
+ s := tkcmd(e, ".ft.t dump " + sel);
+ e.output(s);
+ }
+}
+
+text(e: ref Env)
+{
+ sel := tkcmd(e, ".ft.t tag ranges sel");
+ if (sel != nil)
+ tkcmd(e, ".ft.t tag remove sel " + sel);
+ s := tkcmd(e, ".ft.t dump 1.0 end");
+ if (sel != nil)
+ tkcmd(e, ".ft.t tag add sel " + sel);
+ e.output(s);
+}
+
+notice0 := array[] of
+{
+ "frame .f -borderwidth 2 -relief groove -padx 3 -pady 3",
+ "frame .f.f",
+ "label .f.f.l -bitmap error -foreground red",
+};
+
+notice1 := array[] of
+{
+ "button .f.b -text { OK } -command {send cmd done}",
+ "pack .f.f.l .f.f.m -side left -expand 1 -padx 10 -pady 10",
+ "pack .f.f .f.b -padx 10 -pady 10",
+ "pack .f",
+ "update; cursor -default",
+};
+
+notice(mesg: string)
+{
+ x := int tk->cmd(gtop, ". cget -x");
+ y := int tk->cmd(gtop, ". cget -y");
+ where := sys->sprint("-x %d -y %d", x + 30, y + 30);
+ t := tk->toplevel(gctxt.screen, where + " -borderwidth 2 -relief raised");
+ cmd := chan of string;
+ tk->namechan(t, cmd, "cmd");
+ wmlib->tkcmds(t, notice0);
+ tk->cmd(t, "label .f.f.m -text '" + mesg);
+ wmlib->tkcmds(t, notice1);
+ <- cmd;
+}
+
+dodialog(e: ref Env, l: list of string)
+{
+ title := hd l;
+ l = tl l;
+ msg := hd l;
+ l = tl l;
+ x := dialog->prompt(gctxt, gtop.image, nil, title, msg, int hd l, tl l);
+ e.output(string x);
+ focus(e);
+}
diff --git a/appl/cmd/mash/xeq.b b/appl/cmd/mash/xeq.b
new file mode 100644
index 00000000..fd2f1e6f
--- /dev/null
+++ b/appl/cmd/mash/xeq.b
@@ -0,0 +1,543 @@
+#
+# Command execution.
+#
+
+#
+# Entry from parser.
+#
+Cmd.xeq(c: self ref Cmd, e: ref Env)
+{
+ if (e.flags & EDumping) {
+ s := c.text();
+ f := e.outfile();
+ f.puts(s);
+ if (s != nil && s[len s - 1] != '&')
+ f.putc(';');
+ f.putc('\n');
+ f.close();
+ f = nil;
+ }
+ if ((e.flags & ENoxeq) == 0)
+ c.xeqit(e, 1);
+}
+
+#
+# Execute a command. Tail recursion.
+#
+Cmd.xeqit(c: self ref Cmd, e: ref Env, wait: int)
+{
+tail: for (;;) {
+ if (c == nil)
+ return;
+ case c.op {
+ Csimple =>
+ c.simple(e, wait);
+ Casync =>
+ e = e.clone();
+ e.in = e.devnull();
+ e.wait = nil;
+ spawn c.left.xeqit(e, 1);
+ Cgroup =>
+ if (c.redirs != nil) {
+ (ok, in, out) := mkredirs(e, c.redirs);
+ if (!ok)
+ return;
+ e = e.copy();
+ e.in = in;
+ e.out = out;
+ c.left.xeqit(e, 1);
+ } else {
+ c = c.left;
+ continue tail;
+ }
+ Csubgroup =>
+ e = e.clone();
+ if (c.redirs != nil) {
+ (ok, in, out) := mkredirs(e, c.redirs);
+ if (!ok)
+ return;
+ e.in = in;
+ e.out = out;
+ }
+ c = c.left;
+ continue tail;
+ Cseq =>
+ c.left.xeqit(e, 1);
+ c = c.right;
+ continue tail;
+ Cpipe =>
+ do {
+ fds := e.pipe();
+ if (fds == nil)
+ return;
+ n := e.clone();
+ n.out = fds[0];
+ c.left.xeqit(n, 0);
+ n = nil;
+ e = e.clone();
+ e.in = fds[1];
+ fds = nil;
+ c = c.right;
+ } while (c.op == Cpipe);
+ continue tail;
+ Cif =>
+ t := c.left.truth(e);
+ if (c.right.op == Celse) {
+ if (t)
+ c.right.left.xeqit(e, wait);
+ else
+ c.right.right.xeqit(e, wait);
+ } else if (t)
+ c.right.xeqit(e, wait);
+ Celse =>
+ panic("unexpected else");
+ Cwhile =>
+ while (c.left.truth(e))
+ c.right.xeqit(e, wait);
+ Cfor =>
+ (ok, l) := evalw(c.words, e);
+ if (!ok)
+ return;
+ s := c.item.word.text;
+ c = c.left;
+ while (l != nil) {
+ e.let(s, (hd l) :: nil);
+ c.xeqit(e, 1);
+ l = tl l;
+ }
+ Ccase =>
+ (s1, l1) := c.left.eeval(e);
+ r := c.right;
+ while (r != nil) {
+ l := r.left;
+ (s2, l2) := l.left.eeval(e);
+ if (match2(e, s1, l1, s2, l2)) {
+ c = l.right;
+ continue tail;
+ }
+ r = r.right;
+ }
+ Ceq =>
+ c.assign(e, 0);
+ Cdefeq =>
+ c.assign(e, 1);
+ Cfn =>
+ (s, nil, nil) := c.item.ieval(e);
+ if (!ident(s)) {
+ e.report("bad function name");
+ return;
+ }
+ e.define(s, c.left);
+ Crescue =>
+ e.report("rescue not implemented");
+ Cdepend =>
+ c.depend(e);
+ Crule =>
+ c.rule(e);
+ * =>
+ sys->print("number %d\n", c.op);
+ } return; } # tail recursion
+}
+
+#
+# Execute quote or backquote generator. Return generated item.
+#
+Cmd.quote(c: self ref Cmd, e: ref Env, back: int): ref Item
+{
+ e = e.copy();
+ fds := e.pipe();
+ if (fds == nil)
+ return nil;
+ e.out = fds[0];
+ in := bufio->fopen(fds[1], Bufio->OREAD);
+ if (in == nil)
+ e.couldnot("fopen", "pipe");
+ c.xeqit(e, 0);
+ fds = nil;
+ e = nil;
+ if (back) {
+ l: list of string;
+ while ((s := in.gets('\n')) != nil) {
+ (nil, r) := sys->tokenize(s, " \t\r\n");
+ l = prepend(l, r);
+ }
+ return Item.iteml(revstrs(l));
+ } else {
+ s := in.gets('\n');
+ if (s != nil && s[len s - 1] == '\n')
+ s = s[:len s - 1];
+ return Item.itemw(s);
+ }
+}
+
+#
+# Execute serve generator.
+#
+Cmd.serve(c: self ref Cmd, e: ref Env, write: int): ref Item
+{
+ e = e.clone();
+ fds := e.pipe();
+ if (fds == nil)
+ return nil;
+ if (write)
+ e.in = fds[0];
+ else
+ e.out = fds[0];
+ s := e.servefd(fds[1], write);
+ if (s == nil)
+ return nil;
+ c.xeqit(e, 0);
+ return Item.itemw(s);
+}
+
+#
+# Expression evaluation, first pass.
+# Parse tree is copied and word items are evaluated.
+# nil return for error is propagated.
+#
+Cmd.eeval1(c: self ref Cmd, e: ref Env): ref Cmd
+{
+ case c.op {
+ Cword =>
+ l := c.item.ieval1(e);
+ if (l == nil)
+ return nil;
+ return Cmd.cmd1i(Cword, nil, l);
+ Chd or Ctl or Clen or Cnot =>
+ l := c.left.eeval1(e);
+ if (l == nil)
+ return nil;
+ return Cmd.cmd1(c.op, l);
+ Ccaret or Ccons or Ceqeq or Cnoteq or Cmatch =>
+ l := c.left.eeval1(e);
+ r := c.right.eeval1(e);
+ if (l == nil || r == nil)
+ return nil;
+ return Cmd.cmd2(c.op, l, r);
+ }
+ panic("expr1: bad op");
+ return nil;
+}
+
+#
+# Expression evaluation, second pass.
+# Returns a tuple (singleton, list, expand flag).
+#
+Cmd.eeval2(c: self ref Cmd, e: ref Env): (string, list of string, int)
+{
+ case c.op {
+ Cword =>
+ return c.item.ieval2(e);
+ Clist =>
+ return (nil, c.value, 0);
+ Ccaret =>
+ (s1, l1, x1) := c.left.eeval2(e);
+ (s2, l2, x2) := c.right.eeval2(e);
+ return caret(s1, l1, x1, s2, l2, x2);
+ Chd =>
+ (s, l, x) := c.left.eeval2(e);
+ if (s != nil)
+ return (s, nil, x);
+ if (l != nil)
+ return (hd l, nil, 0);
+ Ctl =>
+ (s, l, nil) := c.left.eeval2(e);
+ if (s != nil)
+ break;
+ if (l != nil)
+ return (nil, tl l, 0);
+ Clen =>
+ (s, l, nil) := c.left.eeval2(e);
+ if (s != nil)
+ return ("1", nil, 0);
+ return (string len l, nil, 0);
+ Cnot =>
+ (s, l, nil) := c.left.eeval2(e);
+ if (s == nil && l == nil)
+ return (TRUE, nil, 0);
+ Ccons =>
+ (s1, l1, nil) := c.left.eeval2(e);
+ (s2, l2, nil) := c.right.eeval2(e);
+ if (s1 != nil) {
+ if (s2 != nil)
+ return (nil, s1 :: s2 :: nil, 0);
+ if (l2 != nil)
+ return (nil, s1 :: l2, 0);
+ return (s1, nil, 0);
+ } else if (l1 != nil) {
+ if (s2 != nil)
+ return (nil, prepend(s2 :: nil, revstrs(l1)), 0);
+ if (l2 != nil)
+ return (nil, prepend(l2, revstrs(l1)), 0);
+ return (nil, l1, 0);
+ } else
+ return (s2, l2, 0);
+ Ceqeq =>
+ if (c.evaleq(e))
+ return (TRUE, nil, 0);
+ Cnoteq =>
+ if (!c.evaleq(e))
+ return (TRUE, nil, 0);
+ Cmatch =>
+ if (c.evalmatch(e))
+ return (TRUE, nil, 0);
+ * =>
+ panic("expr2: bad op");
+ }
+ return (nil, nil, 0);
+}
+
+#
+# Evaluate expression. 1st pass, 2nd pass, maybe glob.
+#
+Cmd.eeval(c: self ref Cmd, e: ref Env): (string, list of string)
+{
+ c = c.eeval1(e);
+ if (c == nil)
+ return (nil, nil);
+ (s, l, x) := c.eeval2(e);
+ if (x && s != nil)
+ (s, l) = glob(e, s);
+ return (s, l);
+}
+
+#
+# Assignment - let or set.
+#
+Cmd.assign(c: self ref Cmd, e: ref Env, def: int)
+{
+ i := c.item;
+ if (i == nil)
+ return;
+ (ok, v) := evalw(c.words, e);
+ if (!ok)
+ return;
+ s := c.item.word.text;
+ if (def)
+ e.let(s, v);
+ else
+ e.set(s, v);
+}
+
+#
+# Evaluate command and test for non-empty.
+#
+Cmd.truth(c: self ref Cmd, e: ref Env): int
+{
+ (s, l) := c.eeval(e);
+ return s != nil || l != nil;
+}
+
+#
+# Evaluate word.
+#
+evalw(l: list of ref Item, e: ref Env): (int, list of string)
+{
+ if (l == nil)
+ return (1, nil);
+ w := pass1(e, l);
+ if (w == nil)
+ return (0, nil);
+ return (1, pass2(e, w));
+}
+
+#
+# Evaluate list of items, pass 1 - reverses.
+#
+pass1(e: ref Env, l: list of ref Item): list of ref Item
+{
+ r: list of ref Item;
+ while (l != nil) {
+ i := (hd l).ieval1(e);
+ if (i == nil)
+ return nil;
+ r = i :: r;
+ l = tl l;
+ }
+ return r;
+}
+
+#
+# Evaluate list of items, pass 2 with globbing - reverses (restores order).
+#
+pass2(e: ref Env, l: list of ref Item): list of string
+{
+ r: list of string;
+ while (l != nil) {
+ (s, t, x) := (hd l).ieval2(e);
+ if (x && s != nil)
+ (s, t) = glob(e, s);
+ if (s != nil)
+ r = s :: r;
+ else if (t != nil)
+ r = prepend(r, revstrs(t));
+ l = tl l;
+ }
+ return r;
+}
+
+#
+# Simple command. Maybe a function.
+#
+Cmd.simple(c: self ref Cmd, e: ref Env, wait: int)
+{
+ w := pass1(e, c.words);
+ if (w == nil)
+ return;
+ s := pass2(e, w);
+ if (s == nil)
+ return;
+ if (e.flags & EEcho)
+ echo(e, s);
+ (ok, in, out) := mkredirs(e, c.redirs);
+ if (ok)
+ e.runit(s, in, out, wait);
+}
+
+#
+# Cmd name and arglist. Maybe a function.
+#
+Env.runit(e: self ref Env, s: list of string, in, out: ref Sys->FD, wait: int)
+{
+ d := e.func(hd s);
+ if (d != nil) {
+ if (e.level >= MAXELEV) {
+ e.report(hd s + ": function nesting too deep");
+ return;
+ }
+ e = e.copy();
+ e.level++;
+ e.in = in;
+ e.out = out;
+ e.local = Stab.new();
+ e.local.assign(ARGS, tl s);
+ d.xeqit(e, wait);
+ } else
+ exec(s, e, in, out, wait);
+}
+
+#
+# Item evaluation, first pass. Copy parse tree. Expand variables.
+# Call first pass of expression evaluation. Execute generators.
+#
+Item.ieval1(i: self ref Item, e: ref Env): ref Item
+{
+ if (i == nil)
+ return nil;
+ case i.op {
+ Icaret or Iicaret =>
+ l := i.left.ieval1(e);
+ r := i.right.ieval1(e);
+ if (l == nil || r == nil)
+ return nil;
+ return Item.item2(i.op, l, r);
+ Idollar or Idollarq=>
+ s := e.dollar(i.word.text);
+ if (s == nil) {
+ e.undefined(i.word.text);
+ return nil;
+ }
+ if (s.value == empty)
+ return Item.itemw(nil);
+ if (i.op == Idollar)
+ return Item.iteml(s.value);
+ else
+ return Item.itemw(concat(s.value));
+ Iword or Imatch =>
+ return i;
+ Iexpr =>
+ l := i.cmd.eeval1(e);
+ if (l == nil)
+ return nil;
+ return Item.itemc(Iexpr, l);
+ Ibackq =>
+ return i.cmd.quote(e, 1);
+ Iquote =>
+ return i.cmd.quote(e, 0);
+ Iinpipe =>
+ return i.cmd.serve(e, 0);
+ Ioutpipe =>
+ return i.cmd.serve(e, 1);
+ }
+ panic("ieval1: bad op");
+ return nil;
+}
+
+#
+# Item evaluation, second pass. Outer level carets. Expand matches.
+# Call second pass of expression evaluation.
+#
+Item.ieval2(i: self ref Item, e: ref Env): (string, list of string, int)
+{
+ case i.op {
+ Icaret or Iicaret =>
+ return i.caret(e);
+ Imatch =>
+ return (e.arg(i.word.text), nil, 0);
+ Idollar or Idollarq =>
+ panic("ieval2: unexpected $");
+ Iword =>
+ return (i.word.text, nil, i.word.flags & Wexpand);
+ Iexpr =>
+ return i.cmd.eeval2(e);
+ Ibackq or Iinpipe or Ioutpipe =>
+ panic("ieval2: unexpected generator");
+ }
+ panic("ieval2: bad op");
+ return (nil, nil, 0);
+}
+
+#
+# Item evaluation.
+#
+Item.ieval(i: self ref Item, e: ref Env): (string, list of string, int)
+{
+ i = i.ieval1(e);
+ if (i == nil)
+ return (nil, nil, 0);
+ return i.ieval2(e);
+}
+
+#
+# Redirection item evaluation.
+#
+Item.reval(i: self ref Item, e: ref Env): (int, string)
+{
+ (s, l, nil) := i.ieval(e);
+ if (s == nil) {
+ if (l == nil)
+ e.report("null redirect");
+ else
+ e.report("list for redirect");
+ return (0, nil);
+ }
+ return (1, s);
+}
+
+#
+# Make redirection names.
+#
+mkrdnames(e: ref Env, l: list of ref Redir): (int, array of string)
+{
+ f := array[Rcount] of string;
+ while (l != nil) {
+ r := hd l;
+ (ok, s) := r.word.reval(e);
+ if (!ok)
+ return (0, nil);
+ f[r.op] = s;
+ l = tl l;
+ }
+ return (1, f);
+}
+
+#
+# Perform redirections.
+#
+mkredirs(e: ref Env, l: list of ref Redir): (int, ref Sys->FD, ref Sys->FD)
+{
+ (ok, f) := mkrdnames(e, l);
+ if (!ok)
+ return (0, nil, nil);
+ return redirect(e, f, e.in, e.out);
+}