/*      $Id: cpc.c,v 1.5 2015/02/26 19:30:32 ragge Exp $      */

/*
 * Copyright (c) 2014 Anders Magnusson (ragge@ludd.luth.se).
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
 * Small recursive parser for #if statements.
 */

#include "cpp.h"

static int ctok;

typedef struct nd ND;
struct nd yynode;

void qloop(void (*fun)(ND *), ND *n1, int a0, int a1, int a2, int a3);
void shft(void);
int yyparse(void);
void expr(ND *n1);
void eqcol(ND *n1);
void eoror(ND *n1);
void eandand(ND *n1);
void exor(ND *n1);
void eand(ND *n1);
void eqne(ND *n1);
void eget(ND *n1);
void elrs(ND *n1);
void eplmin(ND *n1);
void emdv(ND *n1);
void eterm(ND *n1);
void eval(int op, ND *n1, ND *n2);


void
shft(void)
{
	ctok = yylex();
}

int
yyparse(void)
{
	ND n1;

	shft();
	expr(&n1);
	if (n1.op == 0)
		error("division by zero");
	if (ctok != '\n')
		error("junk after expression");
	return (int)n1.nd_val;
}

/*
 * evaluate a complete preprocessor #if expression.
 */
void
expr(ND *n1)
{
	for (;;) {
		eqcol(n1);
		if (ctok != ',')
			return;
		shft();
	}
}

/*
 * evaluate ?: conditionals.
 */
void
eqcol(ND *n1)
{
	ND n2, n3;

	eoror(n1);
	if (ctok != '?')
		return;

	shft();
	expr(&n2);
	if (ctok != ':')
		error("no : found");
	shft();
	eqcol(&n3);
	if (n1->nd_val) 
		n1->nd_val = n2.nd_val, n1->op = n2.op;
	else
		n1->nd_val = n3.nd_val, n1->op = n3.op;
}

void
eoror(ND *n1)
{
	qloop(eandand, n1, OROR, 0, 0, 0);
}

void
eandand(ND *n1)
{
	qloop(exor, n1, ANDAND, 0, 0, 0);
}

void
exor(ND *n1)
{
	qloop(eand, n1, '|', '^', 0, 0);
}

void
eand(ND *n1)
{
	qloop(eqne, n1, '&', 0, 0, 0);
}

void
eqne(ND *n1)
{
	qloop(eget, n1, EQ, NE, 0, 0);
}

void
eget(ND *n1)
{
	qloop(elrs, n1, '<', '>', LE, GE);
}

void
elrs(ND *n1)
{
	qloop(eplmin, n1, LS, RS, 0, 0);
}

void
eplmin(ND *n1)
{
	qloop(emdv, n1, '+', '-', 0, 0);
}

void
emdv(ND *n1)
{
	qloop(eterm, n1, '*', '/', '%', 0);
}

/*
 * Loop to evaluate all operators on the same precedence level.
 */
void
qloop(void (*fun)(ND *), ND *n1, int a0, int a1, int a2, int a3)
{
	ND n2;
	int op;

	fun(n1);
	while (ctok == a0 || ctok == a1 || ctok == a2 || ctok == a3) {
		op = ctok;
		shft();
		fun(&n2);
		eval(op, n1, &n2);
	}
}

static void
gnum(int o, ND *n1)
{
	n1->op = yynode.op;
	n1->nd_val = yynode.nd_val;
	shft();
}

/*
 * Highest precedence operators + numeric terminals.
 */
void
eterm(ND *n1)
{
	int o = ctok;

	switch (o) {
	case '~':
	case '!':
	case '+':
	case '-':
		shft();
		eterm(n1);
		eval(o, n1, 0);
		break;

	case NUMBER:
	case UNUMBER:
		gnum(o, n1);
		break;

	case DEFINED:
		shft();
		if (ctok == '(') {
			shft();
			if (ctok != NUMBER)
				error("bad defined");
			gnum(ctok, n1);
			if (ctok != ')')
				error("bad defined");
			shft();
			break;
		} else if (ctok != NUMBER)
			error("bad defined");
		gnum(ctok, n1);
		break;

	case '(':
		shft();
		expr(n1);
		if (ctok == ')') {
			shft();
			break;
		}
		/* FALLTHROUGH */
	default:
		error("bad terminal %s (%d)", yytext, o);
		break;
	}
}

/*
 * keep all numeric evaluation here.
 * evaluated value returned in n1.
 */
void
eval(int op, ND *n1, ND *n2)
{

	if ((op == '/' || op == '%') && n2->nd_val == 0)
		n1->op = 0;

	if (n1->op == 0)
		return; /* div by zero involved */

	/* unary ops */
	if (n2 == 0) {
		switch (op) {
		case '+': break;
		case '-': n1->nd_val = -n1->nd_val; break;
		case '~': n1->nd_val = ~n1->nd_val; break;
		case '!': n1->nd_val = !n1->nd_val; n1->op = NUMBER; break;
		}
		return;
	} else if (n2->op == 0) {
		n1->op = 0;
		return;
	}

	if (n2->op == UNUMBER)
		n1->op = UNUMBER;

	switch (op) {
	case OROR:
		if (n2->nd_val)
			n1->nd_val = 1;
		n1->op = NUMBER;
		break;
	case ANDAND:
		n1->nd_val = (n1->nd_val && n2->nd_val);
		n1->op = NUMBER;
		break;
	case '+': n1->nd_val += n2->nd_val; break;
	case '-': n1->nd_val -= n2->nd_val; break;
	case '|': n1->nd_val |= n2->nd_val; break;
	case '^': n1->nd_val ^= n2->nd_val; break;
	case '&': n1->nd_val &= n2->nd_val; break;
	case LS: n1->nd_val <<= n2->nd_val; break;
	case EQ: n1->nd_val = n1->nd_val == n2->nd_val; n1->op = NUMBER; break;
	case NE: n1->nd_val = n1->nd_val != n2->nd_val; n1->op = NUMBER; break;
	}

	if (n1->op == NUMBER) {
		switch (op) {
		case '*': n1->nd_val *= n2->nd_val; break;
		case '/': n1->nd_val /= n2->nd_val; break;
		case '%': n1->nd_val %= n2->nd_val; break;
		case '<': n1->nd_val = n1->nd_val < n2->nd_val; break;
		case '>': n1->nd_val = n1->nd_val > n2->nd_val; break;
		case LE: n1->nd_val = n1->nd_val <= n2->nd_val; break;
		case GE: n1->nd_val = n1->nd_val >= n2->nd_val; break;
		case RS: n1->nd_val >>= n2->nd_val; break;
		}
		return;
	} else /* op == UNUMBER */ {
		switch (op) {
		case '*': n1->nd_uval *= n2->nd_uval; break;
		case '/': n1->nd_uval /= n2->nd_uval; break;
		case '%': n1->nd_uval %= n2->nd_uval; break;
		case '<': n1->nd_uval = n1->nd_uval < n2->nd_uval; break;
		case '>': n1->nd_uval = n1->nd_uval > n2->nd_uval; break;
		case LE: n1->nd_uval = n1->nd_uval <= n2->nd_uval; break;
		case GE: n1->nd_uval = n1->nd_uval >= n2->nd_uval; break;
		case RS: n1->nd_uval >>= n2->nd_uval; break;
		}
		return;
	}
	error("unexpected arithmetic");
}
