1 /* input.c -- read input from files or strings ($Revision: 1.2 $) */
2 /* stdgetenv is based on the FreeBSD getenv */
3 
4 #include "es.h"
5 #include "input.h"
6 
7 
8 /*
9  * constants
10  */
11 
12 #define	BUFSIZE		((size_t) 1024)		/* buffer size to fill reads into */
13 
14 
15 /*
16  * macros
17  */
18 
19 #define	ISEOF(in)	((in)->fill == eoffill)
20 
21 
22 /*
23  * globals
24  */
25 
26 Input *input;
27 char *prompt, *prompt2;
28 
29 Boolean disablehistory = FALSE;
30 Boolean resetterminal = FALSE;
31 static char *history;
32 static int historyfd = -1;
33 
34 #if READLINE
35 int rl_meta_chars;	/* for editline; ignored for gnu readline */
36 extern char *readline(char *);
37 extern void add_history(char *);
38 extern void rl_reset_terminal(char *);
39 extern char *rl_basic_word_break_characters;
40 extern char *rl_completer_quote_characters;
41 
42 #if ABUSED_GETENV
43 static char *stdgetenv(const char *);
44 static char *esgetenv(const char *);
45 static char *(*realgetenv)(const char *) = stdgetenv;
46 #endif
47 #endif
48 
49 
50 /*
51  * errors and warnings
52  */
53 
54 /* locate -- identify where an error came from */
locate(Input * in,char * s)55 static char *locate(Input *in, char *s) {
56 	return (in->runflags & run_interactive)
57 		? s
58 		: str("%s:%d: %s", in->name, in->lineno, s);
59 }
60 
61 static char *error = NULL;
62 
63 /* yyerror -- yacc error entry point */
yyerror(char * s)64 extern void yyerror(char *s) {
65 #if sgi
66 	/* this is so that trip.es works */
67 	if (streq(s, "Syntax error"))
68 		s = "syntax error";
69 #endif
70 	if (error == NULL)	/* first error is generally the most informative */
71 		error = locate(input, s);
72 }
73 
74 /* warn -- print a warning */
warn(char * s)75 static void warn(char *s) {
76 	eprint("warning: %s\n", locate(input, s));
77 }
78 
79 
80 /*
81  * history
82  */
83 
84 /* loghistory -- write the last command out to a file */
loghistory(const char * cmd,size_t len)85 static void loghistory(const char *cmd, size_t len) {
86 	const char *s, *end;
87 	if (history == NULL || disablehistory)
88 		return;
89 	if (historyfd == -1) {
90 		historyfd = eopen(history, oAppend);
91 		if (historyfd == -1) {
92 			eprint("history(%s): %s\n", history, esstrerror(errno));
93 			vardef("history", NULL, NULL);
94 			return;
95 		}
96 	}
97 	/* skip empty lines and comments in history */
98 	for (s = cmd, end = s + len; s < end; s++)
99 		switch (*s) {
100 		case '#': case '\n':	return;
101 		case ' ': case '\t':	break;
102 		default:		goto writeit;
103 		}
104 	writeit:
105 		;
106 	/*
107 	 * Small unix hack: since read() reads only up to a newline
108 	 * from a terminal, then presumably this write() will write at
109 	 * most only one input line at a time.
110 	 */
111 	ewrite(historyfd, cmd, len);
112 }
113 
114 /* sethistory -- change the file for the history log */
sethistory(char * file)115 extern void sethistory(char *file) {
116 	if (historyfd != -1) {
117 		close(historyfd);
118 		historyfd = -1;
119 	}
120 	history = file;
121 }
122 
123 
124 /*
125  * unget -- character pushback
126  */
127 
128 /* ungetfill -- input->fill routine for ungotten characters */
ungetfill(Input * in)129 static int ungetfill(Input *in) {
130 	int c;
131 	assert(in->ungot > 0);
132 	c = in->unget[--in->ungot];
133 	if (in->ungot == 0) {
134 		assert(in->rfill != NULL);
135 		in->fill = in->rfill;
136 		in->rfill = NULL;
137 		assert(in->rbuf != NULL);
138 		in->buf = in->rbuf;
139 		in->rbuf = NULL;
140 	}
141 	return c;
142 }
143 
144 /* unget -- push back one character */
unget(Input * in,int c)145 extern void unget(Input *in, int c) {
146 	if (in->ungot > 0) {
147 		assert(in->ungot < MAXUNGET);
148 		in->unget[in->ungot++] = c;
149 	} else if (in->bufbegin < in->buf && in->buf[-1] == c && (input->runflags & run_echoinput) == 0)
150 		--in->buf;
151 	else {
152 		assert(in->rfill == NULL);
153 		in->rfill = in->fill;
154 		in->fill = ungetfill;
155 		assert(in->rbuf == NULL);
156 		in->rbuf = in->buf;
157 		in->buf = in->bufend;
158 		assert(in->ungot == 0);
159 		in->ungot = 1;
160 		in->unget[0] = c;
161 	}
162 }
163 
164 
165 /*
166  * getting characters
167  */
168 
169 /* get -- get a character, filter out nulls */
get(Input * in)170 static int get(Input *in) {
171 	int c;
172 	while ((c = (in->buf < in->bufend ? *in->buf++ : (*in->fill)(in))) == '\0')
173 		warn("null character ignored");
174 	return c;
175 }
176 
177 /* getverbose -- get a character, print it to standard error */
getverbose(Input * in)178 static int getverbose(Input *in) {
179 	if (in->fill == ungetfill)
180 		return get(in);
181 	else {
182 		int c = get(in);
183 		if (c != EOF) {
184 			char buf = c;
185 			ewrite(2, &buf, 1);
186 		}
187 		return c;
188 	}
189 }
190 
191 /* eoffill -- report eof when called to fill input buffer */
eoffill(Input * in)192 static int eoffill(Input *in) {
193 	assert(in->fd == -1);
194 	return EOF;
195 }
196 
197 #if READLINE
198 /* callreadline -- readline wrapper */
callreadline(char * prompt)199 static char *callreadline(char *prompt) {
200 	char *r;
201 	if (prompt == NULL)
202 		prompt = ""; /* bug fix for readline 2.0 */
203 	if (resetterminal) {
204 		rl_reset_terminal(NULL);
205 		resetterminal = FALSE;
206 	}
207 	interrupted = FALSE;
208 	if (!setjmp(slowlabel)) {
209 		slow = TRUE;
210 		r = interrupted ? NULL : readline(prompt);
211 	} else
212 		r = NULL;
213 	slow = FALSE;
214 	if (r == NULL)
215 		errno = EINTR;
216 	SIGCHK();
217 	return r;
218 }
219 
220 /* getenv -- fake version of getenv for readline (or other libraries) */
esgetenv(const char * name)221 static char *esgetenv(const char *name) {
222 	List *value = varlookup(name, NULL);
223 	if (value == NULL)
224 		return NULL;
225 	else {
226 		char *export;
227 		static Dict *envdict;
228 		static Boolean initialized = FALSE;
229 		Ref(char *, string, NULL);
230 
231 		gcdisable();
232 		if (!initialized) {
233 			initialized = TRUE;
234 			envdict = mkdict();
235 			globalroot(&envdict);
236 		}
237 
238 		string = dictget(envdict, name);
239 		if (string != NULL)
240 			efree(string);
241 
242 		export = str("%W", value);
243 		string = ealloc(strlen(export) + 1);
244 		strcpy(string, export);
245 		envdict = dictput(envdict, (char *) name, string);
246 
247 		gcenable();
248 		RefReturn(string);
249 	}
250 }
251 
252 #if ABUSED_GETENV
253 
254 static char *
stdgetenv(name)255 stdgetenv(name)
256 	register const char *name;
257 {
258 	extern char **environ;
259 	register int len;
260 	register const char *np;
261 	register char **p, *c;
262 
263 	if (name == NULL || environ == NULL)
264 		return (NULL);
265 	for (np = name; *np && *np != '='; ++np)
266 		continue;
267 	len = np - name;
268 	for (p = environ; (c = *p) != NULL; ++p)
269 		if (strncmp(c, name, len) == 0 && c[len] == '=') {
270 			return (c + len + 1);
271 		}
272 	return (NULL);
273 }
274 
275 char *
getenv(char * name)276 getenv(char *name)
277 {
278 	return realgetenv(name);
279 }
280 
281 extern void
initgetenv(void)282 initgetenv(void)
283 {
284 	realgetenv = esgetenv;
285 }
286 
287 #endif /* ABUSED_GETENV */
288 
289 #endif	/* READLINE */
290 
291 /* fdfill -- fill input buffer by reading from a file descriptor */
fdfill(Input * in)292 static int fdfill(Input *in) {
293 	long nread;
294 	assert(in->buf == in->bufend);
295 	assert(in->fd >= 0);
296 
297 #if READLINE
298 	if (in->runflags & run_interactive && in->fd == 0) {
299 		char *rlinebuf = callreadline(prompt);
300 		if (rlinebuf == NULL)
301 
302 			nread = 0;
303 		else {
304 			if (*rlinebuf != '\0')
305 				add_history(rlinebuf);
306 			nread = strlen(rlinebuf) + 1;
307 			if (in->buflen < nread) {
308 				while (in->buflen < nread)
309 					in->buflen *= 2;
310 				in->bufbegin = erealloc(in->bufbegin, in->buflen);
311 			}
312 			memcpy(in->bufbegin, rlinebuf, nread - 1);
313 			in->bufbegin[nread - 1] = '\n';
314 		}
315 	} else
316 #endif
317 	do {
318 		nread = eread(in->fd, (char *) in->bufbegin, in->buflen);
319 		SIGCHK();
320 	} while (nread == -1 && errno == EINTR);
321 
322 	if (nread <= 0) {
323 		close(in->fd);
324 		in->fd = -1;
325 		in->fill = eoffill;
326 		in->runflags &= ~run_interactive;
327 		if (nread == -1)
328 			fail("$&parse", "%s: %s", in->name == NULL ? "es" : in->name, esstrerror(errno));
329 		return EOF;
330 	}
331 
332 	if (in->runflags & run_interactive)
333 		loghistory((char *) in->bufbegin, nread);
334 
335 	in->buf = in->bufbegin;
336 	in->bufend = &in->buf[nread];
337 	return *in->buf++;
338 }
339 
340 
341 /*
342  * the input loop
343  */
344 
345 /* parse -- call yyparse(), but disable garbage collection and catch errors */
parse(char * pr1,char * pr2)346 extern Tree *parse(char *pr1, char *pr2) {
347 	int result;
348 	assert(error == NULL);
349 
350 	inityy();
351 	emptyherequeue();
352 
353 	if (ISEOF(input))
354 		throw(mklist(mkstr("eof"), NULL));
355 
356 #if READLINE
357 	prompt = (pr1 == NULL) ? "" : pr1;
358 #else
359 	if (pr1 != NULL)
360 		eprint("%s", pr1);
361 #endif
362 	prompt2 = pr2;
363 
364 	gcreserve(300 * sizeof (Tree));
365 	gcdisable();
366 	result = yyparse();
367 	gcenable();
368 
369 	if (result || error != NULL) {
370 		char *e;
371 		assert(error != NULL);
372 		e = error;
373 		error = NULL;
374 		fail("$&parse", "%s", e);
375 	}
376 #if LISPTREES
377 	if (input->runflags & run_lisptrees)
378 		eprint("%B\n", parsetree);
379 #endif
380 	return parsetree;
381 }
382 
383 /* resetparser -- clear parser errors in the signal handler */
resetparser(void)384 extern void resetparser(void) {
385 	error = NULL;
386 }
387 
388 /* runinput -- run from an input source */
runinput(Input * in,int runflags)389 extern List *runinput(Input *in, int runflags) {
390 	volatile int flags = runflags;
391 	List * volatile result;
392 	List *repl, *dispatch;
393 	Push push;
394 	const char *dispatcher[] = {
395 		"fn-%eval-noprint",
396 		"fn-%eval-print",
397 		"fn-%noeval-noprint",
398 		"fn-%noeval-print",
399 	};
400 
401 	flags &= ~eval_inchild;
402 	in->runflags = flags;
403 	in->get = (flags & run_echoinput) ? getverbose : get;
404 	in->prev = input;
405 	input = in;
406 
407 	ExceptionHandler
408 
409 		dispatch
410 	          = varlookup(dispatcher[((flags & run_printcmds) ? 1 : 0)
411 					 + ((flags & run_noexec) ? 2 : 0)],
412 			      NULL);
413 		if (flags & eval_exitonfalse)
414 			dispatch = mklist(mkstr("%exit-on-false"), dispatch);
415 		varpush(&push, "fn-%dispatch", dispatch);
416 
417 		repl = varlookup((flags & run_interactive)
418 				   ? "fn-%interactive-loop"
419 				   : "fn-%batch-loop",
420 				 NULL);
421 		result = (repl == NULL)
422 				? prim("batchloop", NULL, NULL, flags)
423 				: eval(repl, NULL, flags);
424 
425 		varpop(&push);
426 
427 	CatchException (e)
428 
429 		(*input->cleanup)(input);
430 		input = input->prev;
431 		throw(e);
432 
433 	EndExceptionHandler
434 
435 	input = in->prev;
436 	(*in->cleanup)(in);
437 	return result;
438 }
439 
440 
441 /*
442  * pushing new input sources
443  */
444 
445 /* fdcleanup -- cleanup after running from a file descriptor */
fdcleanup(Input * in)446 static void fdcleanup(Input *in) {
447 	unregisterfd(&in->fd);
448 	if (in->fd != -1)
449 		close(in->fd);
450 	efree(in->bufbegin);
451 }
452 
453 /* runfd -- run commands from a file descriptor */
runfd(int fd,const char * name,int flags)454 extern List *runfd(int fd, const char *name, int flags) {
455 	Input in;
456 	List *result;
457 
458 	memzero(&in, sizeof (Input));
459 	in.lineno = 1;
460 	in.fill = fdfill;
461 	in.cleanup = fdcleanup;
462 	in.fd = fd;
463 	registerfd(&in.fd, TRUE);
464 	in.buflen = BUFSIZE;
465 	in.bufbegin = in.buf = ealloc(in.buflen);
466 	in.bufend = in.bufbegin;
467 	in.name = (name == NULL) ? str("fd %d", fd) : name;
468 
469 	RefAdd(in.name);
470 	result = runinput(&in, flags);
471 	RefRemove(in.name);
472 
473 	return result;
474 }
475 
476 /* stringcleanup -- cleanup after running from a string */
stringcleanup(Input * in)477 static void stringcleanup(Input *in) {
478 	efree(in->bufbegin);
479 }
480 
481 /* stringfill -- placeholder than turns into EOF right away */
stringfill(Input * in)482 static int stringfill(Input *in) {
483 	in->fill = eoffill;
484 	return EOF;
485 }
486 
487 /* runstring -- run commands from a string */
runstring(const char * str,const char * name,int flags)488 extern List *runstring(const char *str, const char *name, int flags) {
489 	Input in;
490 	List *result;
491 	unsigned char *buf;
492 
493 	assert(str != NULL);
494 
495 	memzero(&in, sizeof (Input));
496 	in.fd = -1;
497 	in.lineno = 1;
498 	in.name = (name == NULL) ? str : name;
499 	in.fill = stringfill;
500 	in.buflen = strlen(str);
501 	buf = ealloc(in.buflen + 1);
502 	memcpy(buf, str, in.buflen);
503 	in.bufbegin = in.buf = buf;
504 	in.bufend = in.buf + in.buflen;
505 	in.cleanup = stringcleanup;
506 
507 	RefAdd(in.name);
508 	result = runinput(&in, flags);
509 	RefRemove(in.name);
510 	return result;
511 }
512 
513 /* parseinput -- turn an input source into a tree */
parseinput(Input * in)514 extern Tree *parseinput(Input *in) {
515 	Tree * volatile result;
516 
517 	in->prev = input;
518 	in->runflags = 0;
519 	in->get = get;
520 	input = in;
521 
522 	ExceptionHandler
523 		result = parse(NULL, NULL);
524 		if (get(in) != EOF)
525 			fail("$&parse", "more than one value in term");
526 	CatchException (e)
527 		(*input->cleanup)(input);
528 		input = input->prev;
529 		throw(e);
530 	EndExceptionHandler
531 
532 	input = in->prev;
533 	(*in->cleanup)(in);
534 	return result;
535 }
536 
537 /* parsestring -- turn a string into a tree; must be exactly one tree */
parsestring(const char * str)538 extern Tree *parsestring(const char *str) {
539 	Input in;
540 	Tree *result;
541 	unsigned char *buf;
542 
543 	assert(str != NULL);
544 
545 	/* TODO: abstract out common code with runstring */
546 
547 	memzero(&in, sizeof (Input));
548 	in.fd = -1;
549 	in.lineno = 1;
550 	in.name = str;
551 	in.fill = stringfill;
552 	in.buflen = strlen(str);
553 	buf = ealloc(in.buflen + 1);
554 	memcpy(buf, str, in.buflen);
555 	in.bufbegin = in.buf = buf;
556 	in.bufend = in.buf + in.buflen;
557 	in.cleanup = stringcleanup;
558 
559 	RefAdd(in.name);
560 	result = parseinput(&in);
561 	RefRemove(in.name);
562 	return result;
563 }
564 
565 /* isinteractive -- is the innermost input source interactive? */
isinteractive(void)566 extern Boolean isinteractive(void) {
567 	return input == NULL ? FALSE : ((input->runflags & run_interactive) != 0);
568 }
569 
570 
571 /*
572  * initialization
573  */
574 
575 /* initinput -- called at dawn of time from main() */
initinput(void)576 extern void initinput(void) {
577 	input = NULL;
578 
579 	/* declare the global roots */
580 	globalroot(&history);		/* history file */
581 	globalroot(&error);		/* parse errors */
582 	globalroot(&prompt);		/* main prompt */
583 	globalroot(&prompt2);		/* secondary prompt */
584 
585 	/* mark the historyfd as a file descriptor to hold back from forked children */
586 	registerfd(&historyfd, TRUE);
587 
588 	/* call the parser's initialization */
589 	initparse();
590 
591 #if READLINE
592 	rl_meta_chars = 0;
593 	rl_basic_word_break_characters=" \t\n\\'`$><=;|&{()}";
594 		rl_completer_quote_characters="'";
595 #endif
596 }
597