# Sam language taken from acme.
# This file was /appl/acme/edit.b, changed for o/x.
#
# limbo -I /pc/usr/octopus/module regx.b
#
# Main entry point for sam commands.
# This contains the parser and the main loop.
# Calls to cmdexec() (ecmd.b) to actually execute the commands.
implement Sam;
include "mods.m";
Inactive, Inserting, Collecting, resetxec, cmdexec: import samcmd;
elogterm: import samlog;
Tree, Edit, seled, trees: import oxedit;
msg: import oxex;
init(d: Oxdat)
{
initmods(d->mods);
editing = Inactive;
}
linex: con "\n";
wordx: con "\t\n";
# Main tab of Sam commands.
# Editing commands remain like in Sam and Acme.
# File commands have to change, because of the editing model implied by o/mero.
cmdtab = array[] of {
# cmdc text rexp addr defcmd defaddr count token fn
Cmdt ( '\n', 0, 0, 0, 0, aDot, 0, nil, C_nl ),
Cmdt ( 'a', 1, 0, 0, 0, aDot, 0, nil, C_a ),
Cmdt ( 'c', 1, 0, 0, 0, aDot, 0, nil, C_c ),
Cmdt ( 'd', 0, 0, 0, 0, aDot, 0, nil, C_d ),
Cmdt ( 'g', 0, 1, 0, 'p', aDot, 0, nil, C_g ),
Cmdt ( 'i', 1, 0, 0, 0, aDot, 0, nil, C_i ),
Cmdt ( 'm', 0, 0, 1, 0, aDot, 0, nil, C_m ),
Cmdt ( 'p', 0, 0, 0, 0, aDot, 0, nil, C_p ),
Cmdt ( 'r', 0, 0, 0, 0, aDot, 0, wordx, C_e ),
Cmdt ( 's', 0, 1, 0, 0, aDot, 1, nil, C_s ),
Cmdt ( 't', 0, 0, 1, 0, aDot, 0, nil, C_m ),
Cmdt ( 'v', 0, 1, 0, 'p', aDot, 0, nil, C_g ),
Cmdt ( 'w', 0, 0, 0, 0, aAll, 0, wordx, C_w ),
Cmdt ( 'x', 0, 1, 0, 'p', aDot, 0, nil, C_x ),
Cmdt ( 'y', 0, 1, 0, 'p', aDot, 0, nil, C_x ),
Cmdt ( '=', 0, 0, 0, 0, aDot, 0, linex, C_eq ),
Cmdt ( 'B', 0, 0, 0, 0, aNo, 0, linex, C_B ),
Cmdt ( 'D', 0, 0, 0, 0, aNo, 0, linex, C_D ),
Cmdt ( 'X', 0, 1, 0, 'f', aNo, 0, nil, C_X ),
Cmdt ( 'Y', 0, 1, 0, 'f', aNo, 0, nil, C_X ),
Cmdt ( '<', 0, 0, 0, 0, aDot, 0, linex, C_pipe ),
Cmdt ( '|', 0, 0, 0, 0, aDot, 0, linex, C_pipe ),
Cmdt ( '>', 0, 0, 0, 0, aDot, 0, linex, C_pipe ),
# deliberately unimplemented
# Cmdt ( 'b', 0, 0, 0, 0, aNo, 0, linex, C_b ),
# Cmdt ( 'e', 0, 0, 0, 0, aNo, 0, wordx, C_e ),
# Cmdt ( 'f', 0, 0, 0, 0, aNo, 0, wordx, C_f ),
# Cmdt ( 'k', 0, 0, 0, 0, aDot, 0, nil, C_k ),
# Cmdt ( 'n', 0, 0, 0, 0, aNo, 0, nil, C_n ),
# Cmdt ( 'q', 0, 0, 0, 0, aNo, 0, nil, C_q ),
# Cmdt ( 'u', 0, 0, 0, 0, aNo, 2, nil, C_u ),
# Cmdt ( '!', 0, 0, 0, 0, aNo, 0, linex, C_plan9 ),
Cmdt (0, 0, 0, 0, 0, 0, 0, nil, -1 )
};
# buffer that keeps the command
cmdstartp: string;
cmdendp: int;
cmdp: int;
editerrc: chan of string;
lastpat := "";
patset: int;
# notify edit completion process
editwaitproc(pid : int, sync: chan of int, cwait: chan of string)
{
fd : ref Sys->FD;
n : int;
sys->pctl(Sys->FORKFD, nil);
w := sprint("#p/%d/wait", pid);
fd = sys->open(w, Sys->OREAD);
if (fd == nil)
error("fd == nil in editwaitproc");
sync <-= sys->pctl(0, nil);
buf := array[Sys->WAITLEN] of byte;
status := "";
for(;;){
if ((n = sys->read(fd, buf, len buf))<0)
error("bad read in editwaitproc");
status = string buf[0:n];
cwait <-= status;
}
}
# main loop: parsecmd and cmdexec for the edit.
# The command is kept at cmdstartp[0:cmdendp],
editthread(cwait: chan of string)
{
cmdp: ref Cmd;
mypid := sys->pctl(0, nil);
sync := chan of int;
spawn editwaitproc(mypid, sync, cwait);
yourpid := <- sync;
while((cmdp=parsecmd(0)) != nil){
seled.getedits();
if(cmdexec(seled, cmdp) == 0)
break;
}
editerrc <-= nil;
kill(yourpid, "kill");
}
putedits()
{
for (l := trees; l != nil; l = tl l){
tr := hd l;
for (eds := tr.eds; eds != nil; eds = tl eds){
ed := hd eds;
ed.putedits();
}
}
}
clredits()
{
for (l := trees; l != nil; l = tl l){
tr := hd l;
for (eds := tr.eds; eds != nil; eds = tl eds){
ed := hd eds;
ed.clredits();
elogterm(ed);
}
}
}
# Terminates the process after aborting
editerror(s: string)
{
clredits();
editerrc <-= s;
exit;
}
warnthread(tr: ref Tree, d: string, c: chan of string)
{
while ((s := <-c) != nil)
msg(tr, d, s);
}
# fills the buffer at cmdstartp with the command (\n terminated)
# and starts the edit thread, then awaits for completion.
editcmd(ct: ref Edit, r: string)
{
tr := Tree.find(ct.tid);
if(len r == 0)
return;
if(2*(len r) > BUFSIZE){
msg(tr, ct.dir, "string too long\n");
return;
}
cwait := chan of string;
cmdstartp = r;
if(r[len r - 1] != '\n')
cmdstartp[len r] = '\n';
cmdendp = len r;
cmdp = 0;
if(ct.body == nil)
seled = nil;
else
seled = ct;
resetxec();
if(editerrc == nil){
editerrc = chan of string;
lastpat = "";
}
warnc = chan of string;
spawn warnthread(tr, ct.dir, warnc);
spawn editthread(cwait);
e := <- editerrc;
editing = Inactive;
warnc <-= nil;
warnc = nil;
if(e != nil)
msg(tr, ct.dir,sprint("sam: %s\n", e));
putedits();
}
#
# Command parsing
#
getch(): int
{
if(cmdp == cmdendp)
return -1;
return cmdstartp[cmdp++];
}
nextc(): int
{
if(cmdp == cmdendp)
return -1;
return cmdstartp[cmdp];
}
ungetch()
{
if(--cmdp < 0)
error("ungetch");
}
getnum(signok: int): int
{
n: int;
c, sign: int;
n = 0;
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;
}
cmdskipbl(): int
{
c: int;
do
c = getch();
while(c==' ' || c=='\t');
if(c >= 0)
ungetch();
return c;
}
okdelim(c: int)
{
if(c=='\\' || ('a'<=c && c<='z')
|| ('A'<=c && c<='Z') || ('0'<=c && c<='9'))
editerror(sprint("bad delimiter %c\n", c));
}
atnl()
{
c: int;
cmdskipbl();
c = getch();
if(c != '\n')
editerror(sprint("newline expected (saw %c)", c));
}
getrhs(delim: int, cmd: int): string
{
c: int;
s := "";
while((c = getch())>0 && c!=delim && c!='\n'){
if(c == '\\'){
if((c=getch()) <= 0)
error("bad right hand side");
if(c == '\n'){
ungetch();
c='\\';
}else if(c == 'n')
c='\n';
else if(c!=delim && (cmd=='s' || c!='\\')) # s does its own
s[len s] = '\\';
}
s[len s] = c;
}
ungetch(); # let client read whether delimiter, '\n' or whatever
return s;
}
collecttoken(end: string): string
{
c: int;
s := "";
while((c=nextc())==' ' || c=='\t')
s[len s] = getch(); # blanks significant for getname()
while((c=getch())>0 && strchr(end, c)<0)
s[len s] = c;
if(c != '\n')
atnl();
return s;
}
collecttext(): string
{
begline, i, c, delim: int;
s := "";
if(cmdskipbl()=='\n'){
getch();
i = 0;
do{
begline = i;
while((c = getch())>0 && c!='\n'){
i++;
s[len s] = c;
}
i++;
s[len s] = '\n';
if(c < 0)
return s;
}while(s[begline]!='.' || s[begline+1]!='\n');
s = s[0:len s - 2];
}else{
okdelim(delim = getch());
s = getrhs(delim, 'a');
if(nextc()==delim)
getch();
atnl();
}
return s;
}
cmdlookup(c: int): int
{
i: int;
for(i=0; cmdtab[i].cmdc; i++)
if(cmdtab[i].cmdc == c)
return i;
return -1;
}
parsecmd(nest: int): ref Cmd
{
i, c: int;
cp, ncp: ref Cmd;
cmd: ref Cmd;
cmd = ref Cmd;
cmd.next = cmd.cmd = nil;
cmd.re = nil;
cmd.flag = cmd.num = 0;
cmd.addr = compoundaddr();
if(cmdskipbl() == -1)
return nil;
if((c=getch())==-1)
return nil;
cmd.cmdc = c;
if(cmd.cmdc=='c' && nextc()=='d'){ # sleazy two-character case
getch(); # the 'd'
cmd.cmdc='c'|16r100;
}
i = cmdlookup(cmd.cmdc);
if(i >= 0){
if(cmd.cmdc == '\n'){
cp = ref Cmd;
*cp = *cmd;
return cp;
# let nl_cmd work it all out
}
ct := cmdtab[i];
if(ct.defaddr==aNo && cmd.addr != nil)
editerror("command takes no address");
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')){
cmdskipbl();
if((c = getch())=='\n' || c<0)
editerror("no address");
okdelim(c);
cmd.re = getregexp(c);
if(ct.cmdc == 's'){
cmd.text = getrhs(c, 's');
if(nextc() == c){
getch();
if(nextc() == 'g')
cmd.flag = getch();
}
}
}
}
if(ct.addr && (cmd.mtaddr=simpleaddr())==nil)
editerror("bad address");
if(ct.defcmd){
if(cmdskipbl() == '\n'){
getch();
cmd.cmd = ref Cmd;
cmd.cmd.cmdc = ct.defcmd;
}else if((cmd.cmd = parsecmd(nest))==nil)
error("defcmd");
}else if(ct.text)
cmd.text = collecttext();
else if(ct.token != nil)
cmd.text = collecttoken(ct.token);
else
atnl();
}else
case(cmd.cmdc){
'{' =>
cp = nil;
do{
if(cmdskipbl()=='\n')
getch();
ncp = parsecmd(nest+1);
if(cp != nil)
cp.next = ncp;
else
cmd.cmd = ncp;
}while((cp = ncp) != nil);
break;
'}' =>
atnl();
if(nest==0)
editerror("right brace with no left brace");
return nil;
'c'|16r100 =>
editerror("unimplemented command cd");
* =>
editerror(sprint("unknown command %c", cmd.cmdc));
}
cp = ref Cmd;
*cp = *cmd;
return cp;
}
getregexp(delim: int): string
{
c: int;
buf := "";
for(i:=0; ; i++){
if((c = getch())=='\\'){
if(nextc()==delim)
c = getch();
else if(nextc()=='\\'){
buf[len buf] = c;
c = getch();
}
}else if(c==delim || c=='\n')
break;
if(i >= BUFSIZE)
editerror("regular expression too long");
buf[len buf] = c;
}
if(c!=delim && c)
ungetch();
if(len buf> 0){
patset = 1;
lastpat = buf;
}
if(len lastpat== 0)
editerror("no regular expression defined");
return lastpat;
}
simpleaddr(): ref Addr
{
addr: Addr;
ap, nap: ref Addr;
addr.next = nil;
addr.left = nil;
case(cmdskipbl()){
'#' =>
addr.typex = getch();
addr.num = getnum(1);
break;
'0' to '9' =>
addr.num = getnum(1);
addr.typex='l';
break;
'/' or '?' or '"' =>
addr.re = getregexp(addr.typex = getch());
break;
'.' or
'$' or
'+' or
'-' or
'\'' =>
addr.typex = getch();
break;
* =>
return nil;
}
if((addr.next = simpleaddr()) != nil)
case(addr.next.typex){
'.' or
'$' or
'\'' =>
if(addr.typex!='"')
editerror("bad address syntax");
break;
'"' =>
editerror("bad address syntax");
break;
'l' or
'#' =>
if(addr.typex=='"')
break;
if(addr.typex!='+' && addr.typex!='-'){
# insert the missing '+'
nap = ref Addr;
nap.typex='+';
nap.next = addr.next;
addr.next = nap;
}
break;
'/' or
'?' =>
if(addr.typex!='+' && addr.typex!='-'){
# insert the missing '+'
nap = ref Addr;
nap.typex='+';
nap.next = addr.next;
addr.next = nap;
}
break;
'+' or
'-' =>
break;
* =>
error("simpleaddr");
}
ap = ref Addr;
*ap = addr;
return ap;
}
compoundaddr(): ref Addr
{
addr: Addr;
ap, next: ref Addr;
addr.left = simpleaddr();
if((addr.typex = cmdskipbl())!=',' && addr.typex!=';')
return addr.left;
getch();
next = addr.next = compoundaddr();
if(next != nil && (next.typex==',' || next.typex==';') && next.left==nil)
editerror("bad address syntax");
ap = ref Addr;
*ap = addr;
return ap;
}
|