rc

[fork] interactive rc shell
git clone https://hhvn.uk/rc
git clone git://hhvn.uk/rc
Log | Files | Refs | README | LICENSE

input.c (7155B)


      1 /* input.c: i/o routines for files and pseudo-files (strings) */
      2 
      3 #include "rc.h"
      4 
      5 #include <errno.h>
      6 
      7 #include "develop.h"
      8 #include "edit.h"
      9 #include "input.h"
     10 #include "jbwrap.h"
     11 
     12 /* How many characters can we unget? */
     13 enum { UNGETSIZE = 2 };
     14 
     15 typedef enum inputtype {
     16 	iFd, iString, iEdit
     17 } inputtype;
     18 
     19 typedef struct Input {
     20 	bool saved;
     21 	inputtype t;
     22 	int fd, index, read, ungetcount, lineno, last;
     23 	char *ibuf;
     24 	void *cookie;
     25 	int ungetbuf[UNGETSIZE];
     26 	int (*gchar)(void);
     27 } Input;
     28 
     29 #define BUFSIZE ((size_t) 256)
     30 
     31 static char *inbuf;
     32 static size_t istacksize, chars_out, chars_in;
     33 static bool save_lineno = TRUE;
     34 static Input *istack, *itop;
     35 
     36 int lastchar;
     37 
     38 static char *prompt, *prompt2;
     39 
     40 extern void ugchar(int c) {
     41 	assert(istack->ungetcount < UNGETSIZE);
     42 	istack->ungetbuf[istack->ungetcount++] = c;
     43 }
     44 
     45 extern int gchar() {
     46 	int c;
     47 
     48 	if (istack->ungetcount)
     49 		return lastchar = istack->ungetbuf[--istack->ungetcount];
     50 
     51 	while ((c = (*istack->gchar)()) == '\0')
     52 		pr_error("warning: null character ignored", 0);
     53 
     54 	return c;
     55 }
     56 
     57 
     58 /* get the next character from a string. */
     59 
     60 static int stringgchar() {
     61 	return lastchar = (inbuf[chars_out] == '\0' ? EOF : inbuf[chars_out++]);
     62 }
     63 
     64 
     65 /* write last command out to a file if interactive && $history is set */
     66 
     67 static void history() {
     68 	List *hist;
     69 	size_t a;
     70 
     71 	if (!interactive || (hist = varlookup("history")) == NULL)
     72 		return;
     73 
     74 	for (a = 0; a < chars_in; a++) {
     75 		char c = inbuf[a];
     76 
     77 		/* skip empty lines and comments */
     78 		if (c == '#' || c == '\n')
     79 			break;
     80 
     81 		/* line matches [ \t]*[^#\n] so it's ok to write out */
     82 		if (c != ' ' && c != '\t') {
     83 			char *name = hist->w;
     84 			int fd = rc_open(name, rAppend);
     85 			if (fd < 0)
     86 				uerror(name);
     87 			else {
     88 				writeall(fd, inbuf, chars_in);
     89 				close(fd);
     90 			}
     91 			break;
     92 		}
     93 	}
     94 }
     95 
     96 
     97 /* read a character from a file descriptor */
     98 
     99 static int fdgchar() {
    100 	if (chars_out >= chars_in) { /* replenish empty buffer */
    101 		ssize_t r;
    102 		do {
    103 			r = rc_read(istack->fd, inbuf, BUFSIZE);
    104 			sigchk();
    105 			if (r == -1)
    106 				switch (errno) {
    107 				case EAGAIN:
    108 					if (!makeblocking(istack->fd))
    109 						panic("not O_NONBLOCK");
    110 					errno = EINTR;
    111 					break;
    112 				case EIO:
    113 					if (makesamepgrp(istack->fd))
    114 						errno = EINTR;
    115 					else
    116 						errno = EIO;
    117 					break;
    118 				}
    119 		} while (r < 0 && errno == EINTR);
    120 		if (r < 0) {
    121 			uerror("read");
    122 			rc_raise(eError);
    123 		}
    124 		chars_in = (size_t) r;
    125 		if (chars_in == 0)
    126 			return lastchar = EOF;
    127 		chars_out = 0;
    128 		if (dashvee)
    129 			writeall(2, inbuf, chars_in);
    130 		history();
    131 	}
    132 
    133 	return lastchar = inbuf[chars_out++];
    134 }
    135 
    136 /* read a character from a line-editing file descriptor */
    137 
    138 static int editgchar() {
    139 	if (chars_out >= chars_in) { /* replenish empty buffer */
    140 		edit_free(istack->cookie);
    141 		inbuf = edit_alloc(istack->cookie, &chars_in);
    142 		if (inbuf == NULL) {
    143 			chars_in = 0;
    144 			fprint(2, "exit\n");
    145 			return lastchar = EOF;
    146 		}
    147 
    148 		chars_out = 0;
    149 		if (dashvee)
    150 			writeall(2, inbuf, chars_in);
    151 		history();
    152 	}
    153 
    154 	return lastchar = inbuf[chars_out++];
    155 }
    156 
    157 void termchange(void) {
    158 	if (istack->t == iEdit)
    159 		edit_reset(istack->cookie);
    160 }
    161 
    162 
    163 /* set up the input stack, and put a "dead" input at the bottom, so that yyparse will always read eof */
    164 
    165 extern void initinput() {
    166 	istack = itop = ealloc(istacksize = 256 * sizeof (Input));
    167 	istack->ungetcount = 0;
    168 	ugchar(EOF);
    169 }
    170 
    171 /* push an input source onto the stack. set up a new input buffer, and set gchar() */
    172 
    173 static void pushcommon() {
    174 	size_t idiff;
    175 	istack->index = chars_out;
    176 	istack->read = chars_in;
    177 	istack->ibuf = inbuf;
    178 	istack->lineno = lineno;
    179 	istack->saved = save_lineno;
    180 	istack->last = lastchar;
    181 	istack++;
    182 	idiff = istack - itop;
    183 	if (idiff >= istacksize / sizeof (Input)) {
    184 		itop = erealloc(itop, istacksize *= 2);
    185 		istack = itop + idiff;
    186 	}
    187 	chars_out = 0;
    188 	chars_in = 0;
    189 	istack->ungetcount = 0;
    190 }
    191 
    192 extern void pushfd(int fd) {
    193 	pushcommon();
    194 	save_lineno = TRUE;
    195 	istack->fd = fd;
    196 	lineno = 1;
    197 	if (editing && interactive && isatty(fd)) {
    198 		istack->t = iEdit;
    199 		istack->gchar = editgchar;
    200 		istack->cookie = edit_begin(fd);
    201 	} else {
    202 		istack->t = iFd;
    203 		istack->gchar = fdgchar;
    204 		inbuf = ealloc(BUFSIZE);
    205 	}
    206 }
    207 
    208 extern void pushstring(char **a, bool save) {
    209 	pushcommon();
    210 	istack->t = iString;
    211 	save_lineno = save;
    212 	inbuf = mprint("%A", a);
    213 	istack->gchar = stringgchar;
    214 	if (save_lineno)
    215 		lineno = 1;
    216 	else
    217 		--lineno;
    218 }
    219 
    220 
    221 /* remove an input source from the stack. restore associated variables etc. */
    222 
    223 extern void popinput() {
    224 	if (istack->t == iEdit)
    225 		edit_end(istack->cookie);
    226 	if (istack->t == iFd || istack->t == iEdit)
    227 		close(istack->fd);
    228 	efree(inbuf);
    229 	--istack;
    230 	lastchar = istack->last;
    231 	inbuf = istack->ibuf;
    232 	chars_out = istack->index;
    233 	chars_in = istack->read;
    234 	if (save_lineno)
    235 		lineno = istack->lineno;
    236 	else
    237 		lineno++;
    238 	save_lineno = istack->saved;
    239 }
    240 
    241 
    242 /* flush input characters up to newline. Used by scanerror() */
    243 
    244 extern void skiptonl() {
    245 	int c;
    246 	if (lastchar == '\n' || lastchar == EOF)
    247 		return;
    248 	while ((c = gchar()) != '\n' && c != EOF)
    249 		; /* skip to newline */
    250 	if (c == EOF)
    251 		ugchar(c);
    252 }
    253 
    254 
    255 /* the wrapper loop in rc: prompt for commands until EOF, calling yyparse and walk() */
    256 
    257 extern Node *doit(bool clobberexecit) {
    258 	bool eof;
    259 	bool execit;
    260 	Jbwrap j;
    261 	Estack e1;
    262 	Edata jerror;
    263 
    264 	if (dashen)
    265 		clobberexecit = FALSE;
    266 	execit = clobberexecit;
    267 	sigsetjmp(j.j, 1);
    268 	jerror.jb = &j;
    269 	except(eError, jerror, &e1);
    270 	for (eof = FALSE; !eof;) {
    271 		Edata block;
    272 		Estack e2;
    273 
    274 		block.b = newblock();
    275 		except(eArena, block, &e2);
    276 		sigchk();
    277 
    278 		if (interactive) {
    279 			List *s;
    280 			if (!dashen && fnlookup("prompt") != NULL) {
    281 				static bool died = FALSE;
    282 				static char *arglist[] = { "prompt", NULL };
    283 
    284 				if (!died) {
    285 					died = TRUE;
    286 					funcall(arglist);
    287 				}
    288 				died = FALSE;
    289 			}
    290 			s = varlookup("prompt");
    291 			if (s != NULL) {
    292 				prompt = s->w;
    293 				if (s->n != NULL)
    294 					prompt2 = s->n->w;
    295 				else
    296 					prompt2 = "";
    297 			} else {
    298 				prompt = prompt2 = "";
    299 			}
    300 			if (istack->t == iFd)
    301 				fprint(2, "%s", prompt);
    302 			else if (istack->t == iEdit)
    303 				edit_prompt(istack->cookie, prompt);
    304 		}
    305 		inityy();
    306 		if (yyparse() == 1 && execit)
    307 			rc_raise(eError);
    308 		eof = (lastchar == EOF); /* "lastchar" can be clobbered during a walk() */
    309 		if (parsetree != NULL) {
    310 			if (RC_DEVELOP)
    311 				tree_dump(parsetree);
    312 			if (execit)
    313 				walk(parsetree, TRUE);
    314 			else if (dashex && dashen)
    315 				fprint(2, "%T\n", parsetree);
    316 		}
    317 		unexcept(eArena);
    318 	}
    319 	popinput();
    320 	unexcept(eError);
    321 	return parsetree;
    322 }
    323 
    324 /* parse a function imported from the environment */
    325 
    326 extern Node *parseline(char *extdef) {
    327 	bool i = interactive;
    328 	char *in[2];
    329 	Node *fun;
    330 	in[0] = extdef;
    331 	in[1] = NULL;
    332 	interactive = FALSE;
    333 	pushstring(in, TRUE);
    334 	fun = doit(FALSE);
    335 	interactive = i;
    336 	return fun;
    337 }
    338 
    339 /* close file descriptors after a fork() */
    340 
    341 extern void closefds() {
    342 	Input *i;
    343 	for (i = istack; i != itop; --i)	/* close open scripts */
    344 		if (i->t == iFd && i->fd > 2) {
    345 			close(i->fd);
    346 			i->fd = -1;
    347 		}
    348 }
    349 
    350 /* print (or set) prompt(2) */
    351 
    352 extern void nextline() {
    353 	lineno++;
    354 	if (interactive) {
    355 		if (istack->t == iFd)
    356 			fprint(2, "%s", prompt2);
    357 		else if (istack->t == iEdit)
    358 			edit_prompt(istack->cookie, prompt2);
    359 	}
    360 }