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

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

PRIVATE addr_t echo(), print();
const commandtable_t
	command_echo = {"echo", echo, "argument...", "display a line of text"},
	command_print = {"print", print, "argument...", "calculate arguments and display them"};

PRIVATE int	gettoken();
PRIVATE addr_t	expr();
PRIVATE addr_t	expr2();
PRIVATE addr_t	term();

PRIVATE char token_sym[128];

#define	L_DIGIT		300
#define	L_GRGR		301	/* >> */
#define	L_LSLS		302	/* << */
#define	L_GREQ		303	/* >= */
#define	L_LSEQ		304	/* <= */
#define	L_EQEQ		305	/* == */
#define	L_NOTEQ		306	/* != */
#define	L_ANDAND	307	/* && */
#define	L_OROR		308	/* || */
#define	L_PLUSEQ	309	/* += */
#define	L_MINUSEQ	310	/* -= */
#define	L_ANDEQ		311	/* &= */
#define	L_XOREQ		312	/* ^= */
#define	L_OREQ		313	/* |= */
#define	L_IDENT		314
#define	L_SIZEOF	315	/* sizeof */

PRIVATE addr_t
gethex(statep)
const char **statep;
{
	const char *curp = *statep;
	addr_t v, n;

	v = 0;
	for (;;) {
		n = v * 16;
		if (isdigit(*curp)) {
			n += (*curp++ - '0');
		} else if (*curp >= 'a' && *curp <= 'f') {
			n += (*curp++ - 'a' + 10);
		} else if (*curp >= 'A' && *curp <= 'F') {
			n += (*curp++ - 'A' + 10);
		} else {
			break;
		}
		if (v > ULONG_MAX / 16) {
			THROW("digit to big");
		}
		v = n;
	}
	*statep = curp;
	return v;
}

PRIVATE int
gettoken(statep, valuep)
const char **statep;
addr_t *valuep;
{
	const char *curp = *statep;
	const char *save;
	addr_t n;

	while (*curp == ' ')
		curp++;

	save = curp;
	switch (*curp) {
	case 0:
		return 0;
	case '=':
		if (*(curp + 1) == '=') {
			*statep = curp + 2;
			return L_EQEQ;
		}
		*statep = curp + 1;
		return '=';
	case '!':
		if (*(curp + 1) == '=') {
			*statep = curp + 2;
			return L_NOTEQ;
		}
		*statep = curp + 1;
		return '!';
	case '>':
		switch (*(curp + 1)) {
		case '=':
			*statep = curp + 2;
			return L_GREQ;
		case '>':
			*statep = curp + 2;
			return L_GRGR;
		}
		*statep = curp + 1;
		return '>';
	case '<':
		switch (*(curp + 1)) {
		case '=':
			*statep = curp + 2;
			return L_LSEQ;
		case '<':
			*statep = curp + 2;
			return L_LSLS;
		}
		*statep = curp + 1;
		return '<';
	case '0':
		if (*(curp + 1) == 'x') {
			curp += 2;
		}
		/*FALLTHRU*/
	case '1': case '2': case '3': case '4': case '5':
	case '6': case '7': case '8': case '9':
		*statep = curp;
		n = gethex(statep);
		if (valuep) {
			*valuep = n;
		}
		return L_DIGIT;

	default:
		if (*curp != '_' && !isalpha(*curp)) {
			/* special character, return self code */
			*statep = curp + 1;
			return *curp;
		}
		curp++;
		while (isalnum(*curp) || *curp == '_') {
			curp++;
		}
		if (curp - save >= sizeof(token_sym) - 1) {
			THROW("symbol too long");
		}
		memcpy(token_sym, save, curp - save);
		token_sym[curp - save] = '\0';
		*statep = curp;

		if (strcmp(token_sym, "sizeof") == 0) {
			return L_SIZEOF;
		}
		return L_IDENT;
	}
}

enum oppri {
	OPPRIERR = 1,
	OPPRICOMMA, OPPRISET, OPPRIOROR, OPPRIANDAND, OPPRIOR, OPPRIXOR,
	OPPRIAND, OPPRIEQEQ, OPPRIGRLS, OPPRISHIFT, OPPRIADD, OPPRIMUL
};

PRIVATE enum oppri
getoppri(c)
	int	c;
{
	switch (c) {
	case '*':
	case '/':
	case '%':
		return OPPRIMUL;
	case '+':
	case '-':
		return OPPRIADD;
	case L_GRGR:
	case L_LSLS:
		return OPPRISHIFT;
	case '<':
	case '>':
	case L_GREQ:
	case L_LSEQ:
		return OPPRIGRLS;
	case L_EQEQ:
	case L_NOTEQ:
		return OPPRIEQEQ;
	case '&':
		return OPPRIAND;
	case '^':
		return OPPRIXOR;
	case '|':
		return OPPRIOR;
	case L_ANDAND:
		return OPPRIANDAND;
	case L_OROR:
		return OPPRIOROR;
	case '=':
	case L_PLUSEQ:
	case L_MINUSEQ:
	case L_ANDEQ:
	case L_XOREQ:
	case L_OREQ:
		return OPPRISET;
	case ',':
		return OPPRICOMMA;
	}
	return OPPRIERR;
}

PRIVATE addr_t
term(statep)
const char **statep;
{
	int c;
	addr_t value;
	const char *p;
	int brace;

	switch (c = gettoken(statep, &value)) {
	case L_DIGIT:
		return value;
	case '+':
		return term(statep);
	case '-':
		return -term(statep);
	case '!':
		return ! term(statep);
	case '~':
		return ~ term(statep);
	case '*':
		memread(term(statep), sizeof(value), &value, "operator*");
		return value;
	case '(':
		value = expr(statep);
		if ((c = gettoken(statep, &value)) != ')') {
			THROW("unbalanced brace");
		}
		return value;
	case L_IDENT:
		if ((value = searchaddr_bysym(token_sym)) != 0) {
			return value;
		}
		if ((*token_sym >= 'a' && *token_sym <= 'f') || (*token_sym >= 'A' && *token_sym <= 'F')) {
			/* check hexical */
			p = token_sym;
			value = gethex(&p);
			if (*p == 0) {
				return value;
			}
		}
		THROWF("unknown symbol: %s", token_sym);
	case L_SIZEOF:
		c = gettoken(statep, NULL);
		if (c == '(') {
			brace = 1;
			c = gettoken(statep, NULL);
		} else {
			brace = 0;
		}
		switch (c) {
		case L_IDENT:
			value = sizeof_func(token_sym);
			break;
		case L_DIGIT:
			value = sizeof(addr_t);
			break;
		case '*':
			value = sizeof(void *);
			break;
		default:
			THROW("sizeof error");
		}
		if (brace) {
			c = gettoken(statep, NULL);
			if (c != ')') {
				THROW("unbalanced brace");
			}
		}
		return value;
	default:
		THROW("lexical error");
	}
}

PRIVATE addr_t
expr2(statep, value, pri0)
const char **statep;
addr_t value;
enum oppri pri0;
{
	const char *save;
	addr_t nextvalue;
	enum oppri pri1, pri2;
	int c;

	for (;;) {
		save = *statep;
		c = gettoken(statep, (addr_t *)NULL);
		if ((pri1 = getoppri(c)) == OPPRIERR) {
			*statep = save;
			return value;
		}
		nextvalue = term(statep);
		save = *statep;
		pri2 = getoppri(gettoken(statep, (addr_t *)NULL));
		*statep = save;
		if ((int)pri1 <= (int)pri2) {
			nextvalue = expr2(statep, nextvalue, pri1);
		}

		switch (c) {
		case '+':	value += nextvalue;	break;
		case '-':	value -= nextvalue;	break;
		case '*':	value *= nextvalue;	break;
		case '/':	value /= nextvalue;	break;
		case '%':	value %= nextvalue;	break;
		case '|':	value |= nextvalue;	break;
		case '&':	value &= nextvalue;	break;
		case '^':	value ^= nextvalue;	break;
		case ',':	value  = nextvalue;	break;
		case '>':	value = (value > nextvalue);	break;
		case '<':	value = (value < nextvalue);	break;
		case L_GREQ:	value = (value >= nextvalue);	break;
		case L_LSEQ:	value = (value <= nextvalue);	break;
		case L_EQEQ:	value = (value == nextvalue);	break;
		case L_NOTEQ:	value = (value != nextvalue);	break;
		case L_LSLS:	value = (value << nextvalue);	break;
		case L_GRGR:	value = (value >> nextvalue);	break;
		default:	THROW("not implement");
		}

		if ((int)pri1 > (int)pri2 && (int)pri0 >= (int)pri2) {
			return value;
		}
	}
}

PRIVATE addr_t
expr(statep)
const char **statep;
{
	return expr2(statep, term(statep), OPPRIERR);
}

addr_t
getaddr(str)
const char *str;
{
	addr_t n;
	const char **statep = &str;

	n = expr(statep);
	if (gettoken(statep, (addr_t *)NULL) != 0) {
		THROW("expression syntax error");
	}
	return n;
}

PRIVATE addr_t
print()
{
	int i;

	for (i = 1; i < argcnt; i++) {
		mprintf("%lx ", getaddr(args[i]));
	}
	mprintf("\n");
	return 0;
}

PRIVATE addr_t
echo()
{
	int i;

	for (i = 1; i < argcnt; i++) {
		mprintf("%s ", args[i]);
	}
	mprintf("\n");
	return 0;
}
