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; | |
+} |