URI: 
       added sam - 9base - revived minimalist port of Plan 9 userland to Unix
  HTML git clone git://git.suckless.org/9base
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit 9765fabf4d48cc6af2f8870fae4dde5c975ac86c
   DIR parent 631633fb8f9afdeaf4c7002680ae4913d0c36397
  HTML Author: Anselm R Garbe <anselm@garbe.us>
       Date:   Sat, 29 May 2010 14:33:38 +0100
       
       added sam
       Diffstat:
         A sam/Makefile                        |      37 +++++++++++++++++++++++++++++++
         A sam/README                          |      29 +++++++++++++++++++++++++++++
         A sam/_libc.h                         |      40 +++++++++++++++++++++++++++++++
         A sam/address.c                       |     240 +++++++++++++++++++++++++++++++
         A sam/buff.c                          |     302 +++++++++++++++++++++++++++++++
         A sam/cmd.c                           |     608 +++++++++++++++++++++++++++++++
         A sam/disk.c                          |     122 +++++++++++++++++++++++++++++++
         A sam/err                             |      39 +++++++++++++++++++++++++++++++
         A sam/error.c                         |     144 +++++++++++++++++++++++++++++++
         A sam/errors.h                        |      65 +++++++++++++++++++++++++++++++
         A sam/file.c                          |     610 +++++++++++++++++++++++++++++++
         A sam/io.c                            |     278 ++++++++++++++++++++++++++++++
         A sam/list.c                          |      96 +++++++++++++++++++++++++++++++
         A sam/mesg.c                          |     848 ++++++++++++++++++++++++++++++
         A sam/mesg.h                          |     131 +++++++++++++++++++++++++++++++
         A sam/moveto.c                        |     173 +++++++++++++++++++++++++++++++
         A sam/multi.c                         |     123 +++++++++++++++++++++++++++++++
         A sam/parse.h                         |      68 +++++++++++++++++++++++++++++++
         A sam/plan9.c                         |     185 ++++++++++++++++++++++++++++++
         A sam/plumb.h                         |      17 +++++++++++++++++
         A sam/rasp.c                          |     340 +++++++++++++++++++++++++++++++
         A sam/regexp.c                        |     802 +++++++++++++++++++++++++++++++
         A sam/sam.1                           |     908 +++++++++++++++++++++++++++++++
         A sam/sam.c                           |     741 +++++++++++++++++++++++++++++++
         A sam/sam.h                           |     408 +++++++++++++++++++++++++++++++
         A sam/shell.c                         |     165 +++++++++++++++++++++++++++++++
         A sam/string.c                        |     193 +++++++++++++++++++++++++++++++
         A sam/sys.c                           |      60 +++++++++++++++++++++++++++++++
         A sam/unix.c                          |     222 ++++++++++++++++++++++++++++++
         A sam/util.c                          |      54 +++++++++++++++++++++++++++++++
         A sam/xec.c                           |     508 +++++++++++++++++++++++++++++++
       
       31 files changed, 8556 insertions(+), 0 deletions(-)
       ---
   DIR diff --git a/sam/Makefile b/sam/Makefile
       @@ -0,0 +1,37 @@
       +# sam - sam shell unix port from plan9
       +# Depends on ../lib9
       +
       +TARG      = sam
       +OFILES= sam.o address.o buff.o cmd.o disk.o error.o file.o\
       +        io.o list.o mesg.o moveto.o multi.o rasp.o regexp.o\
       +        shell.o string.o sys.o unix.o util.o xec.o
       +MANFILES  = sam.1
       +
       +include ../config.mk
       +
       +all: ${TARG}
       +        @strip ${TARG}
       +        @echo built ${TARG}
       +
       +install: ${TARG}
       +        @mkdir -p ${DESTDIR}${PREFIX}/bin
       +        @cp -f ${TARG} ${DESTDIR}${PREFIX}/bin/
       +        @chmod 755 ${DESTDIR}${PREFIX}/bin/${TARG}
       +        @mkdir -p ${DESTDIR}${MANPREFIX}/man1
       +        @cp -f ${MANFILES} ${DESTDIR}${MANPREFIX}/man1
       +        @chmod 444 ${DESTDIR}${MANPREFIX}/man1/${MANFILES}
       +
       +uninstall:
       +        rm -f ${DESTDIR}${PREFIX}/bin/${TARG}
       +        rm -f ${DESTDIR}${PREFIX}/man1/${MANFILES}
       +
       +.c.o:
       +        @echo CC $*.c
       +        @${CC} ${CFLAGS} -I../lib9 -I${PREFIX}/include -I../lib9 $*.c
       +
       +clean:
       +        rm -f ${OFILES} ${TARG}
       +
       +${TARG}: ${OFILES}
       +        @echo LD ${TARG}
       +        @${CC} ${LDFLAGS} -o ${TARG} ${OFILES} -lm -L${PREFIX}/lib -L../lib9 -l9
   DIR diff --git a/sam/README b/sam/README
       @@ -0,0 +1,29 @@
       +This is sam (not including samterm) from the 4th edition of Plan 9,
       +with changes so that it can be compiled under unix.
       +(Tested on Solaris 7 and Debian 3.0r1.)
       +
       +Some extra libraries are needed.  First, fetch libutf-2.0 and libfmt-2.0
       +from
       +        http://pdos.lcs.mit.edu/~rsc/software/
       +
       +(Beware that in libfmt/fmt.c there is a line that says:
       +        'u',    __ifmt,         /* in Plan 9, __flagfmt */
       +Thus, sam will have to fmtinstall the other thing.  Other ported programs
       +may have to do the same.  The fmt library should probably print messages
       +about bad format characters to stderr, since no one seems to check the
       +return codes.)
       +
       +Compile and install those two libraries.  
       +Set PREFIX in the Makefile to match, then compile sam.
       +
       +Your C compiler will emit many complaints of the form:
       +  sam.c:496: warning: passing arg 1 of `bufread' from incompatible pointer type
       +
       +This is because the Plan 9 compiler has a slightly different (better,
       +ala Oberon) type system than ISO C.  Popular compilers generate the right
       +code, so in an act of civil disobediance I changed just enough to get
       +it to compile, but left the type errors in.  Now the next C standard can
       +adopt this extension, because at least one important C program uses it!
       +
       +-- Scott Schwartz,  4 July 2003
       +
   DIR diff --git a/sam/_libc.h b/sam/_libc.h
       @@ -0,0 +1,40 @@
       +#define __USE_UNIX98  // for pread/pwrite, supposedly
       +#include <unistd.h>
       +#include <stdlib.h>
       +#include <stdarg.h>
       +#include <setjmp.h>
       +#include <string.h>
       +#include <sys/types.h>
       +#include <sys/stat.h>
       +#include <fcntl.h>
       +#include <errno.h>
       +#include <stdio.h>
       +
       +#include "utf.h"
       +#include "fmt.h"
       +
       +#define nil 0
       +#define dup dup2
       +#define exec execv
       +#define seek lseek
       +#define getwd getcwd
       +#define USED(a)
       +#define SET(a)
       +
       +enum {
       +        OREAD = 0,
       +        OWRITE = 1,
       +        ORDWR = 2,
       +        OCEXEC = 4,
       +        ORCLOSE = 8
       +};
       +
       +enum {
       +        ERRMAX = 255
       +};
       +
       +void exits(const char *);
       +void _exits(const char *);
       +int notify (void(*f)(void *, char *));
       +int create(char *, int, int);
       +int errstr(char *, int);
   DIR diff --git a/sam/address.c b/sam/address.c
       @@ -0,0 +1,240 @@
       +#include "sam.h"
       +#include "parse.h"
       +
       +Address        addr;
       +String        lastpat;
       +int        patset;
       +File        *menu;
       +
       +File        *matchfile(String*);
       +Address        charaddr(Posn, Address, int);
       +
       +Address
       +address(Addr *ap, Address a, int sign)
       +{
       +        File *f = a.f;
       +        Address a1, a2;
       +
       +        do{
       +                switch(ap->type){
       +                case 'l':
       +                case '#':
       +                        a = (*(ap->type=='#'?charaddr:lineaddr))(ap->num, a, sign);
       +                        break;
       +
       +                case '.':
       +                        a = f->dot;
       +                        break;
       +
       +                case '$':
       +                        a.r.p1 = a.r.p2 = f->b.nc;
       +                        break;
       +
       +                case '\'':
       +                        a.r = f->mark;
       +                        break;
       +
       +                case '?':
       +                        sign = -sign;
       +                        if(sign == 0)
       +                                sign = -1;
       +                        /* fall through */
       +                case '/':
       +                        nextmatch(f, ap->are, sign>=0? a.r.p2 : a.r.p1, sign);
       +                        a.r = sel.p[0];
       +                        break;
       +
       +                case '"':
       +                        a = matchfile(ap->are)->dot;
       +                        f = a.f;
       +                        if(f->unread)
       +                                load(f);
       +                        break;
       +
       +                case '*':
       +                        a.r.p1 = 0, a.r.p2 = f->b.nc;
       +                        return a;
       +
       +                case ',':
       +                case ';':
       +                        if(ap->left)
       +                                a1 = address(ap->left, a, 0);
       +                        else
       +                                a1.f = a.f, a1.r.p1 = a1.r.p2 = 0;
       +                        if(ap->type == ';'){
       +                                f = a1.f;
       +                                a = a1;
       +                                f->dot = a1;
       +                        }
       +                        if(ap->next)
       +                                a2 = address(ap->next, a, 0);
       +                        else
       +                                a2.f = a.f, a2.r.p1 = a2.r.p2 = f->b.nc;
       +                        if(a1.f != a2.f)
       +                                error(Eorder);
       +                        a.f = a1.f, a.r.p1 = a1.r.p1, a.r.p2 = a2.r.p2;
       +                        if(a.r.p2 < a.r.p1)
       +                                error(Eorder);
       +                        return a;
       +
       +                case '+':
       +                case '-':
       +                        sign = 1;
       +                        if(ap->type == '-')
       +                                sign = -1;
       +                        if(ap->next==0 || ap->next->type=='+' || ap->next->type=='-')
       +                                a = lineaddr(1L, a, sign);
       +                        break;
       +                default:
       +                        panic("address");
       +                        return a;
       +                }
       +        }while(ap = ap->next);        /* assign = */
       +        return a;
       +}
       +
       +void
       +nextmatch(File *f, String *r, Posn p, int sign)
       +{
       +        compile(r);
       +        if(sign >= 0){
       +                if(!execute(f, p, INFINITY))
       +                        error(Esearch);
       +                if(sel.p[0].p1==sel.p[0].p2 && sel.p[0].p1==p){
       +                        if(++p>f->b.nc)
       +                                p = 0;
       +                        if(!execute(f, p, INFINITY))
       +                                panic("address");
       +                }
       +        }else{
       +                if(!bexecute(f, p))
       +                        error(Esearch);
       +                if(sel.p[0].p1==sel.p[0].p2 && sel.p[0].p2==p){
       +                        if(--p<0)
       +                                p = f->b.nc;
       +                        if(!bexecute(f, p))
       +                                panic("address");
       +                }
       +        }
       +}
       +
       +File *
       +matchfile(String *r)
       +{
       +        File *f;
       +        File *match = 0;
       +        int i;
       +
       +        for(i = 0; i<file.nused; i++){
       +                f = file.filepptr[i];
       +                if(f == cmd)
       +                        continue;
       +                if(filematch(f, r)){
       +                        if(match)
       +                                error(Emanyfiles);
       +                        match = f;
       +                }
       +        }
       +        if(!match)
       +                error(Efsearch);
       +        return match;
       +}
       +
       +int
       +filematch(File *f, String *r)
       +{
       +        char *c, buf[STRSIZE+100];
       +        String *t;
       +
       +        c = Strtoc(&f->name);
       +        sprint(buf, "%c%c%c %s\n", " '"[f->mod],
       +                "-+"[f->rasp!=0], " ."[f==curfile], c);
       +        free(c);
       +        t = tmpcstr(buf);
       +        Strduplstr(&genstr, t);
       +        freetmpstr(t);
       +        /* A little dirty... */
       +        if(menu == 0)
       +                menu = fileopen();
       +        bufreset(&menu->b);
       +        bufinsert(&menu->b, 0, genstr.s, genstr.n);
       +        compile(r);
       +        return execute(menu, 0, menu->b.nc);
       +}
       +
       +Address
       +charaddr(Posn l, Address addr, int sign)
       +{
       +        if(sign == 0)
       +                addr.r.p1 = addr.r.p2 = l;
       +        else if(sign < 0)
       +                addr.r.p2 = addr.r.p1-=l;
       +        else if(sign > 0)
       +                addr.r.p1 = addr.r.p2+=l;
       +        if(addr.r.p1<0 || addr.r.p2>addr.f->b.nc)
       +                error(Erange);
       +        return addr;
       +}
       +
       +Address
       +lineaddr(Posn l, Address addr, int sign)
       +{
       +        int n;
       +        int c;
       +        File *f = addr.f;
       +        Address a;
       +        Posn p;
       +
       +        a.f = f;
       +        if(sign >= 0){
       +                if(l == 0){
       +                        if(sign==0 || addr.r.p2==0){
       +                                a.r.p1 = a.r.p2 = 0;
       +                                return a;
       +                        }
       +                        a.r.p1 = addr.r.p2;
       +                        p = addr.r.p2-1;
       +                }else{
       +                        if(sign==0 || addr.r.p2==0){
       +                                p = (Posn)0;
       +                                n = 1;
       +                        }else{
       +                                p = addr.r.p2-1;
       +                                n = filereadc(f, p++)=='\n';
       +                        }
       +                        while(n < l){
       +                                if(p >= f->b.nc)
       +                                        error(Erange);
       +                                if(filereadc(f, p++) == '\n')
       +                                        n++;
       +                        }
       +                        a.r.p1 = p;
       +                }
       +                while(p < f->b.nc && filereadc(f, p++)!='\n')
       +                        ;
       +                a.r.p2 = p;
       +        }else{
       +                p = addr.r.p1;
       +                if(l == 0)
       +                        a.r.p2 = addr.r.p1;
       +                else{
       +                        for(n = 0; n<l; ){        /* always runs once */
       +                                if(p == 0){
       +                                        if(++n != l)
       +                                                error(Erange);
       +                                }else{
       +                                        c = filereadc(f, p-1);
       +                                        if(c != '\n' || ++n != l)
       +                                                p--;
       +                                }
       +                        }
       +                        a.r.p2 = p;
       +                        if(p > 0)
       +                                p--;
       +                }
       +                while(p > 0 && filereadc(f, p-1)!='\n')        /* lines start after a newline */
       +                        p--;
       +                a.r.p1 = p;
       +        }
       +        return a;
       +}
   DIR diff --git a/sam/buff.c b/sam/buff.c
       @@ -0,0 +1,302 @@
       +#include "sam.h"
       +
       +enum
       +{
       +        Slop = 100        /* room to grow with reallocation */
       +};
       +
       +static
       +void
       +sizecache(Buffer *b, uint n)
       +{
       +        if(n <= b->cmax)
       +                return;
       +        b->cmax = n+Slop;
       +        b->c = runerealloc(b->c, b->cmax);
       +}
       +
       +static
       +void
       +addblock(Buffer *b, uint i, uint n)
       +{
       +        if(i > b->nbl)
       +                panic("internal error: addblock");
       +
       +        b->bl = realloc(b->bl, (b->nbl+1)*sizeof b->bl[0]);
       +        if(i < b->nbl)
       +                memmove(b->bl+i+1, b->bl+i, (b->nbl-i)*sizeof(Block*));
       +        b->bl[i] = disknewblock(disk, n);
       +        b->nbl++;
       +}
       +
       +
       +static
       +void
       +delblock(Buffer *b, uint i)
       +{
       +        if(i >= b->nbl)
       +                panic("internal error: delblock");
       +
       +        diskrelease(disk, b->bl[i]);
       +        b->nbl--;
       +        if(i < b->nbl)
       +                memmove(b->bl+i, b->bl+i+1, (b->nbl-i)*sizeof(Block*));
       +        b->bl = realloc(b->bl, b->nbl*sizeof b->bl[0]);
       +}
       +
       +/*
       + * Move cache so b->cq <= q0 < b->cq+b->cnc.
       + * If at very end, q0 will fall on end of cache block.
       + */
       +
       +static
       +void
       +flush(Buffer *b)
       +{
       +        if(b->cdirty || b->cnc==0){
       +                if(b->cnc == 0)
       +                        delblock(b, b->cbi);
       +                else
       +                        diskwrite(disk, &b->bl[b->cbi], b->c, b->cnc);
       +                b->cdirty = FALSE;
       +        }
       +}
       +
       +static
       +void
       +setcache(Buffer *b, uint q0)
       +{
       +        Block **blp, *bl;
       +        uint i, q;
       +
       +        if(q0 > b->nc)
       +                panic("internal error: setcache");
       +        /*
       +         * flush and reload if q0 is not in cache.
       +         */
       +        if(b->nc == 0 || (b->cq<=q0 && q0<b->cq+b->cnc))
       +                return;
       +        /*
       +         * if q0 is at end of file and end of cache, continue to grow this block
       +         */
       +        if(q0==b->nc && q0==b->cq+b->cnc && b->cnc<=Maxblock)
       +                return;
       +        flush(b);
       +        /* find block */
       +        if(q0 < b->cq){
       +                q = 0;
       +                i = 0;
       +        }else{
       +                q = b->cq;
       +                i = b->cbi;
       +        }
       +        blp = &b->bl[i];
       +        while(q+(*blp)->u.n <= q0 && q+(*blp)->u.n < b->nc){
       +                q += (*blp)->u.n;
       +                i++;
       +                blp++;
       +                if(i >= b->nbl)
       +                        panic("block not found");
       +        }
       +        bl = *blp;
       +        /* remember position */
       +        b->cbi = i;
       +        b->cq = q;
       +        sizecache(b, bl->u.n);
       +        b->cnc = bl->u.n;
       +        /*read block*/
       +        diskread(disk, bl, b->c, b->cnc);
       +}
       +
       +void
       +bufinsert(Buffer *b, uint q0, Rune *s, uint n)
       +{
       +        uint i, m, t, off;
       +
       +        if(q0 > b->nc)
       +                panic("internal error: bufinsert");
       +
       +        while(n > 0){
       +                setcache(b, q0);
       +                off = q0-b->cq;
       +                if(b->cnc+n <= Maxblock){
       +                        /* Everything fits in one block. */
       +                        t = b->cnc+n;
       +                        m = n;
       +                        if(b->bl == nil){        /* allocate */
       +                                if(b->cnc != 0)
       +                                        panic("internal error: bufinsert1 cnc!=0");
       +                                addblock(b, 0, t);
       +                                b->cbi = 0;
       +                        }
       +                        sizecache(b, t);
       +                        runemove(b->c+off+m, b->c+off, b->cnc-off);
       +                        runemove(b->c+off, s, m);
       +                        b->cnc = t;
       +                        goto Tail;
       +                }
       +                /*
       +                 * We must make a new block.  If q0 is at
       +                 * the very beginning or end of this block,
       +                 * just make a new block and fill it.
       +                 */
       +                if(q0==b->cq || q0==b->cq+b->cnc){
       +                        if(b->cdirty)
       +                                flush(b);
       +                        m = min(n, Maxblock);
       +                        if(b->bl == nil){        /* allocate */
       +                                if(b->cnc != 0)
       +                                        panic("internal error: bufinsert2 cnc!=0");
       +                                i = 0;
       +                        }else{
       +                                i = b->cbi;
       +                                if(q0 > b->cq)
       +                                        i++;
       +                        }
       +                        addblock(b, i, m);
       +                        sizecache(b, m);
       +                        runemove(b->c, s, m);
       +                        b->cq = q0;
       +                        b->cbi = i;
       +                        b->cnc = m;
       +                        goto Tail;
       +                }
       +                /*
       +                 * Split the block; cut off the right side and
       +                 * let go of it.
       +                 */
       +                m = b->cnc-off;
       +                if(m > 0){
       +                        i = b->cbi+1;
       +                        addblock(b, i, m);
       +                        diskwrite(disk, &b->bl[i], b->c+off, m);
       +                        b->cnc -= m;
       +                }
       +                /*
       +                 * Now at end of block.  Take as much input
       +                 * as possible and tack it on end of block.
       +                 */
       +                m = min(n, Maxblock-b->cnc);
       +                sizecache(b, b->cnc+m);
       +                runemove(b->c+b->cnc, s, m);
       +                b->cnc += m;
       +  Tail:
       +                b->nc += m;
       +                q0 += m;
       +                s += m;
       +                n -= m;
       +                b->cdirty = TRUE;
       +        }
       +}
       +
       +void
       +bufdelete(Buffer *b, uint q0, uint q1)
       +{
       +        uint m, n, off;
       +
       +        if(!(q0<=q1 && q0<=b->nc && q1<=b->nc))
       +                panic("internal error: bufdelete");
       +        while(q1 > q0){
       +                setcache(b, q0);
       +                off = q0-b->cq;
       +                if(q1 > b->cq+b->cnc)
       +                        n = b->cnc - off;
       +                else
       +                        n = q1-q0;
       +                m = b->cnc - (off+n);
       +                if(m > 0)
       +                        runemove(b->c+off, b->c+off+n, m);
       +                b->cnc -= n;
       +                b->cdirty = TRUE;
       +                q1 -= n;
       +                b->nc -= n;
       +        }
       +}
       +
       +uint
       +bufload(Buffer *b, uint q0, int fd, int *nulls)
       +{
       +        char *p;
       +        Rune *r;
       +        int l, m, n, nb, nr;
       +        uint q1;
       +
       +        if(q0 > b->nc)
       +                panic("internal error: bufload");
       +        p = malloc((Maxblock+UTFmax+1)*sizeof p[0]);
       +        if(p == nil)
       +                panic("bufload: malloc failed");
       +        r = runemalloc(Maxblock);
       +        m = 0;
       +        n = 1;
       +        q1 = q0;
       +        /*
       +         * At top of loop, may have m bytes left over from
       +         * last pass, possibly representing a partial rune.
       +         */
       +        while(n > 0){
       +                n = read(fd, p+m, Maxblock);
       +                if(n < 0){
       +                        error(Ebufload);
       +                        break;
       +                }
       +                m += n;
       +                p[m] = 0;
       +                l = m;
       +                if(n > 0)
       +                        l -= UTFmax;
       +                cvttorunes(p, l, r, &nb, &nr, nulls);
       +                memmove(p, p+nb, m-nb);
       +                m -= nb;
       +                bufinsert(b, q1, r, nr);
       +                q1 += nr;
       +        }
       +        free(p);
       +        free(r);
       +        return q1-q0;
       +}
       +
       +void
       +bufread(Buffer *b, uint q0, Rune *s, uint n)
       +{
       +        uint m;
       +
       +        if(!(q0<=b->nc && q0+n<=b->nc))
       +                panic("bufread: internal error");
       +
       +        while(n > 0){
       +                setcache(b, q0);
       +                m = min(n, b->cnc-(q0-b->cq));
       +                runemove(s, b->c+(q0-b->cq), m);
       +                q0 += m;
       +                s += m;
       +                n -= m;
       +        }
       +}
       +
       +void
       +bufreset(Buffer *b)
       +{
       +        int i;
       +
       +        b->nc = 0;
       +        b->cnc = 0;
       +        b->cq = 0;
       +        b->cdirty = 0;
       +        b->cbi = 0;
       +        /* delete backwards to avoid n² behavior */
       +        for(i=b->nbl-1; --i>=0; )
       +                delblock(b, i);
       +}
       +
       +void
       +bufclose(Buffer *b)
       +{
       +        bufreset(b);
       +        free(b->c);
       +        b->c = nil;
       +        b->cnc = 0;
       +        free(b->bl);
       +        b->bl = nil;
       +        b->nbl = 0;
       +}
   DIR diff --git a/sam/cmd.c b/sam/cmd.c
       @@ -0,0 +1,608 @@
       +#include "sam.h"
       +#include "parse.h"
       +
       +static char        linex[]="\n";
       +static char        wordx[]=" \t\n";
       +struct cmdtab cmdtab[]={
       +/*        cmdc        text        regexp        addr        defcmd        defaddr        count        token         fn        */
       +        '\n',        0,        0,        0,        0,        aDot,        0,        0,        nl_cmd,
       +        'a',        1,        0,        0,        0,        aDot,        0,        0,        a_cmd,
       +        'b',        0,        0,        0,        0,        aNo,        0,        linex,        b_cmd,
       +        'B',        0,        0,        0,        0,        aNo,        0,        linex,        b_cmd,
       +        'c',        1,        0,        0,        0,        aDot,        0,        0,        c_cmd,
       +        'd',        0,        0,        0,        0,        aDot,        0,        0,        d_cmd,
       +        'D',        0,        0,        0,        0,        aNo,        0,        linex,        D_cmd,
       +        'e',        0,        0,        0,        0,        aNo,        0,        wordx,        e_cmd,
       +        'f',        0,        0,        0,        0,        aNo,        0,        wordx,        f_cmd,
       +        'g',        0,        1,        0,        'p',        aDot,        0,        0,        g_cmd,
       +        'i',        1,        0,        0,        0,        aDot,        0,        0,        i_cmd,
       +        'k',        0,        0,        0,        0,        aDot,        0,        0,        k_cmd,
       +        'm',        0,        0,        1,        0,        aDot,        0,        0,        m_cmd,
       +        'n',        0,        0,        0,        0,        aNo,        0,        0,        n_cmd,
       +        'p',        0,        0,        0,        0,        aDot,        0,        0,        p_cmd,
       +        'q',        0,        0,        0,        0,        aNo,        0,        0,        q_cmd,
       +        'r',        0,        0,        0,        0,        aDot,        0,        wordx,        e_cmd,
       +        's',        0,        1,        0,        0,        aDot,        1,        0,        s_cmd,
       +        't',        0,        0,        1,        0,        aDot,        0,        0,        m_cmd,
       +        'u',        0,        0,        0,        0,        aNo,        2,        0,        u_cmd,
       +        'v',        0,        1,        0,        'p',        aDot,        0,        0,        g_cmd,
       +        'w',        0,        0,        0,        0,        aAll,        0,        wordx,        w_cmd,
       +        'x',        0,        1,        0,        'p',        aDot,        0,        0,        x_cmd,
       +        'y',        0,        1,        0,        'p',        aDot,        0,        0,        x_cmd,
       +        'X',        0,        1,        0,        'f',        aNo,        0,        0,        X_cmd,
       +        'Y',        0,        1,        0,        'f',        aNo,        0,        0,        X_cmd,
       +        '!',        0,        0,        0,        0,        aNo,        0,        linex,        plan9_cmd,
       +        '>',        0,        0,        0,        0,        aDot,        0,        linex,        plan9_cmd,
       +        '<',        0,        0,        0,        0,        aDot,        0,        linex,        plan9_cmd,
       +        '|',        0,        0,        0,        0,        aDot,        0,        linex,        plan9_cmd,
       +        '=',        0,        0,        0,        0,        aDot,        0,        linex,        eq_cmd,
       +        'c'|0x100,0,        0,        0,        0,        aNo,        0,        wordx,        cd_cmd,
       +        0,        0,        0,        0,        0,        0,        0,        0
       +};
       +Cmd        *parsecmd(int);
       +Addr        *compoundaddr(void);
       +Addr        *simpleaddr(void);
       +void        freecmd(void);
       +void        okdelim(int);
       +
       +Rune        line[BLOCKSIZE];
       +Rune        termline[BLOCKSIZE];
       +Rune        *linep = line;
       +Rune        *terminp = termline;
       +Rune        *termoutp = termline;
       +
       +List        cmdlist = { 'p' };
       +List        addrlist = { 'p' };
       +List        relist = { 'p' };
       +List        stringlist = { 'p' };
       +
       +int        eof;
       +
       +void
       +resetcmd(void)
       +{
       +        linep = line;
       +        *linep = 0;
       +        terminp = termoutp = termline;
       +        freecmd();
       +}
       +
       +int
       +inputc(void)
       +{
       +        int n, nbuf;
       +        char buf[UTFmax];
       +        Rune r;
       +
       +    Again:
       +        nbuf = 0;
       +        if(downloaded){
       +                while(termoutp == terminp){
       +                        cmdupdate();
       +                        if(patset)
       +                                tellpat();
       +                        while(termlocked > 0){
       +                                outT0(Hunlock);
       +                                termlocked--;
       +                        }
       +                        if(rcv() == 0)
       +                                return -1;
       +                }
       +                r = *termoutp++;
       +                if(termoutp == terminp)
       +                        terminp = termoutp = termline;
       +        }else{
       +                   do{
       +                        n = read(0, buf+nbuf, 1);
       +                        if(n <= 0)
       +                                return -1;
       +                        nbuf += n;
       +                }while(!fullrune(buf, nbuf));
       +                chartorune(&r, buf);
       +        }
       +        if(r == 0){
       +                warn(Wnulls);
       +                goto Again;
       +        }
       +        return r;
       +}
       +
       +int
       +inputline(void)
       +{
       +        int i, c, start;
       +
       +        /*
       +         * Could set linep = line and i = 0 here and just
       +         * error(Etoolong) below, but this way we keep
       +         * old input buffer history around for a while.
       +         * This is useful only for debugging.
       +         */
       +        i = linep - line;
       +        do{
       +                if((c = inputc())<=0)
       +                        return -1;
       +                if(i == nelem(line)-1){
       +                        if(linep == line)
       +                                error(Etoolong);
       +                        start = linep - line;
       +                        runemove(line, linep, i-start);
       +                        i -= start;
       +                        linep = line;
       +                }
       +        }while((line[i++]=c) != '\n');
       +        line[i] = 0;
       +        return 1;
       +}
       +
       +int
       +getch(void)
       +{
       +        if(eof)
       +                return -1;
       +        if(*linep==0 && inputline()<0){
       +                eof = TRUE;
       +                return -1;
       +        }
       +        return *linep++;
       +}
       +
       +int
       +nextc(void)
       +{
       +        if(*linep == 0)
       +                return -1;
       +        return *linep;
       +}
       +
       +void
       +ungetch(void)
       +{
       +        if(--linep < line)
       +                panic("ungetch");
       +}
       +
       +Posn
       +getnum(int signok)
       +{
       +        Posn n=0;
       +        int c, sign;
       +
       +        sign = 1;
       +        if(signok>1 && nextc()=='-'){
       +                sign = -1;
       +                getch();
       +        }
       +        if((c=nextc())<'0' || '9'<c)        /* no number defaults to 1 */
       +                return sign;
       +        while('0'<=(c=getch()) && c<='9')
       +                n = n*10 + (c-'0');
       +        ungetch();
       +        return sign*n;
       +}
       +
       +int
       +skipbl(void)
       +{
       +        int c;
       +        do
       +                c = getch();
       +        while(c==' ' || c=='\t');
       +        if(c >= 0)
       +                ungetch();
       +        return c;
       +}
       +
       +void
       +termcommand(void)
       +{
       +        Posn p;
       +
       +        for(p=cmdpt; p<cmd->b.nc; p++){
       +                if(terminp >= &termline[BLOCKSIZE]){
       +                        cmdpt = cmd->b.nc;
       +                        error(Etoolong);
       +                }
       +                *terminp++ = filereadc(cmd, p);
       +        }
       +        cmdpt = cmd->b.nc;
       +}
       +
       +void
       +cmdloop(void)
       +{
       +        Cmd *cmdp;
       +        File *ocurfile;
       +        int loaded;
       +
       +        for(;;){
       +                if(!downloaded && curfile && curfile->unread)
       +                        load(curfile);
       +                if((cmdp = parsecmd(0))==0){
       +                        if(downloaded){
       +                                rescue();
       +                                exits("eof");
       +                        }
       +                        break;
       +                }
       +                ocurfile = curfile;
       +                loaded = curfile && !curfile->unread;
       +                if(cmdexec(curfile, cmdp) == 0)
       +                        break;
       +                freecmd();
       +                cmdupdate();
       +                update();
       +                if(downloaded && curfile &&
       +                    (ocurfile!=curfile || (!loaded && !curfile->unread)))
       +                        outTs(Hcurrent, curfile->tag);
       +                        /* don't allow type ahead on files that aren't bound */
       +                if(downloaded && curfile && curfile->rasp == 0)
       +                        terminp = termoutp;
       +        }
       +}
       +
       +Cmd *
       +newcmd(void){
       +        Cmd *p;
       +
       +        p = emalloc(sizeof(Cmd));
       +        inslist(&cmdlist, cmdlist.nused, (long)p);
       +        return p;
       +}
       +
       +Addr*
       +newaddr(void)
       +{
       +        Addr *p;
       +
       +        p = emalloc(sizeof(Addr));
       +        inslist(&addrlist, addrlist.nused, (long)p);
       +        return p;
       +}
       +
       +String*
       +newre(void)
       +{
       +        String *p;
       +
       +        p = emalloc(sizeof(String));
       +        inslist(&relist, relist.nused, (long)p);
       +        Strinit(p);
       +        return p;
       +}
       +
       +String*
       +newstring(void)
       +{
       +        String *p;
       +
       +        p = emalloc(sizeof(String));
       +        inslist(&stringlist, stringlist.nused, (long)p);
       +        Strinit(p);
       +        return p;
       +}
       +
       +void
       +freecmd(void)
       +{
       +        int i;
       +
       +        while(cmdlist.nused > 0)
       +                free(cmdlist.voidpptr[--cmdlist.nused]);
       +        while(addrlist.nused > 0)
       +                free(addrlist.voidpptr[--addrlist.nused]);
       +        while(relist.nused > 0){
       +                i = --relist.nused;
       +                Strclose(relist.stringpptr[i]);
       +                free(relist.stringpptr[i]);
       +        }
       +        while(stringlist.nused>0){
       +                i = --stringlist.nused;
       +                Strclose(stringlist.stringpptr[i]);
       +                free(stringlist.stringpptr[i]);
       +        }
       +}
       +
       +int
       +lookup(int c)
       +{
       +        int i;
       +
       +        for(i=0; cmdtab[i].cmdc; i++)
       +                if(cmdtab[i].cmdc == c)
       +                        return i;
       +        return -1;
       +}
       +
       +void
       +okdelim(int c)
       +{
       +        if(c=='\\' || ('a'<=c && c<='z')
       +        || ('A'<=c && c<='Z') || ('0'<=c && c<='9'))
       +                error_c(Edelim, c);
       +}
       +
       +void
       +atnl(void)
       +{
       +        skipbl();
       +        if(getch() != '\n')
       +                error(Enewline);
       +}
       +
       +void
       +getrhs(String *s, int delim, int cmd)
       +{
       +        int c;
       +
       +        while((c = getch())>0 && c!=delim && c!='\n'){
       +                if(c == '\\'){
       +                        if((c=getch()) <= 0)
       +                                error(Ebadrhs);
       +                        if(c == '\n'){
       +                                ungetch();
       +                                c='\\';
       +                        }else if(c == 'n')
       +                                c='\n';
       +                        else if(c!=delim && (cmd=='s' || c!='\\'))        /* s does its own */
       +                                Straddc(s, '\\');
       +                }
       +                Straddc(s, c);
       +        }
       +        ungetch();        /* let client read whether delimeter, '\n' or whatever */
       +}
       +
       +String *
       +collecttoken(char *end)
       +{
       +        String *s = newstring();
       +        int c;
       +
       +        while((c=nextc())==' ' || c=='\t')
       +                Straddc(s, getch()); /* blanks significant for getname() */
       +        while((c=getch())>0 && utfrune(end, c)==0)
       +                Straddc(s, c);
       +        Straddc(s, 0);
       +        if(c != '\n')
       +                atnl();
       +        return s;
       +}
       +
       +String *
       +collecttext(void)
       +{
       +        String *s = newstring();
       +        int begline, i, c, delim;
       +
       +        if(skipbl()=='\n'){
       +                getch();
       +                i = 0;
       +                do{
       +                        begline = i;
       +                        while((c = getch())>0 && c!='\n')
       +                                i++, Straddc(s, c);
       +                        i++, Straddc(s, '\n');
       +                        if(c < 0)
       +                                goto Return;
       +                }while(s->s[begline]!='.' || s->s[begline+1]!='\n');
       +                Strdelete(s, s->n-2, s->n);
       +        }else{
       +                okdelim(delim = getch());
       +                getrhs(s, delim, 'a');
       +                if(nextc()==delim)
       +                        getch();
       +                atnl();
       +        }
       +    Return:
       +        Straddc(s, 0);                /* JUST FOR CMDPRINT() */
       +        return s;
       +}
       +
       +Cmd *
       +parsecmd(int nest)
       +{
       +        int i, c;
       +        struct cmdtab *ct;
       +        Cmd *cp, *ncp;
       +        Cmd cmd;
       +
       +        cmd.next = cmd.ccmd = 0;
       +        cmd.re = 0;
       +        cmd.flag = cmd.num = 0;
       +        cmd.addr = compoundaddr();
       +        if(skipbl() == -1)
       +                return 0;
       +        if((c=getch())==-1)
       +                return 0;
       +        cmd.cmdc = c;
       +        if(cmd.cmdc=='c' && nextc()=='d'){        /* sleazy two-character case */
       +                getch();                /* the 'd' */
       +                cmd.cmdc='c'|0x100;
       +        }
       +        i = lookup(cmd.cmdc);
       +        if(i >= 0){
       +                if(cmd.cmdc == '\n')
       +                        goto Return;        /* let nl_cmd work it all out */
       +                ct = &cmdtab[i];
       +                if(ct->defaddr==aNo && cmd.addr)
       +                        error(Enoaddr);
       +                if(ct->count)
       +                        cmd.num = getnum(ct->count);
       +                if(ct->regexp){
       +                        /* x without pattern -> .*\n, indicated by cmd.re==0 */
       +                        /* X without pattern is all files */
       +                        if((ct->cmdc!='x' && ct->cmdc!='X') ||
       +                           ((c = nextc())!=' ' && c!='\t' && c!='\n')){
       +                                skipbl();
       +                                if((c = getch())=='\n' || c<0)
       +                                        error(Enopattern);
       +                                okdelim(c);
       +                                cmd.re = getregexp(c);
       +                                if(ct->cmdc == 's'){
       +                                        cmd.ctext = newstring();
       +                                        getrhs(cmd.ctext, c, 's');
       +                                        if(nextc() == c){
       +                                                getch();
       +                                                if(nextc() == 'g')
       +                                                        cmd.flag = getch();
       +                                        }
       +                        
       +                                }
       +                        }
       +                }
       +                if(ct->addr && (cmd.caddr=simpleaddr())==0)
       +                        error(Eaddress);
       +                if(ct->defcmd){
       +                        if(skipbl() == '\n'){
       +                                getch();
       +                                cmd.ccmd = newcmd();
       +                                cmd.ccmd->cmdc = ct->defcmd;
       +                        }else if((cmd.ccmd = parsecmd(nest))==0)
       +                                panic("defcmd");
       +                }else if(ct->text)
       +                        cmd.ctext = collecttext();
       +                else if(ct->token)
       +                        cmd.ctext = collecttoken(ct->token);
       +                else
       +                        atnl();
       +        }else
       +                switch(cmd.cmdc){
       +                case '{':
       +                        cp = 0;
       +                        do{
       +                                if(skipbl()=='\n')
       +                                        getch();
       +                                ncp = parsecmd(nest+1);
       +                                if(cp)
       +                                        cp->next = ncp;
       +                                else
       +                                        cmd.ccmd = ncp;
       +                        }while(cp = ncp);
       +                        break;
       +                case '}':
       +                        atnl();
       +                        if(nest==0)
       +                                error(Enolbrace);
       +                        return 0;
       +                default:
       +                        error_c(Eunk, cmd.cmdc);
       +                }
       +    Return:
       +        cp = newcmd();
       +        *cp = cmd;
       +        return cp;
       +}
       +
       +String*                                /* BUGGERED */
       +getregexp(int delim)
       +{
       +        String *r = newre();
       +        int c;
       +
       +        for(Strzero(&genstr); ; Straddc(&genstr, c))
       +                if((c = getch())=='\\'){
       +                        if(nextc()==delim)
       +                                c = getch();
       +                        else if(nextc()=='\\'){
       +                                Straddc(&genstr, c);
       +                                c = getch();
       +                        }
       +                }else if(c==delim || c=='\n')
       +                        break;
       +        if(c!=delim && c)
       +                ungetch();
       +        if(genstr.n > 0){
       +                patset = TRUE;
       +                Strduplstr(&lastpat, &genstr);
       +                Straddc(&lastpat, '\0');
       +        }
       +        if(lastpat.n <= 1)
       +                error(Epattern);
       +        Strduplstr(r, &lastpat);
       +        return r;
       +}
       +
       +Addr *
       +simpleaddr(void)
       +{
       +        Addr addr;
       +        Addr *ap, *nap;
       +
       +        addr.next = 0;
       +        addr.left = 0;
       +        addr.num = 0;
       +        switch(skipbl()){
       +        case '#':
       +                addr.type = getch();
       +                addr.num = getnum(1);
       +                break;
       +        case '0': case '1': case '2': case '3': case '4':
       +        case '5': case '6': case '7': case '8': case '9': 
       +                addr.num = getnum(1);
       +                addr.type='l';
       +                break;
       +        case '/': case '?': case '"':
       +                addr.are = getregexp(addr.type = getch());
       +                break;
       +        case '.':
       +        case '$':
       +        case '+':
       +        case '-':
       +        case '\'':
       +                addr.type = getch();
       +                break;
       +        default:
       +                return 0;
       +        }
       +        if(addr.next = simpleaddr())
       +                switch(addr.next->type){
       +                case '.':
       +                case '$':
       +                case '\'':
       +                        if(addr.type!='"')
       +                case '"':
       +                                error(Eaddress);
       +                        break;
       +                case 'l':
       +                case '#':
       +                        if(addr.type=='"')
       +                                break;
       +                        /* fall through */
       +                case '/':
       +                case '?':
       +                        if(addr.type!='+' && addr.type!='-'){
       +                                /* insert the missing '+' */
       +                                nap = newaddr();
       +                                nap->type='+';
       +                                nap->next = addr.next;
       +                                addr.next = nap;
       +                        }
       +                        break;
       +                case '+':
       +                case '-':
       +                        break;
       +                default:
       +                        panic("simpleaddr");
       +                }
       +        ap = newaddr();
       +        *ap = addr;
       +        return ap;
       +}
       +
       +Addr *
       +compoundaddr(void)
       +{
       +        Addr addr;
       +        Addr *ap, *next;
       +
       +        addr.left = simpleaddr();
       +        if((addr.type = skipbl())!=',' && addr.type!=';')
       +                return addr.left;
       +        getch();
       +        next = addr.next = compoundaddr();
       +        if(next && (next->type==',' || next->type==';') && next->left==0)
       +                error(Eaddress);
       +        ap = newaddr();
       +        *ap = addr;
       +        return ap;
       +}
   DIR diff --git a/sam/disk.c b/sam/disk.c
       @@ -0,0 +1,122 @@
       +#include "sam.h"
       +
       +static        Block        *blist;
       +
       +#if 0
       +static int
       +tempdisk(void)
       +{
       +        char buf[128];
       +        int i, fd;
       +
       +        snprint(buf, sizeof buf, "/tmp/X%d.%.4ssam", getpid(), getuser());
       +        for(i='A'; i<='Z'; i++){
       +                buf[5] = i;
       +                if(access(buf, AEXIST) == 0)
       +                        continue;
       +                fd = create(buf, ORDWR|ORCLOSE|OCEXEC, 0600);
       +                if(fd >= 0)
       +                        return fd;
       +        }
       +        return -1;
       +}
       +#else
       +extern int tempdisk(void);
       +#endif
       +
       +Disk*
       +diskinit(void)
       +{
       +        Disk *d;
       +
       +        d = emalloc(sizeof(Disk));
       +        d->fd = tempdisk();
       +        if(d->fd < 0){
       +                fprint(2, "sam: can't create temp file: %r\n");
       +                exits("diskinit");
       +        }
       +        return d;
       +}
       +
       +static
       +uint
       +ntosize(uint n, uint *ip)
       +{
       +        uint size;
       +
       +        if(n > Maxblock)
       +                panic("internal error: ntosize");
       +        size = n;
       +        if(size & (Blockincr-1))
       +                size += Blockincr - (size & (Blockincr-1));
       +        /* last bucket holds blocks of exactly Maxblock */
       +        if(ip)
       +                *ip = size/Blockincr;
       +        return size * sizeof(Rune);
       +}
       +
       +Block*
       +disknewblock(Disk *d, uint n)
       +{
       +        uint i, j, size;
       +        Block *b;
       +
       +        size = ntosize(n, &i);
       +        b = d->free[i];
       +        if(b)
       +                d->free[i] = b->u.next;
       +        else{
       +                /* allocate in chunks to reduce malloc overhead */
       +                if(blist == nil){
       +                        blist = emalloc(100*sizeof(Block));
       +                        for(j=0; j<100-1; j++)
       +                                blist[j].u.next = &blist[j+1];
       +                }
       +                b = blist;
       +                blist = b->u.next;
       +                b->addr = d->addr;
       +                d->addr += size;
       +        }
       +        b->u.n = n;
       +        return b;
       +}
       +
       +void
       +diskrelease(Disk *d, Block *b)
       +{
       +        uint i;
       +
       +        ntosize(b->u.n, &i);
       +        b->u.next = d->free[i];
       +        d->free[i] = b;
       +}
       +
       +void
       +diskwrite(Disk *d, Block **bp, Rune *r, uint n)
       +{
       +        int size, nsize;
       +        Block *b;
       +
       +        b = *bp;
       +        size = ntosize(b->u.n, nil);
       +        nsize = ntosize(n, nil);
       +        if(size != nsize){
       +                diskrelease(d, b);
       +                b = disknewblock(d, n);
       +                *bp = b;
       +        }
       +        if(pwrite(d->fd, r, n*sizeof(Rune), b->addr) != n*sizeof(Rune))
       +                panic("write error to temp file");
       +        b->u.n = n;
       +}
       +
       +void
       +diskread(Disk *d, Block *b, Rune *r, uint n)
       +{
       +        if(n > b->u.n)
       +                panic("internal error: diskread");
       +
       +        ntosize(b->u.n, nil);        /* called only for sanity check on Maxblock */
       +        if(pread(d->fd, r, n*sizeof(Rune), b->addr) != n*sizeof(Rune))
       +                panic("read error from temp file");
       +}
   DIR diff --git a/sam/err b/sam/err
       @@ -0,0 +1,39 @@
       +address.c: In function `filematch':
       +address.c:159: warning: passing arg 1 of `bufreset' from incompatible pointer type
       +address.c:160: warning: passing arg 1 of `bufinsert' from incompatible pointer type
       +file.c: In function `mergeextend':
       +file.c:117: warning: passing arg 1 of `bufread' from incompatible pointer type
       +file.c: In function `fileinsert':
       +file.c:275: warning: passing arg 1 of `bufinsert' from incompatible pointer type
       +file.c: In function `filedelete':
       +file.c:301: warning: passing arg 1 of `bufdelete' from incompatible pointer type
       +file.c: In function `fileundelete':
       +file.c:324: warning: passing arg 1 of `bufread' from incompatible pointer type
       +file.c: In function `filereadc':
       +file.c:339: warning: passing arg 1 of `bufread' from incompatible pointer type
       +file.c: In function `fileload':
       +file.c:405: warning: passing arg 1 of `bufload' from incompatible pointer type
       +file.c: In function `fileundo':
       +file.c:528: warning: passing arg 1 of `bufdelete' from incompatible pointer type
       +file.c:546: warning: passing arg 1 of `bufinsert' from incompatible pointer type
       +file.c: In function `fileclose':
       +file.c:604: warning: passing arg 1 of `bufclose' from incompatible pointer type
       +io.c: In function `readio':
       +io.c:90: warning: passing arg 1 of `bufload' from incompatible pointer type
       +io.c: In function `writeio':
       +io.c:152: warning: passing arg 1 of `bufread' from incompatible pointer type
       +mesg.c: In function `inmesg':
       +mesg.c:248: warning: passing arg 1 of `bufread' from incompatible pointer type
       +mesg.c: In function `snarf':
       +mesg.c:568: warning: passing arg 1 of `bufread' from incompatible pointer type
       +mesg.c: In function `setgenstr':
       +mesg.c:612: warning: passing arg 1 of `bufread' from incompatible pointer type
       +sam.c: In function `readcmd':
       +sam.c:496: warning: passing arg 1 of `bufread' from incompatible pointer type
       +sam.c: In function `copy':
       +sam.c:676: warning: passing arg 1 of `bufread' from incompatible pointer type
       +xec.c: In function `s_cmd':
       +xec.c:234: warning: passing arg 1 of `bufread' from incompatible pointer type
       +xec.c:243: warning: passing arg 1 of `bufread' from incompatible pointer type
       +xec.c: In function `display':
       +xec.c:401: warning: passing arg 1 of `bufread' from incompatible pointer type
   DIR diff --git a/sam/error.c b/sam/error.c
       @@ -0,0 +1,144 @@
       +#include "sam.h"
       +
       +static char *emsg[]={
       +        /* error_s */
       +        "can't open",
       +        "can't create",
       +        "not in menu:",
       +        "changes to",
       +        "I/O error:",
       +        "can't write while changing:",
       +        /* error_c */
       +        "unknown command",
       +        "no operand for",
       +        "bad delimiter",
       +        /* error */
       +        "can't fork",
       +        "interrupt",
       +        "address",
       +        "search",
       +        "pattern",
       +        "newline expected",
       +        "blank expected",
       +        "pattern expected",
       +        "can't nest X or Y",
       +        "unmatched `}'",
       +        "command takes no address",
       +        "addresses overlap",
       +        "substitution",
       +        "& match too long",
       +        "bad \\ in rhs",
       +        "address range",
       +        "changes not in sequence",
       +        "addresses out of order",
       +        "no file name",
       +        "unmatched `('",
       +        "unmatched `)'",
       +        "malformed `[]'",
       +        "malformed regexp",
       +        "reg. exp. list overflow",
       +        "plan 9 command",
       +        "can't pipe",
       +        "no current file",
       +        "string too long",
       +        "changed files",
       +        "empty string",
       +        "file search",
       +        "non-unique match for \"\"",
       +        "tag match too long",
       +        "too many subexpressions",
       +        "temporary file too large",
       +        "file is append-only",
       +        "no destination for plumb message",
       +        "internal read error in buffer load"
       +};
       +static char *wmsg[]={
       +        /* warn_s */
       +        "duplicate file name",
       +        "no such file",
       +        "write might change good version of",
       +        /* warn_S */
       +        "files might be aliased",
       +        /* warn */
       +        "null characters elided",
       +        "can't run pwd",
       +        "last char not newline",
       +        "exit status not 0"
       +};
       +
       +void
       +error(Err s)
       +{
       +        char buf[512];
       +
       +        sprint(buf, "?%s", emsg[s]);
       +        hiccough(buf);
       +}
       +
       +void
       +error_s(Err s, char *a)
       +{
       +        char buf[512];
       +
       +        sprint(buf, "?%s \"%s\"", emsg[s], a);
       +        hiccough(buf);
       +}
       +
       +void
       +error_r(Err s, char *a)
       +{
       +        char buf[512];
       +
       +        sprint(buf, "?%s \"%s\": %r", emsg[s], a);
       +        hiccough(buf);
       +}
       +
       +void
       +error_c(Err s, int c)
       +{
       +        char buf[512];
       +
       +        sprint(buf, "?%s `%C'", emsg[s], c);
       +        hiccough(buf);
       +}
       +
       +void
       +warn(Warn s)
       +{
       +        dprint("?warning: %s\n", wmsg[s]);
       +}
       +
       +void
       +warn_S(Warn s, String *a)
       +{
       +        print_s(wmsg[s], a);
       +}
       +
       +void
       +warn_SS(Warn s, String *a, String *b)
       +{
       +        print_ss(wmsg[s], a, b);
       +}
       +
       +void
       +warn_s(Warn s, char *a)
       +{
       +        dprint("?warning: %s `%s'\n", wmsg[s], a);
       +}
       +
       +void
       +termwrite(char *s)
       +{
       +        String *p;
       +
       +        if(downloaded){
       +                p = tmpcstr(s);
       +                if(cmd)
       +                        loginsert(cmd, cmdpt, p->s, p->n);
       +                else
       +                        Strinsert(&cmdstr, p, cmdstr.n);
       +                cmdptadv += p->n;
       +                free(p);
       +        }else
       +                Write(2, s, strlen(s));
       +}
   DIR diff --git a/sam/errors.h b/sam/errors.h
       @@ -0,0 +1,65 @@
       +typedef enum Err{
       +        /* error_s */
       +        Eopen,
       +        Ecreate,
       +        Emenu,
       +        Emodified,
       +        Eio,
       +        Ewseq,
       +        /* error_c */
       +        Eunk,
       +        Emissop,
       +        Edelim,
       +        /* error */
       +        Efork,
       +        Eintr,
       +        Eaddress,
       +        Esearch,
       +        Epattern,
       +        Enewline,
       +        Eblank,
       +        Enopattern,
       +        EnestXY,
       +        Enolbrace,
       +        Enoaddr,
       +        Eoverlap,
       +        Enosub,
       +        Elongrhs,
       +        Ebadrhs,
       +        Erange,
       +        Esequence,
       +        Eorder,
       +        Enoname,
       +        Eleftpar,
       +        Erightpar,
       +        Ebadclass,
       +        Ebadregexp,
       +        Eoverflow,
       +        Enocmd,
       +        Epipe,
       +        Enofile,
       +        Etoolong,
       +        Echanges,
       +        Eempty,
       +        Efsearch,
       +        Emanyfiles,
       +        Elongtag,
       +        Esubexp,
       +        Etmpovfl,
       +        Eappend,
       +        Ecantplumb,
       +        Ebufload
       +}Err;
       +typedef enum Warn{
       +        /* warn_s */
       +        Wdupname,
       +        Wfile,
       +        Wdate,
       +        /* warn_ss */
       +        Wdupfile,
       +        /* warn */
       +        Wnulls,
       +        Wpwd,
       +        Wnotnewline,
       +        Wbadstatus
       +}Warn;
   DIR diff --git a/sam/file.c b/sam/file.c
       @@ -0,0 +1,610 @@
       +#include "sam.h"
       +
       +/*
       + * Structure of Undo list:
       + *         The Undo structure follows any associated data, so the list
       + *        can be read backwards: read the structure, then read whatever
       + *        data is associated (insert string, file name) and precedes it.
       + *        The structure includes the previous value of the modify bit
       + *        and a sequence number; successive Undo structures with the
       + *        same sequence number represent simultaneous changes.
       + */
       +
       +typedef struct Undo Undo;
       +typedef struct Merge Merge;
       +
       +struct Undo
       +{
       +        short        type;                /* Delete, Insert, Filename, Dot, Mark */
       +        short        mod;                /* modify bit */
       +        uint        seq;                /* sequence number */
       +        uint        p0;                /* location of change (unused in f) */
       +        uint        n;                /* # runes in string or file name */
       +};
       +
       +struct Merge
       +{
       +        File        *f;
       +        uint        seq;                /* of logged change */
       +        uint        p0;                /* location of change (unused in f) */
       +        uint        n;                /* # runes to delete */
       +        uint        nbuf;                /* # runes to insert */
       +        Rune        buf[RBUFSIZE];
       +};
       +
       +enum
       +{
       +        Maxmerge = 50,
       +        Undosize = sizeof(Undo)/sizeof(Rune)
       +};
       +
       +static Merge        merge;
       +
       +File*
       +fileopen(void)
       +{
       +        File *f;
       +
       +        f = emalloc(sizeof(File));
       +        f->dot.f = f;
       +        f->ndot.f = f;
       +        f->seq = 0;
       +        f->mod = FALSE;
       +        f->unread = TRUE;
       +        Strinit0(&f->name);
       +        return f;
       +}
       +
       +int
       +fileisdirty(File *f)
       +{
       +        return f->seq != f->cleanseq;
       +}
       +
       +static void
       +wrinsert(Buffer *delta, int seq, int mod, uint p0, Rune *s, uint ns)
       +{
       +        Undo u;
       +
       +        u.type = Insert;
       +        u.mod = mod;
       +        u.seq = seq;
       +        u.p0 = p0;
       +        u.n = ns;
       +        bufinsert(delta, delta->nc, s, ns);
       +        bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
       +}
       +
       +static void
       +wrdelete(Buffer *delta, int seq, int mod, uint p0, uint p1)
       +{
       +        Undo u;
       +
       +        u.type = Delete;
       +        u.mod = mod;
       +        u.seq = seq;
       +        u.p0 = p0;
       +        u.n = p1 - p0;
       +        bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
       +}
       +
       +void
       +flushmerge(void)
       +{
       +        File *f;
       +
       +        f = merge.f;
       +        if(f == nil)
       +                return;
       +        if(merge.seq != f->seq)
       +                panic("flushmerge seq mismatch");
       +        if(merge.n != 0)
       +                wrdelete(&f->epsilon, f->seq, TRUE, merge.p0, merge.p0+merge.n);
       +        if(merge.nbuf != 0)
       +                wrinsert(&f->epsilon, f->seq, TRUE, merge.p0+merge.n, merge.buf, merge.nbuf);
       +        merge.f = nil;
       +        merge.n = 0;
       +        merge.nbuf = 0;
       +}
       +
       +void
       +mergeextend(File *f, uint p0)
       +{
       +        uint mp0n;
       +
       +        mp0n = merge.p0+merge.n;
       +        if(mp0n != p0){
       +                bufread(&f->b, mp0n, merge.buf+merge.nbuf, p0-mp0n);
       +                merge.nbuf += p0-mp0n;
       +                merge.n = p0-merge.p0;
       +        }
       +}
       +
       +/*
       + * like fileundelete, but get the data from arguments
       + */
       +void
       +loginsert(File *f, uint p0, Rune *s, uint ns)
       +{
       +        if(f->rescuing)
       +                return;
       +        if(ns == 0)
       +                return;
       +        if(ns<0 || ns>STRSIZE)
       +                panic("loginsert");
       +        if(f->seq < seq)
       +                filemark(f);
       +        if(p0 < f->hiposn)
       +                error(Esequence);
       +
       +        if(merge.f != f
       +        || p0-(merge.p0+merge.n)>Maxmerge                        /* too far */
       +        || merge.nbuf+((p0+ns)-(merge.p0+merge.n))>=RBUFSIZE)        /* too long */
       +                flushmerge();
       +
       +        if(ns>=RBUFSIZE){
       +                if(!(merge.n == 0 && merge.nbuf == 0 && merge.f == nil))
       +                        panic("loginsert bad merge state");
       +                wrinsert(&f->epsilon, f->seq, TRUE, p0, s, ns);
       +        }else{
       +                if(merge.f != f){
       +                        merge.f = f;
       +                        merge.p0 = p0;
       +                        merge.seq = f->seq;
       +                }
       +                mergeextend(f, p0);
       +
       +                /* append string to merge */
       +                runemove(merge.buf+merge.nbuf, s, ns);
       +                merge.nbuf += ns;
       +        }
       +
       +        f->hiposn = p0;
       +        if(!f->unread && !f->mod)
       +                state(f, Dirty);
       +}
       +
       +void
       +logdelete(File *f, uint p0, uint p1)
       +{
       +        if(f->rescuing)
       +                return;
       +        if(p0 == p1)
       +                return;
       +        if(f->seq < seq)
       +                filemark(f);
       +        if(p0 < f->hiposn)
       +                error(Esequence);
       +
       +        if(merge.f != f
       +        || p0-(merge.p0+merge.n)>Maxmerge                        /* too far */
       +        || merge.nbuf+(p0-(merge.p0+merge.n))>=RBUFSIZE){        /* too long */
       +                flushmerge();
       +                merge.f = f;
       +                merge.p0 = p0;
       +                merge.seq = f->seq;
       +        }
       +
       +        mergeextend(f, p0);
       +
       +        /* add to deletion */
       +        merge.n = p1-merge.p0;
       +
       +        f->hiposn = p1;
       +        if(!f->unread && !f->mod)
       +                state(f, Dirty);
       +}
       +
       +/*
       + * like fileunsetname, but get the data from arguments
       + */
       +void
       +logsetname(File *f, String *s)
       +{
       +        Undo u;
       +        Buffer *delta;
       +
       +        if(f->rescuing)
       +                return;
       +
       +        if(f->unread){        /* This is setting initial file name */
       +                filesetname(f, s);
       +                return;
       +        }
       +
       +        if(f->seq < seq)
       +                filemark(f);
       +
       +        /* undo a file name change by restoring old name */
       +        delta = &f->epsilon;
       +        u.type = Filename;
       +        u.mod = TRUE;
       +        u.seq = f->seq;
       +        u.p0 = 0;        /* unused */
       +        u.n = s->n;
       +        if(s->n)
       +                bufinsert(delta, delta->nc, s->s, s->n);
       +        bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
       +        if(!f->unread && !f->mod)
       +                state(f, Dirty);
       +}
       +
       +#ifdef NOTEXT
       +File*
       +fileaddtext(File *f, Text *t)
       +{
       +        if(f == nil){
       +                f = emalloc(sizeof(File));
       +                f->unread = TRUE;
       +        }
       +        f->text = realloc(f->text, (f->ntext+1)*sizeof(Text*));
       +        f->text[f->ntext++] = t;
       +        f->curtext = t;
       +        return f;
       +}
       +
       +void
       +filedeltext(File *f, Text *t)
       +{
       +        int i;
       +
       +        for(i=0; i<f->ntext; i++)
       +                if(f->text[i] == t)
       +                        goto Found;
       +        panic("can't find text in filedeltext");
       +
       +    Found:
       +        f->ntext--;
       +        if(f->ntext == 0){
       +                fileclose(f);
       +                return;
       +        }
       +        memmove(f->text+i, f->text+i+1, (f->ntext-i)*sizeof(Text*));
       +        if(f->curtext == t)
       +                f->curtext = f->text[0];
       +}
       +#endif
       +
       +void
       +fileuninsert(File *f, Buffer *delta, uint p0, uint ns)
       +{
       +        Undo u;
       +
       +        /* undo an insertion by deleting */
       +        u.type = Delete;
       +        u.mod = f->mod;
       +        u.seq = f->seq;
       +        u.p0 = p0;
       +        u.n = ns;
       +        bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
       +}
       +
       +void
       +fileundelete(File *f, Buffer *delta, uint p0, uint p1)
       +{
       +        Undo u;
       +        Rune *buf;
       +        uint i, n;
       +
       +        /* undo a deletion by inserting */
       +        u.type = Insert;
       +        u.mod = f->mod;
       +        u.seq = f->seq;
       +        u.p0 = p0;
       +        u.n = p1-p0;
       +        buf = fbufalloc();
       +        for(i=p0; i<p1; i+=n){
       +                n = p1 - i;
       +                if(n > RBUFSIZE)
       +                        n = RBUFSIZE;
       +                bufread(&f->b, i, buf, n);
       +                bufinsert(delta, delta->nc, buf, n);
       +        }
       +        fbuffree(buf);
       +        bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
       +
       +}
       +
       +int
       +filereadc(File *f, uint q)
       +{
       +        Rune r;
       +
       +        if(q >= f->b.nc)
       +                return -1;
       +        bufread(&f->b, q, &r, 1);
       +        return r;
       +}
       +
       +void
       +filesetname(File *f, String *s)
       +{
       +        if(!f->unread)        /* This is setting initial file name */
       +                fileunsetname(f, &f->delta);
       +        Strduplstr(&f->name, s);
       +        sortname(f);
       +        f->unread = TRUE;
       +}
       +
       +void
       +fileunsetname(File *f, Buffer *delta)
       +{
       +        String s;
       +        Undo u;
       +
       +        /* undo a file name change by restoring old name */
       +        u.type = Filename;
       +        u.mod = f->mod;
       +        u.seq = f->seq;
       +        u.p0 = 0;        /* unused */
       +        Strinit(&s);
       +        Strduplstr(&s, &f->name);
       +        fullname(&s);
       +        u.n = s.n;
       +        if(s.n)
       +                bufinsert(delta, delta->nc, s.s, s.n);
       +        bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
       +        Strclose(&s);
       +}
       +
       +void
       +fileunsetdot(File *f, Buffer *delta, Range dot)
       +{
       +        Undo u;
       +
       +        u.type = Dot;
       +        u.mod = f->mod;
       +        u.seq = f->seq;
       +        u.p0 = dot.p1;
       +        u.n = dot.p2 - dot.p1;
       +        bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
       +}
       +
       +void
       +fileunsetmark(File *f, Buffer *delta, Range mark)
       +{
       +        Undo u;
       +
       +        u.type = Mark;
       +        u.mod = f->mod;
       +        u.seq = f->seq;
       +        u.p0 = mark.p1;
       +        u.n = mark.p2 - mark.p1;
       +        bufinsert(delta, delta->nc, (Rune*)&u, Undosize);
       +}
       +
       +uint
       +fileload(File *f, uint p0, int fd, int *nulls)
       +{
       +        if(f->seq > 0)
       +                panic("undo in file.load unimplemented");
       +        return bufload(&f->b, p0, fd, nulls);
       +}
       +
       +int
       +fileupdate(File *f, int notrans, int toterm)
       +{
       +        uint p1, p2;
       +        int mod;
       +
       +        if(f->rescuing)
       +                return FALSE;
       +
       +        flushmerge();
       +
       +        /*
       +         * fix the modification bit
       +         * subtle point: don't save it away in the log.
       +         *
       +         * if another change is made, the correct f->mod
       +         * state is saved  in the undo log by filemark
       +         * when setting the dot and mark.
       +         *
       +         * if the change is undone, the correct state is
       +         * saved from f in the fileun... routines.
       +         */
       +        mod = f->mod;
       +        f->mod = f->prevmod;
       +        if(f == cmd)
       +                notrans = TRUE;
       +        else{
       +                fileunsetdot(f, &f->delta, f->prevdot);
       +                fileunsetmark(f, &f->delta, f->prevmark);
       +        }
       +        f->dot = f->ndot;
       +        fileundo(f, FALSE, !notrans, &p1, &p2, toterm);
       +        f->mod = mod;
       +
       +        if(f->delta.nc == 0)
       +                f->seq = 0;
       +
       +        if(f == cmd)
       +                return FALSE;
       +
       +        if(f->mod){
       +                f->closeok = 0;
       +                quitok = 0;
       +        }else
       +                f->closeok = 1;
       +        return TRUE;
       +}
       +
       +long
       +prevseq(Buffer *b)
       +{
       +        Undo u;
       +        uint up;
       +
       +        up = b->nc;
       +        if(up == 0)
       +                return 0;
       +        up -= Undosize;
       +        bufread(b, up, (Rune*)&u, Undosize);
       +        return u.seq;
       +}
       +
       +long
       +undoseq(File *f, int isundo)
       +{
       +        if(isundo)
       +                return f->seq;
       +
       +        return prevseq(&f->epsilon);
       +}
       +
       +void
       +fileundo(File *f, int isundo, int canredo, uint *q0p, uint *q1p, int flag)
       +{
       +        Undo u;
       +        Rune *buf;
       +        uint i, n, up;
       +        uint stop;
       +        Buffer *delta, *epsilon;
       +
       +        if(isundo){
       +                /* undo; reverse delta onto epsilon, seq decreases */
       +                delta = &f->delta;
       +                epsilon = &f->epsilon;
       +                stop = f->seq;
       +        }else{
       +                /* redo; reverse epsilon onto delta, seq increases */
       +                delta = &f->epsilon;
       +                epsilon = &f->delta;
       +                stop = 0;        /* don't know yet */
       +        }
       +
       +        raspstart(f);
       +        while(delta->nc > 0){
       +                /* rasp and buffer are in sync; sync with wire if needed */
       +                if(needoutflush())
       +                        raspflush(f);
       +                up = delta->nc-Undosize;
       +                bufread(delta, up, (Rune*)&u, Undosize);
       +                if(isundo){
       +                        if(u.seq < stop){
       +                                f->seq = u.seq;
       +                                raspdone(f, flag);
       +                                return;
       +                        }
       +                }else{
       +                        if(stop == 0)
       +                                stop = u.seq;
       +                        if(u.seq > stop){
       +                                raspdone(f, flag);
       +                                return;
       +                        }
       +                }
       +                switch(u.type){
       +                default:
       +                        panic("undo unknown u.type");
       +                        break;
       +
       +                case Delete:
       +                        f->seq = u.seq;
       +                        if(canredo)
       +                                fileundelete(f, epsilon, u.p0, u.p0+u.n);
       +                        f->mod = u.mod;
       +                        bufdelete(&f->b, u.p0, u.p0+u.n);
       +                        raspdelete(f, u.p0, u.p0+u.n, flag);
       +                        *q0p = u.p0;
       +                        *q1p = u.p0;
       +                        break;
       +
       +                case Insert:
       +                        f->seq = u.seq;
       +                        if(canredo)
       +                                fileuninsert(f, epsilon, u.p0, u.n);
       +                        f->mod = u.mod;
       +                        up -= u.n;
       +                        buf = fbufalloc();
       +                        for(i=0; i<u.n; i+=n){
       +                                n = u.n - i;
       +                                if(n > RBUFSIZE)
       +                                        n = RBUFSIZE;
       +                                bufread(delta, up+i, buf, n);
       +                                bufinsert(&f->b, u.p0+i, buf, n);
       +                                raspinsert(f, u.p0+i, buf, n, flag);
       +                        }
       +                        fbuffree(buf);
       +                        *q0p = u.p0;
       +                        *q1p = u.p0+u.n;
       +                        break;
       +
       +                case Filename:
       +                        f->seq = u.seq;
       +                        if(canredo)
       +                                fileunsetname(f, epsilon);
       +                        f->mod = u.mod;
       +                        up -= u.n;
       +
       +                        Strinsure(&f->name, u.n+1);
       +                        bufread(delta, up, f->name.s, u.n);
       +                        f->name.s[u.n] = 0;
       +                        f->name.n = u.n;
       +                        fixname(&f->name);
       +                        sortname(f);
       +                        break;
       +                case Dot:
       +                        f->seq = u.seq;
       +                        if(canredo)
       +                                fileunsetdot(f, epsilon, f->dot.r);
       +                        f->mod = u.mod;
       +                        f->dot.r.p1 = u.p0;
       +                        f->dot.r.p2 = u.p0 + u.n;
       +                        break;
       +                case Mark:
       +                        f->seq = u.seq;
       +                        if(canredo)
       +                                fileunsetmark(f, epsilon, f->mark);
       +                        f->mod = u.mod;
       +                        f->mark.p1 = u.p0;
       +                        f->mark.p2 = u.p0 + u.n;
       +                        break;
       +                }
       +                bufdelete(delta, up, delta->nc);
       +        }
       +        if(isundo)
       +                f->seq = 0;
       +        raspdone(f, flag);
       +}
       +
       +void
       +filereset(File *f)
       +{
       +        bufreset(&f->delta);
       +        bufreset(&f->epsilon);
       +        f->seq = 0;
       +}
       +
       +void
       +fileclose(File *f)
       +{
       +        Strclose(&f->name);
       +        bufclose(&f->b);
       +        bufclose(&f->delta);
       +        bufclose(&f->epsilon);
       +        if(f->rasp)
       +                listfree(f->rasp);
       +        free(f);
       +}
       +
       +void
       +filemark(File *f)
       +{
       +
       +        if(f->unread)
       +                return;
       +        if(f->epsilon.nc)
       +                bufdelete(&f->epsilon, 0, f->epsilon.nc);
       +
       +        if(f != cmd){
       +                f->prevdot = f->dot.r;
       +                f->prevmark = f->mark;
       +                f->prevseq = f->seq;
       +                f->prevmod = f->mod;
       +        }
       +
       +        f->ndot = f->dot;
       +        f->seq = seq;
       +        f->hiposn = 0;
       +}
   DIR diff --git a/sam/io.c b/sam/io.c
       @@ -0,0 +1,278 @@
       +#include "sam.h"
       +
       +#define        NSYSFILE        3
       +#define        NOFILE                128
       +
       +void
       +checkqid(File *f)
       +{
       +        int i, w;
       +        File *g;
       +
       +        w = whichmenu(f);
       +        for(i=1; i<file.nused; i++){
       +                g = file.filepptr[i];
       +                if(w == i)
       +                        continue;
       +                if(f->dev==g->dev && f->qidpath==g->qidpath)
       +                        warn_SS(Wdupfile, &f->name, &g->name);
       +        }
       +}
       +
       +void
       +writef(File *f)
       +{
       +        Posn n;
       +        char *name;
       +        int i, samename, newfile;
       +        ulong dev;
       +        uvlong qid;
       +        long mtime, appendonly, length;
       +
       +        newfile = 0;
       +        samename = Strcmp(&genstr, &f->name) == 0;
       +        name = Strtoc(&f->name);
       +        i = statfile(name, &dev, &qid, &mtime, 0, 0);
       +        if(i == -1)
       +                newfile++;
       +        else if(samename &&
       +                (f->dev!=dev || f->qidpath!=qid || f->mtime<mtime)){
       +                f->dev = dev;
       +                f->qidpath = qid;
       +                f->mtime = mtime;
       +                warn_S(Wdate, &genstr);
       +                return;
       +        }
       +        if(genc)
       +                free(genc);
       +        genc = Strtoc(&genstr);
       +        if((io=create(genc, 1, 0666L)) < 0)
       +                error_r(Ecreate, genc);
       +        dprint("%s: ", genc);
       +        if(statfd(io, 0, 0, 0, &length, &appendonly) > 0 && appendonly && length>0)
       +                error(Eappend);
       +        n = writeio(f);
       +        if(f->name.s[0]==0 || samename){
       +                if(addr.r.p1==0 && addr.r.p2==f->b.nc)
       +                        f->cleanseq = f->seq;
       +                state(f, f->cleanseq==f->seq? Clean : Dirty);
       +        }
       +        if(newfile)
       +                dprint("(new file) ");
       +        if(addr.r.p2>0 && filereadc(f, addr.r.p2-1)!='\n')
       +                warn(Wnotnewline);
       +        closeio(n);
       +        if(f->name.s[0]==0 || samename){
       +                if(statfile(name, &dev, &qid, &mtime, 0, 0) > 0){
       +                        f->dev = dev;
       +                        f->qidpath = qid;
       +                        f->mtime = mtime;
       +                        checkqid(f);
       +                }
       +        }
       +}
       +
       +Posn
       +readio(File *f, int *nulls, int setdate, int toterm)
       +{
       +        int n, b, w;
       +        Rune *r;
       +        Posn nt;
       +        Posn p = addr.r.p2;
       +        ulong dev;
       +        uvlong qid;
       +        long mtime;
       +        char buf[BLOCKSIZE+1], *s;
       +
       +        *nulls = FALSE;
       +        b = 0;
       +        if(f->unread){
       +                nt = bufload(&f->b, 0, io, nulls);
       +                if(toterm)
       +                        raspload(f);
       +        }else
       +                for(nt = 0; (n = read(io, buf+b, BLOCKSIZE-b))>0; nt+=(r-genbuf)){
       +                        n += b;
       +                        b = 0;
       +                        r = genbuf;
       +                        s = buf;
       +                        while(n > 0){
       +                                if((*r = *(uchar*)s) < Runeself){
       +                                        if(*r)
       +                                                r++;
       +                                        else
       +                                                *nulls = TRUE;
       +                                        --n;
       +                                        s++;
       +                                        continue;
       +                                }
       +                                if(fullrune(s, n)){
       +                                        w = chartorune(r, s);
       +                                        if(*r)
       +                                                r++;
       +                                        else
       +                                                *nulls = TRUE;
       +                                        n -= w;
       +                                        s += w;
       +                                        continue;
       +                                }
       +                                b = n;
       +                                memmove(buf, s, b);
       +                                break;
       +                        }
       +                        loginsert(f, p, genbuf, r-genbuf);
       +                }
       +        if(b)
       +                *nulls = TRUE;
       +        if(*nulls)
       +                warn(Wnulls);
       +        if(setdate){
       +                if(statfd(io, &dev, &qid, &mtime, 0, 0) > 0){
       +                        f->dev = dev;
       +                        f->qidpath = qid;
       +                        f->mtime = mtime;
       +                        checkqid(f);
       +                }
       +        }
       +        return nt;
       +}
       +
       +Posn
       +writeio(File *f)
       +{
       +        int m, n;
       +        Posn p = addr.r.p1;
       +        char *c;
       +
       +        while(p < addr.r.p2){
       +                if(addr.r.p2-p>BLOCKSIZE)
       +                        n = BLOCKSIZE;
       +                else
       +                        n = addr.r.p2-p;
       +                bufread(&f->b, p, genbuf, n);
       +                c = Strtoc(tmprstr(genbuf, n));
       +                m = strlen(c);
       +                if(Write(io, c, m) != m){
       +                        free(c);
       +                        if(p > 0)
       +                                p += n;
       +                        break;
       +                }
       +                free(c);
       +                p += n;
       +        }
       +        return p-addr.r.p1;
       +}
       +void
       +closeio(Posn p)
       +{
       +        close(io);
       +        io = 0;
       +        if(p >= 0)
       +                dprint("#%lud\n", p);
       +}
       +
       +int        remotefd0 = 0;
       +int        remotefd1 = 1;
       +
       +void
       +bootterm(char *machine, char **argv)
       +{
       +        int ph2t[2], pt2h[2];
       +
       +        if(machine){
       +                dup(remotefd0, 0);
       +                dup(remotefd1, 1);
       +                close(remotefd0);
       +                close(remotefd1);
       +                argv[0] = "samterm";
       +                execvp(samterm, argv);
       +                fprint(2, "can't exec %s: %r\n", samterm);
       +                _exits("damn");
       +        }
       +        if(pipe(ph2t)==-1 || pipe(pt2h)==-1)
       +                panic("pipe");
       +        switch(fork()){
       +        case 0:
       +                dup(ph2t[0], 0);
       +                dup(pt2h[1], 1);
       +                close(ph2t[0]);
       +                close(ph2t[1]);
       +                close(pt2h[0]);
       +                close(pt2h[1]);
       +                argv[0] = "samterm";
       +                execvp(samterm, argv);
       +                fprint(2, "can't exec: ");
       +                perror(samterm);
       +                _exits("damn");
       +        case -1:
       +                panic("can't fork samterm");
       +        }
       +        dup(pt2h[0], 0);
       +        dup(ph2t[1], 1);
       +        close(ph2t[0]);
       +        close(ph2t[1]);
       +        close(pt2h[0]);
       +        close(pt2h[1]);
       +}
       +
       +void
       +connectto(char *machine, char **argv)
       +{
       +        int p1[2], p2[2];
       +        char **av;
       +        int ac;
       +
       +        /* count args */
       +        for(av = argv; *av; av++)
       +                ;
       +        av = malloc(sizeof(char*)*((av-argv) + 5));
       +        if(av == nil){
       +                dprint("out of memory\n");
       +                exits("fork/exec");
       +        }
       +        ac = 0;
       +        av[ac++] = RX;
       +        av[ac++] = machine;
       +        av[ac++] = rsamname;
       +        av[ac++] = "-R";
       +        while(*argv)
       +                av[ac++] = *argv++;
       +        av[ac] = 0;
       +        if(pipe(p1)<0 || pipe(p2)<0){
       +                dprint("can't pipe\n");
       +                exits("pipe");
       +        }
       +        remotefd0 = p1[0];
       +        remotefd1 = p2[1];
       +        switch(fork()){
       +        case 0:
       +                dup(p2[0], 0);
       +                dup(p1[1], 1);
       +                close(p1[0]);
       +                close(p1[1]);
       +                close(p2[0]);
       +                close(p2[1]);
       +                execvp(RXPATH, av);
       +                dprint("can't exec %s\n", RXPATH);
       +                exits("exec");
       +
       +        case -1:
       +                dprint("can't fork\n");
       +                exits("fork");
       +        }
       +        free(av);
       +        close(p1[1]);
       +        close(p2[0]);
       +}
       +
       +void
       +startup(char *machine, int Rflag, char **argv, char **files)
       +{
       +        if(machine)
       +                connectto(machine, files);
       +        if(!Rflag)
       +                bootterm(machine, argv);
       +        downloaded = 1;
       +        outTs(Hversion, VERSION);
       +}
   DIR diff --git a/sam/list.c b/sam/list.c
       @@ -0,0 +1,96 @@
       +#include "sam.h"
       +
       +/*
       + * Check that list has room for one more element.
       + */
       +static void
       +growlist(List *l, int esize)
       +{
       +        uchar *p;
       +
       +        if(l->listptr == nil || l->nalloc == 0){
       +                l->nalloc = INCR;
       +                l->listptr = emalloc(INCR*esize);
       +                l->nused = 0;
       +        }
       +        else if(l->nused == l->nalloc){
       +                p = erealloc(l->listptr, (l->nalloc+INCR)*esize);
       +                l->listptr = p;
       +                memset(p+l->nalloc*esize, 0, INCR*esize);
       +                l->nalloc += INCR;
       +        }
       +}
       +
       +/*
       + * Remove the ith element from the list
       + */
       +void
       +dellist(List *l, int i)
       +{
       +        Posn *pp;
       +        void **vpp;
       +
       +        l->nused--;
       +
       +        switch(l->type){
       +        case 'P':
       +                pp = l->posnptr+i;
       +                memmove(pp, pp+1, (l->nused-i)*sizeof(*pp));
       +                break;
       +        case 'p':
       +                vpp = l->voidpptr+i;
       +                memmove(vpp, vpp+1, (l->nused-i)*sizeof(*vpp));
       +                break;
       +        }
       +}
       +
       +/*
       + * Add a new element, whose position is i, to the list
       + */
       +void
       +inslist(List *l, int i, ...)
       +{
       +        Posn *pp;
       +        void **vpp;
       +        va_list list;
       +
       +
       +        va_start(list, i);
       +        switch(l->type){
       +        case 'P':
       +                growlist(l, sizeof(*pp));
       +                pp = l->posnptr+i;
       +                memmove(pp+1, pp, (l->nused-i)*sizeof(*pp));
       +                *pp = va_arg(list, Posn);
       +                break;
       +        case 'p':
       +                growlist(l, sizeof(*vpp));
       +                vpp = l->voidpptr+i;
       +                memmove(vpp+1, vpp, (l->nused-i)*sizeof(*vpp));
       +                *vpp = va_arg(list, void*);
       +                break;
       +        }
       +        va_end(list);
       +
       +        l->nused++;
       +}
       +
       +void
       +listfree(List *l)
       +{
       +        free(l->listptr);
       +        free(l);
       +}
       +
       +List*
       +listalloc(int type)
       +{
       +        List *l;
       +
       +        l = emalloc(sizeof(List));
       +        l->type = type;
       +        l->nalloc = 0;
       +        l->nused = 0;
       +
       +        return l;
       +}
   DIR diff --git a/sam/mesg.c b/sam/mesg.c
       @@ -0,0 +1,848 @@
       +#include "sam.h"
       +Header        h;
       +uchar        indata[DATASIZE];
       +uchar        outdata[2*DATASIZE+3];        /* room for overflow message */
       +uchar        *inp;
       +uchar        *outp;
       +uchar        *outmsg = outdata;
       +Posn        cmdpt;
       +Posn        cmdptadv;
       +Buffer        snarfbuf;
       +int        waitack;
       +int        outbuffered;
       +int        tversion;
       +
       +int        inshort(void);
       +long        inlong(void);
       +vlong        invlong(void);
       +int        inmesg(Tmesg);
       +
       +void        outshort(int);
       +void        outlong(long);
       +void        outvlong(vlong);
       +void        outcopy(int, void*);
       +void        outsend(void);
       +void        outstart(Hmesg);
       +
       +void        setgenstr(File*, Posn, Posn);
       +
       +#ifdef DEBUG
       +char *hname[] = {
       +        [Hversion]        "Hversion",
       +        [Hbindname]        "Hbindname",
       +        [Hcurrent]        "Hcurrent",
       +        [Hnewname]        "Hnewname",
       +        [Hmovname]        "Hmovname",
       +        [Hgrow]                "Hgrow",
       +        [Hcheck0]        "Hcheck0",
       +        [Hcheck]        "Hcheck",
       +        [Hunlock]        "Hunlock",
       +        [Hdata]                "Hdata",
       +        [Horigin]        "Horigin",
       +        [Hunlockfile]        "Hunlockfile",
       +        [Hsetdot]        "Hsetdot",
       +        [Hgrowdata]        "Hgrowdata",
       +        [Hmoveto]        "Hmoveto",
       +        [Hclean]        "Hclean",
       +        [Hdirty]        "Hdirty",
       +        [Hcut]                "Hcut",
       +        [Hsetpat]        "Hsetpat",
       +        [Hdelname]        "Hdelname",
       +        [Hclose]        "Hclose",
       +        [Hsetsnarf]        "Hsetsnarf",
       +        [Hsnarflen]        "Hsnarflen",
       +        [Hack]                "Hack",
       +        [Hexit]                "Hexit",
       +//        [Hplumb]                "Hplumb"
       +};
       +
       +char *tname[] = {
       +        [Tversion]        "Tversion",
       +        [Tstartcmdfile]        "Tstartcmdfile",
       +        [Tcheck]        "Tcheck",
       +        [Trequest]        "Trequest",
       +        [Torigin]        "Torigin",
       +        [Tstartfile]        "Tstartfile",
       +        [Tworkfile]        "Tworkfile",
       +        [Ttype]                "Ttype",
       +        [Tcut]                "Tcut",
       +        [Tpaste]        "Tpaste",
       +        [Tsnarf]        "Tsnarf",
       +        [Tstartnewfile]        "Tstartnewfile",
       +        [Twrite]        "Twrite",
       +        [Tclose]        "Tclose",
       +        [Tlook]                "Tlook",
       +        [Tsearch]        "Tsearch",
       +        [Tsend]                "Tsend",
       +        [Tdclick]        "Tdclick",
       +        [Tstartsnarf]        "Tstartsnarf",
       +        [Tsetsnarf]        "Tsetsnarf",
       +        [Tack]                "Tack",
       +        [Texit]                "Texit",
       +//        [Tplumb]                "Tplumb"
       +};
       +
       +void
       +journal(int out, char *s)
       +{
       +        static int fd = 0;
       +
       +        if(fd <= 0)
       +                fd = create("/tmp/sam.out", 1, 0666L);
       +        fprint(fd, "%s%s\n", out? "out: " : "in:  ", s);
       +}
       +
       +void
       +journaln(int out, long n)
       +{
       +        char buf[32];
       +
       +        snprint(buf, sizeof buf, "%ld", n);
       +        journal(out, buf);
       +}
       +
       +void
       +journalv(int out, vlong v)
       +{
       +        char buf[32];
       +
       +        snprint(buf, sizeof buf, "%lld", v);
       +        journal(out, buf);
       +}
       +
       +#else
       +#define        journal(a, b)
       +#define journaln(a, b)
       +#endif
       +
       +int
       +rcvchar(void){
       +        static uchar buf[64];
       +        static int i, nleft = 0;
       +
       +        if(nleft <= 0){
       +                nleft = read(0, (char *)buf, sizeof buf);
       +                if(nleft <= 0)
       +                        return -1;
       +                i = 0;
       +        }
       +        --nleft;
       +        return buf[i++];
       +}
       +
       +int
       +rcv(void){
       +        int c;
       +        static int state = 0;
       +        static int count = 0;
       +        static int i = 0;
       +
       +        while((c=rcvchar()) != -1)
       +                switch(state){
       +                case 0:
       +                        h.type = c;
       +                        state++;
       +                        break;
       +
       +                case 1:
       +                        h.count0 = c;
       +                        state++;
       +                        break;
       +
       +                case 2:
       +                        h.count1 = c;
       +                        count = h.count0|(h.count1<<8);
       +                        i = 0;
       +                        if(count > DATASIZE)
       +                                panic("count>DATASIZE");
       +                        if(count == 0)
       +                                goto zerocount;
       +                        state++;
       +                        break;
       +
       +                case 3:
       +                        indata[i++] = c;
       +                        if(i == count){
       +                zerocount:
       +                                indata[i] = 0;
       +                                state = count = 0;
       +                                return inmesg(h.type);
       +                        }
       +                        break;
       +                }
       +        return 0;
       +}
       +
       +File *
       +whichfile(int tag)
       +{
       +        int i;
       +
       +        for(i = 0; i<file.nused; i++)
       +                if(file.filepptr[i]->tag==tag)
       +                        return file.filepptr[i];
       +        hiccough((char *)0);
       +        return 0;
       +}
       +
       +int
       +inmesg(Tmesg type)
       +{
       +        Rune buf[1025];
       +        char cbuf[64];
       +        int i, m;
       +        short s;
       +        long l, l1;
       +        vlong v;
       +        File *f;
       +        Posn p0, p1, p;
       +        Range r;
       +        String *str;
       +        char *c, *wdir;
       +        Rune *rp;
       +        Plumbmsg *pm;
       +
       +        if(type > TMAX)
       +                panic("inmesg");
       +
       +        journal(0, tname[type]);
       +
       +        inp = indata;
       +        switch(type){
       +        case -1:
       +                panic("rcv error");
       +
       +        default:
       +                fprint(2, "unknown type %d\n", type);
       +                panic("rcv unknown");
       +
       +        case Tversion:
       +                tversion = inshort();
       +                journaln(0, tversion);
       +                break;
       +
       +        case Tstartcmdfile:
       +                v = invlong();                /* for 64-bit pointers */
       +                journaln(0, v);
       +                Strdupl(&genstr, samname);
       +                cmd = newfile();
       +                cmd->unread = 0;
       +                outTsv(Hbindname, cmd->tag, v);
       +                outTs(Hcurrent, cmd->tag);
       +                logsetname(cmd, &genstr);
       +                cmd->rasp = listalloc('P');
       +                cmd->mod = 0;
       +                if(cmdstr.n){
       +                        loginsert(cmd, 0L, cmdstr.s, cmdstr.n);
       +                        Strdelete(&cmdstr, 0L, (Posn)cmdstr.n);
       +                }
       +                fileupdate(cmd, FALSE, TRUE);
       +                outT0(Hunlock);
       +                break;
       +
       +        case Tcheck:
       +                /* go through whichfile to check the tag */
       +                outTs(Hcheck, whichfile(inshort())->tag);
       +                break;
       +
       +        case Trequest:
       +                f = whichfile(inshort());
       +                p0 = inlong();
       +                p1 = p0+inshort();
       +                journaln(0, p0);
       +                journaln(0, p1-p0);
       +                if(f->unread)
       +                        panic("Trequest: unread");
       +                if(p1>f->b.nc)
       +                        p1 = f->b.nc;
       +                if(p0>f->b.nc) /* can happen e.g. scrolling during command */
       +                        p0 = f->b.nc;
       +                if(p0 == p1){
       +                        i = 0;
       +                        r.p1 = r.p2 = p0;
       +                }else{
       +                        r = rdata(f->rasp, p0, p1-p0);
       +                        i = r.p2-r.p1;
       +                        bufread(&f->b, r.p1, buf, i);
       +                }
       +                buf[i]=0;
       +                outTslS(Hdata, f->tag, r.p1, tmprstr(buf, i+1));
       +                break;
       +
       +        case Torigin:
       +                s = inshort();
       +                l = inlong();
       +                l1 = inlong();
       +                journaln(0, l1);
       +                lookorigin(whichfile(s), l, l1);
       +                break;
       +
       +        case Tstartfile:
       +                termlocked++;
       +                f = whichfile(inshort());
       +                if(!f->rasp)        /* this might be a duplicate message */
       +                        f->rasp = listalloc('P');
       +                current(f);
       +                outTsv(Hbindname, f->tag, invlong());        /* for 64-bit pointers */
       +                outTs(Hcurrent, f->tag);
       +                journaln(0, f->tag);
       +                if(f->unread)
       +                        load(f);
       +                else{
       +                        if(f->b.nc>0){
       +                                rgrow(f->rasp, 0L, f->b.nc);
       +                                outTsll(Hgrow, f->tag, 0L, f->b.nc);
       +                        }
       +                        outTs(Hcheck0, f->tag);
       +                        moveto(f, f->dot.r);
       +                }
       +                break;
       +
       +        case Tworkfile:
       +                i = inshort();
       +                f = whichfile(i);
       +                current(f);
       +                f->dot.r.p1 = inlong();
       +                f->dot.r.p2 = inlong();
       +                f->tdot = f->dot.r;
       +                journaln(0, i);
       +                journaln(0, f->dot.r.p1);
       +                journaln(0, f->dot.r.p2);
       +                break;
       +
       +        case Ttype:
       +                f = whichfile(inshort());
       +                p0 = inlong();
       +                journaln(0, p0);
       +                journal(0, (char*)inp);
       +                str = tmpcstr((char*)inp);
       +                i = str->n;
       +                loginsert(f, p0, str->s, str->n);
       +                if(fileupdate(f, FALSE, FALSE))
       +                        seq++;
       +                if(f==cmd && p0==f->b.nc-i && i>0 && str->s[i-1]=='\n'){
       +                        freetmpstr(str);
       +                        termlocked++;
       +                        termcommand();
       +                }else
       +                        freetmpstr(str);
       +                f->dot.r.p1 = f->dot.r.p2 = p0+i; /* terminal knows this already */
       +                f->tdot = f->dot.r;
       +                break;
       +
       +        case Tcut:
       +                f = whichfile(inshort());
       +                p0 = inlong();
       +                p1 = inlong();
       +                journaln(0, p0);
       +                journaln(0, p1);
       +                logdelete(f, p0, p1);
       +                if(fileupdate(f, FALSE, FALSE))
       +                        seq++;
       +                f->dot.r.p1 = f->dot.r.p2 = p0;
       +                f->tdot = f->dot.r;   /* terminal knows the value of dot already */
       +                break;
       +
       +        case Tpaste:
       +                f = whichfile(inshort());
       +                p0 = inlong();
       +                journaln(0, p0);
       +                for(l=0; l<snarfbuf.nc; l+=m){
       +                        m = snarfbuf.nc-l;
       +                        if(m>BLOCKSIZE)
       +                                m = BLOCKSIZE;
       +                        bufread(&snarfbuf, l, genbuf, m);
       +                        loginsert(f, p0, tmprstr(genbuf, m)->s, m);
       +                }
       +                if(fileupdate(f, FALSE, TRUE))
       +                        seq++;
       +                f->dot.r.p1 = p0;
       +                f->dot.r.p2 = p0+snarfbuf.nc;
       +                f->tdot.p1 = -1; /* force telldot to tell (arguably a BUG) */
       +                telldot(f);
       +                outTs(Hunlockfile, f->tag);
       +                break;
       +
       +        case Tsnarf:
       +                i = inshort();
       +                p0 = inlong();
       +                p1 = inlong();
       +                snarf(whichfile(i), p0, p1, &snarfbuf, 0);
       +                break;
       +
       +        case Tstartnewfile:
       +                v = invlong();
       +                Strdupl(&genstr, empty);
       +                f = newfile();
       +                f->rasp = listalloc('P');
       +                outTsv(Hbindname, f->tag, v);
       +                logsetname(f, &genstr);
       +                outTs(Hcurrent, f->tag);
       +                current(f);
       +                load(f);
       +                break;
       +
       +        case Twrite:
       +                termlocked++;
       +                i = inshort();
       +                journaln(0, i);
       +                f = whichfile(i);
       +                addr.r.p1 = 0;
       +                addr.r.p2 = f->b.nc;
       +                if(f->name.s[0] == 0)
       +                        error(Enoname);
       +                Strduplstr(&genstr, &f->name);
       +                writef(f);
       +                break;
       +
       +        case Tclose:
       +                termlocked++;
       +                i = inshort();
       +                journaln(0, i);
       +                f = whichfile(i);
       +                current(f);
       +                trytoclose(f);
       +                /* if trytoclose fails, will error out */
       +                delete(f);
       +                break;
       +
       +        case Tlook:
       +                f = whichfile(inshort());
       +                termlocked++;
       +                p0 = inlong();
       +                p1 = inlong();
       +                journaln(0, p0);
       +                journaln(0, p1);
       +                setgenstr(f, p0, p1);
       +                for(l = 0; l<genstr.n; l++){
       +                        i = genstr.s[l];
       +                        if(utfrune(".*+?(|)\\[]^$", i)){
       +                                str = tmpcstr("\\");
       +                                Strinsert(&genstr, str, l++);
       +                                freetmpstr(str);
       +                        }
       +                }
       +                Straddc(&genstr, '\0');
       +                nextmatch(f, &genstr, p1, 1);
       +                moveto(f, sel.p[0]);
       +                break;
       +
       +        case Tsearch:
       +                termlocked++;
       +                if(curfile == 0)
       +                        error(Enofile);
       +                if(lastpat.s[0] == 0)
       +                        panic("Tsearch");
       +                nextmatch(curfile, &lastpat, curfile->dot.r.p2, 1);
       +                moveto(curfile, sel.p[0]);
       +                break;
       +
       +        case Tsend:
       +                termlocked++;
       +                inshort();        /* ignored */
       +                p0 = inlong();
       +                p1 = inlong();
       +                setgenstr(cmd, p0, p1);
       +                bufreset(&snarfbuf);
       +                bufinsert(&snarfbuf, (Posn)0, genstr.s, genstr.n);
       +                outTl(Hsnarflen, genstr.n);
       +                if(genstr.s[genstr.n-1] != '\n')
       +                        Straddc(&genstr, '\n');
       +                loginsert(cmd, cmd->b.nc, genstr.s, genstr.n);
       +                fileupdate(cmd, FALSE, TRUE);
       +                cmd->dot.r.p1 = cmd->dot.r.p2 = cmd->b.nc;
       +                telldot(cmd);
       +                termcommand();
       +                break;
       +
       +        case Tdclick:
       +                f = whichfile(inshort());
       +                p1 = inlong();
       +                doubleclick(f, p1);
       +                f->tdot.p1 = f->tdot.p2 = p1;
       +                telldot(f);
       +                outTs(Hunlockfile, f->tag);
       +                break;
       +
       +        case Tstartsnarf:
       +                if (snarfbuf.nc <= 0) {        /* nothing to export */
       +                        outTs(Hsetsnarf, 0);
       +                        break;
       +                }
       +                c = 0;
       +                i = 0;
       +                m = snarfbuf.nc;
       +                if(m > SNARFSIZE) {
       +                        m = SNARFSIZE;
       +                        dprint("?warning: snarf buffer truncated\n");
       +                }
       +                rp = malloc(m*sizeof(Rune));
       +                if(rp){
       +                        bufread(&snarfbuf, 0, rp, m);
       +                        c = Strtoc(tmprstr(rp, m));
       +                        free(rp);
       +                        i = strlen(c);
       +                }
       +                outTs(Hsetsnarf, i);
       +                if(c){
       +                        Write(1, c, i);
       +                        free(c);
       +                } else
       +                        dprint("snarf buffer too long\n");
       +                break;
       +
       +        case Tsetsnarf:
       +                m = inshort();
       +                if(m > SNARFSIZE)
       +                        error(Etoolong);
       +                c = malloc(m+1);
       +                if(c){
       +                        for(i=0; i<m; i++)
       +                                c[i] = rcvchar();
       +                        c[m] = 0;
       +                        str = tmpcstr(c);
       +                        free(c);
       +                        bufreset(&snarfbuf);
       +                        bufinsert(&snarfbuf, (Posn)0, str->s, str->n);
       +                        freetmpstr(str);
       +                        outT0(Hunlock);
       +                }
       +                break;
       +
       +        case Tack:
       +                waitack = 0;
       +                break;
       +#if 0
       +        case Tplumb:
       +                f = whichfile(inshort());
       +                p0 = inlong();
       +                p1 = inlong();
       +                pm = emalloc(sizeof(Plumbmsg));
       +                pm->src = strdup("sam");
       +                pm->dst = 0;
       +                /* construct current directory */
       +                c = Strtoc(&f->name);
       +                if(c[0] == '/')
       +                        pm->wdir = c;
       +                else{
       +                        wdir = emalloc(1024);
       +                        getwd(wdir, 1024);
       +                        pm->wdir = emalloc(1024);
       +                        snprint(pm->wdir, 1024, "%s/%s", wdir, c);
       +                        cleanname(pm->wdir);
       +                        free(wdir);
       +                        free(c);
       +                }
       +                c = strrchr(pm->wdir, '/');
       +                if(c)
       +                        *c = '\0';
       +                pm->type = strdup("text");
       +                if(p1 > p0)
       +                        pm->attr = nil;
       +                else{
       +                        p = p0;
       +                        while(p0>0 && (i=filereadc(f, p0 - 1))!=' ' && i!='\t' && i!='\n')
       +                                p0--;
       +                        while(p1<f->b.nc && (i=filereadc(f, p1))!=' ' && i!='\t' && i!='\n')
       +                                p1++;
       +                        sprint(cbuf, "click=%ld", p-p0);
       +                        pm->attr = plumbunpackattr(cbuf);
       +                }
       +                if(p0==p1 || p1-p0>=BLOCKSIZE){
       +                        plumbfree(pm);
       +                        break;
       +                }
       +                setgenstr(f, p0, p1);
       +                pm->data = Strtoc(&genstr);
       +                pm->ndata = strlen(pm->data);
       +                c = plumbpack(pm, &i);
       +                if(c != 0){
       +                        outTs(Hplumb, i);
       +                        Write(1, c, i);
       +                        free(c);
       +                }
       +                plumbfree(pm);
       +                break;
       +#endif
       +        case Texit:
       +                exits(0);
       +        }
       +        return TRUE;
       +}
       +
       +void
       +snarf(File *f, Posn p1, Posn p2, Buffer *buf, int emptyok)
       +{
       +        Posn l;
       +        int i;
       +
       +        if(!emptyok && p1==p2)
       +                return;
       +        bufreset(buf);
       +        /* Stage through genbuf to avoid compaction problems (vestigial) */
       +        if(p2 > f->b.nc){
       +                fprint(2, "bad snarf addr p1=%ld p2=%ld f->b.nc=%d\n", p1, p2, f->b.nc); /*ZZZ should never happen, can remove */
       +                p2 = f->b.nc;
       +        }
       +        for(l=p1; l<p2; l+=i){
       +                i = p2-l>BLOCKSIZE? BLOCKSIZE : p2-l;
       +                bufread(&f->b, l, genbuf, i);
       +                bufinsert(buf, buf->nc, tmprstr(genbuf, i)->s, i);
       +        }
       +}
       +
       +int
       +inshort(void)
       +{
       +        ushort n;
       +
       +        n = inp[0] | (inp[1]<<8);
       +        inp += 2;
       +        return n;
       +}
       +
       +long
       +inlong(void)
       +{
       +        ulong n;
       +
       +        n = inp[0] | (inp[1]<<8) | (inp[2]<<16) | (inp[3]<<24);
       +        inp += 4;
       +        return n;
       +}
       +
       +vlong
       +invlong(void)
       +{
       +        vlong v;
       +        
       +        v = (inp[7]<<24) | (inp[6]<<16) | (inp[5]<<8) | inp[4];
       +        v = (v<<16) | (inp[3]<<8) | inp[2];
       +        v = (v<<16) | (inp[1]<<8) | inp[0];
       +        inp += 8;
       +        return v;
       +}
       +
       +void
       +setgenstr(File *f, Posn p0, Posn p1)
       +{
       +        if(p0 != p1){
       +                if(p1-p0 >= TBLOCKSIZE)
       +                        error(Etoolong);
       +                Strinsure(&genstr, p1-p0);
       +                bufread(&f->b, p0, genbuf, p1-p0);
       +                memmove(genstr.s, genbuf, RUNESIZE*(p1-p0));
       +                genstr.n = p1-p0;
       +        }else{
       +                if(snarfbuf.nc == 0)
       +                        error(Eempty);
       +                if(snarfbuf.nc > TBLOCKSIZE)
       +                        error(Etoolong);
       +                bufread(&snarfbuf, (Posn)0, genbuf, snarfbuf.nc);
       +                Strinsure(&genstr, snarfbuf.nc);
       +                memmove(genstr.s, genbuf, RUNESIZE*snarfbuf.nc);
       +                genstr.n = snarfbuf.nc;
       +        }
       +}
       +
       +void
       +outT0(Hmesg type)
       +{
       +        outstart(type);
       +        outsend();
       +}
       +
       +void
       +outTl(Hmesg type, long l)
       +{
       +        outstart(type);
       +        outlong(l);
       +        outsend();
       +}
       +
       +void
       +outTs(Hmesg type, int s)
       +{
       +        outstart(type);
       +        journaln(1, s);
       +        outshort(s);
       +        outsend();
       +}
       +
       +void
       +outS(String *s)
       +{
       +        char *c;
       +        int i;
       +
       +        c = Strtoc(s);
       +        i = strlen(c);
       +        outcopy(i, c);
       +        if(i > 99)
       +                c[99] = 0;
       +        journaln(1, i);
       +        journal(1, c);
       +        free(c);
       +}
       +
       +void
       +outTsS(Hmesg type, int s1, String *s)
       +{
       +        outstart(type);
       +        outshort(s1);
       +        outS(s);
       +        outsend();
       +}
       +
       +void
       +outTslS(Hmesg type, int s1, Posn l1, String *s)
       +{
       +        outstart(type);
       +        outshort(s1);
       +        journaln(1, s1);
       +        outlong(l1);
       +        journaln(1, l1);
       +        outS(s);
       +        outsend();
       +}
       +
       +void
       +outTS(Hmesg type, String *s)
       +{
       +        outstart(type);
       +        outS(s);
       +        outsend();
       +}
       +
       +void
       +outTsllS(Hmesg type, int s1, Posn l1, Posn l2, String *s)
       +{
       +        outstart(type);
       +        outshort(s1);
       +        outlong(l1);
       +        outlong(l2);
       +        journaln(1, l1);
       +        journaln(1, l2);
       +        outS(s);
       +        outsend();
       +}
       +
       +void
       +outTsll(Hmesg type, int s, Posn l1, Posn l2)
       +{
       +        outstart(type);
       +        outshort(s);
       +        outlong(l1);
       +        outlong(l2);
       +        journaln(1, l1);
       +        journaln(1, l2);
       +        outsend();
       +}
       +
       +void
       +outTsl(Hmesg type, int s, Posn l)
       +{
       +        outstart(type);
       +        outshort(s);
       +        outlong(l);
       +        journaln(1, l);
       +        outsend();
       +}
       +
       +void
       +outTsv(Hmesg type, int s, vlong v)
       +{
       +        outstart(type);
       +        outshort(s);
       +        outvlong(v);
       +        journaln(1, v);
       +        outsend();
       +}
       +
       +void
       +outstart(Hmesg type)
       +{
       +        journal(1, hname[type]);
       +        outmsg[0] = type;
       +        outp = outmsg+3;
       +}
       +
       +void
       +outcopy(int count, void *data)
       +{
       +        memmove(outp, data, count);
       +        outp += count;
       +}
       +
       +void
       +outshort(int s)
       +{
       +        *outp++ = s;
       +        *outp++ = s>>8; 
       +}
       +
       +void
       +outlong(long l)
       +{
       +        *outp++ = l;
       +        *outp++ = l>>8;
       +        *outp++ = l>>16;
       +        *outp++ = l>>24;
       +}
       +
       +void
       +outvlong(vlong v)
       +{
       +        int i;
       +
       +        for(i = 0; i < 8; i++){
       +                *outp++ = v;
       +                v >>= 8;
       +        }
       +}
       +
       +void
       +outsend(void)
       +{
       +        int outcount;
       +
       +        if(outp >= outdata+nelem(outdata))
       +                panic("outsend");
       +        outcount = outp-outmsg;
       +        outcount -= 3;
       +        outmsg[1] = outcount;
       +        outmsg[2] = outcount>>8;
       +        outmsg = outp;
       +        if(!outbuffered){
       +                outcount = outmsg-outdata;
       +                if (write(1, (char*) outdata, outcount) != outcount)
       +                        rescue();
       +                outmsg = outdata;
       +                return;
       +        }
       +}
       +
       +int
       +needoutflush(void)
       +{
       +        return outmsg >= outdata+DATASIZE;
       +}
       +
       +void
       +outflush(void)
       +{
       +        if(outmsg == outdata)
       +                return;
       +        outbuffered = 0;
       +        /* flow control */
       +        outT0(Hack);
       +        waitack = 1;
       +        do
       +                if(rcv() == 0){
       +                        rescue();
       +                        exits("eof");
       +                }
       +        while(waitack);
       +        outmsg = outdata;
       +        outbuffered = 1;
       +}
   DIR diff --git a/sam/mesg.h b/sam/mesg.h
       @@ -0,0 +1,131 @@
       +/* VERSION 1 introduces plumbing
       +        2 increases SNARFSIZE from 4096 to 32000
       + */
       +#define        VERSION        2
       +
       +#define        TBLOCKSIZE 512                  /* largest piece of text sent to terminal */
       +#define        DATASIZE  (UTFmax*TBLOCKSIZE+30) /* ... including protocol header stuff */
       +#define        SNARFSIZE 32000                /* maximum length of exchanged snarf buffer, must fit in 15 bits */
       +/*
       + * Messages originating at the terminal
       + */
       +typedef enum Tmesg
       +{
       +        Tversion,        /* version */
       +        Tstartcmdfile,        /* terminal just opened command frame */
       +        Tcheck,                /* ask host to poke with Hcheck */
       +        Trequest,        /* request data to fill a hole */
       +        Torigin,        /* gimme an Horigin near here */
       +        Tstartfile,        /* terminal just opened a file's frame */
       +        Tworkfile,        /* set file to which commands apply */
       +        Ttype,                /* add some characters, but terminal already knows */
       +        Tcut,
       +        Tpaste,
       +        Tsnarf,
       +        Tstartnewfile,        /* terminal just opened a new frame */
       +        Twrite,                /* write file */
       +        Tclose,                /* terminal requests file close; check mod. status */
       +        Tlook,                /* search for literal current text */
       +        Tsearch,        /* search for last regular expression */
       +        Tsend,                /* pretend he typed stuff */
       +        Tdclick,        /* double click */
       +        Tstartsnarf,        /* initiate snarf buffer exchange */
       +        Tsetsnarf,        /* remember string in snarf buffer */
       +        Tack,                /* acknowledge Hack */
       +        Texit,                /* exit */
       +        Tplumb,                /* send plumb message */
       +        TMAX
       +}Tmesg;
       +/*
       + * Messages originating at the host
       + */
       +typedef enum Hmesg
       +{
       +        Hversion,        /* version */
       +        Hbindname,        /* attach name[0] to text in terminal */
       +        Hcurrent,        /* make named file the typing file */
       +        Hnewname,        /* create "" name in menu */
       +        Hmovname,        /* move file name in menu */
       +        Hgrow,                /* insert space in rasp */
       +        Hcheck0,        /* see below */
       +        Hcheck,                /* ask terminal to check whether it needs more data */
       +        Hunlock,        /* command is finished; user can do things */
       +        Hdata,                /* store this data in previously allocated space */
       +        Horigin,        /* set origin of file/frame in terminal */
       +        Hunlockfile,        /* unlock file in terminal */
       +        Hsetdot,        /* set dot in terminal */
       +        Hgrowdata,        /* Hgrow + Hdata folded together */
       +        Hmoveto,        /* scrolling, context search, etc. */
       +        Hclean,                /* named file is now 'clean' */
       +        Hdirty,                /* named file is now 'dirty' */
       +        Hcut,                /* remove space from rasp */
       +        Hsetpat,        /* set remembered regular expression */
       +        Hdelname,        /* delete file name from menu */
       +        Hclose,                /* close file and remove from menu */
       +        Hsetsnarf,        /* remember string in snarf buffer */
       +        Hsnarflen,        /* report length of implicit snarf */
       +        Hack,                /* request acknowledgement */
       +        Hexit,
       +        Hplumb,                /* return plumb message to terminal - version 1 */
       +        HMAX
       +}Hmesg;
       +typedef struct Header{
       +        uchar        type;                /* one of the above */
       +        uchar        count0;                /* low bits of data size */
       +        uchar        count1;                /* high bits of data size */
       +        uchar        data[1];        /* variable size */
       +}Header;
       +
       +/*
       + * File transfer protocol schematic, a la Holzmann
       + * #define N        6
       + * 
       + * chan h = [4] of { mtype };
       + * chan t = [4] of { mtype };
       + * 
       + * mtype = {        Hgrow, Hdata,
       + *                 Hcheck, Hcheck0,
       + *                 Trequest, Tcheck,
       + *         };
       + * 
       + * active proctype host()
       + * {        byte n;
       + * 
       + *         do
       + *         :: n <  N -> n++; t!Hgrow
       + *         :: n == N -> n++; t!Hcheck0
       + * 
       + *         :: h?Trequest -> t!Hdata
       + *         :: h?Tcheck   -> t!Hcheck
       + *         od
       + * }
       + * 
       + * active proctype term()
       + * {
       + *         do
       + *         :: t?Hgrow   -> h!Trequest
       + *         :: t?Hdata   -> skip
       + *         :: t?Hcheck0 -> h!Tcheck
       + *         :: t?Hcheck  ->
       + *                 if
       + *                 :: h!Trequest -> progress: h!Tcheck
       + *                 :: break
       + *                 fi
       + *         od;
       + *         printf("term exits\n")
       + * }
       + *
       + * From: gerard@research.bell-labs.com
       + * Date: Tue Jul 17 13:47:23 EDT 2001
       + * To: rob@research.bell-labs.com
       + * 
       + * spin -c         (or -a) spec
       + * pcc -DNP -o pan pan.c
       + * pan -l
       + * 
       + * proves that there are no non-progress cycles
       + * (infinite executions *not* passing through
       + * the statement marked with a label starting
       + * with the prefix "progress")
       + * 
       + */
   DIR diff --git a/sam/moveto.c b/sam/moveto.c
       @@ -0,0 +1,173 @@
       +#include "sam.h"
       +
       +void
       +moveto(File *f, Range r)
       +{
       +        Posn p1 = r.p1, p2 = r.p2;
       +
       +        f->dot.r.p1 = p1;
       +        f->dot.r.p2 = p2;
       +        if(f->rasp){
       +                telldot(f);
       +                outTsl(Hmoveto, f->tag, f->dot.r.p1);
       +        }
       +}
       +
       +void
       +telldot(File *f)
       +{
       +        if(f->rasp == 0)
       +                panic("telldot");
       +        if(f->dot.r.p1==f->tdot.p1 && f->dot.r.p2==f->tdot.p2)
       +                return;
       +        outTsll(Hsetdot, f->tag, f->dot.r.p1, f->dot.r.p2);
       +        f->tdot = f->dot.r;
       +}
       +
       +void
       +tellpat(void)
       +{
       +        outTS(Hsetpat, &lastpat);
       +        patset = FALSE;
       +}
       +
       +#define        CHARSHIFT        128
       +
       +void
       +lookorigin(File *f, Posn p0, Posn ls)
       +{
       +        int nl, nc, c;
       +        Posn p, oldp0;
       +
       +        if(p0 > f->b.nc)
       +                p0 = f->b.nc;
       +        oldp0 = p0;
       +        p = p0;
       +        for(nl=nc=c=0; c!=-1 && nl<ls && nc<ls*CHARSHIFT; nc++)
       +                if((c=filereadc(f, --p)) == '\n'){
       +                        nl++;
       +                        oldp0 = p0-nc;
       +                }
       +        if(c == -1)
       +                p0 = 0;
       +        else if(nl==0){
       +                if(p0>=CHARSHIFT/2)
       +                        p0-=CHARSHIFT/2;
       +                else
       +                        p0 = 0;
       +        }else
       +                p0 = oldp0;
       +        outTsl(Horigin, f->tag, p0);
       +}
       +
       +int
       +alnum(int c)
       +{
       +        /*
       +         * Hard to get absolutely right.  Use what we know about ASCII
       +         * and assume anything above the Latin control characters is
       +         * potentially an alphanumeric.
       +         */
       +        if(c<=' ')
       +                return 0;
       +        if(0x7F<=c && c<=0xA0)
       +                return 0;
       +        if(utfrune("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", c))
       +                return 0;
       +        return 1;
       +}
       +
       +int
       +clickmatch(File *f, int cl, int cr, int dir, Posn *p)
       +{
       +        int c;
       +        int nest = 1;
       +
       +        for(;;){
       +                if(dir > 0){
       +                        if(*p >= f->b.nc)
       +                                break;
       +                        c = filereadc(f, (*p)++);
       +                }else{
       +                        if(*p == 0)
       +                                break;
       +                        c = filereadc(f, --(*p));
       +                }
       +                if(c == cr){
       +                        if(--nest==0)
       +                                return 1;
       +                }else if(c == cl)
       +                        nest++;
       +        }
       +        return cl=='\n' && nest==1;
       +}
       +
       +Rune*
       +strrune(Rune *s, Rune c)
       +{
       +        Rune c1;
       +
       +        if(c == 0) {
       +                while(*s++)
       +                        ;
       +                return s-1;
       +        }
       +
       +        while(c1 = *s++)
       +                if(c1 == c)
       +                        return s-1;
       +        return 0;
       +}
       +
       +void
       +doubleclick(File *f, Posn p1)
       +{
       +        int c, i;
       +        Rune *r, *l;
       +        Posn p;
       +
       +        if(p1 > f->b.nc)
       +                return;
       +        f->dot.r.p1 = f->dot.r.p2 = p1;
       +        for(i=0; left[i]; i++){
       +                l = left[i];
       +                r = right[i];
       +                /* try left match */
       +                p = p1;
       +                if(p1 == 0)
       +                        c = '\n';
       +                else
       +                        c = filereadc(f, p - 1);
       +                if(strrune(l, c)){
       +                        if(clickmatch(f, c, r[strrune(l, c)-l], 1, &p)){
       +                                f->dot.r.p1 = p1;
       +                                f->dot.r.p2 = p-(c!='\n');
       +                        }
       +                        return;
       +                }
       +                /* try right match */
       +                p = p1;
       +                if(p1 == f->b.nc)
       +                        c = '\n';
       +                else
       +                        c = filereadc(f, p);
       +                if(strrune(r, c)){
       +                        if(clickmatch(f, c, l[strrune(r, c)-r], -1, &p)){
       +                                f->dot.r.p1 = p;
       +                                if(c!='\n' || p!=0 || filereadc(f, 0)=='\n')
       +                                        f->dot.r.p1++;
       +                                f->dot.r.p2 = p1+(p1<f->b.nc && c=='\n');
       +                        }
       +                        return;
       +                }
       +        }
       +        /* try filling out word to right */
       +        p = p1;
       +        while(p < f->b.nc && alnum(filereadc(f, p++)))
       +                f->dot.r.p2++;
       +        /* try filling out word to left */
       +        p = p1;
       +        while(--p >= 0 && alnum(filereadc(f, p)))
       +                f->dot.r.p1--;
       +}
       +
   DIR diff --git a/sam/multi.c b/sam/multi.c
       @@ -0,0 +1,123 @@
       +#include "sam.h"
       +
       +List        file = { 'p' };
       +ushort        tag;
       +
       +File *
       +newfile(void)
       +{
       +        File *f;
       +
       +        f = fileopen();
       +        inslist(&file, 0, f);
       +        f->tag = tag++;
       +        if(downloaded)
       +                outTs(Hnewname, f->tag);
       +        /* already sorted; file name is "" */
       +        return f;
       +}
       +
       +int
       +whichmenu(File *f)
       +{
       +        int i;
       +
       +        for(i=0; i<file.nused; i++)
       +                if(file.filepptr[i]==f)
       +                        return i;
       +        return -1;
       +}
       +
       +void
       +delfile(File *f)
       +{
       +        int w = whichmenu(f);
       +
       +        if(w < 0)        /* e.g. x/./D */
       +                return;
       +        if(downloaded)
       +                outTs(Hdelname, f->tag);
       +        dellist(&file, w);
       +        fileclose(f);
       +}
       +
       +void
       +fullname(String *name)
       +{
       +        if(name->n > 0 && name->s[0]!='/' && name->s[0]!=0)
       +                Strinsert(name, &curwd, (Posn)0);
       +}
       +
       +void
       +fixname(String *name)
       +{
       +        String *t;
       +        char *s;
       +
       +        fullname(name);
       +        s = Strtoc(name);
       +        if(strlen(s) > 0)
       +                s = cleanname(s);
       +        t = tmpcstr(s);
       +        Strduplstr(name, t);
       +        free(s);
       +        freetmpstr(t);
       +
       +        if(Strispre(&curwd, name))
       +                Strdelete(name, 0, curwd.n);
       +}
       +
       +void
       +sortname(File *f)
       +{
       +        int i, cmp, w;
       +        int dupwarned;
       +
       +        w = whichmenu(f);
       +        dupwarned = FALSE;
       +        dellist(&file, w);
       +        if(f == cmd)
       +                i = 0;
       +        else{
       +                for(i=0; i<file.nused; i++){
       +                        cmp = Strcmp(&f->name, &file.filepptr[i]->name);
       +                        if(cmp==0 && !dupwarned){
       +                                dupwarned = TRUE;
       +                                warn_S(Wdupname, &f->name);
       +                        }else if(cmp<0 && (i>0 || cmd==0))
       +                                break;
       +                }
       +        }
       +        inslist(&file, i, f);
       +        if(downloaded)
       +                outTsS(Hmovname, f->tag, &f->name);
       +}
       +
       +void
       +state(File *f, int cleandirty)
       +{
       +        if(f == cmd)
       +                return;
       +        f->unread = FALSE;
       +        if(downloaded && whichmenu(f)>=0){        /* else flist or menu */
       +                if(f->mod && cleandirty!=Dirty)
       +                        outTs(Hclean, f->tag);
       +                else if(!f->mod && cleandirty==Dirty)
       +                        outTs(Hdirty, f->tag);
       +        }
       +        if(cleandirty == Clean)
       +                f->mod = FALSE;
       +        else
       +                f->mod = TRUE;
       +}
       +
       +File *
       +lookfile(String *s)
       +{
       +        int i;
       +
       +        for(i=0; i<file.nused; i++)
       +                if(Strcmp(&file.filepptr[i]->name, s) == 0)
       +                        return file.filepptr[i];
       +        return 0;
       +}
   DIR diff --git a/sam/parse.h b/sam/parse.h
       @@ -0,0 +1,68 @@
       +typedef struct Addr Addr;
       +typedef struct Cmd Cmd;
       +struct Addr
       +{
       +        char        type;        /* # (char addr), l (line addr), / ? . $ + - , ; */
       +        union{
       +                String        *re;
       +                Addr        *aleft;                /* left side of , and ; */
       +        } g;
       +        Posn        num;
       +        Addr        *next;                        /* or right side of , and ; */
       +};
       +
       +#define        are        g.re
       +#define        left        g.aleft
       +
       +struct Cmd
       +{
       +        Addr        *addr;                        /* address (range of text) */
       +        String        *re;                        /* regular expression for e.g. 'x' */
       +        union{
       +                Cmd        *cmd;                /* target of x, g, {, etc. */
       +                String        *text;                /* text of a, c, i; rhs of s */
       +                Addr        *addr;                /* address for m, t */
       +        } g;
       +        Cmd        *next;                        /* pointer to next element in {} */
       +        short        num;
       +        ushort        flag;                        /* whatever */
       +        ushort        cmdc;                        /* command character; 'x' etc. */
       +};
       +
       +#define        ccmd        g.cmd
       +#define        ctext        g.text
       +#define        caddr        g.addr
       +
       +extern struct cmdtab{
       +        ushort        cmdc;                /* command character */
       +        uchar        text;                /* takes a textual argument? */
       +        uchar        regexp;                /* takes a regular expression? */
       +        uchar        addr;                /* takes an address (m or t)? */
       +        uchar        defcmd;                /* default command; 0==>none */
       +        uchar        defaddr;        /* default address */
       +        uchar        count;                /* takes a count e.g. s2/// */
       +        char        *token;                /* takes text terminated by one of these */
       +        int        (*fn)(File*, Cmd*);        /* function to call with parse tree */
       +}cmdtab[];
       +
       +enum Defaddr{        /* default addresses */
       +        aNo,
       +        aDot,
       +        aAll
       +};
       +
       +int        nl_cmd(File*, Cmd*), a_cmd(File*, Cmd*), b_cmd(File*, Cmd*);
       +int        c_cmd(File*, Cmd*), cd_cmd(File*, Cmd*), d_cmd(File*, Cmd*);
       +int        D_cmd(File*, Cmd*), e_cmd(File*, Cmd*);
       +int        f_cmd(File*, Cmd*), g_cmd(File*, Cmd*), i_cmd(File*, Cmd*);
       +int        k_cmd(File*, Cmd*), m_cmd(File*, Cmd*), n_cmd(File*, Cmd*);
       +int        p_cmd(File*, Cmd*), q_cmd(File*, Cmd*);
       +int        s_cmd(File*, Cmd*), u_cmd(File*, Cmd*), w_cmd(File*, Cmd*);
       +int        x_cmd(File*, Cmd*), X_cmd(File*, Cmd*), plan9_cmd(File*, Cmd*);
       +int        eq_cmd(File*, Cmd*);
       +
       +
       +String        *getregexp(int);
       +Addr        *newaddr(void);
       +Address        address(Addr*, Address, int);
       +int        cmdexec(File*, Cmd*);
   DIR diff --git a/sam/plan9.c b/sam/plan9.c
       @@ -0,0 +1,185 @@
       +#include "sam.h"
       +
       +Rune        samname[] = L"~~sam~~";
       +
       +Rune *left[]= {
       +        L"{[(<«",
       +        L"\n",
       +        L"'\"`",
       +        0
       +};
       +Rune *right[]= {
       +        L"}])>»",
       +        L"\n",
       +        L"'\"`",
       +        0
       +};
       +
       +char        RSAM[] = "sam";
       +char        SAMTERM[] = "/bin/aux/samterm";
       +char        HOME[] = "HOME";
       +char        TMPDIR[] = "/tmp";
       +char        SH[] = "rc";
       +char        SHPATH[] = "/bin/rc";
       +char        RX[] = "rx";
       +char        RXPATH[] = "/bin/rx";
       +char        SAMSAVECMD[] = "/bin/rc\n/sys/lib/samsave";
       +
       +void
       +dprint(char *z, ...)
       +{
       +        char buf[BLOCKSIZE];
       +        va_list arg;
       +
       +        va_start(arg, z);
       +        vseprint(buf, &buf[BLOCKSIZE], z, arg);
       +        va_end(arg);
       +        termwrite(buf);
       +}
       +
       +void
       +print_ss(char *s, String *a, String *b)
       +{
       +        dprint("?warning: %s: `%.*S' and `%.*S'\n", s, a->n, a->s, b->n, b->s);
       +}
       +
       +void
       +print_s(char *s, String *a)
       +{
       +        dprint("?warning: %s `%.*S'\n", s, a->n, a->s);
       +}
       +
       +char*
       +getuser(void)
       +{
       +        static char user[64];
       +        int fd;
       +
       +        if(user[0] == 0){
       +                fd = open("/dev/user", 0);
       +                if(fd<0 || read(fd, user, sizeof user-1)<=0)
       +                        strcpy(user, "none");
       +                close(fd);
       +        }
       +        return user;
       +}
       +
       +int
       +statfile(char *name, ulong *dev, uvlong *id, long *time, long *length, long *appendonly)
       +{
       +        Dir *dirb;
       +
       +        dirb = dirstat(name);
       +        if(dirb == nil)
       +                return -1;
       +        if(dev)
       +                *dev = dirb->type|(dirb->dev<<16);
       +        if(id)
       +                *id = dirb->qid.path;
       +        if(time)
       +                *time = dirb->mtime;
       +        if(length)
       +                *length = dirb->length;
       +        if(appendonly)
       +                *appendonly = dirb->mode & DMAPPEND;
       +        free(dirb);
       +        return 1;
       +}
       +
       +int
       +statfd(int fd, ulong *dev, uvlong *id, long *time, long *length, long *appendonly)
       +{
       +        Dir *dirb;
       +
       +        dirb = dirfstat(fd);
       +        if(dirb == nil)
       +                return -1;
       +        if(dev)
       +                *dev = dirb->type|(dirb->dev<<16);
       +        if(id)
       +                *id = dirb->qid.path;
       +        if(time)
       +                *time = dirb->mtime;
       +        if(length)
       +                *length = dirb->length;
       +        if(appendonly)
       +                *appendonly = dirb->mode & DMAPPEND;
       +        free(dirb);
       +        return 1;
       +}
       +
       +void
       +notifyf(void *a, char *s)
       +{
       +        USED(a);
       +        if(bpipeok && strcmp(s, "sys: write on closed pipe") == 0)
       +                noted(NCONT);
       +        if(strcmp(s, "interrupt") == 0)
       +                noted(NCONT);
       +        panicking = 1;
       +        rescue();
       +        noted(NDFLT);
       +}
       +
       +int
       +newtmp(int num)
       +{
       +        int i, fd;
       +        static char        tempnam[30];
       +
       +        i = getpid();
       +        do
       +                snprint(tempnam, sizeof tempnam, "%s/%d%.4s%dsam", TMPDIR, num, getuser(), i++);
       +        while(access(tempnam, 0) == 0);
       +        fd = create(tempnam, ORDWR|OCEXEC|ORCLOSE, 0000);
       +        if(fd < 0){
       +                remove(tempnam);
       +                fd = create(tempnam, ORDWR|OCEXEC|ORCLOSE, 0000);
       +        }
       +        return fd;
       +}
       +
       +int
       +waitfor(int pid)
       +{
       +        int msg;
       +        Waitmsg *w;
       +
       +        while((w = wait()) != nil){
       +                if(w->pid != pid){
       +                        free(w);
       +                        continue;
       +                }
       +                msg = (w->msg[0] != '\0');
       +                free(w);
       +                return msg;
       +        }
       +        return -1;
       +}
       +
       +void
       +samerr(char *buf)
       +{
       +        sprint(buf, "%s/sam.err", TMPDIR);
       +}
       +
       +void*
       +emalloc(ulong n)
       +{
       +        void *p;
       +
       +        p = malloc(n);
       +        if(p == 0)
       +                panic("malloc fails");
       +        memset(p, 0, n);
       +        return p;
       +}
       +
       +void*
       +erealloc(void *p, ulong n)
       +{
       +        p = realloc(p, n);
       +        if(p == 0)
       +                panic("realloc fails");
       +        return p;
       +}
   DIR diff --git a/sam/plumb.h b/sam/plumb.h
       @@ -0,0 +1,17 @@
       +typedef struct Plumbmsg Plumbmsg;
       +
       +struct Plumbmsg {
       +        char *src;
       +        char *dst;
       +        char *wdir;
       +        char *type;
       +        char *attr;
       +        char *data;
       +        int ndata;
       +};
       +
       +char *plumbunpackattr(char*);
       +char *plumbpack(Plumbmsg *, int *);
       +int plumbfree(Plumbmsg *);
       +char *cleanname(char*);
       +
   DIR diff --git a/sam/rasp.c b/sam/rasp.c
       @@ -0,0 +1,340 @@
       +#include "sam.h"
       +/*
       + * GROWDATASIZE must be big enough that all errors go out as Hgrowdata's,
       + * so they will be scrolled into visibility in the ~~sam~~ window (yuck!).
       + */
       +#define        GROWDATASIZE        50        /* if size is <= this, send data with grow */
       +
       +void        rcut(List*, Posn, Posn);
       +int        rterm(List*, Posn);
       +void        rgrow(List*, Posn, Posn);
       +
       +static        Posn        growpos;
       +static        Posn        grown;
       +static        Posn        shrinkpos;
       +static        Posn        shrunk;
       +
       +/*
       + * rasp routines inform the terminal of changes to the file.
       + *
       + * a rasp is a list of spans within the file, and an indication
       + * of whether the terminal knows about the span.
       + *
       + * optimize by coalescing multiple updates to the same span
       + * if it is not known by the terminal.
       + *
       + * other possible optimizations: flush terminal's rasp by cut everything,
       + * insert everything if rasp gets too large.
       + */
       +
       +/*
       + * only called for initial load of file
       + */
       +void
       +raspload(File *f)
       +{
       +        if(f->rasp == nil)
       +                return;
       +        grown = f->b.nc;
       +        growpos = 0;
       +        if(f->b.nc)
       +                rgrow(f->rasp, 0, f->b.nc);
       +        raspdone(f, 1);
       +}
       +
       +void
       +raspstart(File *f)
       +{
       +        if(f->rasp == nil)
       +                return;
       +        grown = 0;
       +        shrunk = 0;
       +        outbuffered = 1;
       +}
       +
       +void
       +raspdone(File *f, int toterm)
       +{
       +        if(f->dot.r.p1 > f->b.nc)
       +                f->dot.r.p1 = f->b.nc;
       +        if(f->dot.r.p2 > f->b.nc)
       +                f->dot.r.p2 = f->b.nc;
       +        if(f->mark.p1 > f->b.nc)
       +                f->mark.p1 = f->b.nc;
       +        if(f->mark.p2 > f->b.nc)
       +                f->mark.p2 = f->b.nc;
       +        if(f->rasp == nil)
       +                return;
       +        if(grown)
       +                outTsll(Hgrow, f->tag, growpos, grown);
       +        else if(shrunk)
       +                outTsll(Hcut, f->tag, shrinkpos, shrunk);
       +        if(toterm)
       +                outTs(Hcheck0, f->tag);
       +        outflush();
       +        outbuffered = 0;
       +        if(f == cmd){
       +                cmdpt += cmdptadv;
       +                cmdptadv = 0;
       +        }
       +}
       +
       +void
       +raspflush(File *f)
       +{
       +        if(grown){
       +                outTsll(Hgrow, f->tag, growpos, grown);
       +                grown = 0;
       +        }
       +        else if(shrunk){
       +                outTsll(Hcut, f->tag, shrinkpos, shrunk);
       +                shrunk = 0;
       +        }
       +        outflush();
       +}
       +
       +void
       +raspdelete(File *f, uint p1, uint p2, int toterm)
       +{
       +        long n;
       +
       +        n = p2 - p1;
       +        if(n == 0)
       +                return;
       +
       +        if(p2 <= f->dot.r.p1){
       +                f->dot.r.p1 -= n;
       +                f->dot.r.p2 -= n;
       +        }
       +        if(p2 <= f->mark.p1){
       +                f->mark.p1 -= n;
       +                f->mark.p2 -= n;
       +        }
       +
       +        if(f->rasp == nil)
       +                return;
       +
       +        if(f==cmd && p1<cmdpt){
       +                if(p2 <= cmdpt)
       +                        cmdpt -= n;
       +                else
       +                        cmdpt = p1;
       +        }
       +        if(toterm){
       +                if(grown){
       +                        outTsll(Hgrow, f->tag, growpos, grown);
       +                        grown = 0;
       +                }else if(shrunk && shrinkpos!=p1 && shrinkpos!=p2){
       +                        outTsll(Hcut, f->tag, shrinkpos, shrunk);
       +                        shrunk = 0;
       +                }
       +                if(!shrunk || shrinkpos==p2)
       +                        shrinkpos = p1;
       +                shrunk += n;
       +        }
       +        rcut(f->rasp, p1, p2);
       +}
       +
       +void
       +raspinsert(File *f, uint p1, Rune *buf, uint n, int toterm)
       +{
       +        Range r;
       +
       +        if(n == 0)
       +                return;
       +
       +        if(p1 < f->dot.r.p1){
       +                f->dot.r.p1 += n;
       +                f->dot.r.p2 += n;
       +        }
       +        if(p1 < f->mark.p1){
       +                f->mark.p1 += n;
       +                f->mark.p2 += n;
       +        }
       +
       +
       +        if(f->rasp == nil)
       +                return;
       +        if(f==cmd && p1<cmdpt)
       +                cmdpt += n;
       +        if(toterm){
       +                if(shrunk){
       +                        outTsll(Hcut, f->tag, shrinkpos, shrunk);
       +                        shrunk = 0;
       +                }
       +                if(n>GROWDATASIZE || !rterm(f->rasp, p1)){
       +                        rgrow(f->rasp, p1, n);
       +                        if(grown && growpos+grown!=p1 && growpos!=p1){
       +                                outTsll(Hgrow, f->tag, growpos, grown);
       +                                grown = 0;
       +                        }
       +                        if(!grown)
       +                                growpos = p1;
       +                        grown += n;
       +                }else{
       +                        if(grown){
       +                                outTsll(Hgrow, f->tag, growpos, grown);
       +                                grown = 0;
       +                        }
       +                        rgrow(f->rasp, p1, n);
       +                        r = rdata(f->rasp, p1, n);
       +                        if(r.p1!=p1 || r.p2!=p1+n)
       +                                panic("rdata in toterminal");
       +                        outTsllS(Hgrowdata, f->tag, p1, n, tmprstr(buf, n));
       +                }
       +        }else{
       +                rgrow(f->rasp, p1, n);
       +                r = rdata(f->rasp, p1, n);
       +                if(r.p1!=p1 || r.p2!=p1+n)
       +                        panic("rdata in toterminal");
       +        }
       +}
       +
       +#define        M        0x80000000L
       +#define        P(i)        r->posnptr[i]
       +#define        T(i)        (P(i)&M)        /* in terminal */
       +#define        L(i)        (P(i)&~M)        /* length of this piece */
       +
       +void
       +rcut(List *r, Posn p1, Posn p2)
       +{
       +        Posn p, x;
       +        int i;
       +
       +        if(p1 == p2)
       +                panic("rcut 0");
       +        for(p=0,i=0; i<r->nused && p+L(i)<=p1; p+=L(i++))
       +                ;
       +        if(i == r->nused)
       +                panic("rcut 1");
       +        if(p < p1){        /* chop this piece */
       +                if(p+L(i) < p2){
       +                        x = p1-p;
       +                        p += L(i);
       +                }else{
       +                        x = L(i)-(p2-p1);
       +                        p = p2;
       +                }
       +                if(T(i))
       +                        P(i) = x|M;
       +                else
       +                        P(i) = x;
       +                i++;
       +        }
       +        while(i<r->nused && p+L(i)<=p2){
       +                p += L(i);
       +                dellist(r, i);
       +        }
       +        if(p < p2){
       +                if(i == r->nused)
       +                        panic("rcut 2");
       +                x = L(i)-(p2-p);
       +                if(T(i))
       +                        P(i) = x|M;
       +                else
       +                        P(i) = x;
       +        }
       +        /* can we merge i and i-1 ? */
       +        if(i>0 && i<r->nused && T(i-1)==T(i)){
       +                x = L(i-1)+L(i);
       +                dellist(r, i--);
       +                if(T(i))
       +                        P(i)=x|M;
       +                else
       +                        P(i)=x;
       +        }
       +}
       +
       +void
       +rgrow(List *r, Posn p1, Posn n)
       +{
       +        Posn p;
       +        int i;
       +
       +        if(n == 0)
       +                panic("rgrow 0");
       +        for(p=0,i=0; i<r->nused && p+L(i)<=p1; p+=L(i++))
       +                ;
       +        if(i == r->nused){        /* stick on end of file */
       +                if(p!=p1)
       +                        panic("rgrow 1");
       +                if(i>0 && !T(i-1))
       +                        P(i-1)+=n;
       +                else
       +                        inslist(r, i, n);
       +        }else if(!T(i))                /* goes in this empty piece */
       +                P(i)+=n;
       +        else if(p==p1 && i>0 && !T(i-1))        /* special case; simplifies life */
       +                P(i-1)+=n;
       +        else if(p==p1)
       +                inslist(r, i, n);
       +        else{                        /* must break piece in terminal */
       +                inslist(r, i+1, (L(i)-(p1-p))|M);
       +                inslist(r, i+1, n);
       +                P(i) = (p1-p)|M;
       +        }
       +}
       +
       +int
       +rterm(List *r, Posn p1)
       +{
       +        Posn p;
       +        int i;
       +
       +        for(p = 0,i = 0; i<r->nused && p+L(i)<=p1; p+=L(i++))
       +                ;
       +        if(i==r->nused && (i==0 || !T(i-1)))
       +                return 0;
       +        return T(i);
       +}
       +
       +Range
       +rdata(List *r, Posn p1, Posn n)
       +{
       +        Posn p;
       +        int i;
       +        Range rg;
       +
       +        if(n==0)
       +                panic("rdata 0");
       +        for(p = 0,i = 0; i<r->nused && p+L(i)<=p1; p+=L(i++))
       +                ;
       +        if(i==r->nused)
       +                panic("rdata 1");
       +        if(T(i)){
       +                n-=L(i)-(p1-p);
       +                if(n<=0){
       +                        rg.p1 = rg.p2 = p1;
       +                        return rg;
       +                }
       +                p+=L(i++);
       +                p1 = p;
       +        }
       +        if(T(i) || i==r->nused)
       +                panic("rdata 2");
       +        if(p+L(i)<p1+n)
       +                n = L(i)-(p1-p);
       +        rg.p1 = p1;
       +        rg.p2 = p1+n;
       +        if(p!=p1){
       +                inslist(r, i+1, L(i)-(p1-p));
       +                P(i)=p1-p;
       +                i++;
       +        }
       +        if(L(i)!=n){
       +                inslist(r, i+1, L(i)-n);
       +                P(i)=n;
       +        }
       +        P(i)|=M;
       +        /* now i is set; can we merge? */
       +        if(i<r->nused-1 && T(i+1)){
       +                P(i)=(n+=L(i+1))|M;
       +                dellist(r, i+1);
       +        }
       +        if(i>0 && T(i-1)){
       +                P(i)=(n+L(i-1))|M;
       +                dellist(r, i-1);
       +        }
       +        return rg;
       +}
       +
   DIR diff --git a/sam/regexp.c b/sam/regexp.c
       @@ -0,0 +1,802 @@
       +#include "sam.h"
       +
       +Rangeset        sel;
       +String                lastregexp;
       +/*
       + * Machine Information
       + */
       +typedef struct Inst Inst;
       +
       +struct Inst
       +{
       +        long        type;        /* < 0x10000 ==> literal, otherwise action */
       +        union {
       +                int rsid;
       +                int rsubid;
       +                int class;
       +                struct Inst *rother;
       +                struct Inst *rright;
       +        } r;
       +        union{
       +                struct Inst *lleft;
       +                struct Inst *lnext;
       +        } l;
       +};
       +#define        sid        r.rsid
       +#define        subid        r.rsubid
       +#define        rclass        r.class
       +#define        other        r.rother
       +#define        right        r.rright
       +#define        left        l.lleft
       +#define        next        l.lnext
       +
       +#define        NPROG        1024
       +Inst        program[NPROG];
       +Inst        *progp;
       +Inst        *startinst;        /* First inst. of program; might not be program[0] */
       +Inst        *bstartinst;        /* same for backwards machine */
       +
       +typedef struct Ilist Ilist;
       +struct Ilist
       +{
       +        Inst        *inst;                /* Instruction of the thread */
       +        Rangeset se;
       +        Posn        startp;                /* first char of match */
       +};
       +
       +#define        NLIST        127
       +
       +Ilist        *tl, *nl;        /* This list, next list */
       +Ilist        list[2][NLIST+1];        /* +1 for trailing null */
       +static        Rangeset sempty;
       +
       +/*
       + * Actions and Tokens
       + *
       + *        0x100xx are operators, value == precedence
       + *        0x200xx are tokens, i.e. operands for operators
       + */
       +#define        OPERATOR        0x10000        /* Bitmask of all operators */
       +#define        START                0x10000        /* Start, used for marker on stack */
       +#define        RBRA                0x10001        /* Right bracket, ) */
       +#define        LBRA                0x10002        /* Left bracket, ( */
       +#define        OR                0x10003        /* Alternation, | */
       +#define        CAT                0x10004        /* Concatentation, implicit operator */
       +#define        STAR                0x10005        /* Closure, * */
       +#define        PLUS                0x10006        /* a+ == aa* */
       +#define        QUEST                0x10007        /* a? == a|nothing, i.e. 0 or 1 a's */
       +#define        ANY                0x20000        /* Any character but newline, . */
       +#define        NOP                0x20001        /* No operation, internal use only */
       +#define        BOL                0x20002        /* Beginning of line, ^ */
       +#define        EOL                0x20003        /* End of line, $ */
       +#define        CCLASS                0x20004        /* Character class, [] */
       +#define        NCCLASS                0x20005        /* Negated character class, [^] */
       +#define        END                0x20077        /* Terminate: match found */
       +
       +#define        ISATOR                0x10000
       +#define        ISAND                0x20000
       +
       +/*
       + * Parser Information
       + */
       +typedef struct Node Node;
       +struct Node
       +{
       +        Inst        *first;
       +        Inst        *last;
       +};
       +
       +#define        NSTACK        20
       +Node        andstack[NSTACK];
       +Node        *andp;
       +int        atorstack[NSTACK];
       +int        *atorp;
       +int        lastwasand;        /* Last token was operand */
       +int        cursubid;
       +int        subidstack[NSTACK];
       +int        *subidp;
       +int        backwards;
       +int        nbra;
       +Rune        *exprp;                /* pointer to next character in source expression */
       +#define        DCLASS        10        /* allocation increment */
       +int        nclass;                /* number active */
       +int        Nclass;                /* high water mark */
       +Rune        **class;
       +int        negateclass;
       +
       +int        addinst(Ilist *l, Inst *inst, Rangeset *sep);
       +void        newmatch(Rangeset*);
       +void        bnewmatch(Rangeset*);
       +void        pushand(Inst*, Inst*);
       +void        pushator(int);
       +Node        *popand(int);
       +int        popator(void);
       +void        startlex(Rune*);
       +int        lex(void);
       +void        operator(int);
       +void        operand(int);
       +void        evaluntil(int);
       +void        optimize(Inst*);
       +void        bldcclass(void);
       +
       +void
       +regerror(Err e)
       +{
       +        Strzero(&lastregexp);
       +        error(e);
       +}
       +
       +void
       +regerror_c(Err e, int c)
       +{
       +        Strzero(&lastregexp);
       +        error_c(e, c);
       +}
       +
       +Inst *
       +newinst(int t)
       +{
       +        if(progp >= &program[NPROG])
       +                regerror(Etoolong);
       +        progp->type = t;
       +        progp->left = 0;
       +        progp->right = 0;
       +        return progp++;
       +}
       +
       +Inst *
       +realcompile(Rune *s)
       +{
       +        int token;
       +
       +        startlex(s);
       +        atorp = atorstack;
       +        andp = andstack;
       +        subidp = subidstack;
       +        cursubid = 0;
       +        lastwasand = FALSE;
       +        /* Start with a low priority operator to prime parser */
       +        pushator(START-1);
       +        while((token=lex()) != END){
       +                if((token&ISATOR) == OPERATOR)
       +                        operator(token);
       +                else
       +                        operand(token);
       +        }
       +        /* Close with a low priority operator */
       +        evaluntil(START);
       +        /* Force END */
       +        operand(END);
       +        evaluntil(START);
       +        if(nbra)
       +                regerror(Eleftpar);
       +        --andp;        /* points to first and only operand */
       +        return andp->first;
       +}
       +
       +void
       +compile(String *s)
       +{
       +        int i;
       +        Inst *oprogp;
       +
       +        if(Strcmp(s, &lastregexp)==0)
       +                return;
       +        for(i=0; i<nclass; i++)
       +                free(class[i]);
       +        nclass = 0;
       +        progp = program;
       +        backwards = FALSE;
       +        startinst = realcompile(s->s);
       +        optimize(program);
       +        oprogp = progp;
       +        backwards = TRUE;
       +        bstartinst = realcompile(s->s);
       +        optimize(oprogp);
       +        Strduplstr(&lastregexp, s);
       +}
       +
       +void
       +operand(int t)
       +{
       +        Inst *i;
       +        if(lastwasand)
       +                operator(CAT);        /* catenate is implicit */
       +        i = newinst(t);
       +        if(t == CCLASS){
       +                if(negateclass)
       +                        i->type = NCCLASS;        /* UGH */
       +                i->rclass = nclass-1;                /* UGH */
       +        }
       +        pushand(i, i);
       +        lastwasand = TRUE;
       +}
       +
       +void
       +operator(int t)
       +{
       +        if(t==RBRA && --nbra<0)
       +                regerror(Erightpar);
       +        if(t==LBRA){
       +/*
       + *                if(++cursubid >= NSUBEXP)
       + *                        regerror(Esubexp);
       + */
       +                cursubid++;        /* silently ignored */
       +                nbra++;
       +                if(lastwasand)
       +                        operator(CAT);
       +        }else
       +                evaluntil(t);
       +        if(t!=RBRA)
       +                pushator(t);
       +        lastwasand = FALSE;
       +        if(t==STAR || t==QUEST || t==PLUS || t==RBRA)
       +                lastwasand = TRUE;        /* these look like operands */
       +}
       +
       +void
       +cant(char *s)
       +{
       +        char buf[100];
       +
       +        sprint(buf, "regexp: can't happen: %s", s);
       +        panic(buf);
       +}
       +
       +void
       +pushand(Inst *f, Inst *l)
       +{
       +        if(andp >= &andstack[NSTACK])
       +                cant("operand stack overflow");
       +        andp->first = f;
       +        andp->last = l;
       +        andp++;
       +}
       +
       +void
       +pushator(int t)
       +{
       +        if(atorp >= &atorstack[NSTACK])
       +                cant("operator stack overflow");
       +        *atorp++=t;
       +        if(cursubid >= NSUBEXP)
       +                *subidp++= -1;
       +        else
       +                *subidp++=cursubid;
       +}
       +
       +Node *
       +popand(int op)
       +{
       +        if(andp <= &andstack[0])
       +                if(op)
       +                        regerror_c(Emissop, op);
       +                else
       +                        regerror(Ebadregexp);
       +        return --andp;
       +}
       +
       +int
       +popator(void)
       +{
       +        if(atorp <= &atorstack[0])
       +                cant("operator stack underflow");
       +        --subidp;
       +        return *--atorp;
       +}
       +
       +void
       +evaluntil(int pri)
       +{
       +        Node *op1, *op2, *t;
       +        Inst *inst1, *inst2;
       +
       +        while(pri==RBRA || atorp[-1]>=pri){
       +                switch(popator()){
       +                case LBRA:
       +                        op1 = popand('(');
       +                        inst2 = newinst(RBRA);
       +                        inst2->subid = *subidp;
       +                        op1->last->next = inst2;
       +                        inst1 = newinst(LBRA);
       +                        inst1->subid = *subidp;
       +                        inst1->next = op1->first;
       +                        pushand(inst1, inst2);
       +                        return;                /* must have been RBRA */
       +                default:
       +                        panic("unknown regexp operator");
       +                        break;
       +                case OR:
       +                        op2 = popand('|');
       +                        op1 = popand('|');
       +                        inst2 = newinst(NOP);
       +                        op2->last->next = inst2;
       +                        op1->last->next = inst2;
       +                        inst1 = newinst(OR);
       +                        inst1->right = op1->first;
       +                        inst1->left = op2->first;
       +                        pushand(inst1, inst2);
       +                        break;
       +                case CAT:
       +                        op2 = popand(0);
       +                        op1 = popand(0);
       +                        if(backwards && op2->first->type!=END)
       +                                t = op1, op1 = op2, op2 = t;
       +                        op1->last->next = op2->first;
       +                        pushand(op1->first, op2->last);
       +                        break;
       +                case STAR:
       +                        op2 = popand('*');
       +                        inst1 = newinst(OR);
       +                        op2->last->next = inst1;
       +                        inst1->right = op2->first;
       +                        pushand(inst1, inst1);
       +                        break;
       +                case PLUS:
       +                        op2 = popand('+');
       +                        inst1 = newinst(OR);
       +                        op2->last->next = inst1;
       +                        inst1->right = op2->first;
       +                        pushand(op2->first, inst1);
       +                        break;
       +                case QUEST:
       +                        op2 = popand('?');
       +                        inst1 = newinst(OR);
       +                        inst2 = newinst(NOP);
       +                        inst1->left = inst2;
       +                        inst1->right = op2->first;
       +                        op2->last->next = inst2;
       +                        pushand(inst1, inst2);
       +                        break;
       +                }
       +        }
       +}
       +
       +
       +void
       +optimize(Inst *start)
       +{
       +        Inst *inst, *target;
       +
       +        for(inst=start; inst->type!=END; inst++){
       +                target = inst->next;
       +                while(target->type == NOP)
       +                        target = target->next;
       +                inst->next = target;
       +        }
       +}
       +
       +#ifdef        DEBUG
       +void
       +dumpstack(void){
       +        Node *stk;
       +        int *ip;
       +
       +        dprint("operators\n");
       +        for(ip = atorstack; ip<atorp; ip++)
       +                dprint("0%o\n", *ip);
       +        dprint("operands\n");
       +        for(stk = andstack; stk<andp; stk++)
       +                dprint("0%o\t0%o\n", stk->first->type, stk->last->type);
       +}
       +void
       +dump(void){
       +        Inst *l;
       +
       +        l = program;
       +        do{
       +                dprint("%d:\t0%o\t%d\t%d\n", l-program, l->type,
       +                        l->left-program, l->right-program);
       +        }while(l++->type);
       +}
       +#endif
       +
       +void
       +startlex(Rune *s)
       +{
       +        exprp = s;
       +        nbra = 0;
       +}
       +
       +
       +int
       +lex(void){
       +        int c= *exprp++;
       +
       +        switch(c){
       +        case '\\':
       +                if(*exprp)
       +                        if((c= *exprp++)=='n')
       +                                c='\n';
       +                break;
       +        case 0:
       +                c = END;
       +                --exprp;        /* In case we come here again */
       +                break;
       +        case '*':
       +                c = STAR;
       +                break;
       +        case '?':
       +                c = QUEST;
       +                break;
       +        case '+':
       +                c = PLUS;
       +                break;
       +        case '|':
       +                c = OR;
       +                break;
       +        case '.':
       +                c = ANY;
       +                break;
       +        case '(':
       +                c = LBRA;
       +                break;
       +        case ')':
       +                c = RBRA;
       +                break;
       +        case '^':
       +                c = BOL;
       +                break;
       +        case '$':
       +                c = EOL;
       +                break;
       +        case '[':
       +                c = CCLASS;
       +                bldcclass();
       +                break;
       +        }
       +        return c;
       +}
       +
       +long
       +nextrec(void){
       +        if(exprp[0]==0 || (exprp[0]=='\\' && exprp[1]==0))
       +                regerror(Ebadclass);
       +        if(exprp[0] == '\\'){
       +                exprp++;
       +                if(*exprp=='n'){
       +                        exprp++;
       +                        return '\n';
       +                }
       +                return *exprp++|0x10000;
       +        }
       +        return *exprp++;
       +}
       +
       +void
       +bldcclass(void)
       +{
       +        long c1, c2, n, na;
       +        Rune *classp;
       +
       +        classp = emalloc(DCLASS*RUNESIZE);
       +        n = 0;
       +        na = DCLASS;
       +        /* we have already seen the '[' */
       +        if(*exprp == '^'){
       +                classp[n++] = '\n';        /* don't match newline in negate case */
       +                negateclass = TRUE;
       +                exprp++;
       +        }else
       +                negateclass = FALSE;
       +        while((c1 = nextrec()) != ']'){
       +                if(c1 == '-'){
       +    Error:
       +                        free(classp);
       +                        regerror(Ebadclass);
       +                }
       +                if(n+4 >= na){                /* 3 runes plus NUL */
       +                        na += DCLASS;
       +                        classp = erealloc(classp, na*RUNESIZE);
       +                }
       +                if(*exprp == '-'){
       +                        exprp++;        /* eat '-' */
       +                        if((c2 = nextrec()) == ']')
       +                                goto Error;
       +                        classp[n+0] = Runemax;
       +                        classp[n+1] = c1;
       +                        classp[n+2] = c2;
       +                        n += 3;
       +                }else
       +                        classp[n++] = c1;
       +        }
       +        classp[n] = 0;
       +        if(nclass == Nclass){
       +                Nclass += DCLASS;
       +                class = erealloc(class, Nclass*sizeof(Rune*));
       +        }
       +        class[nclass++] = classp;
       +}
       +
       +int
       +classmatch(int classno, int c, int negate)
       +{
       +        Rune *p;
       +
       +        p = class[classno];
       +        while(*p){
       +                if(*p == Runemax){
       +                        if(p[1]<=c && c<=p[2])
       +                                return !negate;
       +                        p += 3;
       +                }else if(*p++ == c)
       +                        return !negate;
       +        }
       +        return negate;
       +}
       +
       +/*
       + * Note optimization in addinst:
       + *         *l must be pending when addinst called; if *l has been looked
       + *                at already, the optimization is a bug.
       + */
       +int
       +addinst(Ilist *l, Inst *inst, Rangeset *sep)
       +{
       +        Ilist *p;
       +
       +        for(p = l; p->inst; p++){
       +                if(p->inst==inst){
       +                        if((sep)->p[0].p1 < p->se.p[0].p1)
       +                                p->se= *sep;        /* this would be bug */
       +                        return 0;        /* It's already there */
       +                }
       +        }
       +        p->inst = inst;
       +        p->se= *sep;
       +        (p+1)->inst = 0;
       +        return 1;
       +}
       +
       +int
       +execute(File *f, Posn startp, Posn eof)
       +{
       +        int flag = 0;
       +        Inst *inst;
       +        Ilist *tlp;
       +        Posn p = startp;
       +        int nnl = 0, ntl;
       +        int c;
       +        int wrapped = 0;
       +        int startchar = startinst->type<OPERATOR? startinst->type : 0;
       +
       +        list[0][0].inst = list[1][0].inst = 0;
       +        sel.p[0].p1 = -1;
       +        /* Execute machine once for each character */
       +        for(;;p++){
       +        doloop:
       +                c = filereadc(f, p);
       +                if(p>=eof || c<0){
       +                        switch(wrapped++){
       +                        case 0:                /* let loop run one more click */
       +                        case 2:
       +                                break;
       +                        case 1:                /* expired; wrap to beginning */
       +                                if(sel.p[0].p1>=0 || eof!=INFINITY)
       +                                        goto Return;
       +                                list[0][0].inst = list[1][0].inst = 0;
       +                                p = 0;
       +                                goto doloop;
       +                        default:
       +                                goto Return;
       +                        }
       +                }else if(((wrapped && p>=startp) || sel.p[0].p1>0) && nnl==0)
       +                        break;
       +                /* fast check for first char */
       +                if(startchar && nnl==0 && c!=startchar)
       +                        continue;
       +                tl = list[flag];
       +                nl = list[flag^=1];
       +                nl->inst = 0;
       +                ntl = nnl;
       +                nnl = 0;
       +                if(sel.p[0].p1<0 && (!wrapped || p<startp || startp==eof)){
       +                        /* Add first instruction to this list */
       +                        sempty.p[0].p1 = p;
       +                        if(addinst(tl, startinst, &sempty))
       +                        if(++ntl >= NLIST)
       +        Overflow:
       +                                error(Eoverflow);
       +                }
       +                /* Execute machine until this list is empty */
       +                for(tlp = tl; inst = tlp->inst; tlp++){        /* assignment = */
       +        Switchstmt:
       +                        switch(inst->type){
       +                        default:        /* regular character */
       +                                if(inst->type==c){
       +        Addinst:
       +                                        if(addinst(nl, inst->next, &tlp->se))
       +                                        if(++nnl >= NLIST)
       +                                                goto Overflow;
       +                                }
       +                                break;
       +                        case LBRA:
       +                                if(inst->subid>=0)
       +                                        tlp->se.p[inst->subid].p1 = p;
       +                                inst = inst->next;
       +                                goto Switchstmt;
       +                        case RBRA:
       +                                if(inst->subid>=0)
       +                                        tlp->se.p[inst->subid].p2 = p;
       +                                inst = inst->next;
       +                                goto Switchstmt;
       +                        case ANY:
       +                                if(c!='\n')
       +                                        goto Addinst;
       +                                break;
       +                        case BOL:
       +                                if(p==0 || filereadc(f, p - 1)=='\n'){
       +        Step:
       +                                        inst = inst->next;
       +                                        goto Switchstmt;
       +                                }
       +                                break;
       +                        case EOL:
       +                                if(c == '\n')
       +                                        goto Step;
       +                                break;
       +                        case CCLASS:
       +                                if(c>=0 && classmatch(inst->rclass, c, 0))
       +                                        goto Addinst;
       +                                break;
       +                        case NCCLASS:
       +                                if(c>=0 && classmatch(inst->rclass, c, 1))
       +                                        goto Addinst;
       +                                break;
       +                        case OR:
       +                                /* evaluate right choice later */
       +                                if(addinst(tl, inst->right, &tlp->se))
       +                                if(++ntl >= NLIST)
       +                                        goto Overflow;
       +                                /* efficiency: advance and re-evaluate */
       +                                inst = inst->left;
       +                                goto Switchstmt;
       +                        case END:        /* Match! */
       +                                tlp->se.p[0].p2 = p;
       +                                newmatch(&tlp->se);
       +                                break;
       +                        }
       +                }
       +        }
       +    Return:
       +        return sel.p[0].p1>=0;
       +}
       +
       +void
       +newmatch(Rangeset *sp)
       +{
       +        int i;
       +
       +        if(sel.p[0].p1<0 || sp->p[0].p1<sel.p[0].p1 ||
       +           (sp->p[0].p1==sel.p[0].p1 && sp->p[0].p2>sel.p[0].p2))
       +                for(i = 0; i<NSUBEXP; i++)
       +                        sel.p[i] = sp->p[i];
       +}
       +
       +int
       +bexecute(File *f, Posn startp)
       +{
       +        int flag = 0;
       +        Inst *inst;
       +        Ilist *tlp;
       +        Posn p = startp;
       +        int nnl = 0, ntl;
       +        int c;
       +        int wrapped = 0;
       +        int startchar = bstartinst->type<OPERATOR? bstartinst->type : 0;
       +
       +        list[0][0].inst = list[1][0].inst = 0;
       +        sel.p[0].p1= -1;
       +        /* Execute machine once for each character, including terminal NUL */
       +        for(;;--p){
       +        doloop:
       +                if((c = filereadc(f, p - 1))==-1){
       +                        switch(wrapped++){
       +                        case 0:                /* let loop run one more click */
       +                        case 2:
       +                                break;
       +                        case 1:                /* expired; wrap to end */
       +                                if(sel.p[0].p1>=0)
       +                        case 3:
       +                                        goto Return;
       +                                list[0][0].inst = list[1][0].inst = 0;
       +                                p = f->b.nc;
       +                                goto doloop;
       +                        default:
       +                                goto Return;
       +                        }
       +                }else if(((wrapped && p<=startp) || sel.p[0].p1>0) && nnl==0)
       +                        break;
       +                /* fast check for first char */
       +                if(startchar && nnl==0 && c!=startchar)
       +                        continue;
       +                tl = list[flag];
       +                nl = list[flag^=1];
       +                nl->inst = 0;
       +                ntl = nnl;
       +                nnl = 0;
       +                if(sel.p[0].p1<0 && (!wrapped || p>startp)){
       +                        /* Add first instruction to this list */
       +                        /* the minus is so the optimizations in addinst work */
       +                        sempty.p[0].p1 = -p;
       +                        if(addinst(tl, bstartinst, &sempty))
       +                        if(++ntl >= NLIST)
       +        Overflow:
       +                                error(Eoverflow);
       +                }
       +                /* Execute machine until this list is empty */
       +                for(tlp = tl; inst = tlp->inst; tlp++){        /* assignment = */
       +        Switchstmt:
       +                        switch(inst->type){
       +                        default:        /* regular character */
       +                                if(inst->type == c){
       +        Addinst:
       +                                        if(addinst(nl, inst->next, &tlp->se))
       +                                        if(++nnl >= NLIST)
       +                                                goto Overflow;
       +                                }
       +                                break;
       +                        case LBRA:
       +                                if(inst->subid>=0)
       +                                        tlp->se.p[inst->subid].p1 = p;
       +                                inst = inst->next;
       +                                goto Switchstmt;
       +                        case RBRA:
       +                                if(inst->subid >= 0)
       +                                        tlp->se.p[inst->subid].p2 = p;
       +                                inst = inst->next;
       +                                goto Switchstmt;
       +                        case ANY:
       +                                if(c != '\n')
       +                                        goto Addinst;
       +                                break;
       +                        case BOL:
       +                                if(c=='\n' || p==0){
       +        Step:
       +                                        inst = inst->next;
       +                                        goto Switchstmt;
       +                                }
       +                                break;
       +                        case EOL:
       +                                if(p==f->b.nc || filereadc(f, p)=='\n')
       +                                        goto Step;
       +                                break;
       +                        case CCLASS:
       +                                if(c>=0 && classmatch(inst->rclass, c, 0))
       +                                        goto Addinst;
       +                                break;
       +                        case NCCLASS:
       +                                if(c>=0 && classmatch(inst->rclass, c, 1))
       +                                        goto Addinst;
       +                                break;
       +                        case OR:
       +                                /* evaluate right choice later */
       +                                if(addinst(tlp, inst->right, &tlp->se))
       +                                if(++ntl >= NLIST)
       +                                        goto Overflow;
       +                                /* efficiency: advance and re-evaluate */
       +                                inst = inst->left;
       +                                goto Switchstmt;
       +                        case END:        /* Match! */
       +                                tlp->se.p[0].p1 = -tlp->se.p[0].p1; /* minus sign */
       +                                tlp->se.p[0].p2 = p;
       +                                bnewmatch(&tlp->se);
       +                                break;
       +                        }
       +                }
       +        }
       +    Return:
       +        return sel.p[0].p1>=0;
       +}
       +
       +void
       +bnewmatch(Rangeset *sp)
       +{
       +        int  i;
       +        if(sel.p[0].p1<0 || sp->p[0].p1>sel.p[0].p2 || (sp->p[0].p1==sel.p[0].p2 && sp->p[0].p2<sel.p[0].p1))
       +                for(i = 0; i<NSUBEXP; i++){       /* note the reversal; p1<=p2 */
       +                        sel.p[i].p1 = sp->p[i].p2;
       +                        sel.p[i].p2 = sp->p[i].p1;
       +                }
       +}
   DIR diff --git a/sam/sam.1 b/sam/sam.1
       @@ -0,0 +1,908 @@
       +.TH SAM 1
       +.ds a \fR*\ \fP
       +.SH NAME
       +sam, B, E, sam.save, samterm, samsave \- screen editor with structural regular expressions 
       +.SH SYNOPSIS
       +.B sam
       +[
       +.I option ...
       +] [
       +.I files
       +]
       +.PP
       +.B sam
       +.B -r
       +.I machine
       +.PP
       +.B sam.save
       +.PP
       +.B B
       +.IB file \fR[\fP: line \fR]
       +\&...
       +.PP
       +.B E
       +.I file
       +.SH DESCRIPTION
       +.I Sam
       +is a multi-file editor.
       +It modifies a local copy of an external file.
       +The copy is here called a
       +.IR file .
       +The files are listed in a menu available through mouse button 3
       +or the
       +.B n
       +command.
       +Each file has an associated name, usually the name of the
       +external file from which it was read, and a `modified' bit that indicates whether
       +the editor's file agrees with the external file.
       +The external file is not read into
       +the editor's file until it first becomes the current file\(emthat to
       +which editing commands apply\(emwhereupon its menu entry is printed.
       +The options are
       +.TF -rmachine
       +.TP
       +.B -a
       +Autoindent.  In this mode, when a newline character is typed
       +in the terminal interface, 
       +.I samterm
       +copies leading white space on the current line to the new line.
       +.TP
       +.B -d
       +Do not `download' the terminal part of
       +.IR sam .
       +Editing will be done with the command language only, as in
       +.IR ed (1).
       +.TP
       +.BI -r " machine
       +Run the host part remotely
       +on the specified machine, the terminal part locally.
       +.TP
       +.BI -s " path
       +Start the host part from the specified file on the remote host.
       +Only meaningful with the
       +.BI -r
       +option.
       +.TP
       +.BI -t " path
       +Start the terminal part from the specified file.  Useful
       +for debugging.
       +.PD
       +.SS Regular expressions
       +Regular expressions are as in
       +.IR regexp (7)
       +with the addition of
       +.BR \en
       +to represent newlines.
       +A regular expression may never contain a literal newline character.
       +The empty
       +regular expression stands for the last complete expression encountered.
       +A regular expression in
       +.I sam
       +matches the longest leftmost substring formally
       +matched by the expression.
       +Searching in the reverse direction is equivalent
       +to searching backwards with the catenation operations reversed in
       +the expression.
       +.SS Addresses
       +An address identifies a substring in a file.
       +In the following, `character
       +.IR n '
       +means the null string
       +after the
       +.IR n -th
       +character in the file, with 1 the
       +first character in the file.
       +`Line
       +.IR n '
       +means the
       +.IR n -th
       +match,
       +starting at the beginning of the file, of the regular expression
       +.LR .*\en? .
       +All files always have a current substring, called dot,
       +that is the default address.
       +.SS Simple Addresses
       +.PD 0
       +.TP
       +.BI # n
       +The empty string after character
       +.IR n ;
       +.B #0
       +is the beginning of the file.
       +.TP
       +.I n
       +Line
       +.IR n ;
       +.B 0
       +is the beginning of the file.
       +.TP
       +.BI  / regexp /
       +.PD 0
       +.TP
       +.BI ? regexp ?
       +The substring that matches the regular expression,
       +found by looking toward the end 
       +.RB ( / )
       +or beginning
       +.RB ( ? )
       +of the file,
       +and if necessary continuing the search from the other end to the
       +starting point of the search.
       +The matched substring may straddle
       +the starting point.
       +When entering a pattern containing a literal question mark
       +for a backward search, the question mark should be
       +specified as a member of a class.
       +.PD
       +.TP
       +.B 0
       +The string before the first full line.
       +This is not necessarily
       +the null string; see
       +.B +
       +and
       +.B -
       +below.
       +.TP
       +.B $
       +The null string at the end of the file.
       +.TP
       +.B .
       +Dot.
       +.TP
       +.B \&'
       +The mark in the file (see the
       +.B k
       +command below).
       +.TP
       +\fB"\f2regexp\fB"\f1\f1
       +Preceding a simple address (default
       +.BR . ),
       +refers to the address evaluated in the unique file whose menu line
       +matches the regular expression.
       +.PD
       +.SS Compound Addresses
       +In the following,
       +.I a1
       +and
       +.I a2
       +are addresses.
       +.TF a1+a2
       +.TP
       +.IB a1 + a2
       +The address
       +.I a2
       +evaluated starting at the end of
       +.IR a1 .
       +.TP
       +.IB a1 - a2
       +The address
       +.I a2
       +evaluated looking in the reverse direction
       +starting at the beginning of
       +.IR a1 .
       +.TP
       +.IB a1 , a2
       +The substring from the beginning of
       +.I a1
       +to the end of
       +.IR a2 .
       +If
       +.I a1
       +is missing,
       +.B 0
       +is substituted.
       +If
       +.I a2
       +is missing,
       +.B $
       +is substituted.
       +.TP
       +.IB  a1 ; a2
       +Like
       +.IB a1 , a2\f1,
       +but with
       +.I a2
       +evaluated at the end of, and dot set to,
       +.IR a1 .
       +.PD
       +.PP
       +The operators
       +.B +
       +and
       +.B -
       +are high precedence, while
       +.B ,
       +and
       +.B ;
       +are low precedence.
       +.PP
       +In both
       +.B +
       +and
       +.B -
       +forms, if
       +.I a2
       +is a line or character address with a missing
       +number, the number defaults to 1.
       +If
       +.I a1
       +is missing,
       +.L .
       +is substituted.
       +If both
       +.I a1
       +and
       +.I a2
       +are present and distinguishable,
       +.B +
       +may be elided.
       +.I a2
       +may be a regular
       +expression; if it is delimited by
       +.LR ? 's,
       +the effect of the
       +.B +
       +or
       +.B -
       +is reversed.
       +.PP
       +It is an error for a compound address to represent a malformed substring.
       +Some useful idioms: 
       +.IB a1 +-
       +\%(\f2a1\fB-+\f1)
       +selects the line containing
       +the end (beginning) of a1.
       +.BI 0/ regexp /
       +locates the first match of the expression in the file.
       +(The form
       +.B 0;//
       +sets dot unnecessarily.)
       +.BI ./ regexp /// 
       +finds the second following occurrence of the expression,
       +and
       +.BI .,/ regexp /
       +extends dot.
       +.SS Commands
       +In the following, text demarcated by slashes represents text delimited
       +by any printable
       +character except alphanumerics.
       +Any number of
       +trailing delimiters may be elided, with multiple elisions then representing
       +null strings, but the first delimiter must always
       +be present.
       +In any delimited text,
       +newline may not appear literally;
       +.B \en
       +may be typed for newline; and
       +.B \e/
       +quotes the delimiter, here 
       +.LR / .
       +Backslash is otherwise interpreted literally, except in
       +.B s
       +commands.
       +.PP
       +Most commands may be prefixed by an address to indicate their range
       +of operation.
       +Those that may not are marked with a 
       +.L *
       +below.
       +If a command takes
       +an address and none is supplied, dot is used.
       +The sole exception is
       +the
       +.B w
       +command, which defaults to
       +.BR 0,$ .
       +In the description, `range' is used
       +to represent whatever address is supplied.
       +Many commands set the
       +value of dot as a side effect.
       +If so, it is always set to the `result'
       +of the change: the empty string for a deletion, the new text for an
       +insertion, etc. (but see the
       +.B s
       +and
       +.B e
       +commands).
       +.br
       +.ne 1.2i
       +.SS Text commands
       +.PD 0
       +.TP
       +.BI a/ text /
       +.TP
       +or
       +.TP
       +.B  a
       +.TP
       +.I lines of text
       +.TP
       +.B .
       +Insert the text into the file after the range.
       +Set dot.
       +.PD
       +.TP
       +.B c\fP
       +.br
       +.ns
       +.TP
       +.B i\fP
       +Same as
       +.BR a ,
       +but
       +.B c
       +replaces the text, while
       +.B i
       +inserts
       +.I before
       +the range.
       +.TP
       +.B d
       +Delete the text in the range.
       +Set dot.
       +.TP
       +.BI s/ regexp / text /
       +Substitute
       +.I text
       +for the first match to the regular expression in the range.
       +Set dot to the modified range.
       +In 
       +.I text
       +the character
       +.B &
       +stands for the string
       +that matched the expression. 
       +Backslash behaves as usual unless followed by
       +a digit:
       +.BI \e d
       +stands for the string that matched the
       +subexpression begun by the
       +.IR d -th
       +left parenthesis.
       +If
       +.I s
       +is followed immediately by a
       +number
       +.IR n ,
       +as in
       +.BR s2/x/y/ ,
       +the
       +.IR n -th
       +match in the range is substituted.
       +If the
       +command is followed by a
       +.BR g ,
       +as in
       +.BR s/x/y/g ,
       +all matches in the range
       +are substituted.
       +.TP
       +.BI m " a1
       +.br
       +.ns
       +.TP
       +.BI t " a1
       +Move
       +.RB ( m )
       +or copy
       +.RB ( t )
       +the range to after
       +.IR a1 .
       +Set dot.
       +.SS Display commands
       +.PD 0
       +.TP
       +.B p
       +Print the text in the range.
       +Set dot.
       +.TP
       +.B =
       +Print the line address and character address of the range.
       +.TP
       +.B =#
       +Print just the character address of the range.
       +.PD
       +.SS File commands
       +.PD 0
       +.TP
       +.BI \*ab " file-list
       +Set the current file to the first file named in the list
       +that
       +.I sam
       +also has in its menu.
       +The list may be expressed
       +.BI < "Plan 9 command"
       +in which case the file names are taken as words (in the shell sense)
       +generated by the Plan 9 command.
       +.TP
       +.BI \*aB " file-list
       +Same as
       +.BR b ,
       +except that file names not in the menu are entered there,
       +and all file names in the list are examined.
       +.TP
       +.B \*an
       +Print a menu of files.
       +The format is:
       +.RS
       +.TP 11
       +.BR ' " or blank
       +indicating the file is modified or clean,
       +.TP 11
       +.BR - " or \&" +
       +indicating the file is unread or has been read
       +(in the terminal,
       +.B *
       +means more than one window is open),
       +.TP 11
       +.BR . " or blank
       +indicating the current file,
       +.TP 11
       +a blank,
       +.TP 11
       +and the file name.
       +.RE
       +.TP 0
       +.BI \*aD " file-list
       +Delete the named files from the menu.
       +If no files are named, the current file is deleted.
       +It is an error to
       +.B D
       +a modified file, but a subsequent
       +.B D
       +will delete such a file.
       +.PD
       +.SS I/O Commands
       +.PD 0
       +.TP
       +.BI \*ae " filename
       +Replace the file by the contents of the named external file.
       +Set dot to the beginning of the file.
       +.TP
       +.BI r " filename
       +Replace the text in the range by the contents of the named external file.
       +Set dot.
       +.TP
       +.BI w " filename
       +Write the range (default
       +.BR 0,$ )
       +to the named external file.
       +.TP
       +.BI \*af " filename
       +Set the file name and print the resulting menu entry.
       +.PP
       +If the file name is absent from any of these, the current file name is used.
       +.B e
       +always sets the file name;
       +.B r
       +and
       +.B w
       +do so if the file has no name.
       +.TP
       +.BI < " Plan 9-command
       +Replace the range by the standard output of the
       +Plan 9 command.
       +.TP
       +.BI > " Plan 9-command
       +Send the range to the standard input of the
       +Plan 9 command.
       +.TP
       +.BI | " Plan 9-command
       +Send the range to the standard input, and replace it by
       +the standard output, of the
       +Plan 9 command.
       +.TP
       +.BI \*a! " Plan 9-command
       +Run the
       +Plan 9 command.
       +.TP
       +.BI \*acd " directory
       +Change working directory.
       +If no directory is specified,
       +.B $home
       +is used.
       +.PD
       +.PP
       +In any of
       +.BR < ,
       +.BR > ,
       +.B |
       +or
       +.BR ! ,
       +if the
       +.I Plan 9 command
       +is omitted the last
       +.I Plan 9 command
       +(of any type) is substituted.
       +If
       +.I sam
       +is
       +.I downloaded
       +(using the mouse and raster display, i.e. not using option
       +.BR -d ),
       +.B !
       +sets standard input to
       +.BR /dev/null ,
       +and otherwise
       +unassigned output
       +.RB ( stdout
       +for
       +.B !
       +and
       +.BR > ,
       +.B stderr
       +for all) is placed in
       +.B /tmp/sam.err
       +and the first few lines are printed.
       +.SS Loops and Conditionals
       +.PD 0
       +.TP
       +.BI x/ regexp / " command
       +For each match of the regular expression in the range, run the command
       +with dot set to the match.
       +Set dot to the last match.
       +If the regular
       +expression and its slashes are omitted, 
       +.L /.*\en/
       +is assumed.
       +Null string matches potentially occur before every character
       +of the range and at the end of the range.
       +.TP
       +.BI y/ regexp / " command
       +Like
       +.BR x ,
       +but run the command for each substring that lies before, between,
       +or after
       +the matches that would be generated by
       +.BR x .
       +There is no default regular expression.
       +Null substrings potentially occur before every character
       +in the range.
       +.TP
       +.BI \*aX/ regexp / " command
       +For each file whose menu entry matches the regular expression,
       +make that the current file and
       +run the command.
       +If the expression is omitted, the command is run
       +in every file.
       +.TP
       +.BI \*aY/ regexp / " command
       +Same as
       +.BR X ,
       +but for files that do not match the regular expression,
       +and the expression is required.
       +.TP
       +.BI g/ regexp / " command
       +.br
       +.ns
       +.TP
       +.BI v/ regexp / " command
       +If the range contains
       +.RB ( g )
       +or does not contain
       +.RB ( v )
       +a match for the expression,
       +set dot to the range and run the command.
       +.PP
       +These may be nested arbitrarily deeply, but only one instance of either
       +.B X
       +or
       +.B Y
       +may appear in a \%single command.
       +An empty command in an
       +.B x
       +or
       +.B y
       +defaults to
       +.BR p ;
       +an empty command in
       +.B X
       +or
       +.B Y
       +defaults to
       +.BR f .
       +.B g
       +and
       +.B v
       +do not have defaults.
       +.PD
       +.SS Miscellany
       +.TF (empty)
       +.TP
       +.B k
       +Set the current file's mark to the range.  Does not set dot.
       +.TP
       +.B \*aq
       +Quit.
       +It is an error to quit with modified files, but a second
       +.B q
       +will succeed.
       +.TP
       +.BI \*au " n
       +Undo the last
       +.I n
       +(default 1)
       +top-level commands that changed the contents or name of the
       +current file, and any other file whose most recent change was simultaneous
       +with the current file's change.
       +Successive
       +.BR u 's
       +move further back in time.
       +The only commands for which u is ineffective are
       +.BR cd ,
       +.BR u ,
       +.BR q ,
       +.B w
       +and
       +.BR D .
       +If
       +.I n
       +is negative,
       +.B u
       +`redoes,' undoing the undo, going forwards in time again.
       +.TP
       +(empty)
       +If the range is explicit, set dot to the range.
       +If
       +.I sam
       +is downloaded, the resulting dot is selected on the screen;
       +otherwise it is printed.
       +If no address is specified (the
       +command is a newline) dot is extended in either direction to
       +line boundaries and printed.
       +If dot is thereby unchanged, it is set to
       +.B .+1 
       +and printed.
       +.PD
       +.SS Grouping and multiple changes
       +Commands may be grouped by enclosing them in braces
       +.BR {} .
       +Commands within the braces must appear on separate lines (no backslashes are
       +required between commands).
       +Semantically, an opening brace is like a command:
       +it takes an (optional) address and sets dot for each sub-command.
       +Commands within the braces are executed sequentially, but changes made
       +by one command are not visible to other commands (see the next
       +paragraph).
       +Braces may be nested arbitrarily.
       +.PP
       +When a command makes a number of changes to a file, as in
       +.BR x/re/c/text/ ,
       +the addresses of all changes to the file are computed in the original file.
       +If the changes are in sequence,
       +they are applied to the file.
       +Successive insertions at the same address are catenated into a single
       +insertion composed of the several insertions in the order applied.
       +.SS The terminal
       +What follows refers to behavior of
       +.I sam
       +when downloaded, that is, when
       +operating as a display editor on a raster display.
       +This is the default
       +behavior; invoking
       +.I sam
       +with the
       +.B -d
       +(no download) option provides access
       +to the command language only.
       +.PP
       +Each file may have zero or more windows open.
       +Each window is equivalent
       +and is updated simultaneously with changes in other windows on the same file.
       +Each window has an independent value of dot, indicated by a highlighted
       +substring on the display.
       +Dot may be in a region not within
       +the window.
       +There is usually a `current window',
       +marked with a dark border, to which typed text and editing
       +commands apply.
       +Text may be typed and edited as in
       +.IR rio (1);
       +also the escape key (ESC) selects (sets dot to) text typed
       +since the last mouse button hit.
       +.PP
       +The button 3 menu controls window operations.
       +The top of the menu
       +provides the following operators, each of which uses one or
       +more
       +.IR rio -like
       +cursors to prompt for selection of a window or sweeping
       +of a rectangle.
       +`Sweeping' a null rectangle gets a large window, disjoint
       +from the command window or the whole screen, depending on
       +where the null rectangle is.
       +.TF resize
       +.TP 
       +.B new
       +Create a new, empty file.
       +.TP
       +.B zerox
       +Create a copy of an existing window.
       +.TP
       +.B resize
       +As in
       +.IR rio .
       +.TP
       +.B close
       +Delete the window.
       +In the last window of a file,
       +.B close
       +is equivalent to a
       +.B D
       +for the file.
       +.TP
       +.B write
       +Equivalent to a
       +.B w
       +for the file.
       +.PD
       +.PP
       +Below these operators is a list of available files, starting with
       +.BR ~~sam~~ ,
       +the command window.
       +Selecting a file from the list makes the most recently
       +used window on that file current, unless it is already current, in which
       +case selections cycle through the open windows.
       +If no windows are open
       +on the file, the user is prompted to open one.
       +Files other than
       +.B ~~sam~~
       +are marked with one of the characters
       +.B -+*
       +according as zero, one, or more windows
       +are open on the file.
       +A further mark
       +.L .
       +appears on the file in the current window and
       +a single quote,
       +.BR ' ,
       +on a file modified since last write.
       +.PP
       +The command window, created automatically when
       +.B sam
       +starts, is an ordinary window except that text typed to it
       +is interpreted as commands for the editor rather than passive text,
       +and text printed by editor commands appears in it.
       +The behavior is like
       +.IR rio ,
       +with an `output point' that separates commands being typed from
       +previous output.
       +Commands typed in the command window apply to the
       +current open file\(emthe file in the most recently
       +current window.
       +.SS Manipulating text
       +Button 1 changes selection, much like
       +.IR rio .
       +Pointing to a non-current window with button 1 makes it current;
       +within the current window, button 1 selects text, thus setting dot.
       +Double-clicking selects text to the boundaries of words, lines,
       +quoted strings or bracketed strings, depending on the text at the click.
       +.PP
       +Button 2 provides a menu of editing commands:
       +.TF /regexp
       +.TP
       +.B cut
       +Delete dot and save the deleted text in the snarf buffer.
       +.TP
       +.B paste
       +Replace the text in dot by the contents of the snarf buffer.
       +.TP
       +.B snarf
       +Save the text in dot in the snarf buffer.
       +.TP
       +.B plumb
       +Send the text in the selection as a plumb
       +message.  If the selection is empty,
       +the white-space-delimited block of text is sent as a plumb message
       +with a
       +.B click
       +attribute defining where the selection lies (see
       +.IR plumb (7)).
       +.TP
       +.B look
       +Search forward for the next occurrence of the literal text in dot.
       +If dot is the null string, the text in the snarf buffer is
       +used.
       +The snarf buffer is unaffected.
       +.TP
       +.B <rio>
       +Exchange snarf buffers with
       +.IR rio .
       +.TP
       +.BI / regexp
       +Search forward for the next match of the last regular expression
       +typed in a command.
       +(Not in command window.)
       +.TP
       +.B send
       +Send the text in dot, or the snarf buffer if
       +dot is the null string, as if it were typed to the command window.
       +Saves the sent text in the snarf buffer.
       +(Command window only.) 
       +.PD
       +.SS External communication
       +.I Sam
       +listens to the
       +.B edit
       +plumb port.
       +If plumbing is not active,
       +on invocation
       +.I sam
       +creates a named pipe
       +.BI /srv/sam. user
       +which acts as an additional source of commands.  Characters written to
       +the named pipe are treated as if they had been typed in the command window.
       +.PP
       +.I B
       +is a shell-level command that causes an instance of
       +.I sam
       +running on the same terminal to load the named
       +.IR files .
       +.I B
       +uses either plumbing or the named pipe, whichever service is available.
       +If plumbing is not enabled,
       +the option allows a line number to be specified for
       +the initial position to display in the last named file
       +(plumbing provides a more general mechanism for this ability).
       +.PP
       +.I E
       +is a shell-level command that can be used as
       +.B $EDITOR
       +in a Unix environment.
       +It runs
       +.I B
       +on
       +.I file
       +and then does not exit until
       +.I file
       +is changed, which is taken as a signal that
       +.I file
       +is done being edited.
       +.SS Abnormal termination
       +If
       +.I sam
       +terminates other than by a
       +.B q
       +command (by hangup, deleting its window, etc.), modified
       +files are saved in an
       +executable file,
       +.BR $HOME/sam.save .
       +This program, when executed, asks whether to write
       +each file back to a external file.
       +The answer
       +.L y
       +causes writing; anything else skips the file.
       +.SH FILES
       +.TF $HOME/sam.save
       +.TP
       +.B $HOME/sam.save
       +.TP
       +.B $HOME/sam.err
       +.TP
       +.B \*9/bin/samsave
       +the program called to unpack
       +.BR $HOME/sam.save .
       +.SH SOURCE
       +.TF \*9/src/cmd/samterm
       +.TP
       +.B \*9/src/cmd/sam
       +source for
       +.I sam
       +itself
       +.TP
       +.B \*9/src/cmd/samterm
       +source for the separate terminal part
       +.TP
       +.B \*9/bin/B
       +.TP
       +.B \*9/bin/E
       +.SH SEE ALSO
       +.IR ed (1),
       +.IR sed (1),
       +.IR grep (1),
       +.IR rio (1),
       +.IR regexp (7).
       +.PP
       +Rob Pike,
       +``The text editor sam''.
   DIR diff --git a/sam/sam.c b/sam/sam.c
       @@ -0,0 +1,741 @@
       +#include "sam.h"
       +
       +Rune        genbuf[BLOCKSIZE];
       +int        io;
       +int        panicking;
       +int        rescuing;
       +String        genstr;
       +String        rhs;
       +String        curwd;
       +String        cmdstr;
       +Rune        empty[] = { 0 };
       +char        *genc;
       +File        *curfile;
       +File        *flist;
       +File        *cmd;
       +jmp_buf        mainloop;
       +List        tempfile = { 'p' };
       +int        quitok = TRUE;
       +int        downloaded;
       +int        dflag;
       +int        Rflag;
       +char        *machine;
       +char        *home;
       +int        bpipeok;
       +int        termlocked;
       +char        *samterm = SAMTERM;
       +char        *rsamname = RSAM;
       +File        *lastfile;
       +Disk        *disk;
       +long        seq;
       +
       +char *winsize;
       +
       +Rune        baddir[] = { '<', 'b', 'a', 'd', 'd', 'i', 'r', '>', '\n'};
       +
       +void        usage(void);
       +
       +extern int notify(void(*)(void*,char*));
       +
       +void
       +main(int _argc, char **_argv)
       +{
       +        volatile int i, argc;
       +        char **volatile argv;
       +        String *t;
       +        char *termargs[10], **ap;
       +        
       +        argc = _argc;
       +        argv = _argv;
       +        ap = termargs;
       +        *ap++ = "samterm";
       +        ARGBEGIN{
       +        case 'd':
       +                dflag++;
       +                break;
       +        case 'r':
       +                machine = EARGF(usage());
       +                break;
       +        case 'R':
       +                Rflag++;
       +                break;
       +        case 't':
       +                samterm = EARGF(usage());
       +                break;
       +        case 's':
       +                rsamname = EARGF(usage());
       +                break;
       +        default:
       +                dprint("sam: unknown flag %c\n", ARGC());
       +                usage();
       +        /* options for samterm */
       +        case 'a':
       +                *ap++ = "-a";
       +                break;
       +        case 'W':
       +                *ap++ = "-W";
       +                *ap++ = EARGF(usage());
       +                break;
       +        }ARGEND
       +        *ap = nil;
       +
       +        Strinit(&cmdstr);
       +        Strinit0(&lastpat);
       +        Strinit0(&lastregexp);
       +        Strinit0(&genstr);
       +        Strinit0(&rhs);
       +        Strinit0(&curwd);
       +        Strinit0(&plan9cmd);
       +        home = getenv(HOME);
       +        disk = diskinit();
       +        if(home == 0)
       +                home = "/";
       +        if(!dflag)
       +                startup(machine, Rflag, termargs, (char**)argv);
       +        notify(notifyf);
       +        getcurwd();
       +        if(argc>0){
       +                for(i=0; i<argc; i++){
       +                        if(!setjmp(mainloop)){
       +                                t = tmpcstr(argv[i]);
       +                                Straddc(t, '\0');
       +                                Strduplstr(&genstr, t);
       +                                freetmpstr(t);
       +                                fixname(&genstr);
       +                                logsetname(newfile(), &genstr);
       +                        }
       +                }
       +        }else if(!downloaded)
       +                newfile();
       +        seq++;
       +        if(file.nused)
       +                current(file.filepptr[0]);
       +        setjmp(mainloop);
       +        cmdloop();
       +        trytoquit();        /* if we already q'ed, quitok will be TRUE */
       +        exits(0);
       +}
       +
       +void
       +usage(void)
       +{
       +        dprint("usage: sam [-d] [-t samterm] [-s sam name] [-r machine] [file ...]\n");
       +        exits("usage");
       +}
       +
       +void
       +rescue(void)
       +{
       +        int i, nblank = 0;
       +        File *f;
       +        char *c;
       +        char buf[256];
       +        char *root;
       +
       +        if(rescuing++)
       +                return;
       +        io = -1;
       +        for(i=0; i<file.nused; i++){
       +                f = file.filepptr[i];
       +                if(f==cmd || f->b.nc==0 || !fileisdirty(f))
       +                        continue;
       +                if(io == -1){
       +                        sprint(buf, "%s/sam.save", home);
       +                        io = create(buf, 1, 0777);
       +                        if(io<0)
       +                                return;
       +                }
       +                if(f->name.s[0]){
       +                        c = Strtoc(&f->name);
       +                        strncpy(buf, c, sizeof buf-1);
       +                        buf[sizeof buf-1] = 0;
       +                        free(c);
       +                }else
       +                        sprint(buf, "nameless.%d", nblank++);
       +                root = getenv("PLAN9");
       +                if(root == nil)
       +                        root = "/usr/local/plan9";
       +                fprint(io, "#!/bin/sh\n%s/bin/samsave '%s' $* <<'---%s'\n", root, buf, buf);
       +                addr.r.p1 = 0, addr.r.p2 = f->b.nc;
       +                writeio(f);
       +                fprint(io, "\n---%s\n", (char *)buf);
       +        }
       +}
       +
       +void
       +panic(char *s)
       +{
       +        int wasd;
       +
       +        if(!panicking++ && !setjmp(mainloop)){
       +                wasd = downloaded;
       +                downloaded = 0;
       +                dprint("sam: panic: %s: %r\n", s);
       +                if(wasd)
       +                        fprint(2, "sam: panic: %s: %r\n", s);
       +                rescue();
       +                abort();
       +        }
       +}
       +
       +void
       +hiccough(char *s)
       +{
       +        File *f;
       +        int i;
       +
       +        if(rescuing)
       +                exits("rescue");
       +        if(s)
       +                dprint("%s\n", s);
       +        resetcmd();
       +        resetxec();
       +        resetsys();
       +        if(io > 0)
       +                close(io);
       +
       +        /*
       +         * back out any logged changes & restore old sequences
       +         */
       +        for(i=0; i<file.nused; i++){
       +                f = file.filepptr[i];
       +                if(f==cmd)
       +                        continue;
       +                if(f->seq==seq){
       +                        bufdelete(&f->epsilon, 0, f->epsilon.nc);
       +                        f->seq = f->prevseq;
       +                        f->dot.r = f->prevdot;
       +                        f->mark = f->prevmark;
       +                        state(f, f->prevmod ? Dirty: Clean);
       +                }
       +        }
       +
       +        update();
       +        if (curfile) {
       +                if (curfile->unread)
       +                        curfile->unread = FALSE;
       +                else if (downloaded)
       +                        outTs(Hcurrent, curfile->tag);
       +        }
       +        longjmp(mainloop, 1);
       +}
       +
       +void
       +intr(void)
       +{
       +        error(Eintr);
       +}
       +
       +void
       +trytoclose(File *f)
       +{
       +        char *t;
       +        char buf[256];
       +
       +        if(f == cmd)        /* possible? */
       +                return;
       +        if(f->deleted)
       +                return;
       +        if(fileisdirty(f) && !f->closeok){
       +                f->closeok = TRUE;
       +                if(f->name.s[0]){
       +                        t = Strtoc(&f->name);
       +                        strncpy(buf, t, sizeof buf-1);
       +                        free(t);
       +                }else
       +                        strcpy(buf, "nameless file");
       +                error_s(Emodified, buf);
       +        }
       +        f->deleted = TRUE;
       +}
       +
       +void
       +trytoquit(void)
       +{
       +        int c;
       +        File *f;
       +
       +        if(!quitok){
       +                for(c = 0; c<file.nused; c++){
       +                        f = file.filepptr[c];
       +                        if(f!=cmd && fileisdirty(f)){
       +                                quitok = TRUE;
       +                                eof = FALSE;
       +                                error(Echanges);
       +                        }
       +                }
       +        }
       +}
       +
       +void
       +load(File *f)
       +{
       +        Address saveaddr;
       +
       +        Strduplstr(&genstr, &f->name);
       +        filename(f);
       +        if(f->name.s[0]){
       +                saveaddr = addr;
       +                edit(f, 'I');
       +                addr = saveaddr;
       +        }else{
       +                f->unread = 0;
       +                f->cleanseq = f->seq;
       +        }
       +
       +        fileupdate(f, TRUE, TRUE);
       +}
       +
       +void
       +cmdupdate(void)
       +{
       +        if(cmd && cmd->seq!=0){
       +                fileupdate(cmd, FALSE, downloaded);
       +                cmd->dot.r.p1 = cmd->dot.r.p2 = cmd->b.nc;
       +                telldot(cmd);
       +        }
       +}
       +
       +void
       +delete(File *f)
       +{
       +        if(downloaded && f->rasp)
       +                outTs(Hclose, f->tag);
       +        delfile(f);
       +        if(f == curfile)
       +                current(0);
       +}
       +
       +void
       +update(void)
       +{
       +        int i, anymod;
       +        File *f;
       +
       +        settempfile();
       +        for(anymod = i=0; i<tempfile.nused; i++){
       +                f = tempfile.filepptr[i];
       +                if(f==cmd)        /* cmd gets done in main() */
       +                        continue;
       +                if(f->deleted) {
       +                        delete(f);
       +                        continue;
       +                }
       +                if(f->seq==seq && fileupdate(f, FALSE, downloaded))
       +                        anymod++;
       +                if(f->rasp)
       +                        telldot(f);
       +        }
       +        if(anymod)
       +                seq++;
       +}
       +
       +File *
       +current(File *f)
       +{
       +        return curfile = f;
       +}
       +
       +void
       +edit(File *f, int cmd)
       +{
       +        int empty = TRUE;
       +        Posn p;
       +        int nulls;
       +
       +        if(cmd == 'r')
       +                logdelete(f, addr.r.p1, addr.r.p2);
       +        if(cmd=='e' || cmd=='I'){
       +                logdelete(f, (Posn)0, f->b.nc);
       +                addr.r.p2 = f->b.nc;
       +        }else if(f->b.nc!=0 || (f->name.s[0] && Strcmp(&genstr, &f->name)!=0))
       +                empty = FALSE;
       +        if((io = open(genc, OREAD))<0) {
       +                if (curfile && curfile->unread)
       +                        curfile->unread = FALSE;
       +                error_r(Eopen, genc);
       +        }
       +        p = readio(f, &nulls, empty, TRUE);
       +        closeio((cmd=='e' || cmd=='I')? -1 : p);
       +        if(cmd == 'r')
       +                f->ndot.r.p1 = addr.r.p2, f->ndot.r.p2 = addr.r.p2+p;
       +        else
       +                f->ndot.r.p1 = f->ndot.r.p2 = 0;
       +        f->closeok = empty;
       +        if (quitok)
       +                quitok = empty;
       +        else
       +                quitok = FALSE;
       +        state(f, empty && !nulls? Clean : Dirty);
       +        if(empty && !nulls)
       +                f->cleanseq = f->seq;
       +        if(cmd == 'e')
       +                filename(f);
       +}
       +
       +int
       +getname(File *f, String *s, int save)
       +{
       +        int c, i;
       +
       +        Strzero(&genstr);
       +        if(genc){
       +                free(genc);
       +                genc = 0;
       +        }
       +        if(s==0 || (c = s->s[0])==0){                /* no name provided */
       +                if(f)
       +                        Strduplstr(&genstr, &f->name);
       +                goto Return;
       +        }
       +        if(c!=' ' && c!='\t')
       +                error(Eblank);
       +        for(i=0; (c=s->s[i])==' ' || c=='\t'; i++)
       +                ;
       +        while(s->s[i] > ' ')
       +                Straddc(&genstr, s->s[i++]);
       +        if(s->s[i])
       +                error(Enewline);
       +        fixname(&genstr);
       +        if(f && (save || f->name.s[0]==0)){
       +                logsetname(f, &genstr);
       +                if(Strcmp(&f->name, &genstr)){
       +                        quitok = f->closeok = FALSE;
       +                        f->qidpath = 0;
       +                        f->mtime = 0;
       +                        state(f, Dirty); /* if it's 'e', fix later */
       +                }
       +        }
       +    Return:
       +        genc = Strtoc(&genstr);
       +        i = genstr.n;
       +        if(i && genstr.s[i-1]==0)
       +                i--;
       +        return i;        /* strlen(name) */
       +}
       +
       +void
       +filename(File *f)
       +{
       +        if(genc)
       +                free(genc);
       +        genc = Strtoc(&genstr);
       +        dprint("%c%c%c %s\n", " '"[f->mod],
       +                "-+"[f->rasp!=0], " ."[f==curfile], genc);
       +}
       +
       +void
       +undostep(File *f, int isundo)
       +{
       +        uint p1, p2;
       +        int mod;
       +
       +        mod = f->mod;
       +        fileundo(f, isundo, 1, &p1, &p2, TRUE);
       +        f->ndot = f->dot;
       +        if(f->mod){
       +                f->closeok = 0;
       +                quitok = 0;
       +        }else
       +                f->closeok = 1;
       +
       +        if(f->mod != mod){
       +                f->mod = mod;
       +                if(mod)
       +                        mod = Clean;
       +                else
       +                        mod = Dirty;
       +                state(f, mod);
       +        }
       +}
       +
       +int
       +undo(int isundo)
       +{
       +        File *f;
       +        int i;
       +        Mod max;
       +
       +        max = undoseq(curfile, isundo);
       +        if(max == 0)
       +                return 0;
       +        settempfile();
       +        for(i = 0; i<tempfile.nused; i++){
       +                f = tempfile.filepptr[i];
       +                if(f!=cmd && undoseq(f, isundo)==max)
       +                        undostep(f, isundo);
       +        }
       +        return 1;
       +}
       +
       +int
       +readcmd(String *s)
       +{
       +        int retcode;
       +
       +        if(flist != 0)
       +                fileclose(flist);
       +        flist = fileopen();
       +
       +        addr.r.p1 = 0, addr.r.p2 = flist->b.nc;
       +        retcode = plan9(flist, '<', s, FALSE);
       +        fileupdate(flist, FALSE, FALSE);
       +        flist->seq = 0;
       +        if (flist->b.nc > BLOCKSIZE)
       +                error(Etoolong);
       +        Strzero(&genstr);
       +        Strinsure(&genstr, flist->b.nc);
       +        bufread(&flist->b, (Posn)0, genbuf, flist->b.nc);
       +        memmove(genstr.s, genbuf, flist->b.nc*RUNESIZE);
       +        genstr.n = flist->b.nc;
       +        Straddc(&genstr, '\0');
       +        return retcode;
       +}
       +
       +void
       +getcurwd(void)
       +{
       +        String *t;
       +        char buf[256];
       +
       +        buf[0] = 0;
       +        getwd(buf, sizeof(buf));
       +        t = tmpcstr(buf);
       +        Strduplstr(&curwd, t);
       +        freetmpstr(t);
       +        if(curwd.n == 0)
       +                warn(Wpwd);
       +        else if(curwd.s[curwd.n-1] != '/')
       +                Straddc(&curwd, '/');
       +}
       +
       +void
       +cd(String *str)
       +{
       +        int i, fd;
       +        char *s;
       +        File *f;
       +        String owd;
       +
       +        getcurwd();
       +        if(getname((File *)0, str, FALSE))
       +                s = genc;
       +        else
       +                s = home;
       +        if(chdir(s))
       +                syserror("chdir");
       +        fd = open("/dev/wdir", OWRITE);
       +        if(fd > 0)
       +                write(fd, s, strlen(s));
       +        dprint("!\n");
       +        Strinit(&owd);
       +        Strduplstr(&owd, &curwd);
       +        getcurwd();
       +        settempfile();
       +        /*
       +         * Two passes so that if we have open
       +         * /a/foo.c and /b/foo.c and cd from /b to /a,
       +         * we don't ever have two foo.c simultaneously.
       +         */
       +        for(i=0; i<tempfile.nused; i++){
       +                f = tempfile.filepptr[i];
       +                if(f!=cmd && f->name.s[0]!='/' && f->name.s[0]!=0){
       +                        Strinsert(&f->name, &owd, (Posn)0);
       +                        fixname(&f->name);
       +                        sortname(f);
       +                }
       +        }
       +        for(i=0; i<tempfile.nused; i++){
       +                f = tempfile.filepptr[i];
       +                if(f != cmd && Strispre(&curwd, &f->name)){
       +                        fixname(&f->name);
       +                        sortname(f);
       +                }
       +        }
       +        Strclose(&owd);
       +}
       +
       +int
       +loadflist(String *s)
       +{
       +        int c, i;
       +
       +        c = s->s[0];
       +        for(i = 0; s->s[i]==' ' || s->s[i]=='\t'; i++)
       +                ;
       +        if((c==' ' || c=='\t') && s->s[i]!='\n'){
       +                if(s->s[i]=='<'){
       +                        Strdelete(s, 0L, (long)i+1);
       +                        readcmd(s);
       +                }else{
       +                        Strzero(&genstr);
       +                        while((c = s->s[i++]) && c!='\n')
       +                                Straddc(&genstr, c);
       +                        Straddc(&genstr, '\0');
       +                }
       +        }else{
       +                if(c != '\n')
       +                        error(Eblank);
       +                Strdupl(&genstr, empty);
       +        }
       +        if(genc)
       +                free(genc);
       +        genc = Strtoc(&genstr);
       +        return genstr.s[0];
       +}
       +
       +File *
       +readflist(int readall, int delete)
       +{
       +        Posn i;
       +        int c;
       +        File *f;
       +        String t;
       +
       +        Strinit(&t);
       +        for(i=0,f=0; f==0 || readall || delete; i++){        /* ++ skips blank */
       +                Strdelete(&genstr, (Posn)0, i);
       +                for(i=0; (c = genstr.s[i])==' ' || c=='\t' || c=='\n'; i++)
       +                        ;
       +                if(i >= genstr.n)
       +                        break;
       +                Strdelete(&genstr, (Posn)0, i);
       +                for(i=0; (c=genstr.s[i]) && c!=' ' && c!='\t' && c!='\n'; i++)
       +                        ;
       +
       +                if(i == 0)
       +                        break;
       +                genstr.s[i] = 0;
       +                Strduplstr(&t, tmprstr(genstr.s, i+1));
       +                fixname(&t);
       +                f = lookfile(&t);
       +                if(delete){
       +                        if(f == 0)
       +                                warn_S(Wfile, &t);
       +                        else
       +                                trytoclose(f);
       +                }else if(f==0 && readall)
       +                        logsetname(f = newfile(), &t);
       +        }
       +        Strclose(&t);
       +        return f;
       +}
       +
       +File *
       +tofile(String *s)
       +{
       +        File *f;
       +
       +        if(s->s[0] != ' ')
       +                error(Eblank);
       +        if(loadflist(s) == 0){
       +                f = lookfile(&genstr);        /* empty string ==> nameless file */
       +                if(f == 0)
       +                        error_s(Emenu, genc);
       +        }else if((f=readflist(FALSE, FALSE)) == 0)
       +                error_s(Emenu, genc);
       +        return current(f);
       +}
       +
       +File *
       +getfile(String *s)
       +{
       +        File *f;
       +
       +        if(loadflist(s) == 0)
       +                logsetname(f = newfile(), &genstr);
       +        else if((f=readflist(TRUE, FALSE)) == 0)
       +                error(Eblank);
       +        return current(f);
       +}
       +
       +void
       +closefiles(File *f, String *s)
       +{
       +        if(s->s[0] == 0){
       +                if(f == 0)
       +                        error(Enofile);
       +                trytoclose(f);
       +                return;
       +        }
       +        if(s->s[0] != ' ')
       +                error(Eblank);
       +        if(loadflist(s) == 0)
       +                error(Enewline);
       +        readflist(FALSE, TRUE);
       +}
       +
       +void
       +copy(File *f, Address addr2)
       +{
       +        Posn p;
       +        int ni;
       +        for(p=addr.r.p1; p<addr.r.p2; p+=ni){
       +                ni = addr.r.p2-p;
       +                if(ni > BLOCKSIZE)
       +                        ni = BLOCKSIZE;
       +                bufread(&f->b, p, genbuf, ni);
       +                loginsert(addr2.f, addr2.r.p2, tmprstr(genbuf, ni)->s, ni);
       +        }
       +        addr2.f->ndot.r.p2 = addr2.r.p2+(f->dot.r.p2-f->dot.r.p1);
       +        addr2.f->ndot.r.p1 = addr2.r.p2;
       +}
       +
       +void
       +move(File *f, Address addr2)
       +{
       +        if(addr.r.p2 <= addr2.r.p2){
       +                logdelete(f, addr.r.p1, addr.r.p2);
       +                copy(f, addr2);
       +        }else if(addr.r.p1 >= addr2.r.p2){
       +                copy(f, addr2);
       +                logdelete(f, addr.r.p1, addr.r.p2);
       +        }else
       +                error(Eoverlap);
       +}
       +
       +Posn
       +nlcount(File *f, Posn p0, Posn p1)
       +{
       +        Posn nl = 0;
       +
       +        while(p0 < p1)
       +                if(filereadc(f, p0++)=='\n')
       +                        nl++;
       +        return nl;
       +}
       +
       +void
       +printposn(File *f, int charsonly)
       +{
       +        Posn l1, l2;
       +
       +        if(!charsonly){
       +                l1 = 1+nlcount(f, (Posn)0, addr.r.p1);
       +                l2 = l1+nlcount(f, addr.r.p1, addr.r.p2);
       +                /* check if addr ends with '\n' */
       +                if(addr.r.p2>0 && addr.r.p2>addr.r.p1 && filereadc(f, addr.r.p2-1)=='\n')
       +                        --l2;
       +                dprint("%lud", l1);
       +                if(l2 != l1)
       +                        dprint(",%lud", l2);
       +                dprint("; ");
       +        }
       +        dprint("#%lud", addr.r.p1);
       +        if(addr.r.p2 != addr.r.p1)
       +                dprint(",#%lud", addr.r.p2);
       +        dprint("\n");
       +}
       +
       +void
       +settempfile(void)
       +{
       +        if(tempfile.nalloc < file.nused){
       +                if(tempfile.filepptr)
       +                        free(tempfile.filepptr);
       +                tempfile.filepptr = emalloc(sizeof(File*)*file.nused);
       +                tempfile.nalloc = file.nused;
       +        }
       +        memmove(tempfile.filepptr, file.filepptr, sizeof(File*)*file.nused);
       +        tempfile.nused = file.nused;
       +}
   DIR diff --git a/sam/sam.h b/sam/sam.h
       @@ -0,0 +1,408 @@
       +#include <u.h>
       +#include <libc.h>
       +#include <plumb.h>
       +#include "errors.h"
       +
       +#undef waitfor
       +#define waitfor samwaitfor
       +
       +#undef warn
       +#define warn samwarn
       +
       +/*
       + * BLOCKSIZE is relatively small to keep memory consumption down.
       + */
       +
       +#define        BLOCKSIZE        2048
       +#define        RUNESIZE        sizeof(Rune)
       +#define        NDISC                5
       +#define        NBUFFILES        3+2*NDISC        /* plan 9+undo+snarf+NDISC*(transcript+buf) */
       +#define NSUBEXP        10
       +
       +#define        TRUE                1
       +#define        FALSE                0
       +
       +#undef INFINITY        /* Darwin declares this as HUGE_VAL */
       +#define        INFINITY        0x7FFFFFFFL
       +#define        INCR                25
       +#define        STRSIZE                (2*BLOCKSIZE)
       +
       +typedef long                Posn;                /* file position or address */
       +typedef        ushort                Mod;                /* modification number */
       +
       +typedef struct Address        Address;
       +typedef struct Block        Block;
       +typedef struct Buffer        Buffer;
       +typedef struct Disk        Disk;
       +typedef struct Discdesc        Discdesc;
       +typedef struct File        File;
       +typedef struct List        List;
       +typedef struct Range        Range;
       +typedef struct Rangeset        Rangeset;
       +typedef struct String        String;
       +
       +enum State
       +{
       +        Clean =                ' ',
       +        Dirty =                '\'',
       +        Unread =        '-'
       +};
       +
       +struct Range
       +{
       +        Posn        p1, p2;
       +};
       +
       +struct Rangeset
       +{
       +        Range        p[NSUBEXP];
       +};
       +
       +struct Address
       +{
       +        Range        r;
       +        File        *f;
       +};
       +
       +struct String
       +{
       +        short        n;
       +        short        size;
       +        Rune        *s;
       +};
       +
       +struct List        /* code depends on a long being able to hold a pointer */
       +{
       +        int        type;        /* 'p' for pointer, 'P' for Posn */
       +        int        nalloc;
       +        int        nused;
       +        union{
       +                void*        listp;
       +                void**        voidp;
       +                Posn*        posnp;
       +                String**stringp;
       +                File**        filep;
       +        }g;
       +};
       +
       +#define        listptr                g.listp
       +#define        voidpptr        g.voidp
       +#define        posnptr                g.posnp
       +#define        stringpptr        g.stringp
       +#define        filepptr        g.filep
       +
       +enum
       +{
       +        Blockincr =        256,
       +        Maxblock =         8*1024,
       +
       +        BUFSIZE = Maxblock,        /* size from fbufalloc() */
       +        RBUFSIZE = BUFSIZE/sizeof(Rune)
       +};
       +
       +
       +enum
       +{
       +        Null                = '-',
       +        Delete                = 'd',
       +        Insert                = 'i',
       +        Filename        = 'f',
       +        Dot                = 'D',
       +        Mark                = 'm'
       +};
       +
       +struct Block
       +{
       +        uint                addr;        /* disk address in bytes */
       +        union {
       +                uint        n;        /* number of used runes in block */
       +                Block        *next;        /* pointer to next in free list */
       +        } u;
       +};
       +
       +struct Disk
       +{
       +        int                fd;
       +        uint                addr;        /* length of temp file */
       +        Block                *free[Maxblock/Blockincr+1];
       +};
       +
       +Disk*                diskinit(void);
       +Block*                disknewblock(Disk*, uint);
       +void                diskrelease(Disk*, Block*);
       +void                diskread(Disk*, Block*, Rune*, uint);
       +void                diskwrite(Disk*, Block**, Rune*, uint);
       +
       +struct Buffer
       +{
       +        uint                nc;
       +        Rune                *c;        /* cache */
       +        uint                cnc;        /* bytes in cache */
       +        uint                cmax;        /* size of allocated cache */
       +        uint                cq;        /* position of cache */
       +        int                cdirty;        /* cache needs to be written */
       +        uint                cbi;        /* index of cache Block */
       +        Block                **bl;        /* array of blocks */
       +        uint                nbl;        /* number of blocks */
       +};
       +void                bufinsert(Buffer*, uint, Rune*, uint);
       +void                bufdelete(Buffer*, uint, uint);
       +uint                bufload(Buffer*, uint, int, int*);
       +void                bufread(Buffer*, uint, Rune*, uint);
       +void                bufclose(Buffer*);
       +void                bufreset(Buffer*);
       +
       +struct File
       +{
       +        Buffer         b;                                /* the data */
       +        Buffer                delta;                /* transcript of changes */
       +        Buffer                epsilon;        /* inversion of delta for redo */
       +        String                name;                /* name of associated file */
       +        uvlong                qidpath;        /* of file when read */
       +        uint                mtime;                /* of file when read */
       +        int                dev;                /* of file when read */
       +        int                unread;                /* file has not been read from disk */
       +
       +        long                seq;                /* if seq==0, File acts like Buffer */
       +        long                cleanseq;        /* f->seq at last read/write of file */
       +        int                mod;                /* file appears modified in menu */
       +        char                rescuing;        /* sam exiting; this file unusable */
       +
       +#if 0
       +//        Text                *curtext;        /* most recently used associated text */
       +//        Text                **text;                /* list of associated texts */
       +//        int                ntext;
       +//        int                dumpid;                /* used in dumping zeroxed windows */
       +#endif
       +
       +        Posn                hiposn;                /* highest address touched this Mod */
       +        Address                dot;                /* current position */
       +        Address                ndot;                /* new current position after update */
       +        Range                tdot;                /* what terminal thinks is current range */
       +        Range                mark;                /* tagged spot in text (don't confuse with Mark) */
       +        List                *rasp;                /* map of what terminal's got */
       +        short                tag;                /* for communicating with terminal */
       +        char                closeok;        /* ok to close file? */
       +        char                deleted;        /* delete at completion of command */
       +        Range                prevdot;        /* state before start of change */
       +        Range                prevmark;
       +        long                prevseq;
       +        int                prevmod;
       +};
       +/*File*                fileaddtext(File*, Text*); */
       +void                fileclose(File*);
       +void                filedelete(File*, uint, uint);
       +/*void                filedeltext(File*, Text*); */
       +void                fileinsert(File*, uint, Rune*, uint);
       +uint                fileload(File*, uint, int, int*);
       +void                filemark(File*);
       +void                filereset(File*);
       +void                filesetname(File*, String*);
       +void                fileundelete(File*, Buffer*, uint, uint);
       +void                fileuninsert(File*, Buffer*, uint, uint);
       +void                fileunsetname(File*, Buffer*);
       +void                fileundo(File*, int, int, uint*, uint*, int);
       +int                fileupdate(File*, int, int);
       +
       +int                filereadc(File*, uint);
       +File                *fileopen(void);
       +void                loginsert(File*, uint, Rune*, uint);
       +void                logdelete(File*, uint, uint);
       +void                logsetname(File*, String*);
       +int                fileisdirty(File*);
       +long                undoseq(File*, int);
       +long                prevseq(Buffer*);
       +
       +void                raspload(File*);
       +void                raspstart(File*);
       +void                raspdelete(File*, uint, uint, int);
       +void                raspinsert(File*, uint, Rune*, uint, int);
       +void                raspdone(File*, int);
       +void                raspflush(File*);
       +
       +/*
       + * acme fns
       + */
       +void*        fbufalloc(void);
       +void        fbuffree(void*);
       +uint        min(uint, uint);
       +void        cvttorunes(char*, int, Rune*, int*, int*, int*);
       +
       +#define        runemalloc(a)                (Rune*)emalloc((a)*sizeof(Rune))
       +#define        runerealloc(a, b)        (Rune*)realloc((a), (b)*sizeof(Rune))
       +#define        runemove(a, b, c)        memmove((a), (b), (c)*sizeof(Rune))
       +
       +int        alnum(int);
       +int        Read(int, void*, int);
       +void        Seek(int, long, int);
       +int        plan9(File*, int, String*, int);
       +int        Write(int, void*, int);
       +int        bexecute(File*, Posn);
       +void        cd(String*);
       +void        closefiles(File*, String*);
       +void        closeio(Posn);
       +void        cmdloop(void);
       +void        cmdupdate(void);
       +void        compile(String*);
       +void        copy(File*, Address);
       +File        *current(File*);
       +void        delete(File*);
       +void        delfile(File*);
       +void        dellist(List*, int);
       +void        doubleclick(File*, Posn);
       +void        dprint(char*, ...);
       +void        edit(File*, int);
       +void        *emalloc(ulong);
       +void        *erealloc(void*, ulong);
       +void        error(Err);
       +void        error_c(Err, int);
       +void        error_r(Err, char*);
       +void        error_s(Err, char*);
       +int        execute(File*, Posn, Posn);
       +int        filematch(File*, String*);
       +void        filename(File*);
       +void        fixname(String*);
       +void        fullname(String*);
       +void        getcurwd(void);
       +File        *getfile(String*);
       +int        getname(File*, String*, int);
       +long        getnum(int);
       +void        hiccough(char*);
       +void        inslist(List*, int, ...);
       +Address        lineaddr(Posn, Address, int);
       +List        *listalloc(int);
       +void        listfree(List*);
       +void        load(File*);
       +File        *lookfile(String*);
       +void        lookorigin(File*, Posn, Posn);
       +int        lookup(int);
       +void        move(File*, Address);
       +void        moveto(File*, Range);
       +File        *newfile(void);
       +void        nextmatch(File*, String*, Posn, int);
       +int        newtmp(int);
       +void        notifyf(void*, char*);
       +void        panic(char*);
       +void        printposn(File*, int);
       +void        print_ss(char*, String*, String*);
       +void        print_s(char*, String*);
       +int        rcv(void);
       +Range        rdata(List*, Posn, Posn);
       +Posn        readio(File*, int*, int, int);
       +void        rescue(void);
       +void        resetcmd(void);
       +void        resetsys(void);
       +void        resetxec(void);
       +void        rgrow(List*, Posn, Posn);
       +void        samerr(char*);
       +void        settempfile(void);
       +int        skipbl(void);
       +void        snarf(File*, Posn, Posn, Buffer*, int);
       +void        sortname(File*);
       +void        startup(char*, int, char**, char**);
       +void        state(File*, int);
       +int        statfd(int, ulong*, uvlong*, long*, long*, long*);
       +int        statfile(char*, ulong*, uvlong*, long*, long*, long*);
       +void        Straddc(String*, int);
       +void        Strclose(String*);
       +int        Strcmp(String*, String*);
       +void        Strdelete(String*, Posn, Posn);
       +void        Strdupl(String*, Rune*);
       +void        Strduplstr(String*, String*);
       +void        Strinit(String*);
       +void        Strinit0(String*);
       +void        Strinsert(String*, String*, Posn);
       +void        Strinsure(String*, ulong);
       +int        Strispre(String*, String*);
       +void        Strzero(String*);
       +int        Strlen(Rune*);
       +char        *Strtoc(String*);
       +void        syserror(char*);
       +void        telldot(File*);
       +void        tellpat(void);
       +String        *tmpcstr(char*);
       +String        *tmprstr(Rune*, int);
       +void        freetmpstr(String*);
       +void        termcommand(void);
       +void        termwrite(char*);
       +File        *tofile(String*);
       +void        trytoclose(File*);
       +void        trytoquit(void);
       +int        undo(int);
       +void        update(void);
       +int        waitfor(int);
       +void        warn(Warn);
       +void        warn_s(Warn, char*);
       +void        warn_SS(Warn, String*, String*);
       +void        warn_S(Warn, String*);
       +int        whichmenu(File*);
       +void        writef(File*);
       +Posn        writeio(File*);
       +Discdesc *Dstart(void);
       +
       +extern Rune        samname[];        /* compiler dependent */
       +extern Rune        *left[];
       +extern Rune        *right[];
       +
       +extern char        RSAM[];                /* system dependent */
       +extern char        SAMTERM[];
       +extern char        HOME[];
       +extern char        TMPDIR[];
       +extern char        SH[];
       +extern char        SHPATH[];
       +extern char        RX[];
       +extern char        RXPATH[];
       +
       +/*
       + * acme globals
       + */
       +extern long                seq;
       +extern Disk                *disk;
       +
       +extern char        *rsamname;        /* globals */
       +extern char        *samterm;
       +extern Rune        genbuf[];
       +extern char        *genc;
       +extern int        io;
       +extern int        patset;
       +extern int        quitok;
       +extern Address        addr;
       +extern Buffer        snarfbuf;
       +extern Buffer        plan9buf;
       +extern List        file;
       +extern List        tempfile;
       +extern File        *cmd;
       +extern File        *curfile;
       +extern File        *lastfile;
       +extern Mod        modnum;
       +extern Posn        cmdpt;
       +extern Posn        cmdptadv;
       +extern Rangeset        sel;
       +extern String        curwd;
       +extern String        cmdstr;
       +extern String        genstr;
       +extern String        lastpat;
       +extern String        lastregexp;
       +extern String        plan9cmd;
       +extern int        downloaded;
       +extern int        eof;
       +extern int        bpipeok;
       +extern int        panicking;
       +extern Rune        empty[];
       +extern int        termlocked;
       +extern int        outbuffered;
       +
       +#include "mesg.h"
       +
       +void        outTs(Hmesg, int);
       +void        outT0(Hmesg);
       +void        outTl(Hmesg, long);
       +void        outTslS(Hmesg, int, long, String*);
       +void        outTS(Hmesg, String*);
       +void        outTsS(Hmesg, int, String*);
       +void        outTsllS(Hmesg, int, long, long, String*);
       +void        outTsll(Hmesg, int, long, long);
       +void        outTsl(Hmesg, int, long);
       +void        outTsv(Hmesg, int, vlong);
       +void        outflush(void);
       +int needoutflush(void);
   DIR diff --git a/sam/shell.c b/sam/shell.c
       @@ -0,0 +1,165 @@
       +#include "sam.h"
       +#include "parse.h"
       +
       +extern        jmp_buf        mainloop;
       +
       +char        errfile[64];
       +String        plan9cmd;        /* null terminated */
       +Buffer        plan9buf;
       +void        checkerrs(void);
       +
       +void
       +setname(File *f)
       +{
       +        char buf[1024];
       +        if(f)
       +                snprint(buf, sizeof buf, "%.*S", f->name.n, f->name.s);
       +        else
       +                buf[0] = 0;
       +        putenv("samfile", buf);
       +}
       +
       +int
       +plan9(File *f, int type, String *s, int nest)
       +{
       +        long l;
       +        int m;
       +        int volatile pid;
       +        int fd;
       +        int retcode;
       +        int pipe1[2], pipe2[2];
       +
       +        if(s->s[0]==0 && plan9cmd.s[0]==0)
       +                error(Enocmd);
       +        else if(s->s[0])
       +                Strduplstr(&plan9cmd, s);
       +        if(downloaded){
       +                samerr(errfile);
       +                remove(errfile);
       +        }
       +        if(type!='!' && pipe(pipe1)==-1)
       +                error(Epipe);
       +        if(type=='|')
       +                snarf(f, addr.r.p1, addr.r.p2, &plan9buf, 1);
       +        if((pid=fork()) == 0){
       +                setname(f);
       +                if(downloaded){        /* also put nasty fd's into errfile */
       +                        fd = create(errfile, 1, 0666L);
       +                        if(fd < 0)
       +                                fd = create("/dev/null", 1, 0666L);
       +                        dup(fd, 2);
       +                        close(fd);
       +                        /* 2 now points at err file */
       +                        if(type == '>')
       +                                dup(2, 1);
       +                        else if(type=='!'){
       +                                dup(2, 1);
       +                                fd = open("/dev/null", 0);
       +                                dup(fd, 0);
       +                                close(fd);
       +                        }
       +                }
       +                if(type != '!') {
       +                        if(type=='<' || type=='|')
       +                                dup(pipe1[1], 1);
       +                        else if(type == '>')
       +                                dup(pipe1[0], 0);
       +                        close(pipe1[0]);
       +                        close(pipe1[1]);
       +                }
       +                if(type == '|'){
       +                        if(pipe(pipe2) == -1)
       +                                exits("pipe");
       +                        if((pid = fork())==0){
       +                                /*
       +                                 * It's ok if we get SIGPIPE here
       +                                 */
       +                                close(pipe2[0]);
       +                                io = pipe2[1];
       +                                if(retcode=!setjmp(mainloop)){        /* assignment = */
       +                                        char *c;
       +                                        for(l = 0; l<plan9buf.nc; l+=m){
       +                                                m = plan9buf.nc-l;
       +                                                if(m>BLOCKSIZE-1)
       +                                                        m = BLOCKSIZE-1;
       +                                                bufread(&plan9buf, l, genbuf, m);
       +                                                genbuf[m] = 0;
       +                                                c = Strtoc(tmprstr(genbuf, m+1));
       +                                                Write(pipe2[1], c, strlen(c));
       +                                                free(c);
       +                                        }
       +                                }
       +                                exits(retcode? "error" : 0);
       +                        }
       +                        if(pid==-1){
       +                                fprint(2, "Can't fork?!\n");
       +                                exits("fork");
       +                        }
       +                        dup(pipe2[0], 0);
       +                        close(pipe2[0]);
       +                        close(pipe2[1]);
       +                }
       +                if(type=='<'){
       +                        close(0);        /* so it won't read from terminal */
       +                        open("/dev/null", 0);
       +                }
       +                execl(SHPATH, SH, "-c", Strtoc(&plan9cmd), (char *)0);
       +                exits("exec");
       +        }
       +        if(pid == -1)
       +                error(Efork);
       +        if(type=='<' || type=='|'){
       +                int nulls;
       +                if(downloaded && addr.r.p1 != addr.r.p2)
       +                        outTl(Hsnarflen, addr.r.p2-addr.r.p1);
       +                snarf(f, addr.r.p1, addr.r.p2, &snarfbuf, 0);
       +                logdelete(f, addr.r.p1, addr.r.p2);
       +                close(pipe1[1]);
       +                io = pipe1[0];
       +                f->tdot.p1 = -1;
       +                f->ndot.r.p2 = addr.r.p2+readio(f, &nulls, 0, FALSE);
       +                f->ndot.r.p1 = addr.r.p2;
       +                closeio((Posn)-1);
       +        }else if(type=='>'){
       +                close(pipe1[0]);
       +                io = pipe1[1];
       +                bpipeok = 1;
       +                writeio(f);
       +                bpipeok = 0;
       +                closeio((Posn)-1);
       +        }
       +        retcode = waitfor(pid);
       +        if(type=='|' || type=='<')
       +                if(retcode!=0)
       +                        warn(Wbadstatus);
       +        if(downloaded)
       +                checkerrs();
       +        if(!nest)
       +                dprint("!\n");
       +        return retcode;
       +}
       +
       +void
       +checkerrs(void)
       +{
       +        char buf[BLOCKSIZE-10];
       +        int f, n, nl;
       +        char *p;
       +        long l;
       +
       +        if(statfile(errfile, 0, 0, 0, &l, 0) > 0 && l != 0){
       +                if((f=open(errfile, 0)) != -1){
       +                        if((n=read(f, buf, sizeof buf-1)) > 0){
       +                                for(nl=0,p=buf; nl<25 && p<&buf[n]; p++)
       +                                        if(*p=='\n')
       +                                                nl++;
       +                                *p = 0;
       +                                dprint("%s", buf);
       +                                if(p-buf < l-1)
       +                                        dprint("(sam: more in %s)\n", errfile);
       +                        }
       +                        close(f);
       +                }
       +        }else
       +                remove(errfile);
       +}
   DIR diff --git a/sam/string.c b/sam/string.c
       @@ -0,0 +1,193 @@
       +#include "sam.h"
       +
       +#define        MINSIZE        16                /* minimum number of chars allocated */
       +#define        MAXSIZE        256                /* maximum number of chars for an empty string */
       +
       +
       +void
       +Strinit(String *p)
       +{
       +        p->s = emalloc(MINSIZE*RUNESIZE);
       +        p->n = 0;
       +        p->size = MINSIZE;
       +}
       +
       +void
       +Strinit0(String *p)
       +{
       +        p->s = emalloc(MINSIZE*RUNESIZE);
       +        p->s[0] = 0;
       +        p->n = 1;
       +        p->size = MINSIZE;
       +}
       +
       +void
       +Strclose(String *p)
       +{
       +        free(p->s);
       +}
       +
       +void
       +Strzero(String *p)
       +{
       +        if(p->size > MAXSIZE){
       +                p->s = erealloc(p->s, RUNESIZE*MAXSIZE); /* throw away the garbage */
       +                p->size = MAXSIZE;
       +        }
       +        p->n = 0;
       +}
       +
       +int
       +Strlen(Rune *r)
       +{
       +        Rune *s;
       +
       +        for(s=r; *s; s++)
       +                ;
       +        return s-r;
       +}
       +
       +void
       +Strdupl(String *p, Rune *s)        /* copies the null */
       +{
       +        p->n = Strlen(s)+1;
       +        Strinsure(p, p->n);
       +        memmove(p->s, s, p->n*RUNESIZE);
       +}
       +
       +void
       +Strduplstr(String *p, String *q)        /* will copy the null if there's one there */
       +{
       +        Strinsure(p, q->n);
       +        p->n = q->n;
       +        memmove(p->s, q->s, q->n*RUNESIZE);
       +}
       +
       +void
       +Straddc(String *p, int c)
       +{
       +        Strinsure(p, p->n+1);
       +        p->s[p->n++] = c;
       +}
       +
       +void
       +Strinsure(String *p, ulong n)
       +{
       +        if(n > STRSIZE)
       +                error(Etoolong);
       +        if(p->size < n){        /* p needs to grow */
       +                n += 100;
       +                p->s = erealloc(p->s, n*RUNESIZE);
       +                p->size = n;
       +        }
       +}
       +
       +void
       +Strinsert(String *p, String *q, Posn p0)
       +{
       +        Strinsure(p, p->n+q->n);
       +        memmove(p->s+p0+q->n, p->s+p0, (p->n-p0)*RUNESIZE);
       +        memmove(p->s+p0, q->s, q->n*RUNESIZE);
       +        p->n += q->n;
       +}
       +
       +void
       +Strdelete(String *p, Posn p1, Posn p2)
       +{
       +        memmove(p->s+p1, p->s+p2, (p->n-p2)*RUNESIZE);
       +        p->n -= p2-p1;
       +}
       +
       +int
       +Strcmp(String *a, String *b)
       +{
       +        int i, c;
       +
       +        for(i=0; i<a->n && i<b->n; i++)
       +                if(c = (a->s[i] - b->s[i]))        /* assign = */
       +                        return c;
       +        /* damn NULs confuse everything */
       +        i = a->n - b->n;
       +        if(i == 1){
       +                if(a->s[a->n-1] == 0)
       +                        return 0;
       +        }else if(i == -1){
       +                if(b->s[b->n-1] == 0)
       +                        return 0;
       +        }
       +        return i;
       +}
       +
       +int
       +Strispre(String *a, String *b)
       +{
       +        int i;
       +
       +        for(i=0; i<a->n && i<b->n; i++){
       +                if(a->s[i] - b->s[i]){        /* assign = */
       +                        if(a->s[i] == 0)
       +                                return 1;
       +                        return 0;
       +                }
       +        }
       +        return i == a->n;
       +}
       +
       +char*
       +Strtoc(String *s)
       +{
       +        int i;
       +        char *c, *d;
       +        Rune *r;
       +        c = emalloc(s->n*UTFmax + 1);  /* worst case UTFmax bytes per rune, plus NUL */
       +        d = c;
       +        r = s->s;
       +        for(i=0; i<s->n; i++)
       +                d += runetochar(d, r++);
       +        if(d==c || d[-1]!=0)
       +                *d = 0;
       +        return c;
       +
       +}
       +
       +/*
       + * Build very temporary String from Rune*
       + */
       +String*
       +tmprstr(Rune *r, int n)
       +{
       +        static String p;
       +
       +        p.s = r;
       +        p.n = n;
       +        p.size = n;
       +        return &p;
       +}
       +
       +/*
       + * Convert null-terminated char* into String
       + */
       +String*
       +tmpcstr(char *s)
       +{
       +        String *p;
       +        Rune *r;
       +        int i, n;
       +
       +        n = utflen(s);        /* don't include NUL */
       +        p = emalloc(sizeof(String));
       +        r = emalloc(n*RUNESIZE);
       +        p->s = r;
       +        for(i=0; i<n; i++,r++)
       +                s += chartorune(r, s);
       +        p->n = n;
       +        p->size = n;
       +        return p;
       +}
       +
       +void
       +freetmpstr(String *s)
       +{
       +        free(s->s);
       +        free(s);
       +}
   DIR diff --git a/sam/sys.c b/sam/sys.c
       @@ -0,0 +1,60 @@
       +#include "sam.h"
       +
       +static int inerror=FALSE;
       +
       +/*
       + * A reasonable interface to the system calls
       + */
       +
       +void
       +resetsys(void)
       +{
       +        inerror = FALSE;
       +}
       +
       +void
       +syserror(char *a)
       +{
       +        char buf[ERRMAX];
       +
       +        if(!inerror){
       +                inerror=TRUE;
       +                errstr(buf, sizeof buf);
       +                dprint("%s: ", a);
       +                error_s(Eio, buf);
       +        }
       +}
       +
       +int
       +Read(int f, void *a, int n)
       +{
       +        char buf[ERRMAX];
       +
       +        if(read(f, (char *)a, n)!=n) {
       +                if (lastfile)
       +                        lastfile->rescuing = 1;
       +                errstr(buf, sizeof buf);
       +                if (downloaded)
       +                        fprint(2, "read error: %s\n", buf);
       +                rescue();
       +                exits("read");
       +        }
       +        return n;
       +}
       +
       +int
       +Write(int f, void *a, int n)
       +{
       +        int m;
       +
       +        if((m=write(f, (char *)a, n))!=n)
       +                syserror("write");
       +        return m;
       +}
       +
       +void
       +Seek(int f, long n, int w)
       +{
       +        if(seek(f, n, w)==-1)
       +                syserror("seek");
       +}
   DIR diff --git a/sam/unix.c b/sam/unix.c
       @@ -0,0 +1,222 @@
       +#include <u.h>
       +#include <sys/types.h>
       +#include <sys/stat.h>
       +#include <sys/wait.h>
       +#include <pwd.h>
       +#include <signal.h>
       +#include <fcntl.h>
       +#include <errno.h>
       +
       +#include "sam.h"
       +
       +Rune    samname[] = { '~', '~', 's', 'a', 'm', '~', '~', 0 };
       + 
       +static Rune l1[] = { '{', '[', '(', '<', 0253, 0};
       +static Rune l2[] = { '\n', 0};
       +static Rune l3[] = { '\'', '"', '`', 0};
       +Rune *left[]= { l1, l2, l3, 0};
       +
       +static Rune r1[] = {'}', ']', ')', '>', 0273, 0};
       +static Rune r2[] = {'\n', 0};
       +static Rune r3[] = {'\'', '"', '`', 0};
       +Rune *right[]= { r1, r2, r3, 0};
       +
       +#ifndef SAMTERMNAME
       +#define SAMTERMNAME "samterm"
       +#endif
       +#ifndef TMPDIRNAME
       +#define TMPDIRNAME "/tmp"
       +#endif
       +#ifndef SHNAME
       +#define SHNAME "sh"
       +#endif
       +#ifndef SHPATHNAME
       +#define SHPATHNAME "/bin/sh"
       +#endif
       +#ifndef RXNAME
       +#define RXNAME "ssh"
       +#endif
       +#ifndef RXPATHNAME
       +#define RXPATHNAME "ssh"
       +#endif
       +
       +char        RSAM[] = "sam";
       +char        SAMTERM[] = SAMTERMNAME;
       +char        HOME[] = "HOME";
       +char        TMPDIR[] = TMPDIRNAME;
       +char        SH[] = SHNAME;
       +char        SHPATH[] = SHPATHNAME;
       +char        RX[] = RXNAME;
       +char        RXPATH[] = RXPATHNAME;
       +
       +
       +void
       +dprint(char *z, ...)
       +{
       +        char buf[BLOCKSIZE];
       +        va_list arg;
       +
       +        va_start(arg, z);
       +        vseprint(buf, &buf[BLOCKSIZE], z, arg);
       +        va_end(arg);
       +        termwrite(buf);
       +}
       +
       +void
       +print_ss(char *s, String *a, String *b)
       +{
       +        dprint("?warning: %s: `%.*S' and `%.*S'\n", s, a->n, a->s, b->n, b->s);
       +}
       +
       +void
       +print_s(char *s, String *a)
       +{
       +        dprint("?warning: %s `%.*S'\n", s, a->n, a->s);
       +}
       +
       +char*
       +getuser(void)
       +{
       +        static char user[64];
       +        if(user[0] == 0){
       +                struct passwd *pw = getpwuid(getuid());
       +                strcpy(user, pw ? pw->pw_name : "nobody");
       +        }
       +        return user;
       +}
       +
       +int     
       +statfile(char *name, ulong *dev, uvlong *id, long *time, long *length, long *appendonly) 
       +{
       +        struct stat dirb;
       +
       +        if (stat(name, &dirb) == -1)
       +                return -1;
       +        if (dev)
       +                *dev = dirb.st_dev;   
       +        if (id)
       +                *id = dirb.st_ino;
       +        if (time)
       +                *time = dirb.st_mtime;
       +        if (length)
       +                *length = dirb.st_size;
       +        if(appendonly)
       +                *appendonly = 0;
       +        return 1;
       +}
       +
       +int
       +statfd(int fd, ulong *dev, uvlong *id, long *time, long *length, long *appendonly)
       +{
       +        struct stat dirb;
       +
       +        if (fstat(fd, &dirb) == -1)   
       +                return -1;
       +        if (dev)
       +                *dev = dirb.st_dev;
       +        if (id) 
       +                *id = dirb.st_ino;
       +        if (time)
       +                *time = dirb.st_mtime;
       +        if (length)
       +                *length = dirb.st_size;
       +        if(appendonly)
       +                *appendonly = 0;
       +        return 1;
       +}
       +
       +void
       +hup(int sig)
       +{
       +        panicking = 1; /* ??? */
       +        rescue();
       +        exit(1);
       +}
       +
       +int
       +notify(void(*f)(void *, char *))
       +{
       +        signal(SIGINT, SIG_IGN);
       +        signal(SIGPIPE, SIG_IGN);  /* XXX - bpipeok? */
       +        signal(SIGHUP, hup);
       +        return 1;
       +}
       +
       +void
       +notifyf(void *a, char *b)       /* never called; hup is instead */
       +{
       +}
       +
       +static int
       +temp_file(char *buf, int bufsize)
       +{
       +        char *tmp;
       +        int n, fd;
       +
       +        tmp = getenv("TMPDIR");
       +        if (!tmp)
       +                tmp = TMPDIR;
       +
       +        n = snprint(buf, bufsize, "%s/sam.%d.XXXXXXX", tmp, getuid());
       +        if (bufsize <= n)
       +                return -1;
       +        if ((fd = mkstemp(buf)) < 0)  /* SES - linux sometimes uses mode 0666 */
       +                return -1;
       +        if (fcntl(fd, F_SETFD, fcntl(fd,F_GETFD,0) | FD_CLOEXEC) < 0)
       +                return -1;
       +        return fd;
       +}
       +
       +int
       +tempdisk(void)
       +{
       +        char buf[4096];
       +        int fd = temp_file(buf, sizeof buf);
       +        if (fd >= 0)
       +                remove(buf);
       +        return fd; 
       +}
       +
       +#undef waitfor
       +int     
       +samwaitfor(int pid)
       +{
       +        int r;
       +        Waitmsg *w;
       +
       +        w = p9waitfor(pid);
       +        if(w == nil)
       +                return -1;
       +        r = atoi(w->msg);
       +        free(w);
       +        return r;
       +}
       +
       +void
       +samerr(char *buf)
       +{
       +        sprint(buf, "%s/sam.%s.err", TMPDIR, getuser());
       +}
       +
       +void*
       +emalloc(ulong n)
       +{
       +        void *p;
       +
       +        p = malloc(n);
       +        if(p == 0)
       +                panic("malloc fails");
       +        memset(p, 0, n);
       +        return p;
       +}
       +
       +void*
       +erealloc(void *p, ulong n)
       +{
       +        p = realloc(p, n);
       +        if(p == 0)
       +                panic("realloc fails");
       +        return p;
       +}
       +
       +
   DIR diff --git a/sam/util.c b/sam/util.c
       @@ -0,0 +1,54 @@
       +#include "sam.h"
       +
       +void
       +cvttorunes(char *p, int n, Rune *r, int *nb, int *nr, int *nulls)
       +{
       +        uchar *q;
       +        Rune *s;
       +        int j, w;
       +
       +        /*
       +         * Always guaranteed that n bytes may be interpreted
       +         * without worrying about partial runes.  This may mean
       +         * reading up to UTFmax-1 more bytes than n; the caller
       +         * knows this.  If n is a firm limit, the caller should
       +         * set p[n] = 0.
       +         */
       +        q = (uchar*)p;
       +        s = r;
       +        for(j=0; j<n; j+=w){
       +                if(*q < Runeself){
       +                        w = 1;
       +                        *s = *q++;
       +                }else{
       +                        w = chartorune(s, (char*)q);
       +                        q += w;
       +                }
       +                if(*s)
       +                        s++;
       +                else if(nulls)
       +                        *nulls = TRUE;
       +        }
       +        *nb = (char*)q-p;
       +        *nr = s-r;
       +}
       +
       +void*
       +fbufalloc(void)
       +{
       +        return emalloc(BUFSIZE);
       +}
       +
       +void
       +fbuffree(void *f)
       +{
       +        free(f);
       +}
       +
       +uint
       +min(uint a, uint b)
       +{
       +        if(a < b)
       +                return a;
       +        return b;
       +}
   DIR diff --git a/sam/xec.c b/sam/xec.c
       @@ -0,0 +1,508 @@
       +#include "sam.h"
       +#include "parse.h"
       +
       +int        Glooping;
       +int        nest;
       +
       +int        append(File*, Cmd*, Posn);
       +int        display(File*);
       +void        looper(File*, Cmd*, int);
       +void        filelooper(Cmd*, int);
       +void        linelooper(File*, Cmd*);
       +
       +void
       +resetxec(void)
       +{
       +        Glooping = nest = 0;
       +}
       +
       +int
       +cmdexec(File *f, Cmd *cp)
       +{
       +        int i;
       +        Addr *ap;
       +        Address a;
       +
       +        if(f && f->unread)
       +                load(f);
       +        if(f==0 && (cp->addr==0 || cp->addr->type!='"') &&
       +            !utfrune("bBnqUXY!", cp->cmdc) &&
       +            cp->cmdc!=('c'|0x100) && !(cp->cmdc=='D' && cp->ctext))
       +                error(Enofile);
       +        i = lookup(cp->cmdc);
       +        if(i >= 0 && cmdtab[i].defaddr != aNo){
       +                if((ap=cp->addr)==0 && cp->cmdc!='\n'){
       +                        cp->addr = ap = newaddr();
       +                        ap->type = '.';
       +                        if(cmdtab[i].defaddr == aAll)
       +                                ap->type = '*';
       +                }else if(ap && ap->type=='"' && ap->next==0 && cp->cmdc!='\n'){
       +                        ap->next = newaddr();
       +                        ap->next->type = '.';
       +                        if(cmdtab[i].defaddr == aAll)
       +                                ap->next->type = '*';
       +                }
       +                if(cp->addr){        /* may be false for '\n' (only) */
       +                        static Address none = {0,0,0};
       +                        if(f)
       +                                addr = address(ap, f->dot, 0);
       +                        else        /* a " */
       +                                addr = address(ap, none, 0);
       +                        f = addr.f;
       +                }
       +        }
       +        current(f);
       +        switch(cp->cmdc){
       +        case '{':
       +                a = cp->addr? address(cp->addr, f->dot, 0): f->dot;
       +                for(cp = cp->ccmd; cp; cp = cp->next){
       +                        a.f->dot = a;
       +                        cmdexec(a.f, cp);
       +                }
       +                break;
       +        default:
       +                i=(*cmdtab[i].fn)(f, cp);
       +                return i;
       +        }
       +        return 1;
       +}
       +
       +
       +int
       +a_cmd(File *f, Cmd *cp)
       +{
       +        return append(f, cp, addr.r.p2);
       +}
       +
       +int
       +b_cmd(File *f, Cmd *cp)
       +{
       +        USED(f);
       +        f = cp->cmdc=='b'? tofile(cp->ctext) : getfile(cp->ctext);
       +        if(f->unread)
       +                load(f);
       +        else if(nest == 0)
       +                filename(f);
       +        return TRUE;
       +}
       +
       +int
       +c_cmd(File *f, Cmd *cp)
       +{
       +        logdelete(f, addr.r.p1, addr.r.p2);
       +        f->ndot.r.p1 = f->ndot.r.p2 = addr.r.p2;
       +        return append(f, cp, addr.r.p2);
       +}
       +
       +int
       +d_cmd(File *f, Cmd *cp)
       +{
       +        USED(cp);
       +        logdelete(f, addr.r.p1, addr.r.p2);
       +        f->ndot.r.p1 = f->ndot.r.p2 = addr.r.p1;
       +        return TRUE;
       +}
       +
       +int
       +D_cmd(File *f, Cmd *cp)
       +{
       +        closefiles(f, cp->ctext);
       +        return TRUE;
       +}
       +
       +int
       +e_cmd(File *f, Cmd *cp)
       +{
       +        if(getname(f, cp->ctext, cp->cmdc=='e')==0)
       +                error(Enoname);
       +        edit(f, cp->cmdc);
       +        return TRUE;
       +}
       +
       +int
       +f_cmd(File *f, Cmd *cp)
       +{
       +        getname(f, cp->ctext, TRUE);
       +        filename(f);
       +        return TRUE;
       +}
       +
       +int
       +g_cmd(File *f, Cmd *cp)
       +{
       +        if(f!=addr.f)panic("g_cmd f!=addr.f");
       +        compile(cp->re);
       +        if(execute(f, addr.r.p1, addr.r.p2) ^ cp->cmdc=='v'){
       +                f->dot = addr;
       +                return cmdexec(f, cp->ccmd);
       +        }
       +        return TRUE;
       +}
       +
       +int
       +i_cmd(File *f, Cmd *cp)
       +{
       +        return append(f, cp, addr.r.p1);
       +}
       +
       +int
       +k_cmd(File *f, Cmd *cp)
       +{
       +        USED(cp);
       +        f->mark = addr.r;
       +        return TRUE;
       +}
       +
       +int
       +m_cmd(File *f, Cmd *cp)
       +{
       +        Address addr2;
       +
       +        addr2 = address(cp->caddr, f->dot, 0);
       +        if(cp->cmdc=='m')
       +                move(f, addr2);
       +        else
       +                copy(f, addr2);
       +        return TRUE;
       +}
       +
       +int
       +n_cmd(File *f, Cmd *cp)
       +{
       +        int i;
       +        USED(f);
       +        USED(cp);
       +        for(i = 0; i<file.nused; i++){
       +                if(file.filepptr[i] == cmd)
       +                        continue;
       +                f = file.filepptr[i];
       +                Strduplstr(&genstr, &f->name);
       +                filename(f);
       +        }
       +        return TRUE;
       +}
       +
       +int
       +p_cmd(File *f, Cmd *cp)
       +{
       +        USED(cp);
       +        return display(f);
       +}
       +
       +int
       +q_cmd(File *f, Cmd *cp)
       +{
       +        USED(cp);
       +        USED(f);
       +        trytoquit();
       +        if(downloaded){
       +                outT0(Hexit);
       +                return TRUE;
       +        }
       +        return FALSE;
       +}
       +
       +int
       +s_cmd(File *f, Cmd *cp)
       +{
       +        int i, j, c, n;
       +        Posn p1, op, didsub = 0, delta = 0;
       +
       +        n = cp->num;
       +        op= -1;
       +        compile(cp->re);
       +        for(p1 = addr.r.p1; p1<=addr.r.p2 && execute(f, p1, addr.r.p2); ){
       +                if(sel.p[0].p1==sel.p[0].p2){        /* empty match? */
       +                        if(sel.p[0].p1==op){
       +                                p1++;
       +                                continue;
       +                        }
       +                        p1 = sel.p[0].p2+1;
       +                }else
       +                        p1 = sel.p[0].p2;
       +                op = sel.p[0].p2;
       +                if(--n>0)
       +                        continue;
       +                Strzero(&genstr);
       +                for(i = 0; i<cp->ctext->n; i++)
       +                        if((c = cp->ctext->s[i])=='\\' && i<cp->ctext->n-1){
       +                                c = cp->ctext->s[++i];
       +                                if('1'<=c && c<='9') {
       +                                        j = c-'0';
       +                                        if(sel.p[j].p2-sel.p[j].p1>BLOCKSIZE)
       +                                                error(Elongtag);
       +                                        bufread(&f->b, sel.p[j].p1, genbuf, sel.p[j].p2-sel.p[j].p1);
       +                                        Strinsert(&genstr, tmprstr(genbuf, (sel.p[j].p2-sel.p[j].p1)), genstr.n);
       +                                }else
       +                                         Straddc(&genstr, c);
       +                        }else if(c!='&')
       +                                Straddc(&genstr, c);
       +                        else{
       +                                if(sel.p[0].p2-sel.p[0].p1>BLOCKSIZE)
       +                                        error(Elongrhs);
       +                                bufread(&f->b, sel.p[0].p1, genbuf, sel.p[0].p2-sel.p[0].p1);
       +                                Strinsert(&genstr,
       +                                        tmprstr(genbuf, (int)(sel.p[0].p2-sel.p[0].p1)),
       +                                        genstr.n);
       +                        }
       +                if(sel.p[0].p1!=sel.p[0].p2){
       +                        logdelete(f, sel.p[0].p1, sel.p[0].p2);
       +                        delta-=sel.p[0].p2-sel.p[0].p1;
       +                }
       +                if(genstr.n){
       +                        loginsert(f, sel.p[0].p2, genstr.s, genstr.n);
       +                        delta+=genstr.n;
       +                }
       +                didsub = 1;
       +                if(!cp->flag)
       +                        break;
       +        }
       +        if(!didsub && nest==0)
       +                error(Enosub);
       +        f->ndot.r.p1 = addr.r.p1, f->ndot.r.p2 = addr.r.p2+delta;
       +        return TRUE;
       +}
       +
       +int
       +u_cmd(File *f, Cmd *cp)
       +{
       +        int n;
       +
       +        USED(f);
       +        USED(cp);
       +        n = cp->num;
       +        if(n >= 0)
       +                while(n-- && undo(TRUE))
       +                        ;
       +        else
       +                while(n++ && undo(FALSE))
       +                        ;
       +        return TRUE;
       +}
       +
       +int
       +w_cmd(File *f, Cmd *cp)
       +{
       +        int fseq;
       +
       +        fseq = f->seq;
       +        if(getname(f, cp->ctext, FALSE)==0)
       +                error(Enoname);
       +        if(fseq == seq)
       +                error_s(Ewseq, genc);
       +        writef(f);
       +        return TRUE;
       +}
       +
       +int
       +x_cmd(File *f, Cmd *cp)
       +{
       +        if(cp->re)
       +                looper(f, cp, cp->cmdc=='x');
       +        else
       +                linelooper(f, cp);
       +        return TRUE;
       +}
       +
       +int
       +X_cmd(File *f, Cmd *cp)
       +{
       +        USED(f);
       +        filelooper(cp, cp->cmdc=='X');
       +        return TRUE;
       +}
       +
       +int
       +plan9_cmd(File *f, Cmd *cp)
       +{
       +        plan9(f, cp->cmdc, cp->ctext, nest);
       +        return TRUE;
       +}
       +
       +int
       +eq_cmd(File *f, Cmd *cp)
       +{
       +        int charsonly;
       +
       +        switch(cp->ctext->n){
       +        case 1:
       +                charsonly = FALSE;
       +                break;
       +        case 2:
       +                if(cp->ctext->s[0]=='#'){
       +                        charsonly = TRUE;
       +                        break;
       +                }
       +        default:
       +                SET(charsonly);
       +                error(Enewline);
       +        }
       +        printposn(f, charsonly);
       +        return TRUE;
       +}
       +
       +int
       +nl_cmd(File *f, Cmd *cp)
       +{
       +        Address a;
       +
       +        if(cp->addr == 0){
       +                /* First put it on newline boundaries */
       +                addr = lineaddr((Posn)0, f->dot, -1);
       +                a = lineaddr((Posn)0, f->dot, 1);
       +                addr.r.p2 = a.r.p2;
       +                if(addr.r.p1==f->dot.r.p1 && addr.r.p2==f->dot.r.p2)
       +                        addr = lineaddr((Posn)1, f->dot, 1);
       +                display(f);
       +        }else if(downloaded)
       +                moveto(f, addr.r);
       +        else
       +                display(f);
       +        return TRUE;
       +}
       +
       +int
       +cd_cmd(File *f, Cmd *cp)
       +{
       +        USED(f);
       +        cd(cp->ctext);
       +        return TRUE;
       +}
       +
       +int
       +append(File *f, Cmd *cp, Posn p)
       +{
       +        if(cp->ctext->n>0 && cp->ctext->s[cp->ctext->n-1]==0)
       +                --cp->ctext->n;
       +        if(cp->ctext->n>0)
       +                loginsert(f, p, cp->ctext->s, cp->ctext->n);
       +        f->ndot.r.p1 = p;
       +        f->ndot.r.p2 = p+cp->ctext->n;
       +        return TRUE;
       +}
       +
       +int
       +display(File *f)
       +{
       +        Posn p1, p2;
       +        int np;
       +        char *c;
       +
       +        p1 = addr.r.p1;
       +        p2 = addr.r.p2;
       +        if(p2 > f->b.nc){
       +                fprint(2, "bad display addr p1=%ld p2=%ld f->b.nc=%d\n", p1, p2, f->b.nc); /*ZZZ should never happen, can remove */
       +                p2 = f->b.nc;
       +        }
       +        while(p1 < p2){
       +                np = p2-p1;
       +                if(np>BLOCKSIZE-1)
       +                        np = BLOCKSIZE-1;
       +                bufread(&f->b, p1, genbuf, np);
       +                genbuf[np] = 0;
       +                c = Strtoc(tmprstr(genbuf, np+1));
       +                if(downloaded)
       +                        termwrite(c);
       +                else
       +                        Write(1, c, strlen(c));
       +                free(c);
       +                p1 += np;
       +        }
       +        f->dot = addr;
       +        return TRUE;
       +}
       +
       +void
       +looper(File *f, Cmd *cp, int xy)
       +{
       +        Posn p, op;
       +        Range r;
       +
       +        r = addr.r;
       +        op= xy? -1 : r.p1;
       +        nest++;
       +        compile(cp->re);
       +        for(p = r.p1; p<=r.p2; ){
       +                if(!execute(f, p, r.p2)){ /* no match, but y should still run */
       +                        if(xy || op>r.p2)
       +                                break;
       +                        f->dot.r.p1 = op, f->dot.r.p2 = r.p2;
       +                        p = r.p2+1;        /* exit next loop */
       +                }else{
       +                        if(sel.p[0].p1==sel.p[0].p2){        /* empty match? */
       +                                if(sel.p[0].p1==op){
       +                                        p++;
       +                                        continue;
       +                                }
       +                                p = sel.p[0].p2+1;
       +                        }else
       +                                p = sel.p[0].p2;
       +                        if(xy)
       +                                f->dot.r = sel.p[0];
       +                        else
       +                                f->dot.r.p1 = op, f->dot.r.p2 = sel.p[0].p1;
       +                }
       +                op = sel.p[0].p2;
       +                cmdexec(f, cp->ccmd);
       +                compile(cp->re);
       +        }
       +        --nest;
       +}
       +
       +void
       +linelooper(File *f, Cmd *cp)
       +{
       +        Posn p;
       +        Range r, linesel;
       +        Address a, a3;
       +
       +        nest++;
       +        r = addr.r;
       +        a3.f = f;
       +        a3.r.p1 = a3.r.p2 = r.p1;
       +        for(p = r.p1; p<r.p2; p = a3.r.p2){
       +                a3.r.p1 = a3.r.p2;
       +/*pjw                if(p!=r.p1 || (linesel = lineaddr((Posn)0, a3, 1)).r.p2==p)*/
       +                if(p!=r.p1 || (a = lineaddr((Posn)0, a3, 1), linesel = a.r, linesel.p2==p)){
       +                        a = lineaddr((Posn)1, a3, 1);
       +                        linesel = a.r;
       +                }
       +                if(linesel.p1 >= r.p2)
       +                        break;
       +                if(linesel.p2 >= r.p2)
       +                        linesel.p2 = r.p2;
       +                if(linesel.p2 > linesel.p1)
       +                        if(linesel.p1>=a3.r.p2 && linesel.p2>a3.r.p2){
       +                                f->dot.r = linesel;
       +                                cmdexec(f, cp->ccmd);
       +                                a3.r = linesel;
       +                                continue;
       +                        }
       +                break;
       +        }
       +        --nest;
       +}
       +
       +void
       +filelooper(Cmd *cp, int XY)
       +{
       +        File *f, *cur;
       +        int i;
       +
       +        if(Glooping++)
       +                error(EnestXY);
       +        nest++;
       +        settempfile();
       +        cur = curfile;
       +        for(i = 0; i<tempfile.nused; i++){
       +                f = tempfile.filepptr[i];
       +                if(f==cmd)
       +                        continue;
       +                if(cp->re==0 || filematch(f, cp->re)==XY)
       +                        cmdexec(f, cp->ccmd);
       +        }
       +        if(cur && whichmenu(cur)>=0)        /* check that cur is still a file */
       +                current(cur);
       +        --Glooping;
       +        --nest;
       +}