/*	$OpenBSD: extend.c,v 1.75 2021/05/06 14:16:12 lum Exp $	*/
/* This file is in the public domain. */

/*
 *	Extended (M-x) commands, rebinding, and	startup file processing.
 */

#include <sys/queue.h>
#include <sys/types.h>
#include <regex.h>
#include <ctype.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "chrdef.h"
#include "def.h"
#include "funmap.h"
#include "kbd.h"
#include "key.h"
#include "macro.h"

static int	 remap(KEYMAP *, int, PF, KEYMAP *);
static KEYMAP	*reallocmap(KEYMAP *);
static void	 fixmap(KEYMAP *, KEYMAP *, KEYMAP *);
static int	 dobind(KEYMAP *, const char *, int);
static char	*parsetoken(char *);
static int	 bindkey(KEYMAP **, const char *, KCHAR *, int);

/*
 * Insert a string, mainly for use from macros (created by selfinsert).
 */
/* ARGSUSED */
int
insert(int f, int n)
{
	char	 buf[BUFSIZE], *bufp, *cp;
	int	 count, c;

	if (inmacro) {
		while (--n >= 0) {
			for (count = 0; count < maclcur->l_used; count++) {
				if ((((c = maclcur->l_text[count]) ==
				    *curbp->b_nlchr)
				    ? lnewline() : linsert(1, c)) != TRUE)
					return (FALSE);
			}
		}
		maclcur = maclcur->l_fp;
		return (TRUE);
	}
	if (n == 1)
		/* CFINS means selfinsert can tack on the end */
		thisflag |= CFINS;

	if ((bufp = eread("Insert: ", buf, sizeof(buf), EFNEW)) == NULL)
		return (ABORT);
	else if (bufp[0] == '\0')
		return (FALSE);
	while (--n >= 0) {
		cp = buf;
		while (*cp) {
			if (((*cp == *curbp->b_nlchr) ?
			    lnewline() : linsert(1, *cp))
			    != TRUE)
				return (FALSE);
			cp++;
		}
	}
	return (TRUE);
}

/*
 * Bind a key to a function.  Cases range from the trivial (replacing an
 * existing binding) to the extremely complex (creating a new prefix in a
 * map_element that already has one, so the map_element must be split,
 * but the keymap doesn't have enough room for another map_element, so
 * the keymap is reallocated).	No attempt is made to reclaim space no
 * longer used, if this is a problem flags must be added to indicate
 * malloced versus static storage in both keymaps and map_elements.
 * Structure assignments would come in real handy, but K&R based compilers
 * don't have them.  Care is taken so running out of memory will leave
 * the keymap in a usable state.
 * Parameters are:
 * curmap:  	pointer to the map being changed
 * c:		character being changed
 * funct: 	function being changed to
 * pref_map: 	if funct==NULL, map to bind to or NULL for new
 */
static int
remap(KEYMAP *curmap, int c, PF funct, KEYMAP *pref_map)
{
	int		 i, n1, n2, nold;
	KEYMAP		*mp, *newmap;
	PF		*pfp;
	struct map_element	*mep;

	if (ele >= &curmap->map_element[curmap->map_num] || c < ele->k_base) {
		if (ele > &curmap->map_element[0] && (funct != NULL ||
		    (ele - 1)->k_prefmap == NULL))
			n1 = c - (ele - 1)->k_num;
		else
			n1 = HUGE;
		if (ele < &curmap->map_element[curmap->map_num] &&
		    (funct != NULL || ele->k_prefmap == NULL))
			n2 = ele->k_base - c;
		else
			n2 = HUGE;
		if (n1 <= MAPELEDEF && n1 <= n2) {
			ele--;
			if ((pfp = calloc(c - ele->k_base + 1,
			    sizeof(PF))) == NULL)
				return (dobeep_msg("Out of memory"));

			nold = ele->k_num - ele->k_base + 1;
			for (i = 0; i < nold; i++)
				pfp[i] = ele->k_funcp[i];
			while (--n1)
				pfp[i++] = curmap->map_default;
			pfp[i] = funct;
			ele->k_num = c;
			ele->k_funcp = pfp;
		} else if (n2 <= MAPELEDEF) {
			if ((pfp = calloc(ele->k_num - c + 1,
			    sizeof(PF))) == NULL)
				return (dobeep_msg("Out of memory"));

			nold = ele->k_num - ele->k_base + 1;
			for (i = 0; i < nold; i++)
				pfp[i + n2] = ele->k_funcp[i];
			while (--n2)
				pfp[n2] = curmap->map_default;
			pfp[0] = funct;
			ele->k_base = c;
			ele->k_funcp = pfp;
		} else {
			if (curmap->map_num >= curmap->map_max) {
				if ((newmap = reallocmap(curmap)) == NULL)
					return (FALSE);
				curmap = newmap;
			}
			if ((pfp = malloc(sizeof(PF))) == NULL)
				return (dobeep_msg("Out of memory"));

			pfp[0] = funct;
			for (mep = &curmap->map_element[curmap->map_num];
			    mep > ele; mep--) {
				mep->k_base = (mep - 1)->k_base;
				mep->k_num = (mep - 1)->k_num;
				mep->k_funcp = (mep - 1)->k_funcp;
				mep->k_prefmap = (mep - 1)->k_prefmap;
			}
			ele->k_base = c;
			ele->k_num = c;
			ele->k_funcp = pfp;
			ele->k_prefmap = NULL;
			curmap->map_num++;
		}
		if (funct == NULL) {
			if (pref_map != NULL)
				ele->k_prefmap = pref_map;
			else {
				if ((mp = malloc(sizeof(KEYMAP) +
				    (MAPINIT - 1) * sizeof(struct map_element))) == NULL) {
					(void)dobeep_msg("Out of memory");
					ele->k_funcp[c - ele->k_base] =
					    curmap->map_default;
					return (FALSE);
				}
				mp->map_num = 0;
				mp->map_max = MAPINIT;
				mp->map_default = rescan;
				ele->k_prefmap = mp;
			}
		}
	} else {
		n1 = c - ele->k_base;
		if (ele->k_funcp[n1] == funct && (funct != NULL ||
		    pref_map == NULL || pref_map == ele->k_prefmap))
			/* no change */
			return (TRUE);
		if (funct != NULL || ele->k_prefmap == NULL) {
			if (ele->k_funcp[n1] == NULL)
				ele->k_prefmap = NULL;
			/* easy case */
			ele->k_funcp[n1] = funct;
			if (funct == NULL) {
				if (pref_map != NULL)
					ele->k_prefmap = pref_map;
				else {
					if ((mp = malloc(sizeof(KEYMAP) +
					    (MAPINIT - 1) *
					    sizeof(struct map_element))) == NULL) {
						(void)dobeep_msg("Out of memory");
						ele->k_funcp[c - ele->k_base] =
						    curmap->map_default;
						return (FALSE);
					}
					mp->map_num = 0;
					mp->map_max = MAPINIT;
					mp->map_default = rescan;
					ele->k_prefmap = mp;
				}
			}
		} else {
			/*
			 * This case is the splits.
			 * Determine which side of the break c goes on
			 * 0 = after break; 1 = before break
			 */
			n2 = 1;
			for (i = 0; n2 && i < n1; i++)
				n2 &= ele->k_funcp[i] != NULL;
			if (curmap->map_num >= curmap->map_max) {
				if ((newmap = reallocmap(curmap)) == NULL)
					return (FALSE);
				curmap = newmap;
			}
			if ((pfp = calloc(ele->k_num - c + !n2,
			    sizeof(PF))) == NULL)
				return (dobeep_msg("Out of memory"));

			ele->k_funcp[n1] = NULL;
			for (i = n1 + n2; i <= ele->k_num - ele->k_base; i++)
				pfp[i - n1 - n2] = ele->k_funcp[i];
			for (mep = &curmap->map_element[curmap->map_num];
			    mep > ele; mep--) {
				mep->k_base = (mep - 1)->k_base;
				mep->k_num = (mep - 1)->k_num;
				mep->k_funcp = (mep - 1)->k_funcp;
				mep->k_prefmap = (mep - 1)->k_prefmap;
			}
			ele->k_num = c - !n2;
			(ele + 1)->k_base = c + n2;
			(ele + 1)->k_funcp = pfp;
			ele += !n2;
			ele->k_prefmap = NULL;
			curmap->map_num++;
			if (pref_map == NULL) {
				if ((mp = malloc(sizeof(KEYMAP) + (MAPINIT - 1)
				    * sizeof(struct map_element))) == NULL) {
					(void)dobeep_msg("Out of memory");
					ele->k_funcp[c - ele->k_base] =
					    curmap->map_default;
					return (FALSE);
				}
				mp->map_num = 0;
				mp->map_max = MAPINIT;
				mp->map_default = rescan;
				ele->k_prefmap = mp;
			} else
				ele->k_prefmap = pref_map;
		}
	}
	return (TRUE);
}

/*
 * Reallocate a keymap. Returns NULL (without trashing the current map)
 * on failure.
 */
static KEYMAP *
reallocmap(KEYMAP *curmap)
{
	struct maps_s	*mps;
	KEYMAP	*mp;
	int	 i;

	if (curmap->map_max > SHRT_MAX - MAPGROW) {
		(void)dobeep_msg("keymap too large");
		return (NULL);
	}
	if ((mp = malloc(sizeof(KEYMAP) + (curmap->map_max + (MAPGROW - 1)) *
	    sizeof(struct map_element))) == NULL) {
		(void)dobeep_msg("Out of memory");
		return (NULL);
	}
	mp->map_num = curmap->map_num;
	mp->map_max = curmap->map_max + MAPGROW;
	mp->map_default = curmap->map_default;
	for (i = curmap->map_num; i--;) {
		mp->map_element[i].k_base = curmap->map_element[i].k_base;
		mp->map_element[i].k_num = curmap->map_element[i].k_num;
		mp->map_element[i].k_funcp = curmap->map_element[i].k_funcp;
		mp->map_element[i].k_prefmap = curmap->map_element[i].k_prefmap;
	}
	for (mps = maps; mps != NULL; mps = mps->p_next) {
		if (mps->p_map == curmap)
			mps->p_map = mp;
		else
			fixmap(curmap, mp, mps->p_map);
	}
	ele = &mp->map_element[ele - &curmap->map_element[0]];
	return (mp);
}

/*
 * Fix references to a reallocated keymap (recursive).
 */
static void
fixmap(KEYMAP *curmap, KEYMAP *mp, KEYMAP *mt)
{
	int	 i;

	for (i = mt->map_num; i--;) {
		if (mt->map_element[i].k_prefmap != NULL) {
			if (mt->map_element[i].k_prefmap == curmap)
				mt->map_element[i].k_prefmap = mp;
			else
				fixmap(curmap, mp, mt->map_element[i].k_prefmap);
		}
	}
}

/*
 * Do the input for local-set-key, global-set-key  and define-key
 * then call remap to do the work.
 */
static int
dobind(KEYMAP *curmap, const char *p, int unbind)
{
	KEYMAP	*pref_map = NULL;
	PF	 funct;
	char	 bprompt[80], *bufp, *pep;
	int	 c, s, n;

	if (macrodef) {
		/*
		 * Keystrokes aren't collected. Not hard, but pretty useless.
		 * Would not work for function keys in any case.
		 */
		return (dobeep_msg("Can't rebind key in macro"));
	}
	if (inmacro) {
		for (s = 0; s < maclcur->l_used - 1; s++) {
			if (doscan(curmap, c = CHARMASK(maclcur->l_text[s]), &curmap)
			    != NULL) {
				if (remap(curmap, c, NULL, NULL)
				    != TRUE)
					return (FALSE);
			}
		}
		(void)doscan(curmap, c = maclcur->l_text[s], NULL);
		maclcur = maclcur->l_fp;
	} else {
		n = strlcpy(bprompt, p, sizeof(bprompt));
		if (n >= sizeof(bprompt))
			n = sizeof(bprompt) - 1;
		pep = bprompt + n;
		for (;;) {
			ewprintf("%s", bprompt);
			pep[-1] = ' ';
			pep = getkeyname(pep, sizeof(bprompt) -
			    (pep - bprompt), c = getkey(FALSE));
			if (doscan(curmap, c, &curmap) != NULL)
				break;
			*pep++ = '-';
			*pep = '\0';
		}
	}
	if (unbind)
		funct = rescan;
	else {
		if ((bufp = eread("%s to command: ", bprompt, sizeof(bprompt),
		    EFFUNC | EFNEW, bprompt)) == NULL)
			return (ABORT);
		else if (bufp[0] == '\0')
			return (FALSE);
		if (((funct = name_function(bprompt)) == NULL) ?
		    (pref_map = name_map(bprompt)) == NULL : funct == NULL)
			return (dobeep_msg("[No match]"));

	}
	return (remap(curmap, c, funct, pref_map));
}

/*
 * bindkey: bind key sequence to a function in the specified map.  Used by
 * excline so it can bind function keys.  To close to release to change
 * calling sequence, should just pass KEYMAP *curmap rather than
 * KEYMAP **mapp.
 */
static int
bindkey(KEYMAP **mapp, const char *fname, KCHAR *keys, int kcount)
{
	KEYMAP	*curmap = *mapp;
	KEYMAP	*pref_map = NULL;
	PF	 funct;
	int	 c;

	if (fname == NULL)
		funct = rescan;
	else if (((funct = name_function(fname)) == NULL) ?
	    (pref_map = name_map(fname)) == NULL : funct == NULL) {
		dobeep();
		ewprintf("[No match: %s]", fname);
		return (FALSE);
	}
	while (--kcount) {
		if (doscan(curmap, c = *keys++, &curmap) != NULL) {
			if (remap(curmap, c, NULL, NULL) != TRUE)
				return (FALSE);
			/*
			 * XXX - Bizzarreness. remap creates an empty KEYMAP
			 *       that the last key is supposed to point to.
			 */
			curmap = ele->k_prefmap;
		}
	}
	(void)doscan(curmap, c = *keys, NULL);
	return (remap(curmap, c, funct, pref_map));
}

/*
 * Wrapper for bindkey() that converts escapes.
 */
int
dobindkey(KEYMAP *map, const char *func, const char *str)
{
	int	 i;

	for (i = 0; *str && i < MAXKEY; i++) {
		/* XXX - convert numbers w/ strol()? */
		if (*str == '^' && *(str + 1) !=  '\0') {
			key.k_chars[i] = CCHR(toupper((unsigned char)*++str));
		} else if (*str == '\\' && *(str + 1) != '\0') {
			switch (*++str) {
			case '^':
				key.k_chars[i] = '^';
				break;
			case 't':
			case 'T':
				key.k_chars[i] = '\t';
				break;
			case 'n':
			case 'N':
				key.k_chars[i] = *curbp->b_nlchr;
				break;
			case 'r':
			case 'R':
				key.k_chars[i] = '\r';
				break;
			case 'e':
			case 'E':
				key.k_chars[i] = CCHR('[');
				break;
			case '\\':
				key.k_chars[i] = '\\';
				break;
			}
		} else
			key.k_chars[i] = *str;
		str++;
	}
	key.k_count = i;
	return (bindkey(&map, func, key.k_chars, key.k_count));
}

/*
 * This function modifies the fundamental keyboard map.
 */
/* ARGSUSED */
int
bindtokey(int f, int n)
{
	return (dobind(fundamental_map, "Global set key: ", FALSE));
}

/*
 * This function modifies the current mode's keyboard map.
 */
/* ARGSUSED */
int
localbind(int f, int n)
{
	return (dobind(curbp->b_modes[curbp->b_nmodes]->p_map,
	    "Local set key: ", FALSE));
}

/*
 * This function redefines a key in any keymap.
 */
/* ARGSUSED */
int
redefine_key(int f, int n)
{
	static char	 buf[48];
	char		 tmp[32], *bufp;
	KEYMAP		*mp;

	(void)strlcpy(buf, "Define key map: ", sizeof(buf));
	if ((bufp = eread("%s", tmp, sizeof(tmp), EFNEW, buf)) == NULL)
		return (ABORT);
	else if (bufp[0] == '\0')
		return (FALSE);
	(void)strlcat(buf, tmp, sizeof(buf));
	if ((mp = name_map(tmp)) == NULL)
		return (dobeep_msgs("Unknown map ", tmp));

	if (strlcat(buf, "key: ", sizeof(buf)) >= sizeof(buf))
		return (FALSE);

	return (dobind(mp, buf, FALSE));
}

/* ARGSUSED */
int
unbindtokey(int f, int n)
{
	return (dobind(fundamental_map, "Global unset key: ", TRUE));
}

/* ARGSUSED */
int
localunbind(int f, int n)
{
	return (dobind(curbp->b_modes[curbp->b_nmodes]->p_map,
	    "Local unset key: ", TRUE));
}

/*
 * Extended command. Call the message line routine to read in the command
 * name and apply autocompletion to it. When it comes back, look the name
 * up in the symbol table and run the command if it is found.  Print an
 * error if there is anything wrong.
 */
int
extend(int f, int n)
{
	PF	 funct;
	char	 xname[NXNAME], *bufp;

	if (!(f & FFARG))
		bufp = eread("M-x ", xname, NXNAME, EFNEW | EFFUNC);
	else
		bufp = eread("%d M-x ", xname, NXNAME, EFNEW | EFFUNC, n);
	if (bufp == NULL)
		return (ABORT);
	else if (bufp[0] == '\0')
		return (FALSE);
	if ((funct = name_function(bufp)) != NULL) {
		if (macrodef) {
			struct line	*lp = maclcur;
			macro[macrocount - 1].m_funct = funct;
			maclcur = lp->l_bp;
			maclcur->l_fp = lp->l_fp;
			free(lp);
		}
		return ((*funct)(f, n));
	}
	return (dobeep_msg("[No match]"));
}

/*
 * Define the commands needed to do startup-file processing.
 * This code is mostly a kludge just so we can get startup-file processing.
 *
 * If you're serious about having this code, you should rewrite it.
 * To wit:
 *	It has lots of funny things in it to make the startup-file look
 *	like a GNU startup file; mostly dealing with parens and semicolons.
 *	This should all vanish.
 *
 * We define eval-expression because it's easy.	 It can make
 * *-set-key or define-key set an arbitrary key sequence, so it isn't
 * useless.
 */

/*
 * evalexpr - get one line from the user, and run it.
 * Use strlen for length of line, assume user is not typing in a '\0' in the
 * modeline. llen only used for foundparen() so old-school will be ok.
 */
/* ARGSUSED */
int
evalexpr(int f, int n)
{
	char	 exbuf[BUFSIZE], *bufp;
	int	 llen;

	if ((bufp = eread("Eval: ", exbuf, sizeof(exbuf),
	    EFNEW | EFCR)) == NULL)
		return (ABORT);
	else if (bufp[0] == '\0')
		return (FALSE);
	llen = strlen(bufp);

	return (excline(exbuf, llen, 1));
}

/*
 * evalbuffer - evaluate the current buffer as line commands. Useful for
 * testing startup files.
 */
/* ARGSUSED */
int
evalbuffer(int f, int n)
{
	struct line		*lp;
	struct buffer		*bp = curbp;
	int		 s, llen, lnum = 0;
	static char	 excbuf[BUFSIZE];

	for (lp = bfirstlp(bp); lp != bp->b_headp; lp = lforw(lp)) {
		lnum++;
		llen = llength(lp);
		if (llen >= BUFSIZE)
			return (FALSE);
		(void)strncpy(excbuf, ltext(lp), llen);

		/* make sure the line is terminated */
		excbuf[llen] = '\0';
		if ((s = excline(excbuf, llen, lnum)) != TRUE) {
			cleanup();
			return (s);
		}
	}
	cleanup();
	return (TRUE);
}

/*
 * evalfile - go get a file and evaluate it as line commands. You can
 *	go get your own startup file if need be.
 */
/* ARGSUSED */
int
evalfile(int f, int n)
{
	char	 fname[NFILEN], *bufp;

	if ((bufp = eread("Load file: ", fname, NFILEN,
	    EFNEW | EFCR)) == NULL)
		return (ABORT);
	else if (bufp[0] == '\0')
		return (FALSE);
	return (load(fname));
}

/*
 * load - go load the file name we got passed.
 */
int
load(const char *fname)
{
	int	 s = TRUE, line, ret;
	int	 nbytes = 0;
	char	 excbuf[BUFSIZE], fncpy[NFILEN];
	FILE    *ffp;

	if ((fname = adjustname(fname, TRUE)) == NULL)
		/* just to be careful */
		return (FALSE);

	ret = ffropen(&ffp, fname, NULL);
	if (ret != FIOSUC) {
		if (ret == FIODIR)
			(void)ffclose(ffp, NULL);
		return (FALSE);
	}

	/* keep a note of fname incase of errors in loaded file. */
	(void)strlcpy(fncpy, fname, sizeof(fncpy));
	line = 0;
	while ((s = ffgetline(ffp, excbuf, sizeof(excbuf) - 1, &nbytes))
	    == FIOSUC) {
		line++;
		excbuf[nbytes] = '\0';
		if (excline(excbuf, nbytes, line) != TRUE) {
			s = FIOERR;
			dobeep();
			ewprintf("Error loading file %s at line %d", fncpy, line);
			break;
		}
	}
	(void)ffclose(ffp, NULL);
	excbuf[nbytes] = '\0';
	if (s != FIOEOF || (nbytes && excline(excbuf, nbytes, ++line) != TRUE))
		return (FALSE);
	return (TRUE);
}

/*
 * excline - run a line from a load file or eval-expression.
 */
int
excline(char *line, int llen, int lnum)
{
	PF	 fp;
	struct line	*lp, *np;
	int	 status, c, f, n;
	char	*funcp, *tmp;
	char	*argp = NULL;
	long	 nl;
	int	 bind;
	KEYMAP	*curmap = NULL;
#define BINDARG		0  /* this arg is key to bind (local/global set key) */
#define	BINDNO		1  /* not binding or non-quoted BINDARG */
#define BINDNEXT	2  /* next arg " (define-key) */
#define BINDDO		3  /* already found key to bind */
#define BINDEXT		1  /* space for trailing \0 */

	lp = NULL;

	if (macrodef || inmacro)
		return (dobeep_msg("Not now!"));

	f = 0;
	n = 1;
	funcp = skipwhite(line);
	if (*funcp == '\0')
		return (TRUE);	/* No error on blank lines */
	if (*funcp == '(')
		return (foundparen(funcp, llen, lnum));
	line = parsetoken(funcp);
	if (*line != '\0') {
		*line++ = '\0';
		line = skipwhite(line);
		if (ISDIGIT(*line) || *line == '-') {
			argp = line;
			line = parsetoken(line);
		}
	}
	if (argp != NULL) {
		f = FFARG;
		nl = strtol(argp, &tmp, 10);
		if (*tmp != '\0')
			return (FALSE);
		if (nl >= INT_MAX || nl <= INT_MIN)
			return (FALSE);
		n = (int)nl;
	}
	if ((fp = name_function(funcp)) == NULL)
		return (dobeep_msgs("Unknown function: ", funcp));

	if (fp == bindtokey || fp == unbindtokey) {
		bind = BINDARG;
		curmap = fundamental_map;
	} else if (fp == localbind || fp == localunbind) {
		bind = BINDARG;
		curmap = curbp->b_modes[curbp->b_nmodes]->p_map;
	} else if (fp == redefine_key)
		bind = BINDNEXT;
	else
		bind = BINDNO;
	/* Pack away all the args now... */
	if ((np = lalloc(0)) == FALSE)
		return (FALSE);
	np->l_fp = np->l_bp = maclcur = np;
	while (*line != '\0') {
		argp = skipwhite(line);
		if (*argp == '\0')
			break;
		line = parsetoken(argp);
		if (*argp != '"') {
			if (*argp == '\'')
				++argp;
			if ((lp = lalloc((int) (line - argp) + BINDEXT)) ==
			    NULL) {
				status = FALSE;
				goto cleanup;
			}
			bcopy(argp, ltext(lp), (int)(line - argp));
			/* don't count BINDEXT */
			lp->l_used--;
			if (bind == BINDARG)
				bind = BINDNO;
		} else {
			/* quoted strings are special */
			++argp;
			if (bind != BINDARG) {
				lp = lalloc((int)(line - argp) + BINDEXT);
				if (lp == NULL) {
					status = FALSE;
					goto cleanup;
				}
				lp->l_used = 0;
			} else
				key.k_count = 0;
			while (*argp != '"' && *argp != '\0') {
				if (*argp != '\\')
					c = *argp++;
				else {
					switch (*++argp) {
					case 't':
					case 'T':
						c = CCHR('I');
						break;
					case 'n':
					case 'N':
						c = CCHR('J');
						break;
					case 'r':
					case 'R':
						c = CCHR('M');
						break;
					case 'e':
					case 'E':
						c = CCHR('[');
						break;
					case '^':
						/*
						 * split into two statements
						 * due to bug in OSK cpp
						 */
						c = CHARMASK(*++argp);
						c = ISLOWER(c) ?
						    CCHR(TOUPPER(c)) : CCHR(c);
						break;
					case '0':
					case '1':
					case '2':
					case '3':
					case '4':
					case '5':
					case '6':
					case '7':
						c = *argp - '0';
						if (argp[1] <= '7' &&
						    argp[1] >= '0') {
							c <<= 3;
							c += *++argp - '0';
							if (argp[1] <= '7' &&
							    argp[1] >= '0') {
								c <<= 3;
								c += *++argp
								    - '0';
							}
						}
						break;
					case 'f':
					case 'F':
						c = *++argp - '0';
						if (ISDIGIT(argp[1])) {
							c *= 10;
							c += *++argp - '0';
						}
						c += KFIRST;
						break;
					default:
						c = CHARMASK(*argp);
						break;
					}
					argp++;
				}
				if (bind == BINDARG)
					key.k_chars[key.k_count++] = c;
				else
					lp->l_text[lp->l_used++] = c;
			}
			if (*line)
				line++;
		}
		switch (bind) {
		case BINDARG:
			bind = BINDDO;
			break;
		case BINDNEXT:
			lp->l_text[lp->l_used] = '\0';
			if ((curmap = name_map(lp->l_text)) == NULL) {
				(void)dobeep_msgs("No such mode: ", lp->l_text);
				status = FALSE;
				free(lp);
				goto cleanup;
			}
			free(lp);
			bind = BINDARG;
			break;
		default:
			lp->l_fp = np->l_fp;
			lp->l_bp = np;
			np->l_fp = lp;
			np = lp;
		}
	}
	switch (bind) {
	default:
		(void)dobeep_msg("Bad args to set key");
		status = FALSE;
		break;
	case BINDDO:
		if (fp != unbindtokey && fp != localunbind) {
			lp->l_text[lp->l_used] = '\0';
			status = bindkey(&curmap, lp->l_text, key.k_chars,
			    key.k_count);
		} else
			status = bindkey(&curmap, NULL, key.k_chars,
			    key.k_count);
		break;
	case BINDNO:
		inmacro = TRUE;
		maclcur = maclcur->l_fp;
		status = (*fp)(f, n);
		inmacro = FALSE;
	}
cleanup:
	lp = maclcur->l_fp;
	while (lp != maclcur) {
		np = lp->l_fp;
		free(lp);
		lp = np;
	}
	free(lp);
	maclhead = NULL;
	macrodef = FALSE;
	return (status);
}

/*
 * a pair of utility functions for the above
 */
char *
skipwhite(char *s)
{
	while (*s == ' ' || *s == '\t')
		s++;
	if ((*s == ';') || (*s == '#'))
		*s = '\0';
	return (s);
}

static char *
parsetoken(char *s)
{
	if (*s != '"') {
		while (*s && *s != ' ' && *s != '\t' && *s != ')' && *s != '(')
			s++;
		if (*s == ';')
			*s = '\0';
	} else
		do {
			/*
			 * Strings get special treatment.
			 * Beware: You can \ out the end of the string!
			 */
			if (*s == '\\')
				++s;
		} while (*++s != '"' && *s != '\0');
	return (s);
}
