Plan 9 from Bell Labs’s /usr/web/sources/contrib/nemo/octopus/port/x/sam.b

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


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


Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.