/* Command table manipulation functions and vectors.

   Copyright (C) 1993 Sebastiano Vigna

    This file is part of ne, the nice editor.

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 1, or (at your option)
    any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

In other words, you are welcome to use, share and improve this program.
You are forbidden to forbid anyone else to use, share and improve
what you give them.   Help stamp out software-hoarding!  */

#include "ne.h"
#include "help.h"


/* The standard macro descriptor allocation dimension. */

#define STD_MACRO_DESC_SIZE	1024


/* The maximum width for a command (used for displaying the command
names with the string requester). It allows four columns on an 80x25 screen.
*/

#define MAX_COMMAND_WIDTH		18


/* This structure represents a command. It includes a long and a short name,
a NULL-terminated vector of help strings (of specified length) and some flags
which are related to the syntax and the semantics of the arguments. */

typedef struct {
	const unsigned char *name, *short_name;
	const unsigned char * const *help;
	int help_len;
	int flags;
} command;


#define POSSIBLY_NO_ARGS		(1<<0)			/* This command can be called without argument. */
#define NO_ARGS					(1<<1)			/* This command must be called without argument. */
#define ARG_IS_STRING			(1<<2)			/* The argument is a string (default is a number). */
#define IS_OPTION					(1<<3)			/* The command controls an option,
															and can be played while exec_only_options is true. */
#define DO_NOT_RECORD			(1<<4)			/* Never record this command. */
#define EMPTY_STRING_OK			(1<<5)			/* This command can accept an empty string ("") as an argument. */


/* This macro makes the following vector more readable. */

#define HELP_LEN(x)			(sizeof(x ## _HELP)/sizeof(unsigned char *)-1)


/* This is the command vector. Is *has* to be in the same order as the
action enum in ne.h. I.e., each action identifier indexes exactly and
directly its command. Note that the command names come from names.h, and the
help names come from help.h. */

static const command commands[ACTION_COUNT] = {

	{ NOP_NAME, NOP_ABBREV, NOP_HELP, HELP_LEN(NOP), NO_ARGS },

	{ OPEN_NAME, OPEN_ABBREV, OPEN_HELP, HELP_LEN(OPEN), POSSIBLY_NO_ARGS | ARG_IS_STRING },
	{ SAVE_NAME, SAVE_ABBREV, SAVE_HELP, HELP_LEN(SAVE), NO_ARGS },
	{ SAVEAS_NAME, SAVEAS_ABBREV, SAVEAS_HELP, HELP_LEN(SAVEAS), POSSIBLY_NO_ARGS | ARG_IS_STRING },
	{ CLEAR_NAME, CLEAR_ABBREV, CLEAR_HELP, HELP_LEN(CLEAR), NO_ARGS },
	{ QUIT_NAME, QUIT_ABBREV, QUIT_HELP, HELP_LEN(QUIT), NO_ARGS },
	{ EXIT_NAME, EXIT_ABBREV, EXIT_HELP, HELP_LEN(EXIT), NO_ARGS },
	{ ABOUT_NAME, ABOUT_ABBREV, ABOUT_HELP, HELP_LEN(ABOUT), NO_ARGS },

	{ NEWDOC_NAME, NEWDOC_ABBREV, NEWDOC_HELP, HELP_LEN(NEWDOC), NO_ARGS },
	{ CLOSEDOC_NAME, CLOSEDOC_ABBREV, CLOSEDOC_HELP, HELP_LEN(CLOSEDOC), NO_ARGS },
	{ NEXTDOC_NAME, NEXTDOC_ABBREV, NEXTDOC_HELP, HELP_LEN(NEXTDOC), POSSIBLY_NO_ARGS },
	{ PREVDOC_NAME, PREVDOC_ABBREV, PREVDOC_HELP, HELP_LEN(PREVDOC), POSSIBLY_NO_ARGS },
	{ SELECTDOC_NAME, SELECTDOC_ABBREV, SELECTDOC_HELP, HELP_LEN(SELECTDOC), POSSIBLY_NO_ARGS },

	{ MARK_NAME, MARK_ABBREV, MARK_HELP, HELP_LEN(MARK), POSSIBLY_NO_ARGS | IS_OPTION },
	{ CUT_NAME, CUT_ABBREV, CUT_HELP, HELP_LEN(CUT), POSSIBLY_NO_ARGS },
	{ COPY_NAME, COPY_ABBREV, COPY_HELP, HELP_LEN(COPY), POSSIBLY_NO_ARGS },
	{ PASTE_NAME, PASTE_ABBREV, PASTE_HELP, HELP_LEN(PASTE), POSSIBLY_NO_ARGS },
	{ ERASE_NAME, ERASE_ABBREV, ERASE_HELP, HELP_LEN(ERASE), POSSIBLY_NO_ARGS },
	{ MARKVERT_NAME, MARKVERT_ABBREV, MARKVERT_HELP, HELP_LEN(MARKVERT), POSSIBLY_NO_ARGS | IS_OPTION },
	{ PASTEVERT_NAME, PASTEVERT_ABBREV, PASTEVERT_HELP, HELP_LEN(PASTEVERT), POSSIBLY_NO_ARGS },
	{ OPENCLIP_NAME, OPENCLIP_ABBREV, OPENCLIP_HELP, HELP_LEN(OPENCLIP), POSSIBLY_NO_ARGS | ARG_IS_STRING },
	{ SAVECLIP_NAME, SAVECLIP_ABBREV, SAVECLIP_HELP, HELP_LEN(SAVECLIP), POSSIBLY_NO_ARGS | ARG_IS_STRING },

	{ FIND_NAME, FIND_ABBREV, FIND_HELP, HELP_LEN(FIND), POSSIBLY_NO_ARGS | ARG_IS_STRING },
	{ FINDREGEXP_NAME, FINDREGEXP_ABBREV, FINDREGEXP_HELP, HELP_LEN(FINDREGEXP), POSSIBLY_NO_ARGS | ARG_IS_STRING },
	{ REPLACE_NAME, REPLACE_ABBREV, REPLACE_HELP, HELP_LEN(REPLACE), POSSIBLY_NO_ARGS | ARG_IS_STRING | EMPTY_STRING_OK },
	{ REPLACEONCE_NAME, REPLACEONCE_ABBREV, REPLACEONCE_HELP, HELP_LEN(REPLACEONCE), POSSIBLY_NO_ARGS | ARG_IS_STRING | EMPTY_STRING_OK },
	{ REPLACEALL_NAME, REPLACEALL_ABBREV, REPLACEALL_HELP, HELP_LEN(REPLACEALL), POSSIBLY_NO_ARGS | ARG_IS_STRING | EMPTY_STRING_OK },
	{ REPEATLAST_NAME, REPEATLAST_ABBREV, REPEATLAST_HELP, HELP_LEN(REPEATLAST), POSSIBLY_NO_ARGS },
	{ GOTOLINE_NAME, GOTOLINE_ABBREV, GOTOLINE_HELP, HELP_LEN(GOTOLINE), POSSIBLY_NO_ARGS },
	{ GOTOCOLUMN_NAME, GOTOCOLUMN_ABBREV, GOTOCOLUMN_HELP, HELP_LEN(GOTOCOLUMN), POSSIBLY_NO_ARGS },
	{ GOTOMARK_NAME, GOTOMARK_ABBREV, GOTOMARK_HELP, HELP_LEN(GOTOMARK), NO_ARGS },
	{ MATCHBRACKET_NAME, MATCHBRACKET_ABBREV, MATCHBRACKET_HELP, HELP_LEN(MATCHBRACKET), NO_ARGS },

	{ RECORD_NAME, RECORD_ABBREV, RECORD_HELP, HELP_LEN(RECORD), POSSIBLY_NO_ARGS | IS_OPTION | DO_NOT_RECORD },
	{ PLAY_NAME, PLAY_ABBREV, PLAY_HELP, HELP_LEN(PLAY), POSSIBLY_NO_ARGS },
	{ OPENMACRO_NAME, OPENMACRO_ABBREV, OPENMACRO_HELP, HELP_LEN(OPENMACRO), POSSIBLY_NO_ARGS | ARG_IS_STRING },
	{ SAVEMACRO_NAME, SAVEMACRO_ABBREV, SAVEMACRO_HELP, HELP_LEN(SAVEMACRO), POSSIBLY_NO_ARGS | ARG_IS_STRING },
	{ MACRO_NAME, MACRO_ABBREV, MACRO_HELP, HELP_LEN(MACRO), POSSIBLY_NO_ARGS | ARG_IS_STRING | DO_NOT_RECORD },
	{ UNLOADMACROS_NAME, UNLOADMACROS_ABBREV, UNLOADMACROS_HELP, HELP_LEN(UNLOADMACROS), NO_ARGS },

	{ REFRESH_NAME, REFRESH_ABBREV, REFRESH_HELP, HELP_LEN(REFRESH), NO_ARGS },
	{ UNDELLINE_NAME, UNDELLINE_ABBREV, UNDELLINE_HELP, HELP_LEN(UNDELLINE), POSSIBLY_NO_ARGS },
	{ CENTER_NAME, CENTER_ABBREV, CENTER_HELP, HELP_LEN(CENTER), POSSIBLY_NO_ARGS },
	{ PARAGRAPH_NAME, PARAGRAPH_ABBREV, PARAGRAPH_HELP, HELP_LEN(PARAGRAPH), POSSIBLY_NO_ARGS },
	{ TOUPPER_NAME, TOUPPER_ABBREV, TOUPPER_HELP, HELP_LEN(TOUPPER), POSSIBLY_NO_ARGS },
	{ TOLOWER_NAME, TOLOWER_ABBREV, TOLOWER_HELP, HELP_LEN(TOLOWER), POSSIBLY_NO_ARGS },
	{ CAPITALIZE_NAME, CAPITALIZE_ABBREV, CAPITALIZE_HELP, HELP_LEN(CAPITALIZE), POSSIBLY_NO_ARGS },

	{ MOVELEFT_NAME, MOVELEFT_ABBREV, MOVELEFT_HELP, HELP_LEN(MOVELEFT), POSSIBLY_NO_ARGS },
	{ MOVERIGHT_NAME, MOVERIGHT_ABBREV, MOVERIGHT_HELP, HELP_LEN(MOVERIGHT), POSSIBLY_NO_ARGS },
	{ LINEUP_NAME, LINEUP_ABBREV, LINEUP_HELP, HELP_LEN(LINEUP), POSSIBLY_NO_ARGS },
	{ LINEDOWN_NAME, LINEDOWN_ABBREV, LINEDOWN_HELP, HELP_LEN(LINEDOWN), POSSIBLY_NO_ARGS },
	{ PREVPAGE_NAME, PREVPAGE_ABBREV, PREVPAGE_HELP, HELP_LEN(PREVPAGE), POSSIBLY_NO_ARGS },
	{ NEXTPAGE_NAME, NEXTPAGE_ABBREV, NEXTPAGE_HELP, HELP_LEN(NEXTPAGE), POSSIBLY_NO_ARGS },
	{ MOVEEOL_NAME, MOVEEOL_ABBREV, MOVEEOL_HELP, HELP_LEN(MOVEEOL), NO_ARGS },
	{ MOVESOL_NAME, MOVESOL_ABBREV, MOVESOL_HELP, HELP_LEN(MOVESOL), NO_ARGS },
	{ MOVEEOF_NAME, MOVEEOF_ABBREV, MOVEEOF_HELP, HELP_LEN(MOVEEOF), NO_ARGS },
	{ MOVESOF_NAME, MOVESOF_ABBREV, MOVESOF_HELP, HELP_LEN(MOVESOF), NO_ARGS },
	{ TOGGLESEOF_NAME, TOGGLESEOF_ABBREV, TOGGLESEOF_HELP, HELP_LEN(TOGGLESEOF), NO_ARGS },
	{ TOGGLESEOL_NAME, TOGGLESEOL_ABBREV, TOGGLESEOL_HELP, HELP_LEN(TOGGLESEOL), NO_ARGS },
	{ NEXTWORD_NAME, NEXTWORD_ABBREV, NEXTWORD_HELP, HELP_LEN(NEXTWORD), POSSIBLY_NO_ARGS },
	{ PREVWORD_NAME, PREVWORD_ABBREV, PREVWORD_HELP, HELP_LEN(PREVWORD), POSSIBLY_NO_ARGS },
	{ MOVEEOW_NAME, MOVEEOW_ABBREV, MOVEEOW_HELP, HELP_LEN(MOVEEOW), POSSIBLY_NO_ARGS },
	{ SETBOOKMARK_NAME, SETBOOKMARK_ABBREV, SETBOOKMARK_HELP, HELP_LEN(SETBOOKMARK), POSSIBLY_NO_ARGS },
	{ GOTOBOOKMARK_NAME, GOTOBOOKMARK_ABBREV, GOTOBOOKMARK_HELP, HELP_LEN(GOTOBOOKMARK), POSSIBLY_NO_ARGS },


	{ TABSIZE_NAME, TABSIZE_ABBREV, TABSIZE_HELP, HELP_LEN(TABSIZE), POSSIBLY_NO_ARGS | IS_OPTION },
	{ INSERT_NAME, INSERT_ABBREV, INSERT_HELP, HELP_LEN(INSERT), POSSIBLY_NO_ARGS | IS_OPTION},
	{ FREEFORM_NAME, FREEFORM_ABBREV, FREEFORM_HELP, HELP_LEN(FREEFORM), POSSIBLY_NO_ARGS | IS_OPTION },
	{ STATUSBAR_NAME, STATUSBAR_ABBREV, STATUSBAR_HELP, HELP_LEN(STATUSBAR), POSSIBLY_NO_ARGS | IS_OPTION },
	{ CASESEARCH_NAME, CASESEARCH_ABBREV, CASESEARCH_HELP, HELP_LEN(CASESEARCH), POSSIBLY_NO_ARGS | IS_OPTION },
	{ SEARCHBACK_NAME, SEARCHBACK_ABBREV, SEARCHBACK_HELP, HELP_LEN(SEARCHBACK), POSSIBLY_NO_ARGS | IS_OPTION },
	{ FASTGUI_NAME, FASTGUI_ABBREV, FASTGUI_HELP, HELP_LEN(FASTGUI), POSSIBLY_NO_ARGS | IS_OPTION },
	{ WORDWRAP_NAME, WORDWRAP_ABBREV, WORDWRAP_HELP, HELP_LEN(WORDWRAP), POSSIBLY_NO_ARGS | IS_OPTION },
	{ RIGHTMARGIN_NAME, RIGHTMARGIN_ABBREV, RIGHTMARGIN_HELP, HELP_LEN(RIGHTMARGIN), POSSIBLY_NO_ARGS | IS_OPTION },
	{ AUTOINDENT_NAME, AUTOINDENT_ABBREV, AUTOINDENT_HELP, HELP_LEN(AUTOINDENT), POSSIBLY_NO_ARGS | IS_OPTION },
	{ AUTOPREFS_NAME, AUTOPREFS_ABBREV, AUTOPREFS_HELP, HELP_LEN(AUTOPREFS), POSSIBLY_NO_ARGS | IS_OPTION },
	{ BINARY_NAME, BINARY_ABBREV, BINARY_HELP, HELP_LEN(BINARY), POSSIBLY_NO_ARGS | IS_OPTION },
	{ TURBO_NAME, TURBO_ABBREV, TURBO_HELP, HELP_LEN(TURBO), POSSIBLY_NO_ARGS | IS_OPTION },
	{ NOFILEREQ_NAME, NOFILEREQ_ABBREV, NOFILEREQ_HELP, HELP_LEN(NOFILEREQ), POSSIBLY_NO_ARGS | IS_OPTION },
	{ VERBOSEMACROS_NAME, VERBOSEMACROS_ABBREV, VERBOSEMACROS_HELP, HELP_LEN(VERBOSEMACROS), POSSIBLY_NO_ARGS | IS_OPTION },
	{ DOUNDO_NAME, DOUNDO_ABBREV, DOUNDO_HELP, HELP_LEN(DOUNDO), POSSIBLY_NO_ARGS | IS_OPTION },
	{ READONLY_NAME, READONLY_ABBREV, READONLY_HELP, HELP_LEN(READONLY), POSSIBLY_NO_ARGS | IS_OPTION },
	{ CLIPNUMBER_NAME, CLIPNUMBER_ABBREV, CLIPNUMBER_HELP, HELP_LEN(CLIPNUMBER), POSSIBLY_NO_ARGS | IS_OPTION },
	{ LOADPREFS_NAME, LOADPREFS_ABBREV, LOADPREFS_HELP, HELP_LEN(LOADPREFS), POSSIBLY_NO_ARGS | ARG_IS_STRING },
	{ SAVEPREFS_NAME, SAVEPREFS_ABBREV, SAVEPREFS_HELP, HELP_LEN(SAVEPREFS), POSSIBLY_NO_ARGS | ARG_IS_STRING },
	{ LOADAUTOPREFS_NAME, LOADAUTOPREFS_ABBREV, LOADAUTOPREFS_HELP, HELP_LEN(LOADAUTOPREFS), NO_ARGS },
	{ SAVEAUTOPREFS_NAME, SAVEAUTOPREFS_ABBREV, SAVEAUTOPREFS_HELP, HELP_LEN(SAVEAUTOPREFS), NO_ARGS },
	{ SAVEDEFPREFS_NAME, SAVEDEFPREFS_ABBREV, SAVEDEFPREFS_HELP, HELP_LEN(SAVEDEFPREFS), NO_ARGS },

	{ INSERTCHAR_NAME, INSERTCHAR_ABBREV, INSERTCHAR_HELP, HELP_LEN(INSERTCHAR) },
	{ DELETECHAR_NAME, DELETECHAR_ABBREV, DELETECHAR_HELP, HELP_LEN(DELETECHAR), POSSIBLY_NO_ARGS },
	{ BACKSPACE_NAME, BACKSPACE_ABBREV, BACKSPACE_HELP, HELP_LEN(BACKSPACE), POSSIBLY_NO_ARGS },
	{ INSERTLINE_NAME, INSERTLINE_ABBREV, INSERTLINE_HELP, HELP_LEN(INSERTLINE), POSSIBLY_NO_ARGS },
	{ DELETELINE_NAME, DELETELINE_ABBREV, DELETELINE_HELP, HELP_LEN(DELETELINE), POSSIBLY_NO_ARGS },
	{ DELETEEOL_NAME, DELETEEOL_ABBREV, DELETEEOL_HELP, HELP_LEN(DELETEEOL), NO_ARGS },

	{ UNDO_NAME, UNDO_ABBREV, UNDO_HELP, HELP_LEN(UNDO), POSSIBLY_NO_ARGS },
	{ REDO_NAME, REDO_ABBREV, REDO_HELP, HELP_LEN(REDO), POSSIBLY_NO_ARGS },

	{ HELP_NAME, HELP_ABBREV, HELP_HELP, HELP_LEN(HELP), POSSIBLY_NO_ARGS | ARG_IS_STRING | DO_NOT_RECORD },
	{ BEEP_NAME, BEEP_ABBREV, BEEP_HELP, HELP_LEN(BEEP), NO_ARGS },
	{ FLASH_NAME, FLASH_ABBREV, FLASH_HELP, HELP_LEN(FLASH), NO_ARGS },
	{ EXEC_NAME, EXEC_ABBREV, EXEC_HELP, HELP_LEN(EXEC), POSSIBLY_NO_ARGS | ARG_IS_STRING | DO_NOT_RECORD },
	{ SYSTEM_NAME, SYSTEM_ABBREV, SYSTEM_HELP, HELP_LEN(SYSTEM), POSSIBLY_NO_ARGS | ARG_IS_STRING },
	{ ESCAPE_NAME, ESCAPE_ABBREV, ESCAPE_HELP, HELP_LEN(ESCAPE), POSSIBLY_NO_ARGS | DO_NOT_RECORD }
};


/* This function checks if the command line m starts with the command c. Return
0 on success, non-zero on failure. */

int cmdcmp(const unsigned char *c, const unsigned char *m) {

	assert(c != NULL);
	assert(m != NULL);

   while (*c && up_case[*c] == up_case[*m]) {
      c++ ;
      m++ ;
   }
   return(*c || *m && !isspace(*m)) ;
}



/* These vectors are hash tables with no conflicts. For each command, the
element indexed by the hashed name of the command contains the command
number plus one. Thus, only one strcmp() is necessary when analyzing the
command line. This technique offers a light speed comparison against the
command names, with a very small memory usage. The tables are precompiled,
so they can be moved to the text segment. */

extern const unsigned char hash_table[HASH_TABLE_SIZE];
extern const unsigned char short_hash_table[HASH_TABLE_SIZE];


/* This table *can* have conflicts, so that we can keep its size much smaller. */

static macro_desc *macro_hash_table[MACRO_HASH_TABLE_SIZE];


/* This is the command name hashing function. Note that we consider a string
as a number in base 26 (A=0, etc.), and then we take the number modulo
HASH_TABLE_SIZE. We consider only the 5 least significant bits because they
are the bits which distinguish characters, independently of their case. We
are not interested in strings which contain non-alphabetical characters,
because they will certainly generate an error (the only exception notably
being "R1"). We should subtract 1 to s[i], but this doesn't seem to produce
any improvement. hash_macro() act as hash(), but uses MACRO_HASH_TABLE_SIZE
for its modulo. */


static int hash(const unsigned char *s, int len) {
	int i, h;

	for(h=i=0; i<len; i++) h = (h*26+(s[i] & 0x1F)) % HASH_TABLE_SIZE;

	return(h);
}


static int hash_macro(const unsigned char *s, int len) {
	int i, h;

	for(h=i=0; i<len; i++) h = (h*26+(s[i] & 0x1F)) % MACRO_HASH_TABLE_SIZE;

	return(h);
}



/* This function parses a command line. It has an interface which is
slightly varied with respect to the other functions of ne. In case of a
parsing error, an error index *with sign inverted* is passed back. In case
parsing succeeds, an (greater or equal to zero) action is returned, and the
numerical or string argument is passed in the variables pointed to by
num_arg or string_arg, respectively, if the are non-NULL. Otherwise, the
argument is not passed back. The string argument is free()able. -1 and NULL
denote the lack of an optional numerical or string argument, respectively.
NOP is returned on a NOP command, or on a comment line (any line whose first
non-space character is a non alphabetic character). Note that the various
syntax flags are used here. */

action parse_command_line(const unsigned char *command_line, int *num_arg, unsigned char **string_arg) {

	int h;
	action a;
	macro_desc *md;
	const unsigned char *p = command_line;

	if (num_arg) *num_arg = -1;
	if (string_arg) *string_arg = NULL;

	if (!command_line) return(NOP);

	if (!*p) return(NOP);

	while(isspace(*p)) p++;

	command_line = p;

	if (!isalpha(*p)) return(NOP);

	while(*p && !isspace(*p)) p++;

	h = hash(command_line, p-command_line);

	if ((a = hash_table[h]) && !cmdcmp(commands[--a].name, command_line)
		|| (a = short_hash_table[h]) && !cmdcmp(commands[--a].short_name, command_line)) {

		while(isspace(*p)) p++;

		if (*p || (commands[a].flags & (NO_ARGS | POSSIBLY_NO_ARGS))) {
			if (!*p || !(commands[a].flags & NO_ARGS)) {
				if (!*p || (commands[a].flags & ARG_IS_STRING) || (*p>='0' && *p<='9')) {
					if ((commands[a].flags & IS_OPTION) || !cur_buffer->exec_only_options) {
						if (*p) {
							if ((commands[a].flags & ARG_IS_STRING) && string_arg) {

								int len = strlen(p);

								if (len>1 && *p == '"' && p[len-1] == '"') {
									p++;
									len -= 2;
								}

								if (len == 0 && !(commands[a].flags & EMPTY_STRING_OK)) return(-STRING_IS_EMPTY);

								if (!(*string_arg = malloc(len+1))) return(-OUT_OF_MEMORY);
								memcpy(*string_arg, p, len);
								(*string_arg)[len] = 0;
							}
							else if (num_arg) *num_arg = atoi(p);
						}

						return(a);
					}
					return(-CAN_EXECUTE_ONLY_OPTIONS);
				}
				return(-HAS_NUMERIC_ARGUMENT);
			}
			return(-HAS_NO_ARGUMENT);
		}
		return(-REQUIRES_ARGUMENT);
	}

	return(-NO_SUCH_COMMAND);
}


/* This function parses and executes a command line. Standard error codes
are returned. If the search for a standard command fails, we try to execute
a macro in ~/.ne with the same name. */

int execute_command_line(buffer *b, const unsigned char *command_line) {

	int n;
	action a;
	unsigned char *p;

	if ((a = parse_command_line(command_line, &n, &p)) >= 0) return(do_action(b, a, n, p));

	a = -a;

	if ((a == NO_SUCH_COMMAND) && (a = execute_macro(b, command_line)) == CANT_OPEN_MACRO)
		a = NO_SUCH_COMMAND;

	return(a);
}



/* This function allocates a macro descriptor. It does not allocate
the internal character stream, which has to be allocated and stuffed
in separately. */

macro_desc *alloc_macro_desc(void) {

	return(calloc(1, sizeof(macro_desc)));
}



/* This function frees a macro descriptors. */

void free_macro_desc(macro_desc *md) {

	if (!md) return;

	assert_macro_desc(md);

	free(md->name);
	free_char_stream(md->cs);
	free(md);
}



/* Here we record an action in a character stream. The action name is expanded
in a short or long name, depending on the value of the verbose parameter.
A numerical or string argument are expanded and copied, too. If the command
should not be recorded (for instance, ESCAPE) we return. */

void record_action(char_stream *cs, action a, int c, unsigned char *p, int verbose) {

	unsigned char t[MAX_INT_LEN+2];

	if (commands[a].flags & DO_NOT_RECORD) return;

	if (verbose) add_to_stream(cs, commands[a].name, strlen(commands[a].name));
	else add_to_stream(cs, commands[a].short_name, strlen(commands[a].short_name));


	if (c>=0) {
		sprintf(t, " %d", c);
		add_to_stream(cs, t, strlen(t));
	}
	else if (p) {
		add_to_stream(cs, " ", 1);
		if (!*p || isspace(*p)) add_to_stream(cs, "\"", 1);
		add_to_stream(cs, p, strlen(p));
		if (!*p || isspace(*p)) add_to_stream(cs, "\"", 1);
	}

	add_to_stream(cs, "", 1);

}



/* This function is the ultimate goal of this file. It plays a character
stream, considering each line as a command line. It polls the global stop
variable in order to check for the user interrupting. Note that the macro
is duplicated before execution: this is absolutely necessary, for otherwise
a call to CloseDoc, Record or UnloadMacros could free() the block of
memory which we are executing. */

int play_macro(buffer *b, char_stream *cs) {

	int error = OK, len;
	unsigned char *p, *stream;

	if (!cs) return(ERROR);

	/* If len is 0 or 1, the character stream does not contain anything. */

	if ((len = cs->len) < 2) return(OK);

	if (!(p = stream = malloc(len))) return(OUT_OF_MEMORY);

	memcpy(stream, cs->stream, len);

	stop = FALSE;

	while(!stop && p - stream < len) {
		if (error = execute_command_line(b, p)) break;
		p += strlen(p)+1;
	}

	free(stream);

	return(stop ? STOPPED : error);
}



/* This function loads a macro, and puts it in the global macro hash table.
file_part is applied to the name argument before storing it and hashing it.
Note that if the macro can't be opened, we retry prefixing its name with the
preferences directory name (~/.ne/). Thus, for instance, all autopreferences
file whose name does not conflict with internal commands can be executed
transparently just by typing their name. */


macro_desc *load_macro(const unsigned char *name) {

	int h;
	unsigned char *macro_dir, *prefs_dir;
	char_stream *cs;
	macro_desc *md, **m;

	assert(name != NULL);

	if (!(md = alloc_macro_desc())) return(NULL);

	cs = load_stream(md->cs, name);

	if (!cs && (prefs_dir = exists_prefs_dir()) && (macro_dir = malloc(strlen(prefs_dir)+2+strlen(name)))) {
		strcat(strcpy(macro_dir, prefs_dir), name);
		cs = load_stream(md->cs, macro_dir);
		free(macro_dir);
	}

	if (cs) {
		md->cs = cs;
		md->name = strdup(file_part(name));

		h = hash_macro(md->name, strlen(md->name));

		m = &macro_hash_table[h];

		while(*m) m = &((*m)->next);

		*m = md;

		return(md);
	}

	free_macro_desc(md);
	return(NULL);
}



/* This function executes a named macro. If the macro is not in the global
macro list, it is loaded. A standard error code is returned. */

int execute_macro(buffer *b, const unsigned char *name) {

	const unsigned char *p;
	macro_desc *md;
	int h;

	p = file_part(name);

	h = hash_macro(p, strlen(p));

	md = macro_hash_table[h];

	while(md && cmdcmp(md->name, p)) md = md->next;

	if (!md) md = load_macro(name);

	assert_macro_desc(md);

	if (md) return(play_macro(b, md->cs));

	return(CANT_OPEN_MACRO);
}


void unload_macros(void) {

	int i;
	macro_desc *m, *n;

	for(i=0; i<MACRO_HASH_TABLE_SIZE; i++) {

		m = macro_hash_table[i];
		macro_hash_table[i] = NULL;

		while(m) {
			n = m->next;
			free_macro_desc(m);
			m = n;
		}
	}
}



/* This function helps. The help text relative to the command name pointed
to by p is displayed (p can also contain arguments). If p is NULL, the
alphabetically ordered list of commands is displayed with the string
requester. The help finishes when the user escapes. */

void help(char *p) {

	action a;
	int i;

	do {
		if (p || (i = request_strings(command_names, ACTION_COUNT, MAX_COMMAND_WIDTH)) >= 0) {

			if (p) {
				for(i=0; i<strlen(p); i++) if (isspace(p[i])) break;

				i = hash(p, i);

				if ((a = hash_table[i]) && !cmdcmp(commands[--a].name, p)
				|| (a = short_hash_table[i]) && !cmdcmp(commands[--a].short_name, p)) i = a;
				else i = -1;

				p = NULL;
			}
			else i = parse_command_line(command_names[i], NULL, NULL);

			if (i < 0) {
				i = 0;
				continue;
			}

			assert(i>=0 && i<ACTION_COUNT);

			i = request_strings(commands[i].help, commands[i].help_len, columns);
		}
	} while(i>=0);
}
