Introduction
Introduction Statistics Contact Development Disclaimer Help
added sam - 9base - revived minimalist port of Plan 9 userland to Unix
git clone git://git.suckless.org/9base
Log
Files
Refs
README
LICENSE
---
commit 9765fabf4d48cc6af2f8870fae4dde5c975ac86c
parent 631633fb8f9afdeaf4c7002680ae4913d0c36397
Author: Anselm R Garbe <[email protected]>
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(-)
---
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 -…
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
+
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);
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, s…
+ 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->typ…
+ 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 …
+ p--;
+ a.r.p1 = p;
+ }
+ return a;
+}
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 blo…
+ */
+ 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!…
+ 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!…
+ 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;
+}
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 defa…
+ '\n', 0, 0, 0, 0, aDot, 0, …
+ 'a', 1, 0, 0, 0, aDot, 0, …
+ 'b', 0, 0, 0, 0, aNo, 0, …
+ 'B', 0, 0, 0, 0, aNo, 0, …
+ 'c', 1, 0, 0, 0, aDot, 0, …
+ 'd', 0, 0, 0, 0, aDot, 0, …
+ 'D', 0, 0, 0, 0, aNo, 0, …
+ 'e', 0, 0, 0, 0, aNo, 0, …
+ 'f', 0, 0, 0, 0, aNo, 0, …
+ 'g', 0, 1, 0, 'p', aDot, 0, …
+ 'i', 1, 0, 0, 0, aDot, 0, …
+ 'k', 0, 0, 0, 0, aDot, 0, …
+ 'm', 0, 0, 1, 0, aDot, 0, …
+ 'n', 0, 0, 0, 0, aNo, 0, …
+ 'p', 0, 0, 0, 0, aDot, 0, …
+ 'q', 0, 0, 0, 0, aNo, 0, …
+ 'r', 0, 0, 0, 0, aDot, 0, …
+ 's', 0, 1, 0, 0, aDot, 1, …
+ 't', 0, 0, 1, 0, aDot, 0, …
+ 'u', 0, 0, 0, 0, aNo, 2, …
+ 'v', 0, 1, 0, 'p', aDot, 0, …
+ 'w', 0, 0, 0, 0, aAll, 0, …
+ 'x', 0, 1, 0, 'p', aDot, 0, …
+ 'y', 0, 1, 0, 'p', aDot, 0, …
+ 'X', 0, 1, 0, 'f', aNo, 0, …
+ 'Y', 0, 1, 0, 'f', aNo, 0, …
+ '!', 0, 0, 0, 0, aNo, 0, …
+ '>', 0, 0, 0, 0, aDot, 0, …
+ '<', 0, 0, 0, 0, aDot, 0, …
+ '|', 0, 0, 0, 0, aDot, 0, …
+ '=', 0, 0, 0, 0, aDot, 0, …
+ 'c'|0x100,0, 0, 0, 0, aNo, 0, …
+ 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…
+ Straddc(s, '\\');
+ }
+ Straddc(s, c);
+ }
+ ungetch(); /* let client read whether delimeter, '\n' or whatev…
+}
+
+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;
+}
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 Maxblo…
+ if(pread(d->fd, r, n*sizeof(Rune), b->addr) != n*sizeof(Rune))
+ panic("read error from temp file");
+}
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 …
+address.c:160: warning: passing arg 1 of `bufinsert' from incompatible pointer…
+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 ty…
+file.c: In function `filedelete':
+file.c:301: warning: passing arg 1 of `bufdelete' from incompatible pointer ty…
+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 ty…
+file.c:546: warning: passing arg 1 of `bufinsert' from incompatible pointer ty…
+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
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));
+}
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;
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, Ma…
+ 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.bu…
+ 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 lo…
+ 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;
+}
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 && leng…
+ 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-genbu…
+ 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);
+}
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;
+}
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 poi…
+ 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 alrea…
+ 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 alrea…
+ 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'…
+ p0--;
+ while(p1<f->b.nc && (i=filereadc(f, p1))!=' ' && i!='\…
+ 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,…
+ 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;
+}
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 t…
+#define DATASIZE (UTFmax*TBLOCKSIZE+30) /* ... including protocol head…
+#define SNARFSIZE 32000 /* maximum length of exchanged s…
+/*
+ * 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 kno…
+ Tcut,
+ Tpaste,
+ Tsnarf,
+ Tstartnewfile, /* terminal just opened a new frame */
+ Twrite, /* write file */
+ Tclose, /* terminal requests file close; check mod. sta…
+ 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 …
+ 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: [email protected]
+ * Date: Tue Jul 17 13:47:23 EDT 2001
+ * To: [email protected]
+ *
+ * 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")
+ *
+ */
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--;
+}
+
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;
+}
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.…
+ union{
+ Cmd *cmd; /* target of x, g, {, etc. */
+ String *text; /* text of a, c, i; rhs of…
+ Addr *addr; /* address for m, t */
+ } g;
+ Cmd *next; /* pointer to next element in…
+ short num;
+ ushort flag; /* whatever */
+ ushort cmdc; /* command character; 'x' e…
+};
+
+#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 …
+ int (*fn)(File*, Cmd*); /* function to call with parse t…
+}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*);
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 *a…
+{
+ 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 *appendo…
+{
+ 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…
+ 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;
+}
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*);
+
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 …
+
+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 li…
+ 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;
+}
+
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…
+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 o…
+#define RBRA 0x10001 /* Right bracket, ) */
+#define LBRA 0x10002 /* Left bracket, ( */
+#define OR 0x10003 /* Alternation, | */
+#define CAT 0x10004 /* Concatentation, implicit o…
+#define STAR 0x10005 /* Closure, * */
+#define PLUS 0x10006 /* a+ == aa* */
+#define QUEST 0x10007 /* a? == a|nothing, i.e. 0 …
+#define ANY 0x20000 /* Any character but newline,…
+#define NOP 0x20001 /* No operation, internal use…
+#define BOL 0x20002 /* Beginning of line, ^ */
+#define EOL 0x20003 /* End of line, $ */
+#define CCLASS 0x20004 /* Character class, [] */
+#define NCCLASS 0x20005 /* Negated character clas…
+#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 expr…
+#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 ca…
+ 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 s…
+ 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].…
+ 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;
+ }
+}
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…
+.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 whet…
+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''.
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 …
+ 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", ro…
+ 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 fil…
+ 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.p…
+ --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;
+}
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*(t…
+#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 numbe…
+
+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 fr…
+
+ long seq; /* if seq==0, File acts like B…
+ long cleanseq; /* f->seq at last read/write of f…
+ int mod; /* file appears modified in men…
+ char rescuing; /* sam exiting; this file unusabl…
+
+#if 0
+// Text *curtext; /* most recently used associate…
+// Text **text; /* list of associated tex…
+// int ntext;
+// int dumpid; /* used in dumping zeroxed…
+#endif
+
+ Posn hiposn; /* highest address touched …
+ Address dot; /* current position */
+ Address ndot; /* new current position af…
+ Range tdot; /* what terminal thinks is c…
+ Range mark; /* tagged spot in text (don'…
+ List *rasp; /* map of what terminal's go…
+ short tag; /* for communicating with ter…
+ 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);
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)){ /* assig…
+ 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, …
+ 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);
+}
diff --git a/sam/string.c b/sam/string.c
@@ -0,0 +1,193 @@
+#include "sam.h"
+
+#define MINSIZE 16 /* minimum number of chars all…
+#define MAXSIZE 256 /* maximum number of chars fo…
+
+
+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 gar…
+ 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 t…
+{
+ 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, pl…
+ 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);
+}
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");
+}
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 *a…
+{
+ 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 *appendo…
+{
+ 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;
+}
+
+
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;
+}
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, se…
+ Strinsert(&genstr, tmprstr(genbuf, (se…
+ }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].p…
+ Strinsert(&genstr,
+ tmprstr(genbuf, (int)(sel.p[0].p2-sel.…
+ 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, p…
+ 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==…
+ if(p!=r.p1 || (a = lineaddr((Posn)0, a3, 1), linesel = a.r, li…
+ 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;
+}
You are viewing proxied material from suckless.org. The copyright of proxied material belongs to its original authors. Any comments or complaints in relation to proxied material should be directed to the original authors of the content concerned. Please see the disclaimer for more details.