/*
 * Extended commands (M-X) and startup file processing.
 */

#include "no_startup.h"
#include "no_macro.h"
#include "fkeys.h"
#include "do_eval.h"
#include "def.h"

#ifndef	NO_MACRO
#include "macro.h"
#endif

#include "line.h"
#include "buffer.h"

#ifdef	ANSI
#include <stdlib.h>
#include <string.h>
#endif

static char    *skipwhite
                PROTO((char *s, char *end));
static char    *skiptowhite
                PROTO((char *s, char *end));
static KCHAR    backquote
                PROTO((char **inp));

/*
 * 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.
 */
extend(f, n)
{
	int             (*funct) PROTO((int, int));
#ifndef	NO_MACRO
	struct macro   *m;
#endif
	int             s;
	char            xname[NXNAME];

	if (!(f & FFARG))
		s = eread("M-x ", xname, NXNAME, EFNEW | EFFUNC);
	else
		s = eread("%d M-x ", xname, NXNAME, EFNEW | EFFUNC, n);
	if (s != TRUE)
		return s;
	if ((funct = name_function(xname)) != NULL)
		return (*funct) (f, n);
#ifndef	NO_MACRO
	else if ((m = find_macro(xname)) != NULL)
		return runmacro(m, f, n, FALSE);
#endif
	ewprintf("[No match]");
	return FALSE;
}

#ifdef DO_EVAL
/*
 * Define the commands needed to do startup-file processing. This code is
 * mostly a kludge just so we can get startup-file processing.
 * 
 * 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.
 */
/* ARGSUSED */
evalexpr(f, n)
{
	int             s;
	char            exbuf[NXNAME];

	if ((s = ereply("Eval: ", exbuf, NXNAME)) != TRUE)
		return s;
	return excline(exbuf, strlen(exbuf));
}
/*
 * evalbuffer - evaluate the current buffer as line commands. Useful for
 * testing startup files.
 */
/* ARGSUSED */
evalbuffer(f, n)
{
	register struct line *lp;
	register struct buffer *bp = curbp;
	register int    s;

	for (lp = lforw(bp->b_linep); lp != bp->b_linep; lp = lforw(lp)) {
		if (llength(lp) >= NLINE)
			return FALSE;
		if ((s = excline(ltext(lp), llength(lp))) != TRUE)
			return s;
	}
	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 */
evalfile(f, n)
{
	register int    s;
	char            fname[NFILEN];

	if ((s = ereply("Load file: ", fname, NFILEN)) != TRUE)
		return s;
	return load(fname);
}

/*
 * load - go load the file name we got passed.
 */
load(fname)
	char           *fname;
{
	int             s = TRUE;
	int             nbytes;
	char            excbuf[128];

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

	if (ffropen(fname) != FIOSUC)
		return FALSE;
	while ((s = ffgetline(excbuf, sizeof(excbuf) - 1, &nbytes)) == FIOSUC) {
		if (excline(excbuf, nbytes) != TRUE) {
			s = FIOERR;
			ewprintf("Error loading file %s", fname);
			break;
		}
	}
	(VOID) ffclose();
	if (s != FIOEOF || (nbytes && excline(excbuf, nbytes) != TRUE))
		return FALSE;
	return TRUE;
}

/*
 * excline - run a line from a load file or eval-expression.
 */
excline(line, len)
	char	*line;
	int	len;
{
	/* end points to where the terminating null should be */
	char           *funcp, *argp = NULL, *end, *inline;
	struct macro   *eval;
	int             status;

	/* Make a copy we can scrawl on */
	if (len == 0) return TRUE ;
	if ((inline = malloc(len + 1)) == NULL) return FALSE ;
	(VOID) bcopy(line, inline, len + 1);
	line = inline ;

	end = line + len;	/* End of string marker */
	funcp = skipwhite(line, end);
	if (funcp >= end) {
		free(inline) ;
		return TRUE;	/* No error on blank lines */
		}
	line = skiptowhite(funcp, end);
	*line++ = '\0';
	if (line < end) {
		argp = skipwhite(line, end);
		if (*argp != '-' && (*argp < '0' || '9' < *argp))
			argp = NULL;
		else {
			line = skiptowhite(argp, end);
			*line++ = '\0';
		}
	}
	if ((eval = (struct macro *) malloc(sizeof(struct macro))) == NULL) {
		free(inline) ;
		return FALSE;
		}
	eval->m_count = eval->m_max = 0;
	eval->m_text = eval->m_cur = NULL;
	if (argp != NULL) {
		add_key_to_macro(CCHR('U'), eval);
		add_string_to_macro(argp, eval);
	}
	add_key_to_macro(METACH, eval);
	add_key_to_macro('X', eval);

	/* Pack in function */
	add_string_to_macro(funcp, eval);
	add_key_to_macro(SOFTCR, eval);

	/* Pack away all the args now...	 */
	while (line < end) {
		argp = skipwhite(line, end);
		if (argp >= end)
			break;

		if (*argp != '"') {
			line = skiptowhite(argp, end);
			*line++ = '\0';
			add_string_to_macro(argp, eval);
		} else {		/* Quoted strings special again */
			++argp;
			while (*argp != '"' && argp < end) {
				if (*argp != '\\')
					add_key_to_macro((KCHAR) *argp++, eval);
				else
					add_key_to_macro(backquote(&argp), eval);
			}
			line = argp + 1;
		}
		add_key_to_macro(SOFTCR, eval);
	}
	status = runmacro(eval, FFOTHARG, 1, TRUE);
	free((char *) eval->m_text);
	free((char *) eval);
	free(inline) ;
	return status;
}
/*
 * a pair of utility functions for the above
 */
static char    *
skipwhite(s, end)
	register char  *s;
	register char  *end;
{
	while (*s == ' ' || *s == '\t' && s < end)
		s++;
	if (*s == ';')
		return end;
	return s;
}

static char    *
skiptowhite(s, end)
	register char  *s;
	register char  *end;
{
	while (s < end && *s != ' ' && *s != '\t')
		s++;
	return s;
}

/*
 * Put a backquoted string element into the keyboard macro. Return pointer to
 * char following backquoted stuff.
 */
static          KCHAR
backquote(inp)
	char          **inp;
{
	register char  *in;
	register KCHAR  c;

	in = *inp;
	in += 1;		/* Skip over backquote */
	c = *in++;
	switch (ISUPPER(c) ? TOLOWER(c) : c) {
	case 't':
		c = CCHR('I');
		break;
	case 'n':
		c = CCHR('J');
		break;
	case 'r':
		c = CCHR('M');
		break;
	case '^':
		c = *in++;
		if (ISLOWER(c))
			c = TOUPPER(c);
		c = CCHR(c);
		break;
	case 'e':
		c = METACH;
		break;
#ifdef	FKEYS
	case 'f':
		c = 0;
		if (ISDIGIT(*in))
			c += *in++ - '0';
		if (ISDIGIT(*in))
			c = (10 * c) + *in++ - '0';
		if ((c += KFIRST) > KLAST)
			c = KLAST;
		break;
#endif
	case '0':
	case '1':
	case '2':
	case '3':
	case '4':
	case '5':
	case '6':
	case '7':
		c = *in - '0';
		if (in[1] <= '7' && in[1] >= '0') {
			c <<= 3;
			c += *++in - '0';
			if (in[1] <= '7' && in[1] >= '0') {
				c <<= 3;
				c += *++in - '0';
			}
		}
		break;
	default:
		c = CHARMASK(c);
		break;
	}
	*inp = in;		/* put the pointer back */
	return c;
}
#endif
