xref: /openbsd-src/usr.bin/mg/extend.c (revision a28daedfc357b214be5c701aa8ba8adb29a7f1c2)
1 /*	$OpenBSD: extend.c,v 1.50 2006/12/30 14:11:06 martin Exp $	*/
2 
3 /* This file is in the public domain. */
4 
5 /*
6  *	Extended (M-X) commands, rebinding, and	startup file processing.
7  */
8 #include <sys/types.h>
9 #include <ctype.h>
10 
11 #include "chrdef.h"
12 #include "def.h"
13 #include "kbd.h"
14 #include "funmap.h"
15 
16 #ifndef NO_MACRO
17 #include "macro.h"
18 #endif /* !NO_MACRO */
19 
20 #ifdef	FKEYS
21 #include "key.h"
22 #ifndef	NO_STARTUP
23 #ifndef	BINDKEY
24 #define	BINDKEY			/* bindkey is used by FKEYS startup code */
25 #endif /* !BINDKEY */
26 #endif /* !NO_STARTUP */
27 #endif /* FKEYS */
28 
29 #include <ctype.h>
30 
31 static int	 remap(KEYMAP *, int, PF, KEYMAP *);
32 static KEYMAP	*reallocmap(KEYMAP *);
33 static void	 fixmap(KEYMAP *, KEYMAP *, KEYMAP *);
34 static int	 dobind(KEYMAP *, const char *, int);
35 static char	*skipwhite(char *);
36 static char	*parsetoken(char *);
37 #ifdef	BINDKEY
38 static int	 bindkey(KEYMAP **, const char *, KCHAR *, int);
39 #endif /* BINDKEY */
40 
41 /*
42  * Insert a string, mainly for use from macros (created by selfinsert).
43  */
44 /* ARGSUSED */
45 int
46 insert(int f, int n)
47 {
48 	char	 buf[128], *bufp, *cp;
49 #ifndef NO_MACRO
50 	int	 count, c;
51 
52 	if (inmacro) {
53 		while (--n >= 0) {
54 			for (count = 0; count < maclcur->l_used; count++) {
55 				if ((((c = maclcur->l_text[count]) == '\n')
56 				    ? lnewline() : linsert(1, c)) != TRUE)
57 					return (FALSE);
58 			}
59 		}
60 		maclcur = maclcur->l_fp;
61 		return (TRUE);
62 	}
63 	if (n == 1)
64 		/* CFINS means selfinsert can tack on the end */
65 		thisflag |= CFINS;
66 #endif /* !NO_MACRO */
67 	if ((bufp = eread("Insert: ", buf, sizeof(buf), EFNEW)) == NULL)
68 		return (ABORT);
69 	else if (bufp[0] == '\0')
70 		return (FALSE);
71 	while (--n >= 0) {
72 		cp = buf;
73 		while (*cp) {
74 			if (((*cp == '\n') ? lnewline() : linsert(1, *cp))
75 			    != TRUE)
76 				return (FALSE);
77 			cp++;
78 		}
79 	}
80 	return (TRUE);
81 }
82 
83 /*
84  * Bind a key to a function.  Cases range from the trivial (replacing an
85  * existing binding) to the extremely complex (creating a new prefix in a
86  * map_element that already has one, so the map_element must be split,
87  * but the keymap doesn't have enough room for another map_element, so
88  * the keymap is reallocated).	No attempt is made to reclaim space no
89  * longer used, if this is a problem flags must be added to indicate
90  * malloced versus static storage in both keymaps and map_elements.
91  * Structure assignments would come in real handy, but K&R based compilers
92  * don't have them.  Care is taken so running out of memory will leave
93  * the keymap in a usable state.
94  * Parameters are:
95  * curmap:  	pointer to the map being changed
96  * c:		character being changed
97  * funct: 	function being changed to
98  * pref_map: 	if funct==NULL, map to bind to or NULL for new
99  */
100 static int
101 remap(KEYMAP *curmap, int c, PF funct, KEYMAP *pref_map)
102 {
103 	int		 i, n1, n2, nold;
104 	KEYMAP		*mp, *newmap;
105 	PF		*pfp;
106 	struct map_element	*mep;
107 
108 	if (ele >= &curmap->map_element[curmap->map_num] || c < ele->k_base) {
109 		if (ele > &curmap->map_element[0] && (funct != NULL ||
110 		    (ele - 1)->k_prefmap == NULL))
111 			n1 = c - (ele - 1)->k_num;
112 		else
113 			n1 = HUGE;
114 		if (ele < &curmap->map_element[curmap->map_num] &&
115 		    (funct != NULL || ele->k_prefmap == NULL))
116 			n2 = ele->k_base - c;
117 		else
118 			n2 = HUGE;
119 		if (n1 <= MAPELEDEF && n1 <= n2) {
120 			ele--;
121 			if ((pfp = calloc(c - ele->k_base + 1,
122 			    sizeof(PF))) == NULL) {
123 				ewprintf("Out of memory");
124 				return (FALSE);
125 			}
126 			nold = ele->k_num - ele->k_base + 1;
127 			for (i = 0; i < nold; i++)
128 				pfp[i] = ele->k_funcp[i];
129 			while (--n1)
130 				pfp[i++] = curmap->map_default;
131 			pfp[i] = funct;
132 			ele->k_num = c;
133 			ele->k_funcp = pfp;
134 		} else if (n2 <= MAPELEDEF) {
135 			if ((pfp = calloc(ele->k_num - c + 1,
136 			    sizeof(PF))) == NULL) {
137 				ewprintf("Out of memory");
138 				return (FALSE);
139 			}
140 			nold = ele->k_num - ele->k_base + 1;
141 			for (i = 0; i < nold; i++)
142 				pfp[i + n2] = ele->k_funcp[i];
143 			while (--n2)
144 				pfp[n2] = curmap->map_default;
145 			pfp[0] = funct;
146 			ele->k_base = c;
147 			ele->k_funcp = pfp;
148 		} else {
149 			if (curmap->map_num >= curmap->map_max) {
150 				if ((newmap = reallocmap(curmap)) == NULL)
151 					return (FALSE);
152 				curmap = newmap;
153 			}
154 			if ((pfp = malloc(sizeof(PF))) == NULL) {
155 				ewprintf("Out of memory");
156 				return (FALSE);
157 			}
158 			pfp[0] = funct;
159 			for (mep = &curmap->map_element[curmap->map_num];
160 			    mep > ele; mep--) {
161 				mep->k_base = (mep - 1)->k_base;
162 				mep->k_num = (mep - 1)->k_num;
163 				mep->k_funcp = (mep - 1)->k_funcp;
164 				mep->k_prefmap = (mep - 1)->k_prefmap;
165 			}
166 			ele->k_base = c;
167 			ele->k_num = c;
168 			ele->k_funcp = pfp;
169 			ele->k_prefmap = NULL;
170 			curmap->map_num++;
171 		}
172 		if (funct == NULL) {
173 			if (pref_map != NULL)
174 				ele->k_prefmap = pref_map;
175 			else {
176 				if ((mp = malloc(sizeof(KEYMAP) +
177 				    (MAPINIT - 1) * sizeof(struct map_element))) == NULL) {
178 					ewprintf("Out of memory");
179 					ele->k_funcp[c - ele->k_base] =
180 					    curmap->map_default;
181 					return (FALSE);
182 				}
183 				mp->map_num = 0;
184 				mp->map_max = MAPINIT;
185 				mp->map_default = rescan;
186 				ele->k_prefmap = mp;
187 			}
188 		}
189 	} else {
190 		n1 = c - ele->k_base;
191 		if (ele->k_funcp[n1] == funct && (funct != NULL ||
192 		    pref_map == NULL || pref_map == ele->k_prefmap))
193 			/* no change */
194 			return (TRUE);
195 		if (funct != NULL || ele->k_prefmap == NULL) {
196 			if (ele->k_funcp[n1] == NULL)
197 				ele->k_prefmap = NULL;
198 			/* easy case */
199 			ele->k_funcp[n1] = funct;
200 			if (funct == NULL) {
201 				if (pref_map != NULL)
202 					ele->k_prefmap = pref_map;
203 				else {
204 					if ((mp = malloc(sizeof(KEYMAP) +
205 					    (MAPINIT - 1) *
206 					    sizeof(struct map_element))) == NULL) {
207 						ewprintf("Out of memory");
208 						ele->k_funcp[c - ele->k_base] =
209 						    curmap->map_default;
210 						return (FALSE);
211 					}
212 					mp->map_num = 0;
213 					mp->map_max = MAPINIT;
214 					mp->map_default = rescan;
215 					ele->k_prefmap = mp;
216 				}
217 			}
218 		} else {
219 			/*
220 			 * This case is the splits.
221 			 * Determine which side of the break c goes on
222 			 * 0 = after break; 1 = before break
223 			 */
224 			n2 = 1;
225 			for (i = 0; n2 && i < n1; i++)
226 				n2 &= ele->k_funcp[i] != NULL;
227 			if (curmap->map_num >= curmap->map_max) {
228 				if ((newmap = reallocmap(curmap)) == NULL)
229 					return (FALSE);
230 				curmap = newmap;
231 			}
232 			if ((pfp = calloc(ele->k_num - c + !n2,
233 			    sizeof(PF))) == NULL) {
234 				ewprintf("Out of memory");
235 				return (FALSE);
236 			}
237 			ele->k_funcp[n1] = NULL;
238 			for (i = n1 + n2; i <= ele->k_num - ele->k_base; i++)
239 				pfp[i - n1 - n2] = ele->k_funcp[i];
240 			for (mep = &curmap->map_element[curmap->map_num];
241 			    mep > ele; mep--) {
242 				mep->k_base = (mep - 1)->k_base;
243 				mep->k_num = (mep - 1)->k_num;
244 				mep->k_funcp = (mep - 1)->k_funcp;
245 				mep->k_prefmap = (mep - 1)->k_prefmap;
246 			}
247 			ele->k_num = c - !n2;
248 			(ele + 1)->k_base = c + n2;
249 			(ele + 1)->k_funcp = pfp;
250 			ele += !n2;
251 			ele->k_prefmap = NULL;
252 			curmap->map_num++;
253 			if (pref_map == NULL) {
254 				if ((mp = malloc(sizeof(KEYMAP) + (MAPINIT - 1)
255 				    * sizeof(struct map_element))) == NULL) {
256 					ewprintf("Out of memory");
257 					ele->k_funcp[c - ele->k_base] =
258 					    curmap->map_default;
259 					return (FALSE);
260 				}
261 				mp->map_num = 0;
262 				mp->map_max = MAPINIT;
263 				mp->map_default = rescan;
264 				ele->k_prefmap = mp;
265 			} else
266 				ele->k_prefmap = pref_map;
267 		}
268 	}
269 	return (TRUE);
270 }
271 
272 /*
273  * Reallocate a keymap. Returns NULL (without trashing the current map)
274  * on failure.
275  */
276 static KEYMAP *
277 reallocmap(KEYMAP *curmap)
278 {
279 	struct maps_s	*mps;
280 	KEYMAP	*mp;
281 	int	 i;
282 
283 	if (curmap->map_max > SHRT_MAX - MAPGROW) {
284 		ewprintf("keymap too large");
285 		return (NULL);
286 	}
287 	if ((mp = malloc(sizeof(KEYMAP) + (curmap->map_max + (MAPGROW - 1)) *
288 	    sizeof(struct map_element))) == NULL) {
289 		ewprintf("Out of memory");
290 		return (NULL);
291 	}
292 	mp->map_num = curmap->map_num;
293 	mp->map_max = curmap->map_max + MAPGROW;
294 	mp->map_default = curmap->map_default;
295 	for (i = curmap->map_num; i--;) {
296 		mp->map_element[i].k_base = curmap->map_element[i].k_base;
297 		mp->map_element[i].k_num = curmap->map_element[i].k_num;
298 		mp->map_element[i].k_funcp = curmap->map_element[i].k_funcp;
299 		mp->map_element[i].k_prefmap = curmap->map_element[i].k_prefmap;
300 	}
301 	for (mps = maps; mps != NULL; mps = mps->p_next) {
302 		if (mps->p_map == curmap)
303 			mps->p_map = mp;
304 		else
305 			fixmap(curmap, mp, mps->p_map);
306 	}
307 	ele = &mp->map_element[ele - &curmap->map_element[0]];
308 	return (mp);
309 }
310 
311 /*
312  * Fix references to a reallocated keymap (recursive).
313  */
314 static void
315 fixmap(KEYMAP *curmap, KEYMAP *mp, KEYMAP *mt)
316 {
317 	int	 i;
318 
319 	for (i = mt->map_num; i--;) {
320 		if (mt->map_element[i].k_prefmap != NULL) {
321 			if (mt->map_element[i].k_prefmap == curmap)
322 				mt->map_element[i].k_prefmap = mp;
323 			else
324 				fixmap(curmap, mp, mt->map_element[i].k_prefmap);
325 		}
326 	}
327 }
328 
329 /*
330  * Do the input for local-set-key, global-set-key  and define-key
331  * then call remap to do the work.
332  */
333 static int
334 dobind(KEYMAP *curmap, const char *p, int unbind)
335 {
336 	KEYMAP	*pref_map = NULL;
337 	PF	 funct;
338 	char	 bprompt[80], *bufp, *pep;
339 	int	 c, s, n;
340 
341 #ifndef NO_MACRO
342 	if (macrodef) {
343 		/*
344 		 * Keystrokes aren't collected. Not hard, but pretty useless.
345 		 * Would not work for function keys in any case.
346 		 */
347 		ewprintf("Can't rebind key in macro");
348 		return (FALSE);
349 	}
350 #ifndef NO_STARTUP
351 	if (inmacro) {
352 		for (s = 0; s < maclcur->l_used - 1; s++) {
353 			if (doscan(curmap, c = CHARMASK(maclcur->l_text[s]), &curmap)
354 			    != NULL) {
355 				if (remap(curmap, c, NULL, NULL)
356 				    != TRUE)
357 					return (FALSE);
358 			}
359 		}
360 		(void)doscan(curmap, c = maclcur->l_text[s], NULL);
361 		maclcur = maclcur->l_fp;
362 	} else {
363 #endif /* !NO_STARTUP */
364 #endif /* !NO_MACRO */
365 		n = strlcpy(bprompt, p, sizeof(bprompt));
366 		if (n >= sizeof(bprompt))
367 			n = sizeof(bprompt) - 1;
368 		pep = bprompt + n;
369 		for (;;) {
370 			ewprintf("%s", bprompt);
371 			pep[-1] = ' ';
372 			pep = getkeyname(pep, sizeof(bprompt) -
373 			    (pep - bprompt), c = getkey(FALSE));
374 			if (doscan(curmap, c, &curmap) != NULL)
375 				break;
376 			*pep++ = '-';
377 			*pep = '\0';
378 		}
379 #ifndef NO_STARTUP
380 	}
381 #endif /* !NO_STARTUP */
382 	if (unbind)
383 		funct = rescan;
384 	else {
385 		if ((bufp = eread("%s to command: ", bprompt, sizeof(bprompt),
386 		    EFFUNC | EFNEW, bprompt)) == NULL)
387 			return (ABORT);
388 		else if (bufp[0] == '\0')
389 			return (FALSE);
390 		if (((funct = name_function(bprompt)) == NULL) ?
391 		    (pref_map = name_map(bprompt)) == NULL : funct == NULL) {
392 			ewprintf("[No match]");
393 			return (FALSE);
394 		}
395 	}
396 	return (remap(curmap, c, funct, pref_map));
397 }
398 
399 /*
400  * bindkey: bind key sequence to a function in the specified map.  Used by
401  * excline so it can bind function keys.  To close to release to change
402  * calling sequence, should just pass KEYMAP *curmap rather than
403  * KEYMAP **mapp.
404  */
405 #ifdef	BINDKEY
406 static int
407 bindkey(KEYMAP **mapp, const char *fname, KCHAR *keys, int kcount)
408 {
409 	KEYMAP	*curmap = *mapp;
410 	KEYMAP	*pref_map = NULL;
411 	PF	 funct;
412 	int	 c;
413 
414 	if (fname == NULL)
415 		funct = rescan;
416 	else if (((funct = name_function(fname)) == NULL) ?
417 	    (pref_map = name_map(fname)) == NULL : funct == NULL) {
418 		ewprintf("[No match: %s]", fname);
419 		return (FALSE);
420 	}
421 	while (--kcount) {
422 		if (doscan(curmap, c = *keys++, &curmap) != NULL) {
423 			if (remap(curmap, c, NULL, NULL) != TRUE)
424 				return (FALSE);
425 			/*
426 			 * XXX - Bizzarreness. remap creates an empty KEYMAP
427 			 *       that the last key is supposed to point to.
428 			 */
429 			curmap = ele->k_prefmap;
430 		}
431 	}
432 	(void)doscan(curmap, c = *keys, NULL);
433 	return (remap(curmap, c, funct, pref_map));
434 }
435 
436 #ifdef FKEYS
437 /*
438  * Wrapper for bindkey() that converts escapes.
439  */
440 int
441 dobindkey(KEYMAP *map, const char *func, const char *str)
442 {
443 	int	 i;
444 
445 	for (i = 0; *str && i < MAXKEY; i++) {
446 		/* XXX - convert numbers w/ strol()? */
447 		if (*str == '^' && *(str + 1) !=  '\0') {
448 			key.k_chars[i] = CCHR(toupper(*++str));
449 		} else if (*str == '\\' && *(str + 1) != '\0') {
450 			switch (*++str) {
451 			case '^':
452 				key.k_chars[i] = '^';
453 				break;
454 			case 't':
455 			case 'T':
456 				key.k_chars[i] = '\t';
457 				break;
458 			case 'n':
459 			case 'N':
460 				key.k_chars[i] = '\n';
461 				break;
462 			case 'r':
463 			case 'R':
464 				key.k_chars[i] = '\r';
465 				break;
466 			case 'e':
467 			case 'E':
468 				key.k_chars[i] = CCHR('[');
469 				break;
470 			case '\\':
471 				key.k_chars[i] = '\\';
472 				break;
473 			}
474 		} else
475 			key.k_chars[i] = *str;
476 		str++;
477 	}
478 	key.k_count = i;
479 	return (bindkey(&map, func, key.k_chars, key.k_count));
480 }
481 #endif /* FKEYS */
482 #endif /* BINDKEY */
483 
484 /*
485  * This function modifies the fundamental keyboard map.
486  */
487 /* ARGSUSED */
488 int
489 bindtokey(int f, int n)
490 {
491 	return (dobind(fundamental_map, "Global set key: ", FALSE));
492 }
493 
494 /*
495  * This function modifies the current mode's keyboard map.
496  */
497 /* ARGSUSED */
498 int
499 localbind(int f, int n)
500 {
501 	return (dobind(curbp->b_modes[curbp->b_nmodes]->p_map,
502 	    "Local set key: ", FALSE));
503 }
504 
505 /*
506  * This function redefines a key in any keymap.
507  */
508 /* ARGSUSED */
509 int
510 redefine_key(int f, int n)
511 {
512 	static char	 buf[48];
513 	char		 tmp[32], *bufp;
514 	KEYMAP		*mp;
515 
516 	(void)strlcpy(buf, "Define key map: ", sizeof(buf));
517 	if ((bufp = eread(buf, tmp, sizeof(tmp), EFNEW)) == NULL)
518 		return (ABORT);
519 	else if (bufp[0] == '\0')
520 		return (FALSE);
521 	(void)strlcat(buf, tmp, sizeof(buf));
522 	if ((mp = name_map(tmp)) == NULL) {
523 		ewprintf("Unknown map %s", tmp);
524 		return (FALSE);
525 	}
526 	if (strlcat(buf, "key: ", sizeof(buf)) >= sizeof(buf))
527 		return (FALSE);
528 
529 	return (dobind(mp, buf, FALSE));
530 }
531 
532 /* ARGSUSED */
533 int
534 unbindtokey(int f, int n)
535 {
536 	return (dobind(fundamental_map, "Global unset key: ", TRUE));
537 }
538 
539 /* ARGSUSED */
540 int
541 localunbind(int f, int n)
542 {
543 	return (dobind(curbp->b_modes[curbp->b_nmodes]->p_map,
544 	    "Local unset key: ", TRUE));
545 }
546 
547 /*
548  * Extended command. Call the message line routine to read in the command
549  * name and apply autocompletion to it. When it comes back, look the name
550  * up in the symbol table and run the command if it is found.  Print an
551  * error if there is anything wrong.
552  */
553 int
554 extend(int f, int n)
555 {
556 	PF	 funct;
557 	char	 xname[NXNAME], *bufp;
558 
559 	if (!(f & FFARG))
560 		bufp = eread("M-x ", xname, NXNAME, EFNEW | EFFUNC);
561 	else
562 		bufp = eread("%d M-x ", xname, NXNAME, EFNEW | EFFUNC, n);
563 	if (bufp == NULL)
564 		return (ABORT);
565 	else if (bufp[0] == '\0')
566 		return (FALSE);
567 	if ((funct = name_function(bufp)) != NULL) {
568 #ifndef NO_MACRO
569 		if (macrodef) {
570 			struct line	*lp = maclcur;
571 			macro[macrocount - 1].m_funct = funct;
572 			maclcur = lp->l_bp;
573 			maclcur->l_fp = lp->l_fp;
574 			free(lp);
575 		}
576 #endif /* !NO_MACRO */
577 		return ((*funct)(f, n));
578 	}
579 	ewprintf("[No match]");
580 	return (FALSE);
581 }
582 
583 #ifndef NO_STARTUP
584 /*
585  * Define the commands needed to do startup-file processing.
586  * This code is mostly a kludge just so we can get startup-file processing.
587  *
588  * If you're serious about having this code, you should rewrite it.
589  * To wit:
590  *	It has lots of funny things in it to make the startup-file look
591  *	like a GNU startup file; mostly dealing with parens and semicolons.
592  *	This should all vanish.
593  *
594  * We define eval-expression because it's easy.	 It can make
595  * *-set-key or define-key set an arbitrary key sequence, so it isn't
596  * useless.
597  */
598 
599 /*
600  * evalexpr - get one line from the user, and run it.
601  */
602 /* ARGSUSED */
603 int
604 evalexpr(int f, int n)
605 {
606 	char	 exbuf[128], *bufp;
607 
608 	if ((bufp = eread("Eval: ", exbuf, sizeof(exbuf),
609 	    EFNEW | EFCR)) == NULL)
610 		return (ABORT);
611 	else if (bufp[0] == '\0')
612 		return (FALSE);
613 	return (excline(exbuf));
614 }
615 
616 /*
617  * evalbuffer - evaluate the current buffer as line commands. Useful for
618  * testing startup files.
619  */
620 /* ARGSUSED */
621 int
622 evalbuffer(int f, int n)
623 {
624 	struct line		*lp;
625 	struct buffer		*bp = curbp;
626 	int		 s;
627 	static char	 excbuf[128];
628 
629 	for (lp = bfirstlp(bp); lp != bp->b_headp; lp = lforw(lp)) {
630 		if (llength(lp) >= 128)
631 			return (FALSE);
632 		(void)strncpy(excbuf, ltext(lp), llength(lp));
633 
634 		/* make sure it's terminated */
635 		excbuf[llength(lp)] = '\0';
636 		if ((s = excline(excbuf)) != TRUE)
637 			return (s);
638 	}
639 	return (TRUE);
640 }
641 
642 /*
643  * evalfile - go get a file and evaluate it as line commands. You can
644  *	go get your own startup file if need be.
645  */
646 /* ARGSUSED */
647 int
648 evalfile(int f, int n)
649 {
650 	char	 fname[NFILEN], *bufp;
651 
652 	if ((bufp = eread("Load file: ", fname, NFILEN,
653 	    EFNEW | EFCR)) == NULL)
654 		return (ABORT);
655 	else if (bufp[0] == '\0')
656 		return (FALSE);
657 	return (load(fname));
658 }
659 
660 /*
661  * load - go load the file name we got passed.
662  */
663 int
664 load(const char *fname)
665 {
666 	int	 s = TRUE, line;
667 	int	 nbytes = 0;
668 	char	 excbuf[128];
669 
670 	if ((fname = adjustname(fname, TRUE)) == NULL)
671 		/* just to be careful */
672 		return (FALSE);
673 
674 	if (ffropen(fname, NULL) != FIOSUC)
675 		return (FALSE);
676 
677 	line = 0;
678 	while ((s = ffgetline(excbuf, sizeof(excbuf) - 1, &nbytes)) == FIOSUC) {
679 		line++;
680 		excbuf[nbytes] = '\0';
681 		if (excline(excbuf) != TRUE) {
682 			s = FIOERR;
683 			ewprintf("Error loading file %s at line %d", fname, line);
684 			break;
685 		}
686 	}
687 	(void)ffclose(NULL);
688 	excbuf[nbytes] = '\0';
689 	if (s != FIOEOF || (nbytes && excline(excbuf) != TRUE))
690 		return (FALSE);
691 	return (TRUE);
692 }
693 
694 /*
695  * excline - run a line from a load file or eval-expression.  If FKEYS is
696  * defined, duplicate functionality of dobind so function key values don't
697  * have to fit in type char.
698  */
699 int
700 excline(char *line)
701 {
702 	PF	 fp;
703 	struct line	*lp, *np;
704 	int	 status, c, f, n;
705 	char	*funcp, *tmp;
706 	char	*argp = NULL;
707 	long	 nl;
708 #ifdef	FKEYS
709 	int	 bind;
710 	KEYMAP	*curmap;
711 #define BINDARG		0  /* this arg is key to bind (local/global set key) */
712 #define	BINDNO		1  /* not binding or non-quoted BINDARG */
713 #define BINDNEXT	2  /* next arg " (define-key) */
714 #define BINDDO		3  /* already found key to bind */
715 #define BINDEXT		1  /* space for trailing \0 */
716 #else /* FKEYS */
717 #define BINDEXT		0
718 #endif /* FKEYS */
719 
720 	lp = NULL;
721 
722 	if (macrodef || inmacro) {
723 		ewprintf("Not now!");
724 		return (FALSE);
725 	}
726 	f = 0;
727 	n = 1;
728 	funcp = skipwhite(line);
729 	if (*funcp == '\0')
730 		return (TRUE);	/* No error on blank lines */
731 	line = parsetoken(funcp);
732 	if (*line != '\0') {
733 		*line++ = '\0';
734 		line = skipwhite(line);
735 		if (ISDIGIT(*line) || *line == '-') {
736 			argp = line;
737 			line = parsetoken(line);
738 		}
739 	}
740 	if (argp != NULL) {
741 		f = FFARG;
742 		nl = strtol(argp, &tmp, 10);
743 		if (*tmp != '\0')
744 			return (FALSE);
745 		if (nl >= INT_MAX || nl <= INT_MIN)
746 			return (FALSE);
747 		n = (int)nl;
748 	}
749 	if ((fp = name_function(funcp)) == NULL) {
750 		ewprintf("Unknown function: %s", funcp);
751 		return (FALSE);
752 	}
753 #ifdef	FKEYS
754 	if (fp == bindtokey || fp == unbindtokey) {
755 		bind = BINDARG;
756 		curmap = fundamental_map;
757 	} else if (fp == localbind || fp == localunbind) {
758 		bind = BINDARG;
759 		curmap = curbp->b_modes[curbp->b_nmodes]->p_map;
760 	} else if (fp == redefine_key)
761 		bind = BINDNEXT;
762 	else
763 		bind = BINDNO;
764 #endif /* FKEYS */
765 	/* Pack away all the args now... */
766 	if ((np = lalloc(0)) == FALSE)
767 		return (FALSE);
768 	np->l_fp = np->l_bp = maclcur = np;
769 	while (*line != '\0') {
770 		argp = skipwhite(line);
771 		if (*argp == '\0')
772 			break;
773 		line = parsetoken(argp);
774 		if (*argp != '"') {
775 			if (*argp == '\'')
776 				++argp;
777 			if ((lp = lalloc((int) (line - argp) + BINDEXT)) ==
778 			    NULL) {
779 				status = FALSE;
780 				goto cleanup;
781 			}
782 			bcopy(argp, ltext(lp), (int)(line - argp));
783 #ifdef	FKEYS
784 			/* don't count BINDEXT */
785 			lp->l_used--;
786 			if (bind == BINDARG)
787 				bind = BINDNO;
788 #endif /* FKEYS */
789 		} else {
790 			/* quoted strings are special */
791 			++argp;
792 #ifdef	FKEYS
793 			if (bind != BINDARG) {
794 #endif /* FKEYS */
795 				lp = lalloc((int)(line - argp) + BINDEXT);
796 				if (lp == NULL) {
797 					status = FALSE;
798 					goto cleanup;
799 				}
800 				lp->l_used = 0;
801 #ifdef	FKEYS
802 			} else
803 				key.k_count = 0;
804 #endif /* FKEYS */
805 			while (*argp != '"' && *argp != '\0') {
806 				if (*argp != '\\')
807 					c = *argp++;
808 				else {
809 					switch (*++argp) {
810 					case 't':
811 					case 'T':
812 						c = CCHR('I');
813 						break;
814 					case 'n':
815 					case 'N':
816 						c = CCHR('J');
817 						break;
818 					case 'r':
819 					case 'R':
820 						c = CCHR('M');
821 						break;
822 					case 'e':
823 					case 'E':
824 						c = CCHR('[');
825 						break;
826 					case '^':
827 						/*
828 						 * split into two statements
829 						 * due to bug in OSK cpp
830 						 */
831 						c = CHARMASK(*++argp);
832 						c = ISLOWER(c) ?
833 						    CCHR(TOUPPER(c)) : CCHR(c);
834 						break;
835 					case '0':
836 					case '1':
837 					case '2':
838 					case '3':
839 					case '4':
840 					case '5':
841 					case '6':
842 					case '7':
843 						c = *argp - '0';
844 						if (argp[1] <= '7' &&
845 						    argp[1] >= '0') {
846 							c <<= 3;
847 							c += *++argp - '0';
848 							if (argp[1] <= '7' &&
849 							    argp[1] >= '0') {
850 								c <<= 3;
851 								c += *++argp
852 								    - '0';
853 							}
854 						}
855 						break;
856 #ifdef	FKEYS
857 					case 'f':
858 					case 'F':
859 						c = *++argp - '0';
860 						if (ISDIGIT(argp[1])) {
861 							c *= 10;
862 							c += *++argp - '0';
863 						}
864 						c += KFIRST;
865 						break;
866 #endif /* FKEYS */
867 					default:
868 						c = CHARMASK(*argp);
869 						break;
870 					}
871 					argp++;
872 				}
873 #ifdef	FKEYS
874 				if (bind == BINDARG)
875 					key.k_chars[key.k_count++] = c;
876 				else
877 #endif /* FKEYS */
878 					lp->l_text[lp->l_used++] = c;
879 			}
880 			if (*line)
881 				line++;
882 		}
883 #ifdef	FKEYS
884 		switch (bind) {
885 		case BINDARG:
886 			bind = BINDDO;
887 			break;
888 		case BINDNEXT:
889 			lp->l_text[lp->l_used] = '\0';
890 			if ((curmap = name_map(lp->l_text)) == NULL) {
891 				ewprintf("No such mode: %s", lp->l_text);
892 				status = FALSE;
893 				free(lp);
894 				goto cleanup;
895 			}
896 			free(lp);
897 			bind = BINDARG;
898 			break;
899 		default:
900 #endif /* FKEYS */
901 			lp->l_fp = np->l_fp;
902 			lp->l_bp = np;
903 			np->l_fp = lp;
904 			np = lp;
905 #ifdef	FKEYS
906 		}
907 #endif /* FKEYS */
908 	}
909 #ifdef	FKEYS
910 	switch (bind) {
911 	default:
912 		ewprintf("Bad args to set key");
913 		status = FALSE;
914 		break;
915 	case BINDDO:
916 		if (fp != unbindtokey && fp != localunbind) {
917 			lp->l_text[lp->l_used] = '\0';
918 			status = bindkey(&curmap, lp->l_text, key.k_chars,
919 			    key.k_count);
920 		} else
921 			status = bindkey(&curmap, NULL, key.k_chars,
922 			    key.k_count);
923 		break;
924 	case BINDNO:
925 #endif /* FKEYS */
926 		inmacro = TRUE;
927 		maclcur = maclcur->l_fp;
928 		status = (*fp)(f, n);
929 		inmacro = FALSE;
930 #ifdef	FKEYS
931 	}
932 #endif /* FKEYS */
933 cleanup:
934 	lp = maclcur->l_fp;
935 	while (lp != maclcur) {
936 		np = lp->l_fp;
937 		free(lp);
938 		lp = np;
939 	}
940 	free(lp);
941 	return (status);
942 }
943 
944 /*
945  * a pair of utility functions for the above
946  */
947 static char *
948 skipwhite(char *s)
949 {
950 	while (*s == ' ' || *s == '\t' || *s == ')' || *s == '(')
951 		s++;
952 	if (*s == ';')
953 		*s = '\0';
954 	return (s);
955 }
956 
957 static char *
958 parsetoken(char *s)
959 {
960 	if (*s != '"') {
961 		while (*s && *s != ' ' && *s != '\t' && *s != ')' && *s != '(')
962 			s++;
963 		if (*s == ';')
964 			*s = '\0';
965 	} else
966 		do {
967 			/*
968 			 * Strings get special treatment.
969 			 * Beware: You can \ out the end of the string!
970 			 */
971 			if (*s == '\\')
972 				++s;
973 		} while (*++s != '"' && *s != '\0');
974 	return (s);
975 }
976 #endif /* !NO_STARTUP */
977