added sam

This commit is contained in:
Anselm R Garbe
2010-05-29 14:33:38 +01:00
parent 631633fb8f
commit 9765fabf4d
31 changed files with 8556 additions and 0 deletions

37
sam/Makefile Normal file
View File

@@ -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

29
sam/README Normal file
View File

@@ -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

40
sam/_libc.h Normal file
View File

@@ -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);

240
sam/address.c Normal file
View File

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

302
sam/buff.c Normal file
View File

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

608
sam/cmd.c Normal file
View File

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

122
sam/disk.c Normal file
View File

@@ -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");
}

39
sam/err Normal file
View File

@@ -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

144
sam/error.c Normal file
View File

@@ -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));
}

65
sam/errors.h Normal file
View File

@@ -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;

610
sam/file.c Normal file
View File

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

278
sam/io.c Normal file
View File

@@ -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);
}

96
sam/list.c Normal file
View File

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

848
sam/mesg.c Normal file
View File

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

131
sam/mesg.h Normal file
View File

@@ -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")
*
*/

173
sam/moveto.c Normal file
View File

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

123
sam/multi.c Normal file
View File

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

68
sam/parse.h Normal file
View File

@@ -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*);

185
sam/plan9.c Normal file
View File

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

17
sam/plumb.h Normal file
View File

@@ -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*);

340
sam/rasp.c Normal file
View File

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

802
sam/regexp.c Normal file
View File

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

908
sam/sam.1 Normal file
View File

@@ -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''.

741
sam/sam.c Normal file
View File

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

408
sam/sam.h Normal file
View File

@@ -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);

165
sam/shell.c Normal file
View File

@@ -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);
}

193
sam/string.c Normal file
View File

@@ -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);
}

60
sam/sys.c Normal file
View File

@@ -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");
}

222
sam/unix.c Normal file
View File

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

54
sam/util.c Normal file
View File

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

508
sam/xec.c Normal file
View File

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