/*
 * Copyright (C) 2000-2002 ASANO Masahiro
 */

#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "crash.h"

struct functiontable *functiontable;

struct statement *translate_until_eof();
struct statement *translate_if_then_block();
struct statement *translate_if_else_block();
struct statement *translate_while_block();

struct if_string {
	const char *current;
} if_string;

char *
input_from_string()
{
	const char *s;
	char *p;
	int n;

	s = if_string.current;
	if (s == NULL || *s == '\0')
		return NULL;
	while (*s != '\0' && *s != '\n')
		s++;
	n = s - if_string.current;
	if ((p = malloc(n + 1)) == NULL) {
		fprintf(stderr, "memory error\n");
		return NULL;
	}
	if (n > 0)
		memcpy(p, if_string.current, n);
	p[n] = 0;
	if_string.current = (*s == '\n')? s + 1: s;
	return p;
}

void
init_function()
{
	int i;

	functiontable = malloc(functiontable_size * sizeof(struct functiontable));
	if (functiontable == NULL) {
		functiontable_size = 0;
		return;
	}

	setinputfunc(input_from_string);
	for (i = 0; i < functiontable_size; i++) {
		if_string.current = initial_function[i * 2 + 1];
		functiontable[i].name = strdup(initial_function[i * 2]);
		functiontable[i].name_sz = strlen(functiontable[i].name);
		functiontable[i].stmt = translate_until_eof(NULL);
	}
}

void init_alias()
{
	int i;

	aliastable = malloc(aliastable_size * sizeof(struct aliastable));
	if (aliastable == NULL) {
		aliastable_size = 0;
		return;
	}
	for (i = 0; i < aliastable_size; i++) {
		aliastable[i].keyin = strdup(initial_alias[i * 2]);
		aliastable[i].alias = strdup(initial_alias[i * 2 + 1]);
		if (aliastable[i].keyin == NULL || aliastable[i].alias == NULL) {
			aliastable_size = i;
			break;
		}
		aliastable[i].keyin_sz = strlen(aliastable[i].keyin);
		aliastable[i].alias_sz = strlen(aliastable[i].alias);
	}
}

void
follow_alias(inp)
	char **inp;
{
	char *in;
	size_t len;
	int i;

	if (inp == NULL || *inp == NULL || **inp == '\0')
		return;
	for (in = *inp; ;in++) {
		if (*in == '\0' || *in == '!' || *in == '|' || *in == '>' || *in == ' ')
			break;
	}
	if ((len = in - *inp) == 0)
		return;
	in = *inp;

	for (i = 0; i < aliastable_size; i++) {
		if (aliastable[i].keyin_sz != len ||
		    strncmp(in, aliastable[i].keyin, len) != 0 ||
		    aliastable[i].alias == NULL)
			continue;

		in = malloc(strlen(*inp) - len + aliastable[i].alias_sz + 1);
		strcpy(in, aliastable[i].alias);
		strcat(in, *inp + len);
		free(*inp);
		*inp = in;
		break;
	}
}

char *
get_one_statement(reason)
	int *reason;
{
	char *in, *save;
	static char *next_in;
	int c;

 input_loop:
	if (next_in) {
		in = next_in;
		next_in = NULL;
	} else {
		if ((in = get_line(reason)) == NULL) {
			return NULL;
		}
	}
	save = in;

	if ((strncmp(in, "then", 4)  == 0 || strncmp(in, "else", 4) == 0) && !isalnum(in[4])) {
		in += 4;
		{	char *p = in;
			while (*p == ' ' || *p == '\t' || *p == ';') {
				p++;
			}
			if (*p) {
				next_in = strdup(p);
			}
		}
		goto break_loop;
	}
	if (strncmp(in, "do", 2)  == 0 && !isalnum(in[2])) {
		in += 2;
		{	char *p = in;
			while (*p == ' ' || *p == '\t' || *p == ';') {
				p++;
			}
			if (*p) {
				next_in = strdup(p);
			}
		}
		goto break_loop;
	}

	for (;;) {
		switch (*in) {
		case '\'':
			in++;
			while ((c = *in++) != '\'') {
				if (c == 0) {
					goto unclosed_quote;
				} else if (c == '\\') {
					if (*in++ == 0) {
						goto unclosed_quote;
					}
				}
			}
			in++;
			break;
		case '"':
			in++;
			while ((c = *in++) != '"') {
				if (c == 0) {
					goto unclosed_quote;
				} else if (c == '\\') {
					if (*in++ == 0) {
						goto unclosed_quote;
					}
				}
			}
			in++;
			break;
		case '\t':
			*in++ = ' ';
			break;
		case '\\':
			in++;
			if (*in++ == 0) {
				goto syntax_error;
			}
			break;
		case ';':
			{	char *p = in + 1;
				while (*p == ' ' || *p == '\t' || *p == ';') {
					p++;
				}
				if (*p) {
					next_in = strdup(p);
				}
			}
			goto break_loop;
		case 0:
			goto break_loop;
		default:
			in++;
		}
	}
 break_loop:

	while (*(in - 1) == ' ') {
		in--;
	}
	*in = '\0';

	if (*save == '\0') {
		/* null command */
		free(save);
		goto input_loop;
	}
	follow_alias(&save);
	return save;

 unclosed_quote:
	free(save);
	if (next_in) {
		free(next_in);
		next_in = NULL;
	}
	fprintf(stderr, "unclosed quote\n");
	if (reason)
		*reason = G_ERROR;
	return NULL;

 syntax_error:
	free(save);
	if (next_in) {
		free(next_in);
		next_in = NULL;
	}
	fprintf(stderr, "syntax error\n");
	if (reason)
		*reason = G_ERROR;
	return NULL;
}

struct statement *
allocstmt(in)
char *in;
{
	struct statement *p;

	if ((p = calloc(1, sizeof(struct statement) + strlen(in))) == NULL) {
		perror("memory");
		return NULL;
	}
	strcpy(p->line, in);
	return p;
}

void
freestmt(p)
	struct statement *p;
{
	if (p->next)
		freestmt(p->next);
	if (p->ext1)
		freestmt(p->ext1);
	if (p->ext2)
		freestmt(p->ext2);
	free(p);
}

struct statement *
translate_until_eof(reason)
	int *reason;
{
	char *in;
	struct statement *p, *prev = NULL;
	struct statement *start = NULL;

	for (;;) {
		if ((in = get_one_statement(reason)) == NULL)
			return start;
		if ((p = translate(reason, in)) == NULL)
			return NULL;

		if (start == NULL)
			start = p;
		else
			prev->next = p;
		prev = p;
	}
}

struct statement *
translate_if_then_block(reason, ifp)
	int *reason;
	struct statement *ifp;
{
	char *in;
	struct statement *p, *prev = NULL;

	for (;;) {
		if ((in = get_one_statement(reason)) == NULL)
			return NULL;
		else if (strcmp(in, "fi") == 0)
			return ifp;
		else if (strcmp(in, "else") == 0)
			return translate_if_else_block(reason, ifp);
		else if ((p = translate(reason, in)) == NULL)
			return NULL;

		if (ifp->ext1 == NULL)
			ifp->ext1 = p;
		else
			prev->next = p;
		prev = p;
	}
}

struct statement *
translate_if_else_block(reason, ifp)
	int *reason;
	struct statement *ifp;
{
	char *in;
	struct statement *p, *prev = NULL;

	for (;;) {
		if ((in = get_one_statement(reason)) == NULL)
			return NULL;
		else if (strcmp(in, "fi") == 0)
			return ifp;
		else if ((p = translate(reason, in)) == NULL)
			return NULL;

		if (ifp->ext2 == NULL)
			ifp->ext2 = p;
		else
			prev->next = p;
		prev = p;
	}
}

struct statement *
translate_while_block(reason, whp)
	int *reason;
	struct statement *whp;
{
	char *in;
	struct statement *p, *prev = NULL;

	for (;;) {
		if ((in = get_one_statement(reason)) == NULL)
			return NULL;
		else if (strcmp(in, "done") == 0)
			return whp;
		else if ((p = translate(reason, in)) == NULL)
			return NULL;

		if (whp->ext1 == NULL)
			whp->ext1 = p;
		else
			prev->next = p;
		prev = p;
	}
}

struct statement *
translate(reason, in)
int *reason;
char *in;
{
	struct statement *p;

	if (in == NULL) {
		if ((in = get_one_statement(reason)) == NULL) {
			return NULL;
		}
	}
	if ((p = allocstmt(in)) == NULL) {
		free(in);
		fprintf(stderr, "no memory\n");
		if (reason)
			*reason = G_ERROR;
		return NULL;
	}
	free(in);
	if (strncmp(p->line, "if ", 3) == 0) {
		p->st_type = IF_CONVENTION;
		setprompt(PS2);
		in = get_one_statement();
		if (strcmp(in, "then") == 0) {
			if (translate_if_then_block(reason, p) == NULL) {
				freestmt(p);
				return NULL;
			}
		} else if (strcmp(in, "else") == 0) {
			if (translate_if_else_block(reason, p) == NULL) {
				freestmt(p);
				return NULL;
			}
		} else {
			fprintf(stderr, "syntax error\n");
			if (reason)
				*reason = G_ERROR;
			return NULL;
		}
		return p;
	}
	if (strncmp(p->line, "while ", 6) == 0) {
		p->st_type = WHILE;
		setprompt(PS2);
		in = get_one_statement();
		if (strcmp(in, "do") != 0) {
			free(in);
			freestmt(p);
			fprintf(stderr, "syntax error\n");
			if (reason)
				*reason = G_ERROR;
			return NULL;
		}
		return translate_while_block(reason, p);
	}
	p->st_type = SIMPLE;
	return p;
}
