/*
 * nasd_rpcgen_math.c
 *
 * Parse mathematical expression
 *
 * Author: Jim Zelenka
 */
/*
 * Copyright (c) of Carnegie Mellon University, 1999.
 *
 * Permission to reproduce, use, and prepare derivative works of
 * this software for internal use is granted provided the copyright
 * and "No Warranty" statements are included with all reproductions
 * and derivative works. This software may also be redistributed
 * without charge provided that the copyright and "No Warranty"
 * statements are included in all redistributions.
 *
 * NO WARRANTY. THIS SOFTWARE IS FURNISHED ON AN "AS IS" BASIS.
 * CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER
 * EXPRESSED OR IMPLIED AS TO THE MATTER INCLUDING, BUT NOT LIMITED
 * TO: WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY
 * OF RESULTS OR RESULTS OBTAINED FROM USE OF THIS SOFTWARE. CARNEGIE
 * MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT
 * TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
 */


#include <nasd/nasd_options.h>

#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "nasd_rpcgen.h"

#define OP_NONE   0
#define OP_ADD    1
#define OP_SUB    2
#define OP_MULT   3
#define OP_DIV    4
#define OP_MOD    5
#define OP_OR     6
#define OP_AND    7
#define OP_XOR    8
#define OP_CONST  9

struct expr {
  int                  operand;
  nasd_rpcgen_int64    result_s;
  nasd_rpcgen_uint64   result_u;
  struct expr         *left;
  struct expr         *right;
};

struct expr *
expr_get()
{
  struct expr *expr;

  expr = malloc(sizeof(struct expr));
  if (expr == NULL) {
    fprintf(stderr, "ERROR: out of memory\n");
    fflush(stderr);
    exit(1);
  }
  expr->operand = OP_NONE;
  expr->left = NULL;
  expr->right = NULL;

  return(expr);
}

void
free_expr(
  struct expr  *expr)
{
  if (expr->left)
    free_expr(expr->left);
  if (expr->right)
    free_expr(expr->right);
  free(expr);
}

void
eval_expr(
  struct expr         *expr,
  nasd_rpcgen_int64   *result_s,
  nasd_rpcgen_uint64  *result_u)
{
  nasd_rpcgen_uint64 lru, rru;
  nasd_rpcgen_int64 lrs, rrs;

  if (expr->operand == OP_CONST) {
    *result_s = expr->result_s;
    *result_u = expr->result_u;
    return;
  }

  NASD_ASSERT(expr->left != NULL);
  NASD_ASSERT(expr->right != NULL);

  eval_expr(expr->left, &lrs, &lru);
  eval_expr(expr->right, &rrs, &rru);

  switch(expr->operand) {
    case OP_ADD:
      expr->result_s = lrs + rrs;
      expr->result_u = lru + rru;
      break;
    case OP_SUB:
      expr->result_s = lrs - rrs;
      expr->result_u = lru - rru;
      break;
    case OP_MULT:
      expr->result_s = lrs * rrs;
      expr->result_u = lru * rru;
      break;
    case OP_DIV:
      expr->result_s = lrs / rrs;
      expr->result_u = lru / rru;
      break;
    case OP_MOD:
      expr->result_s = lrs % rrs;
      expr->result_u = lru % rru;
      break;
    case OP_OR:
      expr->result_s = lrs | rrs;
      expr->result_u = lru | rru;
      break;
    case OP_AND:
      expr->result_s = lrs & rrs;
      expr->result_u = lru & rru;
      break;
    case OP_XOR:
      expr->result_s = lrs ^ rrs;
      expr->result_u = lru ^ rru;
      break;
    default:
      NASD_PANIC();
  }

  *result_s = expr->result_s;
  *result_u = expr->result_u;
}

struct expr *
build_expr(
  nasd_rpcgen_fhandle_t   *fhandle,
  char                    *in_str)
{
  int i, paren_level, f, l, opind, operand, ret;
  char *str, *leftstr, *rightstr;
  struct expr *expr;

  /*
   * Precedence:
   *
   *    ()
   *    * / %
   *    + -
   *    &
   *    ^
   *    |
   */

  operand = OP_NONE;

  /* lose leading whitespace */
  str = in_str;
  l = strlen(str);
  for(f=i=0;i<l;i++) {
    if (!isspace(str[i])) {
      f = i;
      break;
    }
  }
  if (f >= l) {
    fprintf(pserr, "%s:%d  empty expression\n",
      fhandle->cur_filename, fhandle->cur_line);
    parse_error();
    return(NULL);
  }
  str = &in_str[f];

  /* lose trailing whitespace */
  l = strlen(str);
  while((l > 0) && (isspace(str[l-1])))
    l--;
  if (l <= 0) {
    fprintf(pserr, "%s:%d  empty expression\n",
      fhandle->cur_filename, fhandle->cur_line);
    parse_error();
    return(NULL);
  }
  str[l] = '\0';

  /* lose bracketing parens */
  while (l && (str[0] == '(') && (str[l-1] == ')')) {
    str = &str[1];
    l -= 2;
    str[l] = '\0';
  }
  if (l <= 0) {
    fprintf(pserr, "%s:%d  empty expression\n",
      fhandle->cur_filename, fhandle->cur_line);
    parse_error();
    return(NULL);
  }



  /*
   *    |
   */
  paren_level = 0;
  opind = (-1);
  for(i=0;((i<l)&&(opind<0));i++) {
    switch(str[i]) {
      case '(':
        paren_level++;
        break;
      case ')':
        paren_level--;
        break;
      case '|':
        operand = OP_OR;
        if (paren_level == 0)
          opind = i;
        break;
      default:
        break;
    }
  }
  NASD_ASSERT(opind != 0);
  if (opind > 0) {
    if ((opind+1) >= l) {
      fprintf(pserr, "%s:%d  unterminated expression (right-hand missing)\n",
        fhandle->cur_filename, fhandle->cur_line);
      parse_error();
      return(NULL);
    }
    /* got a valid expr */
    leftstr = str;
    rightstr = &str[opind+1];
    str[opind] = '\0';
    expr = expr_get();
    expr->operand = operand;
    expr->left = build_expr(fhandle, leftstr);
    if (expr->left == NULL) {
      free_expr(expr);
      return(NULL);
    }
    expr->right = build_expr(fhandle, rightstr);
    if (expr->right == NULL) {
      free_expr(expr);
      return(NULL);
    }
    return(expr);
  }


  /*
   *    ^
   */
  paren_level = 0;
  opind = (-1);
  for(i=0;((i<l)&&(opind<0));i++) {
    switch(str[i]) {
      case '(':
        paren_level++;
        break;
      case ')':
        paren_level--;
        break;
      case '^':
        operand = OP_XOR;
        if (paren_level == 0)
          opind = i;
        break;
      default:
        break;
    }
  }
  NASD_ASSERT(opind != 0);
  if (opind > 0) {
    if ((opind+1) >= l) {
      fprintf(pserr, "%s:%d  unterminated expression (right-hand missing)\n",
        fhandle->cur_filename, fhandle->cur_line);
      parse_error();
      return(NULL);
    }
    /* got a valid expr */
    leftstr = str;
    rightstr = &str[opind+1];
    str[opind] = '\0';
    expr = expr_get();
    expr->operand = operand;
    expr->left = build_expr(fhandle, leftstr);
    if (expr->left == NULL) {
      free_expr(expr);
      return(NULL);
    }
    expr->right = build_expr(fhandle, rightstr);
    if (expr->right == NULL) {
      free_expr(expr);
      return(NULL);
    }
    return(expr);
  }



  /*
   *    &
   */
  paren_level = 0;
  opind = (-1);
  for(i=0;((i<l)&&(opind<0));i++) {
    switch(str[i]) {
      case '(':
        paren_level++;
        break;
      case ')':
        paren_level--;
        break;
      case '&':
        operand = OP_AND;
        if (paren_level == 0)
          opind = i;
        break;
      default:
        break;
    }
  }
  NASD_ASSERT(opind != 0);
  if (opind > 0) {
    if ((opind+1) >= l) {
      fprintf(pserr, "%s:%d  unterminated expression (right-hand missing)\n",
        fhandle->cur_filename, fhandle->cur_line);
      parse_error();
      return(NULL);
    }
    /* got a valid expr */
    leftstr = str;
    rightstr = &str[opind+1];
    str[opind] = '\0';
    expr = expr_get();
    expr->operand = operand;
    expr->left = build_expr(fhandle, leftstr);
    if (expr->left == NULL) {
      free_expr(expr);
      return(NULL);
    }
    expr->right = build_expr(fhandle, rightstr);
    if (expr->right == NULL) {
      free_expr(expr);
      return(NULL);
    }
    return(expr);
  }



  /*
   *    + -
   */
  paren_level = 0;
  opind = (-1);
  for(i=0;((i<l)&&(opind<0));i++) {
    switch(str[i]) {
      case '(':
        paren_level++;
        break;
      case ')':
        paren_level--;
        break;
      case '+':
        operand = OP_ADD;
        if (paren_level == 0)
          opind = i;
        break;
      case '-':
        operand = OP_SUB;
        if (paren_level == 0)
          opind = i;
        break;
      default:
        break;
    }
  }
  NASD_ASSERT(opind != 0);
  if (opind > 0) {
    if ((opind+1) >= l) {
      fprintf(pserr, "%s:%d  unterminated expression (right-hand missing)\n",
        fhandle->cur_filename, fhandle->cur_line);
      parse_error();
      return(NULL);
    }
    /* got a valid expr */
    leftstr = str;
    rightstr = &str[opind+1];
    str[opind] = '\0';
    expr = expr_get();
    expr->operand = operand;
    expr->left = build_expr(fhandle, leftstr);
    if (expr->left == NULL) {
      free_expr(expr);
      return(NULL);
    }
    expr->right = build_expr(fhandle, rightstr);
    if (expr->right == NULL) {
      free_expr(expr);
      return(NULL);
    }
    return(expr);
  }



  /*
   *    * / %
   */

  paren_level = 0;
  opind = (-1);
  for(i=0;((i<l)&&(opind<0));i++) {
    switch(str[i]) {
      case '(':
        paren_level++;
        break;
      case ')':
        paren_level--;
        break;
      case '*':
        operand = OP_MULT;
        if (paren_level == 0)
          opind = i;
        break;
      case '/':
        operand = OP_DIV;
        if (paren_level == 0)
          opind = i;
        break;
      case '%':
        operand = OP_MOD;
        if (paren_level == 0)
          opind = i;
        break;
      default:
        break;
    }
  }
  NASD_ASSERT(opind != 0);
  if (opind > 0) {
    if ((opind+1) >= l) {
      fprintf(pserr, "%s:%d  unterminated expression (right-hand missing)\n",
        fhandle->cur_filename, fhandle->cur_line);
      parse_error();
      return(NULL);
    }
    /* got a valid expr */
    leftstr = str;
    rightstr = &str[opind+1];
    str[opind] = '\0';
    expr = expr_get();
    expr->operand = operand;
    expr->left = build_expr(fhandle, leftstr);
    if (expr->left == NULL) {
      free_expr(expr);
      return(NULL);
    }
    expr->right = build_expr(fhandle, rightstr);
    if (expr->right == NULL) {
      free_expr(expr);
      return(NULL);
    }
    return(expr);
  }

  /*
   * Whatever we have left should look like a constant
   */
  expr = expr_get();
  expr->operand = OP_CONST;
  ret = parse_val(fhandle, str, NULL, 0, &expr->result_s, &expr->result_u);
  if (ret) {
    fprintf(pserr, "%s:%d  cannot evaluate VAL \"%s\"\n",
      fhandle->cur_filename, fhandle->cur_line, str);
    parse_error();
    free_expr(expr);
    return(NULL);
  }
  return(expr);
}

int
parse_func_math(
  nasd_rpcgen_fhandle_t   *fhandle,
  char                    *str,
  char                   **extra_words,
  int                      extra_word_count,
  nasd_rpcgen_int64       *result_s,
  nasd_rpcgen_uint64      *result_u)
{
  struct expr *expr;

  if (extra_word_count) {
    fprintf(pserr, "%s:%d  ignoring %d extra \"words\"\n",
      fhandle->cur_filename, fhandle->cur_line, extra_word_count);
    parse_error();
  }

  expr = build_expr(fhandle, str);

  if (expr == NULL) {
    fprintf(pserr, "%s:%d  invalid MATH expression\n",
      fhandle->cur_filename, fhandle->cur_line);
    parse_error();
    return(NASD_RPCGEN_BAD_EXPR);
  }

  eval_expr(expr, result_s, result_u);

  free_expr(expr);

  return(NASD_RPCGEN_OK);
}

/* Local Variables:  */
/* indent-tabs-mode: nil */
/* tab-width: 2 */
/* End: */
