/* parse.c -- routines for parsing input */
#include "he.h"

/* some char const for the get/parse routines */
#define CR '\n'
#define SPACE ' '
#define TAB '\t'
#define QUOTE '"'
#define BREAK '\003'		/* C-c */
#define ESCAPE '\\'		/* Quote escape */
#define COMMENT '!'		/* Comment a line if first character */

/* max line size -- change this if you type longer than this in one line */
#define HE_LINE_SZ 512

/* command separator */
#define HE_SEPARATOR ';'

/* is this in batch mode or interactive? */
int he_batch = NO;

/* nesting when if/select is encountered */
int he_nestLevel = 0;

/* prompt is actually "he"<he_prompt><space> */
char *he_prompt = ">";
char he_nestChar = '>';

/* table to associate command to the function --
   add additional functions anywhere in the table BEFORE the
   Marker {0,0} entry */
struct {
    char *str;
    HE_FUNC func;
} he_funcTab[] =
{
{"open", HEopen},
{"close", HEclose},
{"next", HEnext},
{"prev", HEprev},
{"alias", HEalias},
{"unalias", HEunalias},
{"display", HEdisplay},
{"info", HEinfo},
{"if", HEif},
{"select", HEselect},
{"wait", HEwait},
{"delete", HEdelete},
{"quit", HEquit},
{"dump", HEdump},
{"getr8", HEgetR8},
{"putr8", HEputR8},
{"put", HEput},
{"revert", HErevert},
{"write", HEwrite},
{"annotate", HEannotate},
{"help", HEhelp},
{"end", 0},
{NULL, 0},			/* Marker */
};

HE_FUNC findFunc(word)
    char *word;
{
    int len;
    int found = -1;
    register int i;

    len = strlen(word);

    for (i = 0; he_funcTab[i].str; i++)
	if (!strncmp(he_funcTab[i].str, word, len))
	{
	    /* check for exact match */
	    if (strlen(he_funcTab[i].str) == len)
		return he_funcTab[i].func;

	    if (found < 0)
		found = i;
	    else
	    {
		fprintf(stderr,"Ambiguous command: %s.\n", word);
		return NULL;
	    }
	}

    if (found < 0)
    {
	fprintf(stderr, "Unknown command: %s.\n", word);
	return NULL;
    }
    return he_funcTab[found].func;
}

/* prompt -- printout prompt according to the nesting level */
void prompt()
{
    if (!he_nestLevel)
	printf("he%s ", he_prompt);
    else
    {
	register int i;

	printf("  %s", he_prompt);
	for (i = he_nestLevel; i; i--) putchar(he_nestChar);
	putchar(' ');
    }
}


/* reads a line of input into p */
/* Skips all initial spaces and empty commands */
/* always returns with at least a word in p, unless eof */
/* if eof and p is not empty, return HE_OK, else if no word, return EOF */
int getLine(p)
    register char *p;
{
    static int ch = 0;

    do
    {
	if (!he_batch && (ch != EOF)) prompt();
	ch = getchar();
	if (ch == COMMENT)
	{
	    /* Skip this line */
	    do ch = getchar();
	    while ((ch != CR) && (ch != EOF));
	}
	else
	    while ((ch == SPACE) || (ch == TAB) || (ch == HE_SEPARATOR))
		ch = getchar();
	if (ch == EOF) return EOF;
    } while (ch == CR);

    while ((ch != EOF) && (ch != CR))
	switch(ch)
	{
	case ESCAPE:
	    ch = getchar();
	    if (!(ch == CR)) *p++ = ch;
	    ch = getchar();
	    break;
	case QUOTE:
	    ch = getchar();
	    while (ch != QUOTE)
	    {
		if (ch == ESCAPE) ch = getchar();
		*p++ = ch;
		ch = getchar();
	    }
	    ch = getchar();	/* Skip over the QUOTE */
	    break;
	case SPACE:
	case TAB:
	    *p++ = SPACE;
	    while ((ch == SPACE) || (ch == TAB)) ch = getchar();
	    break;
	case HE_SEPARATOR:
	    if (!isspace(*(p-1))) *p++ = SPACE;
	    *p++ = HE_SEPARATOR;
	    ch = SPACE;		/* Ensure next is a space */
	    break;
	default:
	    *p++ = ch;
	    ch = getchar();
	    break;
	}

    *p++ = '\0';
    return ch;
}

/* copy the next (space-delimited) word and advance the pointer as a
   side effect */
char *nextWord(p)
    char **p;
{
    char *word;
    register char *s, *q;
    int len;

    q = *p;
    while (*q && isspace(*q)) q++;
    if (!(*q))
    {
	*p = q;
	return NULL;
    }

    s = q;
    while (*s && !isspace(*s)) s++;
    len = s - q;

    word = (char *) malloc(len + 1);
    strncpy(word, q, len);
    word[len] = '\0';

    *p = s;
    while (**p && (isspace(**p))) (*p)++;

    return word;
}

HE_CMD *parseCmd(p)
    char **p;
{
    char *word;
    HE_CMD *cmd;
    HE_CMD *aliasCmd;
    HE_CMD *cmdTail;

    if (!(**p)) return NULL;

    cmd = (HE_CMD *) calloc(1, sizeof(HE_CMD));
    cmd->next = cmd->sub = (HE_CMD *) NULL;
    cmd->argc = 1;
    cmd->argv[0] = nextWord(p);

    if (aliasCmd = findAlias(cmd->argv[0]))
	cmd = aliasCmd;
    else
	cmd->func = findFunc(cmd->argv[0]);

    if (cmd->func == HEalias || cmd->func == HEwait)
    {
	/* let the alias command handle the parsing */
	cmd->argv[1] = copyStr(*p);
	cmd->argc = 2;

	**p = '\0';
    }
    else
    {
	cmdTail = cmd;
	while (cmdTail->next) cmdTail = cmdTail->next;

	for (word = nextWord(p); word && strcmp(word, ";");
	     word = nextWord(p), cmdTail->argc++)
	    cmdTail->argv[cmdTail->argc] = word;

	while (**p && (isspace(**p) || (**p == ';'))) (*p)++;
    }
    return cmd;
}

/* Inputs a line and returns cmd list of line */
HE_CMD *parse()
{
    static char line[HE_LINE_SZ];
    static char *ptr;
    int notDone = 1;
    HE_CMD *cmd;
    HE_CMD *cmdTail;

    if (getLine(line) == EOF) return NULL;
    ptr = line;

    cmdTail = cmd = parseCmd(&ptr);
    while (cmdTail->next) cmdTail = cmdTail->next;

    while (notDone)
    {
	cmdTail->next = parseCmd(&ptr);
	notDone = (cmdTail->next != NULL);
	while (cmdTail->next) cmdTail = cmdTail->next;
    }
    return cmd;
}


HE_CMD *getCmd()
{
    static HE_CMD *cmdList;
    HE_CMD *cmd;
    HE_CMD *cmdTail;

    if (!cmdList) cmdList = parse();

    cmd = cmdList;
    if (cmdList) cmdList = cmdList->next;
    cmd->next = (HE_CMD *) NULL; /* Cut off links since these will be */
				 /* accessed later */

    if (cmd && ((cmd->func == HEif) || (cmd->func == HEselect)) &&
	!((cmd->argc > 1) && (cmd->argv[1][0] == '-') &&
	  (findOpt(cmd->argv[1]+1) == HE_HELP)))
    {
	he_nestLevel++;

	cmd->sub = getCmd();
	for (cmdTail = cmd->sub;
	     cmdTail && strcmp(cmdTail->argv[0], "end"); /* while != "end" */
	     cmdTail = cmdTail->next)
	    cmdTail->next = getCmd();

	he_nestLevel--;
    }
    return cmd;
}

#define HE_ALIAS_SZ 256

struct he_alias {
    char *str;
    HE_CMD *cmd;
} he_aliasTab[HE_ALIAS_SZ];

int he_numAlias = 0;

int setAlias(str, cmd)
    char *str;
    HE_CMD *cmd;
{
    register int i;

    for (i = 0; i < he_numAlias; i++)
	if (!strcmp(str, he_aliasTab[i].str))
	{
	    he_aliasTab[i].cmd = cmd;
	    return HE_OK;
	}
    if (he_numAlias == HE_ALIAS_SZ)
    {
	fprintf(stderr, "Alias table full.\n");
	return HE_FAIL;
    }
    he_aliasTab[he_numAlias].str = str;
    he_aliasTab[he_numAlias++].cmd = cmd;

    return HE_OK;
}

HE_CMD *mkDupCmd(cmd)
    HE_CMD *cmd;
{
    register int i;
    HE_CMD *dupCmd;

    dupCmd = (HE_CMD *) calloc(1, sizeof(HE_CMD));
    dupCmd->func = cmd->func;
    dupCmd->argc = cmd->argc;
    dupCmd->next = dupCmd->sub = (HE_CMD *) NULL;
    for (i = 0; i < cmd->argc; i++)
	dupCmd->argv[i] = copyStr(cmd->argv[i]);

    return dupCmd;
}

HE_CMD *findAlias(str)
    char *str;
{
    register int i;
    HE_CMD *cmd;
    HE_CMD *dupCmd;
    HE_CMD *cmdTail;

    for (i = 0; i < he_numAlias; i++)
	if (!strcmp(str, he_aliasTab[i].str))
	{
	    cmd = he_aliasTab[i].cmd;
	    dupCmd = mkDupCmd(cmd);

	    cmd = cmd->next;
	    for (cmdTail = dupCmd; cmd;
		 cmd = cmd->next, cmdTail = cmdTail->next)
		cmdTail->next = mkDupCmd(cmd);

	    return dupCmd;
	}
    return NULL;
}

int HEunalias(cmd)
    HE_CMD *cmd;
{
    register int a, i, j;

    for (a = 1; a < cmd->argc; a++)
	for (i = 0; i < he_numAlias; i++)
	    if (!strcmp(cmd->argv[a], he_aliasTab[i].str))
	    {
		he_numAlias--;
		for (j = i; j < he_numAlias; j++)
		{
		    he_aliasTab[j].str = he_aliasTab[j+1].str;
		    he_aliasTab[j].cmd = he_aliasTab[j+1].cmd;
		}
		break;
	    }
    return HE_OK;
}

void printAlias(word, cmd)
    char *word;
    HE_CMD *cmd;
{
    register int j;

    printf("%s:", word);
    for (; cmd; cmd = cmd->next)
    {
	printf("\t");
	for (j = 0; j < cmd->argc; j++) printf("%s ",cmd->argv[j]);
	puts("");
    }
}

int HEalias(cmd)
    HE_CMD *cmd;
{
    char *s;
    char *word;
    register int i;
    HE_CMD *cmdTail;

    s = cmd->argv[1];
    word = nextWord(&s);

    if (!word)
    {
	for (i = 0; i < he_numAlias; i++)
	    printAlias(he_aliasTab[i].str, he_aliasTab[i].cmd);
	return HE_OK;
    }

    cmd = parseCmd(&s);
    if (!cmd)
    {
	cmd = findAlias(word);
	printAlias(word, cmd);
	return HE_OK;
    }
    for (cmdTail = cmd; cmdTail->next;  cmdTail = cmdTail->next);
    while (cmdTail->next = parseCmd(&s))
	for (; cmdTail->next;  cmdTail = cmdTail->next);
    return setAlias(word, cmd);
}

/* end of parse.c */
