diff options
Diffstat (limited to 'appl/cmd/mash')
| -rw-r--r-- | appl/cmd/mash/builtins.b | 347 | ||||
| -rw-r--r-- | appl/cmd/mash/depends.b | 228 | ||||
| -rw-r--r-- | appl/cmd/mash/dump.b | 199 | ||||
| -rw-r--r-- | appl/cmd/mash/exec.b | 401 | ||||
| -rw-r--r-- | appl/cmd/mash/expr.b | 158 | ||||
| -rw-r--r-- | appl/cmd/mash/eyacc.b | 2785 | ||||
| -rw-r--r-- | appl/cmd/mash/eyaccpar | 223 | ||||
| -rw-r--r-- | appl/cmd/mash/history.b | 206 | ||||
| -rw-r--r-- | appl/cmd/mash/lex.b | 547 | ||||
| -rw-r--r-- | appl/cmd/mash/make.b | 723 | ||||
| -rw-r--r-- | appl/cmd/mash/mash.b | 154 | ||||
| -rw-r--r-- | appl/cmd/mash/mash.m | 372 | ||||
| -rw-r--r-- | appl/cmd/mash/mash.y | 269 | ||||
| -rw-r--r-- | appl/cmd/mash/mashfile | 36 | ||||
| -rw-r--r-- | appl/cmd/mash/mashlib.b | 60 | ||||
| -rw-r--r-- | appl/cmd/mash/mashparse.b | 662 | ||||
| -rw-r--r-- | appl/cmd/mash/mashparse.m | 56 | ||||
| -rw-r--r-- | appl/cmd/mash/misc.b | 313 | ||||
| -rw-r--r-- | appl/cmd/mash/mkfile | 78 | ||||
| -rw-r--r-- | appl/cmd/mash/serve.b | 154 | ||||
| -rw-r--r-- | appl/cmd/mash/symb.b | 265 | ||||
| -rw-r--r-- | appl/cmd/mash/tk.b | 603 | ||||
| -rw-r--r-- | appl/cmd/mash/xeq.b | 543 |
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); +} |
