/* -----------------------------------------------------------------------------
 * 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;
}
