mirror of
https://git.suckless.org/9base
synced 2025-08-30 03:43:48 -07:00
added sam
This commit is contained in:
37
sam/Makefile
Normal file
37
sam/Makefile
Normal 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
29
sam/README
Normal 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
40
sam/_libc.h
Normal 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
240
sam/address.c
Normal 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
302
sam/buff.c
Normal 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
608
sam/cmd.c
Normal 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
122
sam/disk.c
Normal 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
39
sam/err
Normal 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
144
sam/error.c
Normal 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
65
sam/errors.h
Normal 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
610
sam/file.c
Normal 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
278
sam/io.c
Normal 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
96
sam/list.c
Normal 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
848
sam/mesg.c
Normal 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
131
sam/mesg.h
Normal 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
173
sam/moveto.c
Normal 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
123
sam/multi.c
Normal 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
68
sam/parse.h
Normal 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
185
sam/plan9.c
Normal 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
17
sam/plumb.h
Normal 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
340
sam/rasp.c
Normal 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
802
sam/regexp.c
Normal 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
908
sam/sam.1
Normal 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
741
sam/sam.c
Normal 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
408
sam/sam.h
Normal 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
165
sam/shell.c
Normal 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
193
sam/string.c
Normal 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
60
sam/sys.c
Normal 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
222
sam/unix.c
Normal 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
54
sam/util.c
Normal 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
508
sam/xec.c
Normal 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;
|
||||
}
|
Reference in New Issue
Block a user