/** For the GIMP project.
 *
 * Introducing eevl eva, the evaluator. A straightforward recursive descent
 * parser, no fuss, no new dependencies. The lexer is hand coded, tedious, not
 * extremely fast but works. It evaluates the expression as it goes along, and
 * does not create a parse tree or anything, and will not optimize anything. It
 * uses doubles for precision, I figured with the given use case, that's enough
 * to combat any rounding errors (as opposed to optimizing the evalutation).
 *
 * Variables can be supplied through a callback, providing a quantity (value
 * and dimensionality) for a specified identifier, which can be useful for
 * allowing the user to specify a height in terms of the width or similar.
 *
 * It must be supplied with a dpi for unit conversions, as pixels are the base
 * unit, and will be the unit of the value returned. A callback can be provided
 * for arbitrary units as an x-per-pixel conversion factor.
 *
 * It does basic unit conversions, and an elementary dimensionality constraint
 * check (e.g. 2mm+3px*4in is an error, as L + L^2 is a missmatch).
 *
 * It uses setjmp/longjmp for try/catch like pattern on error, it uses strtod
 * (stdlib.h) for numeric conversions it's non-destructive in terms of the
 * paramters, and it's thread safe.
 *
 * EBNF:
 *
 * expression := term { ( '+' | '-' ) term }*
 * 	|  <empty string>
 *
 * term := sfactor { ( '*' | '/' ) nfactor }*
 *
 * sfactor := ( '+' | '-' )? factor
 *
 * factor := NUMERIC ( unit )?
 * 	|  IDENTIFIER
 * 	|  '(' expression ')'
 *
 * unit := ( 'px' | 'm' | 'cm' | 'mm' | '"' | '\'' | 'ft' | 'in' )
 * 	|  IDENTIFIER
 *
 * It will allow 1++1 and 1+-1 (resulting in 2 and 0, respectively), but I
 * figured one might want that, and I don't think it's going to throw anyone
 * off.
 *
 * The identifiers are parsed as [A-Za-z_][A-Za-z0-9_]*, as usual. Numerics are
 * parsed as [0-9.] followed by whatever strtod wants to consume. Units are
 * case sensitive for the time being, and I suppose that's rather common to not
 * mix up milli and Mega (not that mega is currently supported). Whitespace is
 * treated as [ \t\v\r\n] and is discarded.
 */

#include <setjmp.h>
#include <stdlib.h>

// tokens out of ascii-range
enum tokens {
	tok_num   = 301,
	tok_ident = 302,

	tok_px    = 400,
	tok_m     = 401,
	tok_cm    = 402,
	tok_mm    = 403,
	tok_ft    = 404,
	tok_in    = 405,

	tok_end   = 500
};

struct symbol {
	int token;
	union {
		double fl;
		struct {
			const char *c;
			int len;
		};
	} value;
};

typedef struct quantity {
	double val;
	int space;
} quant;

typedef quant (*eevl_variable)(const char* ident, int len);
typedef double (*eevl_unit)(const char* ident, int len);

struct eevl {
	const char *data;
	struct symbol sym;
	struct quantity defunit;

	char *error;
	jmp_buf catcher;

	double dpi;
	eevl_variable vars;
	eevl_unit units;
};

static void error(struct eevl *eva, char* msg);

#define S(x) (*(s+(x)))
#define INIDENT(x) (((x)>='a'&&(x)<='z')||((x)>='A'&&(x)<='Z')||((x)>='0'&&(x)<='9')||(x)=='_')
#define ISSPACE(x) ((x)==' '||(x)=='\t'||(x)=='\n'||(x)=='\r'||(x)=='\v')
static int identlen(const char *s, int start) {
	while(INIDENT(S(start))) start++;
	return start;
}
static void lexaccept2(struct eevl *eva, char* to, int token) {
	eva->sym.token = token;
	eva->data = to;
}
static void lexaccept(struct eevl *eva, int count, int token) {
	eva->sym.token = token;
	eva->data += count;
}
static void lex(struct eevl *eva) {
	const char *s;

	if(eva->data) while(ISSPACE(*eva->data)) eva->data++;

	if(!eva->data || !*eva->data) {
		eva->sym.token = tok_end; // all done.
		return;
	}

	s = eva->data;
	if(S(0) == 'c') {
		if(S(1) == 'm' && !INIDENT(S(2))) {
			lexaccept(eva, 2, tok_cm);
		} else {
			eva->sym.value.c = s;
			lexaccept(eva, eva->sym.value.len = identlen(s, 1), tok_ident);
		}
	} else if(S(0) == 'f') {
		if(S(1) == 't' && !INIDENT(S(2))) {
			lexaccept(eva, 2, tok_ft);
		} else {
			eva->sym.value.c = s;
			lexaccept(eva, eva->sym.value.len = identlen(s, 1), tok_ident);
		}
	} else if(S(0) == 'i') {
		if(S(1) == 'n' && !INIDENT(S(2))) {
			lexaccept(eva, 2, tok_in);
		} else {
			eva->sym.value.c = s;
			lexaccept(eva, eva->sym.value.len = identlen(s, 1), tok_ident);
		}
	} else if(S(0) == 'm') {
		if(S(1) == 'm' && !INIDENT(S(2))) {
			lexaccept(eva, 2, tok_mm);
		} else if(!INIDENT(S(1))) {
			lexaccept(eva, 1, tok_m);
		} else {
			eva->sym.value.c = s;
			lexaccept(eva, eva->sym.value.len = identlen(s, 1), tok_ident);
		}
	} else if(S(0) == 'p') {
		if(S(1) == 'x' && !INIDENT(S(2))) {
			lexaccept(eva, 2, tok_px);
		} else {
			eva->sym.value.c = s;
			lexaccept(eva, eva->sym.value.len = identlen(s, 1), tok_ident);
		}
	} else if((S(0) >= '0' && S(0) <= '9') || S(0) == '.') {
		char *endptr = 0;
		double v = strtod(s, &endptr);
		if(endptr == s) {
			error(eva, "error in value conversion");
		} else {
			eva->sym.value.fl = v;
			lexaccept2(eva, endptr, tok_num);
		}
	} else if(INIDENT(S(0))) {
		eva->sym.value.c = s;
		lexaccept(eva, eva->sym.value.len = identlen(s, 0), tok_ident);
	} else {
		lexaccept(eva, 1, S(0));
	}
}

static struct eevl create(const char *data, quant defunit, double dpi, eevl_variable vars, eevl_unit units) {
	struct eevl eva;

	eva.data = data;
	eva.defunit = defunit;

	eva.error = 0;

	eva.dpi = dpi;
	eva.vars = vars;
	eva.units = units;

	lex(&eva); // preload symbol..
	return eva;
}

static int shift(struct eevl *eva, int token, struct symbol *value) {
	if(token == eva->sym.token || !token) {
		if(value) *value = eva->sym;
		lex(eva);
		return 1;
	}
	return 0;
}

static int lookahead(struct eevl *eva) {
	return eva->sym.token;
}

/* This is the error reporting function, pretty much the 'throw'-statement
 * equivalent if you wish. This function never returns.
 */
static void error(struct eevl *eva, char* msg) {
	eva->error = msg;
	longjmp(eva->catcher, 1);
}

/* Short-hand function, only returns if the expected token is in the pipe. */
static void expect(struct eevl *eva, int token, struct symbol *value) {
	if(!shift(eva, token, value)) {
		error(eva, "unexpected token");
	}
}

static quant expression(struct eevl *eva);
static quant factor(struct eevl *eva) {
	quant v = {0, 0};
	int neg = 0;
	struct symbol tok;

	if(!shift(eva, '+', 0))
		neg = shift(eva, '-', 0);

	if(shift(eva, tok_num, &tok)) {
		v.val = tok.value.fl;
		switch(lookahead(eva)) {
			case tok_px:
				v.space = 1;
				shift(eva, 0, 0); break;
			case tok_m:  v.val *= 10; /* fallthrough */
			case tok_cm: v.val *= 10;
			case tok_mm:
				v.val /= 25.4; // 25.4 mm/inch
				v.val *= eva->dpi;
				v.space = 1;
				shift(eva, 0, 0);
				break;
			case tok_ft:
			case '\'':
				v.val *= 12;
			case '"' :
			case tok_in:
				v.val *= eva->dpi;
				v.space = 1;
				shift(eva, 0, 0);
				break;
			case tok_ident: {
				double d = 0;
				shift(eva, 0, &tok);
				if(eva->units) {
					d = eva->units(tok.value.c, tok.value.len);
				}
				if(d == 0) {
					error(eva, "unsupported unit specifier");
				}
			} break;
			default: break; // silence compiler.
		}
	} else if(shift(eva, tok_ident, &tok)) {
		if(eva->vars) {
			v = eva->vars(tok.value.c, tok.value.len);
			if(v.space < 0) error(eva, "unknown variable");
		} else {
			error(eva, "there are no variables available");
		}
	} else if(shift(eva, '(', 0)) {
		v = expression(eva);
		expect(eva, ')', 0);
	} else {
		error(eva, "expected identifier, integer or float.");
	}
	if(neg) v.val = -v.val;

	return v;
}

static quant term(struct eevl *eva) {
	quant v;
	int div = 0;

	v = factor(eva);
	while(shift(eva, '*', 0) || ((div = shift(eva, '/', 0)))) {
		quant f = factor(eva);
		if(div) {
			v.val /= f.val;
			v.space -= f.space;

		} else {
			v.val *= f.val;
			v.space += f.space;
		}
		div = 0;
	}

	return v;
}

static quant expression(struct eevl *eva) {
	int neg = 0;
	quant v;

	v = term(eva);
	while(shift(eva, '+', 0) || ((neg = shift(eva, '-', 0)))) {
		quant t = term(eva);
		if(t.space != v.space) {
			/* dimension missmatch, attempt default unit assignent*/
			if(t.space == 0 && v.space == eva->defunit.space) {
				t.val *= eva->defunit.val;
				t.space = eva->defunit.space;
			} else if(v.space == 0 && t.space == eva->defunit.space) {
				v.val *= eva->defunit.val;
				v.space = eva->defunit.space;
			} else {
				error(eva, "dimension missmatch");
			}
		}
		if(neg) {
			v.val -= t.val;
		} else {
			v.val += t.val;
		}
		neg = 0;
	}
	return v;
}

static quant complete(struct eevl *eva) {
	quant v = {0,0};
	if(shift(eva, tok_end, 0)) {
		return v; // empty expression is 0.
	}
	v = expression(eva);
	expect(eva, tok_end, 0);
	if(v.space == 0) {
		v.val *= eva->defunit.val;
		v.space = eva->defunit.space;
	}
	return v;
}


/* 
 * Beyond this point is test code, and shows basically how it can be used. This
 * should be replaced with glue code, and if used within a widget, there should
 * be some kind of feedback loop to provide the user with an error message
 * (instead of stdout). Also, the callbacks for the variables and units must
 * come from somewhere...
 */

#include <stdio.h>
#include <string.h>

static quant evaluate(char *string, quant defunit, double dpi, eevl_variable vars, eevl_unit units) {
	struct eevl eva = create(string, defunit, dpi, vars, units);
	if(!setjmp(eva.catcher)) { // try...

		return complete(&eva);

	} else { // catch..
		if(eva.data && *eva.data) printf("Parse error: %s (before '%s')\n", eva.error, eva.data);
		else printf("Parse error: %s (at end of input)\n", eva.error);
		quant v = {0,0};
		v.val = 0./0.; // NaN.
		return v;
	}
}

static quant vars(const char *ident, int len) {
	quant x = {0, -1};
	if(0 == strncmp(ident, "width", len)) {
		quant q = { 210, 1 };
		return q;
	}
	return x;
}

int main(int argc, char **argv) {
	int i;
	quant defunit = {21, 1};
	for(i = 1; i < argc; i++) {
		printf("%s = %lg\n", argv[i], evaluate(argv[i], defunit, 72, vars, 0).val);
	}

	return 0;
}
