/* -----------------------------------------------------------------------------
 * This file is part of SWIG, which is licensed as a whole under version 3 
 * (or any later version) of the GNU General Public License. Some additional
 * terms also apply to certain portions of SWIG. The full details of the SWIG
 * license and copyrights can be found in the LICENSE and COPYRIGHT files
 * included with the SWIG source code as distributed by the SWIG developers
 * and at https://www.swig.org/legal.html.
 *
 * expr.c
 *
 * Integer arithmetic expression evaluator used to handle expressions
 * encountered during preprocessing.
 *
 * Note that this is used for expressions in `#if` and the like, but not
 * for expressions in `#define` which SWIG wraps as constants - for those
 * we inject a `%constant` directive which is handled by the parser in
 * `Source/CParse/parser.y`.
 * ----------------------------------------------------------------------------- */

#include "swig.h"
#include "preprocessor.h"

static Scanner *scan = 0;

typedef struct {
  /* One of the EXPR_xxx values defined below. */
  int op;
  /* op == EXPR_OP: value is the token specifying which operator.
   *
   * op == EXPR_VALUE && svalue == NULL: Numeric expression value.
   *
   * Otherwise unused.
   */
  long value;
  /* op == EXPR_VALUE: If non-NULL, string expression value; if NULL see value.
   *
   * Otherwise unused.
   */
  String *svalue;
} exprval;

#define  EXPR_TOP      1
#define  EXPR_VALUE    2
#define  EXPR_OP       3
#define  EXPR_GROUP    4

/* Special token values used here to distinguish from SWIG_TOKEN_MINUS
 * and SWIG_TOKEN_PLUS (which we use here for a two argument versions).
 */
#define  OP_UMINUS   100
#define  OP_UPLUS    101

static exprval stack[256];	/* Parsing stack       */
static int sp = 0;		/* Stack pointer       */
static int prec[256];		/* Precedence rules    */
static int expr_init = 0;	/* Initialization flag */
static const char *errmsg = 0;	/* Parsing error       */

/* Initialize the precedence table for various operators.  Low values have higher precedence */
static void init_precedence(void) {
  prec[SWIG_TOKEN_NOT] = 10;
  prec[SWIG_TOKEN_LNOT] = 10;
  prec[OP_UMINUS] = 10;
  prec[OP_UPLUS] = 10;
  prec[SWIG_TOKEN_STAR] = 20;
  prec[SWIG_TOKEN_SLASH] = 20;
  prec[SWIG_TOKEN_PERCENT] = 20;
  prec[SWIG_TOKEN_PLUS] = 30;
  prec[SWIG_TOKEN_MINUS] = 30;
  prec[SWIG_TOKEN_LSHIFT] = 40;
  prec[SWIG_TOKEN_RSHIFT] = 40;
  prec[SWIG_TOKEN_LESSTHAN] = 50;
  prec[SWIG_TOKEN_GREATERTHAN] = 50;
  prec[SWIG_TOKEN_LTEQUAL] = 50;
  prec[SWIG_TOKEN_GTEQUAL] = 50;
  prec[SWIG_TOKEN_EQUALTO] = 60;
  prec[SWIG_TOKEN_NOTEQUAL] = 60;
  prec[SWIG_TOKEN_AND] = 70;
  prec[SWIG_TOKEN_XOR] = 80;
  prec[SWIG_TOKEN_OR] = 90;
  prec[SWIG_TOKEN_LAND] = 100;
  prec[SWIG_TOKEN_LOR] = 110;
  expr_init = 1;
}

#define UNARY_OP(token) (((token) == SWIG_TOKEN_NOT) || \
			 ((token) == SWIG_TOKEN_LNOT) || \
			 ((token) == OP_UMINUS) || \
			 ((token) == OP_UPLUS))

/* Reduce a single operator on the stack */
/* return 0 on failure, 1 on success */
static int reduce_op(void) {
  long op_token = stack[sp - 1].value;
  assert(sp > 0);
  assert(stack[sp - 1].op == EXPR_OP);
  /* do some basic checking first: */
  if (stack[sp].op != EXPR_VALUE) {
    errmsg = "Right-hand side is not value";
    return 0;
  }
  if (UNARY_OP(op_token)) {
    if (stack[sp].svalue) {
      errmsg = "Syntax error: attempt to apply unary operator to string";
      return 0;
    }
  } else {
    /* binary operator: */
    if (sp == 1) {
      /* top of stack: don't attempt to use sp-2! */
      errmsg = "Missing left-hand side for binary operator";
      return 0;
    }
    if (stack[sp].op != EXPR_VALUE) {
      errmsg = "Left-hand side of binary operator is not a value";
      return 0;
    }
    if ((!stack[sp - 2].svalue) != (!stack[sp].svalue)) {
      errmsg = "Can't mix strings and integers in expression";
      return 0;
    }
  }
  if (stack[sp].svalue) {
    /* A binary string expression */
    switch (stack[sp - 1].value) {
    case SWIG_TOKEN_EQUALTO:
      stack[sp - 2].value = (Strcmp(stack[sp - 2].svalue, stack[sp].svalue) == 0);
      Delete(stack[sp - 2].svalue);
      Delete(stack[sp].svalue);
      sp -= 2;
      break;
    case SWIG_TOKEN_NOTEQUAL:
      stack[sp - 2].value = (Strcmp(stack[sp - 2].svalue, stack[sp].svalue) != 0);
      Delete(stack[sp - 2].svalue);
      Delete(stack[sp].svalue);
      sp -= 2;
      break;
    default:
      errmsg = "Syntax error: bad binary operator for strings";
      return 0;
    }
  } else {
    switch (op_token) {
    case SWIG_TOKEN_STAR:
      stack[sp - 2].value = stack[sp - 2].value * stack[sp].value;
      sp -= 2;
      break;
    case SWIG_TOKEN_EQUALTO:
      stack[sp - 2].value = stack[sp - 2].value == stack[sp].value;
      sp -= 2;
      break;
    case SWIG_TOKEN_NOTEQUAL:
      stack[sp - 2].value = stack[sp - 2].value != stack[sp].value;
      sp -= 2;
      break;
    case SWIG_TOKEN_PLUS:
      stack[sp - 2].value = stack[sp - 2].value + stack[sp].value;
      sp -= 2;
      break;
    case SWIG_TOKEN_MINUS:
      stack[sp - 2].value = stack[sp - 2].value - stack[sp].value;
      sp -= 2;
      break;
    case SWIG_TOKEN_AND:
      stack[sp - 2].value = stack[sp - 2].value & stack[sp].value;
      sp -= 2;
      break;
    case SWIG_TOKEN_LAND:
      stack[sp - 2].value = stack[sp - 2].value && stack[sp].value;
      sp -= 2;
      break;
    case SWIG_TOKEN_OR:
      stack[sp - 2].value = stack[sp - 2].value | stack[sp].value;
      sp -= 2;
      break;
    case SWIG_TOKEN_LOR:
      stack[sp - 2].value = stack[sp - 2].value || stack[sp].value;
      sp -= 2;
      break;
    case SWIG_TOKEN_XOR:
      stack[sp - 2].value = stack[sp - 2].value ^ stack[sp].value;
      sp -= 2;
      break;
    case SWIG_TOKEN_LESSTHAN:
      stack[sp - 2].value = stack[sp - 2].value < stack[sp].value;
      sp -= 2;
      break;
    case SWIG_TOKEN_GREATERTHAN:
      stack[sp - 2].value = stack[sp - 2].value > stack[sp].value;
      sp -= 2;
      break;
    case SWIG_TOKEN_LTEQUAL:
      stack[sp - 2].value = stack[sp - 2].value <= stack[sp].value;
      sp -= 2;
      break;
    case SWIG_TOKEN_GTEQUAL:
      stack[sp - 2].value = stack[sp - 2].value >= stack[sp].value;
      sp -= 2;
      break;
    case SWIG_TOKEN_NOT:
      stack[sp - 1].value = ~stack[sp].value;
      sp--;
      break;
    case SWIG_TOKEN_LNOT:
      stack[sp - 1].value = !stack[sp].value;
      sp--;
      break;
    case OP_UMINUS:
      stack[sp - 1].value = -stack[sp].value;
      sp--;
      break;
    case OP_UPLUS:
      stack[sp - 1].value = stack[sp].value;
      sp--;
      break;
    case SWIG_TOKEN_SLASH:
      if (stack[sp].value != 0) {
	stack[sp - 2].value = stack[sp - 2].value / stack[sp].value;
	sp -= 2;
      } else {
	errmsg = "Division by zero in expression";
	return 0;
      }
      break;
    case SWIG_TOKEN_PERCENT:
      if (stack[sp].value != 0) {
	stack[sp - 2].value = stack[sp - 2].value % stack[sp].value;
	sp -= 2;
      } else {
	errmsg = "Modulo by zero in expression";
	return 0;
      }
      break;
    case SWIG_TOKEN_LSHIFT:
      stack[sp - 2].value = stack[sp - 2].value << stack[sp].value;
      sp -= 2;
      break;
    case SWIG_TOKEN_RSHIFT:
      stack[sp - 2].value = stack[sp - 2].value >> stack[sp].value;
      sp -= 2;
      break;
    default:
      errmsg = "Syntax error: bad operator";
      return 0;
    }
  }
  stack[sp].op = EXPR_VALUE;
  stack[sp].svalue = 0;		/* ensure it's not a string! */
  return 1;
}

/* -----------------------------------------------------------------------------
 * Preprocessor_expr_init()
 *
 * Initialize the expression evaluator
 * ----------------------------------------------------------------------------- */

void Preprocessor_expr_init(void) {
  if (!expr_init)
    init_precedence();
  if (!scan)
    scan = NewScanner();
}

void Preprocessor_expr_delete(void) {
  DelScanner(scan);
}


/* -----------------------------------------------------------------------------
 * Tokenizer 
 * ----------------------------------------------------------------------------- */

static int expr_token(Scanner * s) {
  int t;
  while (1) {
    t = Scanner_token(s);
    if (!((t == SWIG_TOKEN_BACKSLASH) || (t == SWIG_TOKEN_ENDLINE) || (t == SWIG_TOKEN_COMMENT)))
      break;
  }
  return t;
}

/* -----------------------------------------------------------------------------
 * Preprocessor_expr()
 *
 * Evaluates an arithmetic expression.  Returns the result and sets an error code.
 * ----------------------------------------------------------------------------- */

int Preprocessor_expr(DOH *s, int *error) {
  int token = 0;
  int op = 0;

  sp = 0;
  assert(s);
  assert(scan);

  Seek(s, 0, SEEK_SET);
  /* Printf(stdout,"evaluating : '%s'\n", s); */
  *error = 0;
  Scanner_clear(scan);
  Scanner_push(scan, s);

  /* Put initial state onto the stack */
  stack[sp].op = EXPR_TOP;

  while (1) {
    /* Look at the top of the stack */
    switch (stack[sp].op) {
    case EXPR_TOP:
      /* EXPR_TOP is a place-holder which can only appear on the top of the
       * stack.  We can reduce it to any expression - a number, a string, an
       * unary operator, or another expression enclosed in parentheses.
       */
      token = expr_token(scan);
      if (!token) {
	errmsg = "Expected an expression";
	*error = 1;
	return 0;
      }
      if (token == SWIG_TOKEN_BOOL) {
	/* A boolean value.  Reduce EXPR_TOP to an EXPR_VALUE */
	String *cc = Scanner_text(scan);
	if (Strcmp(cc, "true") == 0) {
	  stack[sp].value = (long) 1;
	} else {
	  stack[sp].value = (long) 0;
	}
	stack[sp].svalue = 0;
	stack[sp].op = EXPR_VALUE;
      } else if ((token == SWIG_TOKEN_INT) || (token == SWIG_TOKEN_UINT) || (token == SWIG_TOKEN_LONG) || (token == SWIG_TOKEN_ULONG)) {
	/* A number.  Reduce EXPR_TOP to an EXPR_VALUE */
	char *c = Char(Scanner_text(scan));
	if (c[0] == '0' && (c[1] == 'b' || c[1] == 'B')) {
	  /* strtol() doesn't handle binary constants */
	  stack[sp].value = (long) strtol(c + 2, 0, 2);
	} else {
	  stack[sp].value = (long) strtol(c, 0, 0);
	}
	stack[sp].svalue = 0;
	stack[sp].op = EXPR_VALUE;
      } else if ((token == SWIG_TOKEN_MINUS) || (token == SWIG_TOKEN_PLUS) || (token == SWIG_TOKEN_LNOT) || (token == SWIG_TOKEN_NOT)) {
	if (token == SWIG_TOKEN_MINUS)
	  token = OP_UMINUS;
	else if (token == SWIG_TOKEN_PLUS)
	  token = OP_UPLUS;
	stack[sp].value = token;
	stack[sp].op = EXPR_OP;
	sp++;
	stack[sp].op = EXPR_TOP;
      } else if (token == SWIG_TOKEN_LPAREN) {
	stack[sp].op = EXPR_GROUP;
	sp++;
	stack[sp].op = EXPR_TOP;
      } else if (token == SWIG_TOKEN_ENDLINE) {
      } else if (token == SWIG_TOKEN_STRING) {
	stack[sp].svalue = NewString(Scanner_text(scan));
	stack[sp].op = EXPR_VALUE;
      } else if (token == SWIG_TOKEN_ID) {
	int next_token = expr_token(scan);
	if (next_token == SWIG_TOKEN_LPAREN) {
	  /* This is a use of an unknown function-like macro so we emit a
	   * warning.
	   */
	  errmsg = "Use of undefined function-like macro";
	  *error = 1;
	  return 0;
	}
	Scanner_pushtoken(scan, next_token, Scanner_text(scan));

	/* Defined macros have been expanded already so this is an unknown
	 * macro, which gets treated as zero.
	 */
	stack[sp].value = 0;
	stack[sp].svalue = 0;
	stack[sp].op = EXPR_VALUE;
      } else if (token == SWIG_TOKEN_FLOAT || token == SWIG_TOKEN_DOUBLE || token == SWIG_TOKEN_LONGDOUBLE) {
	errmsg = "Floating point constant in preprocessor expression";
	*error = 1;
	return 0;
      } else
	goto syntax_error;
      break;
    case EXPR_VALUE:
      /* A value is on top of the stack.  We may reduce or evaluate depending
       * on what the next token is.
       */
      token = expr_token(scan);
      if (!token) {
	/* End of input. Might have to reduce if an operator is on stack */
	while (sp > 0) {
	  if (stack[sp - 1].op == EXPR_OP) {
	    if (!reduce_op())
	      goto reduce_error;
	  } else if (stack[sp - 1].op == EXPR_GROUP) {
	    errmsg = "Missing \')\'";
	    *error = 1;
	    return 0;
	  } else
	    goto syntax_error;
	}
	return stack[sp].value;
      }
      /* Token must be an operator */
      switch (token) {
      case SWIG_TOKEN_STAR:
      case SWIG_TOKEN_EQUALTO:
      case SWIG_TOKEN_NOTEQUAL:
      case SWIG_TOKEN_PLUS:
      case SWIG_TOKEN_MINUS:
      case SWIG_TOKEN_AND:
      case SWIG_TOKEN_LAND:
      case SWIG_TOKEN_OR:
      case SWIG_TOKEN_LOR:
      case SWIG_TOKEN_XOR:
      case SWIG_TOKEN_LESSTHAN:
      case SWIG_TOKEN_GREATERTHAN:
      case SWIG_TOKEN_LTEQUAL:
      case SWIG_TOKEN_GTEQUAL:
      case SWIG_TOKEN_SLASH:
      case SWIG_TOKEN_PERCENT:
      case SWIG_TOKEN_LSHIFT:
      case SWIG_TOKEN_RSHIFT:
	if ((sp == 0) || (stack[sp - 1].op == EXPR_GROUP)) {
	  /* No possibility of reduce. Push operator and expression */
	  sp++;
	  stack[sp].op = EXPR_OP;
	  stack[sp].value = token;
	  sp++;
	  stack[sp].op = EXPR_TOP;
	} else {
	  if (stack[sp - 1].op != EXPR_OP)
	    goto syntax_error_expected_operator;
	  op = stack[sp - 1].value;	/* Previous operator */

	  /* Now, depending on the precedence relationship between the last operator and the current
	     we will reduce or push */

	  if (prec[op] <= prec[token]) {
	    /* Reduce the previous operator */
	    if (!reduce_op())
	      goto reduce_error;
	  }
	  sp++;
	  stack[sp].op = EXPR_OP;
	  stack[sp].value = token;
	  sp++;
	  stack[sp].op = EXPR_TOP;
	}
	break;
      case SWIG_TOKEN_RPAREN:
	if (sp == 0)
	  goto extra_rparen;

	/* Might have to reduce operators first */
	while ((sp > 0) && (stack[sp - 1].op == EXPR_OP)) {
	  if (!reduce_op())
	    goto reduce_error;
	}
	if ((sp == 0) || (stack[sp - 1].op != EXPR_GROUP))
	  goto extra_rparen;
	stack[sp - 1].op = EXPR_VALUE;
	stack[sp - 1].value = stack[sp].value;
	stack[sp - 1].svalue = stack[sp].svalue;
	sp--;
	break;
      case SWIG_TOKEN_LTEQUALGT:
	goto spaceship_not_allowed;
      default:
	goto syntax_error_expected_operator;
	break;
      }
      break;

    default:
      Printf(stderr, "Internal error in expression evaluator.\n");
      Exit(EXIT_FAILURE);
    }
  }

syntax_error:
  errmsg = "Syntax error";
  *error = 1;
  return 0;

syntax_error_expected_operator:
  errmsg = "Syntax error: expected operator";
  *error = 1;
  return 0;

reduce_error:
  /*  errmsg has been set by reduce_op */
  *error = 1;
  return 0;

extra_rparen:
  errmsg = "Extra \')\'";
  *error = 1;
  return 0;

spaceship_not_allowed:
  errmsg = "Spaceship operator (<=>) not allowed in preprocessor expression";
  *error = 1;
  return 0;
}

/* -----------------------------------------------------------------------------
 * Preprocessor_expr_error()
 *
 * Return error message set by the evaluator (if any)
 * ----------------------------------------------------------------------------- */

const char *Preprocessor_expr_error(void) {
  return errmsg;
}
