/* $Id$ */
/* Copyright (c) 2004-2018 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS Unix sh */
/* 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, version 3 of the License.
 *
 * 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, see <http://www.gnu.org/licenses/>. */



#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "scanner.h"


/* Scanner */
/* scanner_init */
static void _prompt(Scanner * scanner);
static int _next_file_prompt(Scanner * scanner);
static int _next_file(Scanner * scanner);
static int _next_string(Scanner * scanner);

void scanner_init(Scanner * scanner, Prefs * prefs, FILE * fp,
		char const * string)
{
	if((scanner->fp = fp) == NULL)
		scanner->next = _next_string;
	else if(*prefs & PREFS_i)
		scanner->next = _next_file_prompt;
	else
		scanner->next = _next_file;
	scanner->prompt = SP_PS1;
	scanner->string = string;
}

static void _prompt(Scanner * scanner)
{
	char * prompt;
	char * env[SP_COUNT] = { "PS1", "PS2", "PS4" };
	char * dflt[SP_COUNT] = { "$ ", "> ", "+ " };

	if((prompt = getenv(env[scanner->prompt])) == NULL)
	{
		if(scanner->prompt == SP_PS1 && getuid() == 0)
			prompt = "# ";
		else
			prompt = dflt[scanner->prompt];
	}
	fputs(prompt, stderr);
}

static void _lineno(void)
{
	static unsigned int i = 1;
	char lineno[11];
	const int len = sizeof(lineno) - 1;

	if(snprintf(lineno, len, "%u", i++) >= len)
		lineno[len] = '\0';
	if(setenv("LINENO", lineno, 1) != 0)
		sh_error("setenv", 0);
}

static int _next_file_prompt(Scanner * scanner)
{
	static int c = '\n';

	if(c == '\n')
	{
		_prompt(scanner);
		_lineno();
	}
	if((c = fgetc(scanner->fp)) == EOF)
		fputc('\n', stderr);
	return c;
}

static int _next_file(Scanner * scanner)
{
	int c;

	if((c = fgetc(scanner->fp)) == '\n')
		_lineno();
	return c;
}

static int _next_string(Scanner * scanner)
{
	static unsigned int pos = 0;

	if(scanner->string[pos] == '\0')
		return EOF;
	return scanner->string[pos++];
}


/* scanner_next */
static Token * _next_operator(Scanner * scanner, int * c);
static Token * _next_blank(Scanner * scanner, int * c);
static Token * _next_comment(Scanner * scanner, int * c);
static Token * _next_newline(int * c);
static Token * _next_word(Scanner * scanner, int * c);

Token * scanner_next(Scanner * scanner)
{
	static int c = EOF;
	Token * t;

#ifdef DEBUG
	fprintf(stderr, "%s", "scanner_next()\n");
#endif
	scanner->prompt = SP_PS1;
	if(c == EOF && (c = scanner->next(scanner)) == EOF)
		return token_new(TC_EOI, NULL);
	scanner->prompt = SP_PS2;
	_next_blank(scanner, &c);
	_next_comment(scanner, &c);
	if((t = _next_operator(scanner, &c)) != NULL
			|| (t = _next_newline(&c)) != NULL
			|| (t = _next_word(scanner, &c)) != NULL)
		return t;
	return NULL;
}

static Token * _next_operator(Scanner * scanner, int * c)
{
	unsigned int i;
	unsigned int pos = 0;

	for(i = TC_OP_AND_IF; i <= TC_OP_GREAT;)
	{
		if(sTokenCode[i][pos] == '\0')
			return token_new(i, NULL);
		if(*c == sTokenCode[i][pos])
		{
			*c = scanner->next(scanner);
			pos++;
		}
		else
			i++;
	}
	return NULL;
}

static Token * _next_blank(Scanner * scanner, int * c)
{
	while(*c == '\t' || *c == ' ')
		*c = scanner->next(scanner);
	return NULL;
}

static Token * _next_comment(Scanner * scanner, int * c)
{
	if(*c != '#')
		return NULL;
	*c = scanner->next(scanner);
	while(*c != EOF && *c != '\0' && *c != '\r' && *c != '\n')
		*c = scanner->next(scanner);
	return NULL;
}

static Token * _next_newline(int * c)
{
	if(*c != '\r' && *c != '\n')
		return NULL;
	*c = EOF;
	return token_new(TC_NEWLINE, NULL);
}

static char _word_escape(Scanner * scanner, int * c);
static char * _word_dquote(Scanner * scanner, int * c, char * str,
		unsigned int * len);
static char * _word_quote(Scanner * scanner, int * c, char * str,
		unsigned int * len);
static Token * _next_word(Scanner * scanner, int * c)
{
	char eow[] = "\t \r\n&|;<>";
	char * str = NULL;
	unsigned int len = 0;
	unsigned int i;
	char * p;

	for(; *c != EOF; *c = scanner->next(scanner))
	{
		for(i = 0; eow[i] != '\0' && *c != eow[i]; i++);
		if(*c == eow[i])
			break;
		if(*c == '"')
			p = _word_dquote(scanner, c, str, &len);
		else if(*c == '\'')
			p = _word_quote(scanner, c, str, &len);
		else
		{
			if(*c == '\\' && (*c = _word_escape(scanner, c)) == EOF)
				break;
			if((p = realloc(str, len + 2)) != NULL)
			{
				str = p;
				str[len++] = *c;
				continue;
			}
		}
		if(p == NULL)
		{
			sh_error("malloc", 0);
			free(str);
			return NULL;
		}
		str = p;
	}
	if(str == NULL)
		return NULL;
	str[len] = '\0';
	/* FIXME aliases need to be replaced now; I suggest changing the
	 * scanner->next() function for a while but I don't really like it,
	 * moreover it wouldn't parse the alias into tokens... */
	return token_new(TC_TOKEN, str);
}

static char _word_escape(Scanner * scanner, int * c)
{
	char p;

	if((p = scanner->next(scanner)) == EOF)
		return *c;
	return p;
}

static char const * _read_variable(Scanner * scanner, int * c);
static char * _word_dquote(Scanner * scanner, int * c, char * str,
		unsigned int * len)
{
	char * p;
	char const * var;

	if(str == NULL && (str = malloc(1)) == NULL)
		return NULL;
	for(*c = scanner->next(scanner); *c != EOF && *c != '"';
			*c = scanner->next(scanner))
	{
		if(*c == '\\')
			*c = _word_escape(scanner, c);
		else if(*c == '$')
		{
			if((var = _read_variable(scanner, c)) == NULL)
				return NULL;
			if((p = realloc(str, (*len) + strlen(var) + 1)) == NULL)
				return NULL;
			str = p;
			strcpy(&str[(*len)], var);
			(*len) += strlen(var);
			if(*c == '"')
				break;
		}
		if((p = realloc(str, (*len) + 2)) == NULL)
			return NULL;
		str = p;
		str[(*len)++] = *c;
	}
	return str;
}

static char const * _read_variable(Scanner * scanner, int * c)
{
	char buf[80];
	unsigned long i;
	char * p;
	char delim = '\0';

	for(i = 0; i < sizeof(buf) - 1; i++)
	{
		*c = scanner->next(scanner);
		if(i == 0 && *c == '{')
		{
			delim = '}';
			i--;
			continue;
		}
		if(i != 0 && delim != '\0' && *c == delim)
		{
			*c = scanner->next(scanner);
			break;
		}
		if(*c == EOF || (!isalnum(*c) && *c != '_'))
			break;
		buf[i] = *c;
	}
	if(i == sizeof(buf) - 1)
		return NULL;
	if(i == 0)
		return "$";
	buf[i] = '\0';
	if((p = getenv(buf)) == NULL)
		return "";
	return p;
}

static char * _word_quote(Scanner * scanner, int * c, char * str,
		unsigned int * len)
{
	char * p;

	if(str == NULL && (str = malloc(1)) == NULL)
		return NULL;
	for(*c = scanner->next(scanner); *c != EOF && *c != '\'';
			*c = scanner->next(scanner))
	{
		if((p = realloc(str, (*len) + 2)) == NULL)
			return NULL;
		str = p;
		str[(*len)++] = *c;
	}
	return str;
}
