xref: /openbsd-src/usr.bin/mg/interpreter.c (revision 097a140d792de8b2bbe59ad827d39eabf9b4280a)
1 /*      $OpenBSD: interpreter.c,v 1.22 2021/04/20 16:34:20 lum Exp $	*/
2 /*
3  * This file is in the public domain.
4  *
5  * Author: Mark Lumsden <mark@showcomplex.com>
6  */
7 
8 /*
9  * This file attempts to add some 'scripting' functionality into mg.
10  *
11  * The initial goal is to give mg the ability to use it's existing functions
12  * and structures in a linked-up way. Hopefully resulting in user definable
13  * functions. The syntax is 'scheme' like but currently it is not a scheme
14  * interpreter.
15  *
16  * At the moment there is no manual page reference to this file. The code below
17  * is liable to change, so use at your own risk!
18  *
19  * If you do want to do some testing, you can add some lines to your .mg file
20  * like:
21  *
22  * 1. Give multiple arguments to a function that usually would accept only one:
23  * (find-file "a.txt" "b.txt" "c.txt")
24  *
25  * 2. Define a single value variable:
26  * (define myfile "d.txt")
27  *
28  * 3. Define a list:
29  * (define myfiles(list "e.txt" "f.txt"))
30  *
31  * 4. Use the previously defined variable or list:
32  * (find-file myfiles)
33  *
34  * To do:
35  * 1. multiline parsing - currently only single lines supported.
36  * 2. parsing for '(' and ')' throughout whole string and evaluate correctly.
37  * 3. conditional execution.
38  * 4. deal with special characters in a string: "x\" x" etc
39  * 5. do symbol names need more complex regex patterns? [A-Za-z][.0-9_A-Z+a-z-]
40  *    at the moment.
41  * 6. oh so many things....
42  * [...]
43  * n. implement user definable functions.
44  *
45  * Notes:
46  * - Currently calls to excline() from this file have the line length set to
47  *   zero. That's because excline() uses '\0' as the end of line indicator
48  *   and only the call to foundparen() within excline() uses excline's 2nd
49  *   argument. Importantly, any lines sent to there from here will not be
50  *   coming back here.
51  */
52 #include <sys/queue.h>
53 
54 #include <limits.h>
55 #include <regex.h>
56 #include <signal.h>
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <string.h>
60 
61 #include "def.h"
62 #include "funmap.h"
63 
64 #ifdef  MGLOG
65 #include "kbd.h"
66 #include "log.h"
67 #endif
68 
69 static int	 multiarg(char *, char *, int);
70 static int	 isvar(char **, char **, int);
71 /*static int	 dofunc(char **, char **, int);*/
72 static int	 founddef(char *, int, int, int);
73 static int	 foundlst(char *, int, int);
74 static int	 expandvals(char *, char *, char *);
75 static int	 foundfun(char *, int);
76 static int	 doregex(char *, char *);
77 static void	 clearexp(void);
78 static int	 parse(char *, const char *, const char *, int, int);
79 static int	 parsdef(char *, const char *, const char *, int, int);
80 static int	 parsval(char *, const char *, const char *, int, int);
81 static int	 parsexp(char *, const char *, const char *, int, int);
82 
83 static int	 exitinterpreter(char *, char *, int);
84 
85 TAILQ_HEAD(exphead, expentry) ehead;
86 struct expentry {
87 	TAILQ_ENTRY(expentry) eentry;
88 	char	*fun;		/* The 1st string found between parens.   */
89 	char	 funbuf[BUFSIZE];
90 	const char	*par1;	/* Parenthesis at start of string	  */
91 	const char	*par2;	/* Parenthesis at end of string		  */
92 	int	 expctr;	/* An incremental counter:+1 for each exp */
93 	int	 blkid;		/* Which block are we in?		  */
94 };
95 
96 /*
97  * Structure for variables during buffer evaluation.
98  */
99 struct varentry {
100 	SLIST_ENTRY(varentry) entry;
101 	char	 valbuf[BUFSIZE];
102 	char	*name;
103 	char	*vals;
104 	int	 count;
105 	int	 expctr;
106 	int	 blkid;
107 };
108 SLIST_HEAD(vlisthead, varentry) varhead = SLIST_HEAD_INITIALIZER(varhead);
109 
110 /*
111  * Structure for scheme keywords.
112  */
113 #define NUMSCHKEYS	4
114 #define MAXLENSCHKEYS	17	/* 17 = longest keyword (16)  + 1 */
115 
116 char scharkey[NUMSCHKEYS][MAXLENSCHKEYS] =
117 	{
118 		"define",
119 	  	"list",
120 	  	"if",
121 	  	"lambda"
122 	};
123 
124 const char lp = '(';
125 const char rp = ')';
126 char *defnam = NULL;
127 
128 /*
129  * Line has a '(' as the first non-white char.
130  * Do some very basic parsing of line.
131  * Multi-line not supported at the moment, To do.
132  */
133 int
134 foundparen(char *funstr, int llen)
135 {
136 	const char	*lrp = NULL;
137 	char		*p, *begp = NULL, *endp = NULL, *regs;
138 	int     	 i, ret, pctr, expctr, blkid, inquote;
139 
140 	pctr = expctr = inquote = 0;
141 	blkid = 1;
142 
143 	/*
144 	 * Currently can't do () or (( at the moment,
145 	 * just drop out - stops a segv. TODO.
146 	 */
147 	regs = "[(]+[\t ]*[)]+";
148         if (doregex(regs, funstr))
149 		return(dobeep_msg("Empty lists not supported at moment"));
150 	regs = "[(]+[\t ]*[(]+";
151         if (doregex(regs, funstr))
152 		return(dobeep_msg("Multiple consecutive left parantheses "\
153 		    "found."));
154 	/*
155 	 * load expressions into a list called 'expentry', to be processd
156 	 * when all are obtained.
157 	 * Not really live code at the moment. Just part of the process of
158 	 * working out what needs to be done.
159 	 */
160 	TAILQ_INIT(&ehead);
161 
162 	/*
163 	 * Check for blocks of code with opening and closing ().
164 	 * One block = (cmd p a r a m)
165 	 * Two blocks = (cmd p a r a m s)(hola)
166 	 * Two blocks = (cmd p a r (list a m s))(hola)
167 	 * Only single line at moment, but more for multiline.
168 	 */
169 	p = funstr;
170 
171 	for (i = 0; i < llen; ++i, p++) {
172 		if (*p == '(') {
173 			if (inquote == 1) {
174 				cleanup();
175 				return(dobeep_msg("Opening and closing quote "\
176 				    "char error"));
177 			}
178 			if (begp != NULL) {
179 				if (endp == NULL)
180 					*p = '\0';
181 				else
182 					*endp = '\0';
183 
184 				ret = parse(begp, lrp, &lp, blkid, ++expctr);
185 				if (!ret) {
186 					cleanup();
187 					return(ret);
188 				}
189 			}
190 			lrp = &lp;
191 			begp = endp = NULL;
192 			pctr++;
193 		} else if (*p == ')') {
194 			if (inquote == 1) {
195 				cleanup();
196 				return(dobeep_msg("Opening and closing quote "\
197 				    "char error"));
198 			}
199 			if (begp != NULL) {
200 				if (endp == NULL)
201 					*p = '\0';
202 				else
203 					*endp = '\0';
204 
205 				ret = parse(begp, lrp, &rp, blkid, ++expctr);
206 				if (!ret) {
207 					cleanup();
208 					return(ret);
209 				}
210 			}
211 			lrp = &rp;
212 			begp = endp = NULL;
213 			pctr--;
214 		} else if (*p != ' ' && *p != '\t') {
215 			if (begp == NULL)
216 				begp = p;
217 			if (*p == '"') {
218 				if (inquote == 0)
219 					inquote = 1;
220 				else
221 					inquote = 0;
222 			}
223 			endp = NULL;
224 		} else if (endp == NULL && (*p == ' ' || *p == '\t')) {
225 			*p = ' ';
226 			endp = p;
227 		} else if (*p == '\t')
228 			if (inquote == 0)
229 				*p = ' ';
230 
231 		if (pctr == 0) {
232 			blkid++;
233 			expctr = 0;
234 			defnam = NULL;
235 		}
236 	}
237 
238 	if (pctr != 0) {
239 		cleanup();
240 		return(dobeep_msg("Opening and closing parentheses error"));
241 	}
242 	if (ret == FALSE)
243 		cleanup();
244 	else
245 		clearexp();	/* leave lists but remove expressions */
246 
247 	return (ret);
248 }
249 
250 
251 static int
252 parse(char *begp, const char *par1, const char *par2, int blkid, int expctr)
253 {
254 	char    *regs;
255 	int 	 ret = FALSE;
256 
257 	if (strncmp(begp, "define", 6) == 0) {
258 		ret = parsdef(begp, par1, par2, blkid, expctr);
259 		if (ret == TRUE || ret == FALSE)
260 			return (ret);
261 	} else if (strncmp(begp, "list", 4) == 0)
262 		return(parsval(begp, par1, par2, blkid, expctr));
263 
264 	regs = "^exit$";
265 	if (doregex(regs, begp))
266 		return(exitinterpreter(NULL, NULL, FALSE));
267 
268 	/* mg function name regex */
269 	regs = "^[A-Za-z-]+$";
270         if (doregex(regs, begp))
271 		return(excline(begp, 0));
272 
273 	/* Corner case 1 */
274 	if (strncmp(begp, "global-set-key ", 15) == 0)
275 		/* function name as 2nd param screws up multiarg. */
276 		return(excline(begp, 0));
277 
278 	/* Corner case 2 */
279 	if (strncmp(begp, "define-key ", 11) == 0)
280 		/* function name as 3rd param screws up multiarg. */
281 		return(excline(begp, 0));
282 
283 	return (parsexp(begp, par1, par2, blkid, expctr));
284 }
285 
286 static int
287 parsdef(char *begp, const char *par1, const char *par2, int blkid, int expctr)
288 {
289 	char    *regs;
290 
291 	if ((defnam == NULL) && (expctr != 1))
292 		return(dobeep_msg("'define' incorrectly used"));
293 
294         /* Does the line have a incorrect variable 'define' like: */
295         /* (define i y z) */
296         regs = "^define[ ]+[A-Za-z][.0-9_A-Z+a-z-]*[ ]+.+[ ]+.+$";
297         if (doregex(regs, begp))
298                 return(dobeep_msg("Invalid use of define"));
299 
300         /* Does the line have a single variable 'define' like: */
301         /* (define i 0) */
302         regs = "^define[ ]+[A-Za-z][.0-9_A-Z+a-z-]*[ ]+.*$";
303         if (doregex(regs, begp)) {
304 		if (par1 == &lp && par2 == &rp && expctr == 1)
305 			return(founddef(begp, blkid, expctr, 1));
306 		return(dobeep_msg("Invalid use of define."));
307 	}
308 	/* Does the line have  '(define i(' */
309         regs = "^define[ ]+[A-Za-z][.0-9_A-Z+a-z-]*[ ]*$";
310         if (doregex(regs, begp)) {
311 		if (par1 == &lp && par2 == &lp && expctr == 1)
312                 	return(founddef(begp, blkid, expctr, 0));
313 		return(dobeep_msg("Invalid use of 'define'"));
314 	}
315 	/* Does the line have  '(define (' */
316 	regs = "^define$";
317 	if (doregex(regs, begp)) {
318 		if (par1 == &lp && par2 == &lp && expctr == 1)
319 			return(foundfun(begp, expctr));
320 		return(dobeep_msg("Invalid use of 'define'."));
321 	}
322 
323 	return (ABORT);
324 }
325 
326 static int
327 parsval(char *begp, const char *par1, const char *par2, int blkid, int expctr)
328 {
329 	char    *regs;
330 
331 	/* Does the line have 'list' */
332 	regs = "^list$";
333 	if (doregex(regs, begp))
334 		return(dobeep_msg("Invalid use of list"));
335 
336         /* Does the line have a 'list' like: */
337         /* (list "a" "b") */
338         regs = "^list[ ]+.*$";
339         if (doregex(regs, begp)) {
340 		if (expctr == 1)
341 			return(dobeep_msg("list with no-where to go."));
342 
343 		if (par1 == &lp && expctr > 1)
344 			return(foundlst(begp, blkid, expctr));
345 
346 		return(dobeep_msg("Invalid use of list."));
347 	}
348 	return (FALSE);
349 }
350 
351 static int
352 parsexp(char *begp, const char *par1, const char *par2, int blkid, int expctr)
353 {
354 	struct expentry *e1 = NULL;
355 	PF		 funcp;
356 	char		*cmdp, *fendp, *valp, *fname, *funb = NULL;;
357 	int		 numparams, ret;
358 
359 	cmdp = begp;
360 	fendp = strchr(cmdp, ' ');
361 	*fendp = '\0';
362 
363 	/*
364 	 * If no extant mg command found, just return.
365 	 */
366 	if ((funcp = name_function(cmdp)) == NULL)
367 		return (dobeep_msgs("Unknown command: ", cmdp));
368 
369 	numparams = numparams_function(funcp);
370 	if (numparams == 0)
371 		return (dobeep_msgs("Command takes no arguments:", cmdp));
372 
373 	if (numparams == -1)
374 		return (dobeep_msgs("Interactive command found:", cmdp));
375 
376 	if ((e1 = malloc(sizeof(struct expentry))) == NULL) {
377 		cleanup();
378 		return (dobeep_msg("malloc Error"));
379 	}
380 	TAILQ_INSERT_HEAD(&ehead, e1, eentry);
381 	if ((e1->fun = strndup(cmdp, BUFSIZE)) == NULL) {
382 		cleanup();
383 		return(dobeep_msg("strndup error"));
384 	}
385 	cmdp = e1->fun;
386 	fname = e1->fun;
387 	e1->funbuf[0] = '\0';
388 	funb = e1->funbuf;
389 	e1->expctr = expctr;
390 	e1->blkid = blkid;
391 	/* need to think about these two */
392 	e1->par1 = par1;
393 	e1->par2 = par2;
394 
395 	*fendp = ' ';
396 	valp = fendp + 1;
397 
398 	ret = expandvals(cmdp, valp, funb);
399 	if (!ret)
400 		return (ret);
401 
402 	return (multiarg(fname, funb, numparams));
403 }
404 
405 /*
406  * Pass a list of arguments to a function.
407  */
408 static int
409 multiarg(char *cmdp, char *argbuf, int numparams)
410 {
411 	char	 excbuf[BUFSIZE];
412 	char	*argp, *p, *s = " ";
413 	char	*regs;
414 	int	 spc, numspc;
415 	int	 fin, inquote;
416 
417 	argp = argbuf;
418 	spc = 1; /* initially fake a space so we find first argument */
419 	numspc = fin = inquote = 0;
420 
421 	for (p = argbuf; *p != '\0'; p++) {
422 		if (*(p + 1) == '\0')
423 			fin = 1;
424 
425 		if (*p != ' ') {
426 			if (*p == '"') {
427 				if (inquote == 1)
428 					inquote = 0;
429 				else
430 					inquote = 1;
431 			}
432 			if (spc == 1)
433 				if ((numspc % numparams) == 0) {
434 					argp = p;
435 				}
436 			spc = 0;
437 		}
438 		if ((*p == ' ' && inquote == 0) || fin) {
439 			if (spc == 1)/* || (numspc % numparams == 0))*/
440 				continue;
441 			if ((numspc % numparams) != (numparams - 1)) {
442 				numspc++;
443 				continue;
444 			}
445 			if (*p == ' ') {
446 				*p = '\0';		/* terminate arg string */
447 			}
448 			excbuf[0] = '\0';
449 			regs = "[\"]+.*[\"]+";
450 
451        			if (!doregex(regs, argp)) {
452 				const char *errstr;
453 				int iters;
454 
455 				iters = strtonum(argp, 0, INT_MAX, &errstr);
456 				if (errstr != NULL)
457 					return (dobeep_msgs("Var not found:",
458 					    argp));
459 			}
460 
461 			if (strlcpy(excbuf, cmdp, sizeof(excbuf))
462 			    >= sizeof(excbuf))
463 				return (dobeep_msg("strlcpy error"));
464 			if (strlcat(excbuf, s, sizeof(excbuf))
465 			    >= sizeof(excbuf))
466 				return (dobeep_msg("strlcat error"));
467 			if (strlcat(excbuf, argp, sizeof(excbuf))
468 			    >= sizeof(excbuf))
469 				return (dobeep_msg("strlcat error"));
470 
471 			excline(excbuf, 0);
472 
473 			if (fin)
474 				break;
475 
476 			*p = ' ';		/* unterminate arg string */
477 			numspc++;
478 			spc = 1;
479 		}
480 	}
481 	return (TRUE);
482 }
483 
484 /*
485  * Is an item a value or a variable?
486  */
487 static int
488 isvar(char **argp, char **varbuf, int sizof)
489 {
490 	struct varentry *v1 = NULL;
491 
492 	if (SLIST_EMPTY(&varhead))
493 		return (FALSE);
494 #ifdef  MGLOG
495 	mglog_isvar(*varbuf, *argp, sizof);
496 #endif
497 	SLIST_FOREACH(v1, &varhead, entry) {
498 		if (strcmp(*argp, v1->name) == 0) {
499 			(void)(strlcpy(*varbuf, v1->valbuf, sizof) >= sizof);
500 			return (TRUE);
501 		}
502 	}
503 	return (FALSE);
504 }
505 
506 
507 static int
508 foundfun(char *defstr, int expctr)
509 {
510 	return (TRUE);
511 }
512 
513 static int
514 foundlst(char *defstr, int blkid, int expctr)
515 {
516 	char		*p;
517 
518 	p = strstr(defstr, " ");
519 	p = skipwhite(p);
520 	expandvals(NULL, p, defnam);
521 
522 	return (TRUE);
523 }
524 
525 /*
526  * 'define' strings follow the regex in parsdef().
527  */
528 static int
529 founddef(char *defstr, int blkid, int expctr, int hasval)
530 {
531 	struct varentry *vt, *v1 = NULL;
532 	char		*p, *vnamep, *vendp = NULL, *valp;
533 
534 	p = strstr(defstr, " ");        /* move to first ' ' char.    */
535 	vnamep = skipwhite(p);		/* find first char of var name. */
536 	vendp = vnamep;
537 
538 	/* now find the end of the define/list name */
539 	while (1) {
540 		++vendp;
541 		if (*vendp == ' ')
542 			break;
543 	}
544 	*vendp = '\0';
545 
546 	/*
547 	 * Check list name is not an existing mg function.
548 	 */
549 	if (name_function(vnamep) != NULL)
550 		return(dobeep_msgs("Variable/function name clash:", vnamep));
551 
552 	if (!SLIST_EMPTY(&varhead)) {
553 		SLIST_FOREACH_SAFE(v1, &varhead, entry, vt) {
554 			if (strcmp(vnamep, v1->name) == 0)
555 				SLIST_REMOVE(&varhead, v1, varentry, entry);
556 		}
557 	}
558 	if ((v1 = malloc(sizeof(struct varentry))) == NULL)
559 		return (ABORT);
560 	SLIST_INSERT_HEAD(&varhead, v1, entry);
561 	if ((v1->name = strndup(vnamep, BUFSIZE)) == NULL)
562 		return(dobeep_msg("strndup error"));
563 	vnamep = v1->name;
564 	v1->count = 0;
565 	v1->expctr = expctr;
566 	v1->blkid = blkid;
567 	v1->vals = NULL;
568 	v1->valbuf[0] = '\0';
569 
570 	defnam = v1->valbuf;
571 
572 	if (hasval) {
573 		valp = skipwhite(vendp + 1);
574 
575 		expandvals(NULL, valp, defnam);
576 		defnam = NULL;
577 	}
578 	*vendp = ' ';
579 	return (TRUE);
580 }
581 
582 
583 static int
584 expandvals(char *cmdp, char *valp, char *bp)
585 {
586 	char	 excbuf[BUFSIZE], argbuf[BUFSIZE];
587 	char	 contbuf[BUFSIZE], varbuf[BUFSIZE];
588 	char	*argp, *endp, *p, *v, *s = " ";
589 	char	*regs;
590 	int	 spc, cnt;
591 	int	 inlist, sizof, fin, inquote;
592 
593 	/* now find the first argument */
594 	p = skipwhite(valp);
595 
596 	if (strlcpy(argbuf, p, sizeof(argbuf)) >= sizeof(argbuf))
597 		return (dobeep_msg("strlcpy error"));
598 	argp = argbuf;
599 	spc = 1; /* initially fake a space so we find first argument */
600 	inlist = fin = inquote = cnt = spc = 0;
601 
602 	for (p = argbuf; *p != '\0'; p++) {
603 		if (*(p + 1) == '\0')
604 			fin = 1;
605 
606 		if (*p != ' ') {
607 			if (*p == '"') {
608 				if (inquote == 1)
609 					inquote = 0;
610 				else
611 					inquote = 1;
612 			}
613 			if (spc == 1)
614 				argp = p;
615 			spc = 0;
616 		}
617 		if ((*p == ' ' && inquote == 0) || fin) {
618 			if (spc == 1)
619 				continue;
620 			/* terminate arg string */
621 			if (*p == ' ') {
622 				*p = '\0';
623 			}
624 			endp = p + 1;
625 			excbuf[0] = '\0';
626 			varbuf[0] = '\0';
627 			contbuf[0] = '\0';
628 			sizof = sizeof(varbuf);
629 			v = varbuf;
630 			regs = "[\"]+.*[\"]+";
631        			if (doregex(regs, argp))
632 				;			/* found quotes */
633 			else if (isvar(&argp, &v, sizof)) {
634 
635 				(void)(strlcat(varbuf, " ",
636                                     sizof) >= sizof);
637 
638 				*p = ' ';
639 				(void)(strlcpy(contbuf, endp,
640 				    sizeof(contbuf)) >= sizeof(contbuf));
641 
642 				(void)(strlcat(varbuf, contbuf,
643 				    sizof) >= sizof);
644 
645 				argbuf[0] = ' ';
646 				argbuf[1] = '\0';
647 				(void)(strlcat(argbuf, varbuf,
648 				    sizof) >= sizof);
649 
650 				p = argp = argbuf;
651 				spc = 1;
652 				fin = 0;
653 				continue;
654 			} else {
655 				const char *errstr;
656 				int iters;
657 
658 				iters = strtonum(argp, 0, INT_MAX, &errstr);
659 				if (errstr != NULL)
660 					return (dobeep_msgs("Var not found:",
661 					    argp));
662 			}
663 #ifdef  MGLOG
664         mglog_misc("x|%s|%p|%d|\n", bp, defnam, BUFSIZE);
665 #endif
666 			if (*bp != '\0') {
667 				if (strlcat(bp, s, BUFSIZE) >= BUFSIZE)
668 					return (dobeep_msg("strlcat error"));
669 			}
670 			if (strlcat(bp, argp, BUFSIZE) >= BUFSIZE) {
671 				return (dobeep_msg("strlcat error"));
672 			}
673 /*			v1->count++;*/
674 
675 			if (fin)
676 				break;
677 
678 			*p = ' ';		/* unterminate arg string */
679 			spc = 1;
680 		}
681 	}
682 	return (TRUE);
683 }
684 
685 /*
686  * Finished with buffer evaluation, so clean up any vars.
687  * Perhaps keeps them in mg even after use,...
688  */
689 static int
690 clearvars(void)
691 {
692 	struct varentry	*v1 = NULL;
693 
694 	while (!SLIST_EMPTY(&varhead)) {
695 		v1 = SLIST_FIRST(&varhead);
696 		SLIST_REMOVE_HEAD(&varhead, entry);
697 /*		free(v1->vals);*/
698 		free(v1->name);
699 		free(v1);
700 	}
701 	return (FALSE);
702 }
703 
704 /*
705  * Finished with block evaluation, so clean up any expressions.
706  */
707 static void
708 clearexp(void)
709 {
710 	struct expentry	*e1 = NULL;
711 
712 	while (!TAILQ_EMPTY(&ehead)) {
713 		e1 = TAILQ_FIRST(&ehead);
714 		TAILQ_REMOVE(&ehead, e1, eentry);
715 		free(e1->fun);
716 		free(e1);
717 	}
718 	return;
719 }
720 
721 /*
722  * Cleanup before leaving.
723  */
724 void
725 cleanup(void)
726 {
727 	defnam = NULL;
728 
729 	clearexp();
730 	clearvars();
731 }
732 
733 /*
734  * Test a string against a regular expression.
735  */
736 static int
737 doregex(char *r, char *e)
738 {
739 	regex_t  regex_buff;
740 
741 	if (regcomp(&regex_buff, r, REG_EXTENDED)) {
742 		regfree(&regex_buff);
743 		return(dobeep_msg("Regex compilation error"));
744 	}
745 	if (!regexec(&regex_buff, e, 0, NULL, 0)) {
746 		regfree(&regex_buff);
747 		return(TRUE);
748 	}
749 	regfree(&regex_buff);
750 	return(FALSE);
751 }
752 
753 /*
754  * Display a message so it is apparent that this is the method which stopped
755  * execution.
756  */
757 static int
758 exitinterpreter(char *ptr, char *dobuf, int dosiz)
759 {
760 	cleanup();
761 	if (batch == 0)
762 		return(dobeep_msg("Interpreter exited via exit command."));
763 	return(FALSE);
764 }
765 
766 /*
767  * All code below commented out (until end of file).
768  *
769  * Need to think about how interpreter functions are done.
770  * Probably don't have a choice with string-append().
771 
772 static int 	 getenvironmentvariable(char *, char *, int);
773 static int	 stringappend(char *, char *, int);
774 
775 typedef int	 (*PFI)(char *, char *, int);
776 
777 
778 struct ifunmap {
779 	PFI		 fn_funct;
780 	const char 	*fn_name;
781 	struct ifunmap	*fn_next;
782 };
783 static struct ifunmap *ifuns;
784 
785 static struct ifunmap ifunctnames[] = {
786 	{exitinterpreter, "exit"},
787 	{getenvironmentvariable, "get-environment-variable"},
788 	{stringappend, "string-append"},
789 	{NULL, NULL}
790 };
791 
792 void
793 ifunmap_init(void)
794 {
795 	struct ifunmap *fn;
796 
797 	for (fn = ifunctnames; fn->fn_name != NULL; fn++) {
798 		fn->fn_next = ifuns;
799 		ifuns = fn;
800 	}
801 }
802 
803 PFI
804 name_ifun(const char *ifname)
805 {
806 	struct ifunmap 	*fn;
807 
808 	for (fn = ifuns; fn != NULL; fn = fn->fn_next) {
809 		if (strcmp(fn->fn_name, ifname) == 0)
810 			return (fn->fn_funct);
811 	}
812 
813 	return (NULL);
814 }
815 
816 
817 int
818 dofunc(char **ifname, char **tmpbuf, int sizof)
819 {
820 	PFI 	 fnc;
821 	char	*p, *tmp;
822 
823 	p = strstr(*ifname, " ");
824 	*p = '\0';
825 
826 	fnc = name_ifun(*ifname);
827 	if (fnc == NULL)
828 		return (FALSE);
829 
830 	*p = ' ';
831 
832 	tmp = *tmpbuf;
833 
834 	fnc(p, tmp, sizof);
835 
836 	return (TRUE);
837 }
838 
839 static int
840 getenvironmentvariable(char *ptr, char *dobuf, int dosiz)
841 {
842 	char		*t;
843 	char		*tmp;
844 	const char	*q = "\"";
845 
846 	t = skipwhite(ptr);
847 
848 	if (t[0] == *q || t[strlen(t) - 1] == *q)
849 		return (dobeep_msgs("Please remove '\"' around:", t));
850 	if ((tmp = getenv(t)) == NULL || *tmp == '\0')
851 		return(dobeep_msgs("Envar not found:", t));
852 
853 	dobuf[0] = '\0';
854 	if (strlcat(dobuf, q, dosiz) >= dosiz)
855 		return (dobeep_msg("strlcat error"));
856 	if (strlcat(dobuf, tmp, dosiz) >= dosiz)
857 		return (dobeep_msg("strlcat error"));
858 	if (strlcat(dobuf, q, dosiz) >= dosiz)
859 		return (dobeep_msg("strlcat error"));
860 
861 	return (TRUE);
862 }
863 
864 static int
865 stringappend(char *ptr, char *dobuf, int dosiz)
866 {
867 	char		 varbuf[BUFSIZE], funbuf[BUFSIZE];
868 	char            *p, *f, *v, *vendp;
869 	int		 sizof, fin = 0;
870 
871 	varbuf[0] = funbuf[0] = '\0';
872 	f = funbuf;
873 	v = varbuf;
874 	sizof = sizeof(varbuf);
875 	*dobuf = '\0';
876 
877 	p = skipwhite(ptr);
878 
879 	while (*p != '\0') {
880 		vendp = p;
881 		while (1) {
882 			if (*vendp == ' ') {
883 				break;
884 			} else if (*vendp == '\0') {
885 				fin = 1;
886 				break;
887 			}
888 			++vendp;
889 		}
890         	*vendp = '\0';
891 
892 		if (isvar(&p, &v, sizof)) {
893 			if (v[0] == '"' && v[strlen(v) - 1] == '"' ) {
894 				v[strlen(v) - 1] = '\0';
895 				v = v + 1;
896 			}
897 			if (strlcat(f, v, sizof) >= sizof)
898 				return (dobeep_msg("strlcat error"));
899 		} else {
900 			if (p[0] == '"' && p[strlen(p) - 1] == '"' ) {
901 				p[strlen(p) - 1] = '\0';
902 				p = p + 1;
903 			}
904 			if (strlcat(f, p, sizof) >= sizof)
905 				return (dobeep_msg("strlcat error"));
906 		}
907 		if (fin)
908 			break;
909 		vendp++;
910 		if (*vendp == '\0')
911 			break;
912 		p = skipwhite(vendp);
913 	}
914 
915 	(void)snprintf(dobuf, dosiz, "\"%s\"", f);
916 
917 	return (TRUE);
918 }
919 
920 Index: main.c
921 ===================================================================
922 RCS file: /cvs/src/usr.bin/mg/main.c,v
923 retrieving revision 1.89
924 diff -u -p -u -p -r1.89 main.c
925 --- main.c      20 Mar 2021 09:00:49 -0000      1.89
926 +++ main.c      12 Apr 2021 17:58:52 -0000
927 @@ -133,10 +133,12 @@ main(int argc, char **argv)
928                 extern void grep_init(void);
929                 extern void cmode_init(void);
930                 extern void dired_init(void);
931 +               extern void ifunmap_init(void);
932 
933                 dired_init();
934                 grep_init();
935                 cmode_init();
936 +               ifunmap_init();
937         }
938 
939 
940 */
941