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 <anselm@garbe.us>
Date:   Sat, 29 May 2010 14:33:38 +0100

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