/*
        eoperators.c: this file contains all of the operators called from
        inside eval_instruction() in interpret.c.
*/

#include "base/package_api.h"

#include "packages/ops/parse.h"

void f_and() {
  if (sp->type == T_ARRAY && (sp - 1)->type == T_ARRAY) {
    sp--;
    sp->u.arr = intersect_array((sp + 1)->u.arr, sp->u.arr);
    return;
  }
  CHECK_TYPES(sp - 1, T_NUMBER, 1, F_AND);
  CHECK_TYPES(sp, T_NUMBER, 2, F_AND);
  sp--;
  sp->u.number &= (sp + 1)->u.number;
  sp->subtype = 0;
}

void f_and_eq() {
  svalue_t *argp;

  argp = (sp--)->u.lvalue;

  if (argp->type == T_ARRAY && sp->type == T_ARRAY) {
    sp->u.arr = argp->u.arr = intersect_array(argp->u.arr, sp->u.arr);
    sp->u.arr->ref++; /* since we put it in two places */
    return;
  }
  if (argp->type != T_NUMBER) {
    error("Bad left type to &=\n");
  }
  if (sp->type != T_NUMBER) {
    error("Bad right type to &=\n");
  }
  sp->u.number = argp->u.number &= sp->u.number;
  sp->subtype = 0;
}

void f_div_eq() {
  svalue_t *argp = (sp--)->u.lvalue;

  switch (argp->type | sp->type) {
    case T_NUMBER: {
      if (!sp->u.number) {
        error("Division by 0nn\n");
      }
      sp->u.number = argp->u.number /= sp->u.number;
      sp->subtype = 0;
      break;
    }

    case T_REAL: {
      if (sp->u.real == 0.0) {
        error("Division by 0rr\n");
      }
      sp->u.real = argp->u.real /= sp->u.real;
      break;
    }

    case T_NUMBER | T_REAL: {
      if (sp->type == T_NUMBER) {
        if (!sp->u.number) {
          error("Division by 0rn\n");
        }
        sp->u.real = argp->u.real /= sp->u.number;
        sp->type = T_REAL;
      } else {
        if (sp->u.real == 0.0) {
          error("Division by 0nr\n");
        }
        sp->u.real = argp->u.number /= sp->u.real;
      }
      break;
    }

    default: {
      if (!(sp->type & (T_NUMBER | T_REAL))) {
        error("Bad right type to /=\n");
      } else {
        error("Bad left type to /=\n");
      }
    }
  }
}

void f_eq() {
  int i;

  switch (sp->type | (sp - 1)->type) {
    case T_NUMBER: {
      --sp;
      sp->u.number = sp->u.number == (sp + 1)->u.number;
      sp->subtype = 0;
      return;
    }

    case T_REAL: {
      --sp;
      sp->type = T_NUMBER;
      sp->u.number = sp->u.real == (sp + 1)->u.real;
      sp->subtype = 0;
      return;
    }

    case T_NUMBER | T_REAL: {
      if ((--sp)->type == T_NUMBER) {
        sp->u.number = sp->u.number == (sp + 1)->u.real;
      } else {
        sp->u.number = sp->u.real == (sp + 1)->u.number;
        sp->type = T_NUMBER;
      }
      sp->subtype = 0;
      return;
    }

    case T_ARRAY: {
      i = (sp - 1)->u.arr == sp->u.arr;
      free_array((sp--)->u.arr);
      free_array(sp->u.arr);
      break;
    }

    case T_CLASS: {
      i = (sp - 1)->u.arr == sp->u.arr;
      free_class((sp--)->u.arr);
      free_class(sp->u.arr);
      break;
    }

    case T_MAPPING: {
      i = (sp - 1)->u.map == sp->u.map;
      free_mapping((sp--)->u.map);
      free_mapping(sp->u.map);
      break;
    }

    case T_STRING: {
      if (SVALUE_STRLEN_DIFFERS(sp - 1, sp)) {
        i = 0;
      } else {
        i = !strcmp((sp - 1)->u.string, sp->u.string);
      }
      free_string_svalue(sp--);
      free_string_svalue(sp);
      break;
    }

    case T_OBJECT: {
      i = (sp - 1)->u.ob == sp->u.ob;
      free_object(&(sp--)->u.ob, "f_eq: 1");
      free_object(&sp->u.ob, "f_eq: 2");
      break;
    }

    case T_FUNCTION: {
      i = (sp - 1)->u.fp == sp->u.fp;
      free_funp((sp--)->u.fp);
      free_funp(sp->u.fp);
      break;
    }
    case T_BUFFER: {
      i = (sp - 1)->u.buf == sp->u.buf;
      free_buffer((sp--)->u.buf);
      free_buffer(sp->u.buf);
      break;
    }
    default:
      pop_stack();
      free_svalue(sp, "f_eq");
      i = 0;
  }
  /* args are freed, stack pointer points to spot for return value */
  put_number(i);
}

void f_ge() {
  int i = sp->type;
  switch ((--sp)->type | i) {
    case T_NUMBER:
      sp->u.number = sp->u.number >= (sp + 1)->u.number;
      sp->subtype = 0;
      break;
    case T_REAL:
      i = sp->u.real >= (sp + 1)->u.real;
      put_number(i);
      break;
    case T_NUMBER | T_REAL:
      if (i == T_NUMBER) {
        sp->type = T_NUMBER;
        sp->u.number = sp->u.real >= (sp + 1)->u.number;
      } else {
        sp->u.number = sp->u.number >= (sp + 1)->u.real;
      }
      sp->subtype = 0;
      break;
    case T_STRING:
      i = strcmp(sp->u.string, (sp + 1)->u.string) >= 0;
      free_string_svalue(sp + 1);
      free_string_svalue(sp);
      put_number(i);
      break;
    default: {
      switch ((sp++)->type) {
        case T_NUMBER:
        case T_REAL:
          bad_argument(sp, T_NUMBER | T_REAL, 2, F_GE);
        case T_STRING:
          bad_argument(sp, T_STRING, 2, F_GE);
        default:
          bad_argument(sp - 1, T_NUMBER | T_STRING | T_REAL, 1, F_GE);
      }
    }
  }
}

void f_gt() {
  int i = sp->type;
  switch ((--sp)->type | i) {
    case T_NUMBER:
      sp->u.number = sp->u.number > (sp + 1)->u.number;
      sp->subtype = 0;
      break;
    case T_REAL:
      sp->u.number = sp->u.real > (sp + 1)->u.real;
      sp->type = T_NUMBER;
      sp->subtype = 0;
      break;
    case T_NUMBER | T_REAL:
      if (i == T_NUMBER) {
        sp->type = T_NUMBER;
        sp->u.number = sp->u.real > (sp + 1)->u.number;
      } else {
        sp->u.number = sp->u.number > (sp + 1)->u.real;
      }
      sp->subtype = 0;
      break;
    case T_STRING:
      i = strcmp(sp->u.string, (sp + 1)->u.string) > 0;
      free_string_svalue(sp + 1);
      free_string_svalue(sp);
      put_number(i);
      break;
    default: {
      switch ((sp++)->type) {
        case T_NUMBER:
        case T_REAL:
          bad_argument(sp, T_NUMBER | T_REAL, 2, F_GT);
        case T_STRING:
          bad_argument(sp, T_STRING, 2, F_GT);
        default:
          bad_argument(sp - 1, T_NUMBER | T_REAL | T_STRING, 1, F_GT);
      }
    }
  }
}

void f_le() {
  int i = sp->type;
  switch ((--sp)->type | i) {
    case T_NUMBER:
      sp->u.number = sp->u.number <= (sp + 1)->u.number;
      break;

    case T_REAL:
      sp->u.number = sp->u.real <= (sp + 1)->u.real;
      sp->type = T_NUMBER;
      break;

    case T_NUMBER | T_REAL:
      if (i == T_NUMBER) {
        sp->type = T_NUMBER;
        sp->u.number = sp->u.real <= (sp + 1)->u.number;
      } else {
        sp->u.number = sp->u.number <= (sp + 1)->u.real;
      }
      break;

    case T_STRING:
      i = strcmp(sp->u.string, (sp + 1)->u.string) <= 0;
      free_string_svalue(sp + 1);
      free_string_svalue(sp);
      sp->type = T_NUMBER;
      sp->u.number = i;
      break;

    default: {
      switch ((sp++)->type) {
        case T_NUMBER:
        case T_REAL:
          bad_argument(sp, T_NUMBER | T_REAL, 2, F_LE);

        case T_STRING:
          bad_argument(sp, T_STRING, 2, F_LE);

        default:
          bad_argument(sp - 1, T_NUMBER | T_STRING | T_REAL, 1, F_LE);
      }
    }
  }
  sp->subtype = 0;
}

void f_lt() {
  int i = sp->type;
  switch (i | (--sp)->type) {
    case T_NUMBER:
      sp->u.number = sp->u.number < (sp + 1)->u.number;
      break;
    case T_REAL:
      sp->u.number = sp->u.real < (sp + 1)->u.real;
      sp->type = T_NUMBER;
      break;
    case T_NUMBER | T_REAL:
      if (i == T_NUMBER) {
        sp->type = T_NUMBER;
        sp->u.number = sp->u.real < (sp + 1)->u.number;
      } else {
        sp->u.number = sp->u.number < (sp + 1)->u.real;
      }
      break;
    case T_STRING:
      i = (strcmp(sp->u.string, (sp + 1)->u.string) < 0);
      free_string_svalue(sp + 1);
      free_string_svalue(sp);
      sp->type = T_NUMBER;
      sp->u.number = i;
      break;
    default:
      switch ((sp++)->type) {
        case T_NUMBER:
        case T_REAL:
          bad_argument(sp, T_NUMBER | T_REAL, 2, F_LT);
          break;
        case T_STRING:
          bad_argument(sp, T_STRING, 2, F_LT);
          break;
        default:
          bad_argument(sp - 1, T_NUMBER | T_STRING | T_REAL, 1, F_LT);
          break;
      }
  }
  sp->subtype = 0;
}

void f_lsh() {
  CHECK_TYPES((sp - 1), T_NUMBER, 1, F_LSH);
  CHECK_TYPES(sp, T_NUMBER, 2, F_LSH);
  sp--;
  sp->u.number <<= (sp + 1)->u.number;
}

void f_lsh_eq() {
  svalue_t *argp;

  if ((argp = sp->u.lvalue)->type != T_NUMBER) {
    error("Bad left type to <<=\n");
  }
  if ((--sp)->type != T_NUMBER) {
    error("Bad right type to <<=\n");
  }
  sp->u.number = argp->u.number <<= sp->u.number;
  sp->subtype = 0;
}

void f_mod_eq() {
  svalue_t *argp;

  if ((argp = sp->u.lvalue)->type != T_NUMBER) {
    error("Bad left type to %%=\n");
  }
  if ((--sp)->type != T_NUMBER) {
    error("Bad right type to %%=\n");
  }
  if (sp->u.number == 0) {
    error("Modulo by 0\n");
  }
  sp->u.number = argp->u.number %= sp->u.number;
  sp->subtype = 0;
}

void f_mult_eq() {
  svalue_t *argp = (sp--)->u.lvalue;

  switch (argp->type | sp->type) {
    case T_NUMBER: {
      sp->u.number = argp->u.number *= sp->u.number;
      sp->subtype = 0;
      break;
    }

    case T_REAL: {
      sp->u.real = argp->u.real *= sp->u.real;
      break;
    }

    case T_NUMBER | T_REAL: {
      if (sp->type == T_NUMBER) {
        sp->type = T_REAL;
        sp->u.real = argp->u.real *= sp->u.number;
      } else {
        sp->u.real = argp->u.number *= sp->u.real;
      }
      break;
    }

    case T_MAPPING: {
      mapping_t *m = compose_mapping(argp->u.map, sp->u.map, 0);
      if (argp->u.map != sp->u.map) {
        pop_stack();
        push_mapping(m);
      }
      break;
    }

    default: {
      if (!(sp->type & (T_NUMBER | T_REAL | T_MAPPING))) {
        error("Bad right type to *=\n");
      } else {
        error("Bad left type to *=\n");
      }
    }
  }
}

void f_ne() {
  int i;

  switch (sp->type | (sp - 1)->type) {
    case T_NUMBER: {
      --sp;
      sp->u.number = sp->u.number != (sp + 1)->u.number;
      sp->subtype = 0;
      return;
    }

    case T_REAL: {
      --sp;
      sp->type = T_NUMBER;
      sp->u.number = sp->u.real != (sp + 1)->u.real;
      sp->subtype = 0;
      return;
    }

    case T_NUMBER | T_REAL: {
      if ((--sp)->type == T_NUMBER) {
        sp->u.number = sp->u.number != (sp + 1)->u.real;
      } else {
        sp->u.number = sp->u.real != (sp + 1)->u.number;
        sp->type = T_NUMBER;
      }
      sp->subtype = 0;
      return;
    }

    case T_ARRAY: {
      i = (sp - 1)->u.arr != sp->u.arr;
      free_array((sp--)->u.arr);
      free_array(sp->u.arr);
      break;
    }

    case T_CLASS: {
      i = (sp - 1)->u.arr != sp->u.arr;
      free_class((sp--)->u.arr);
      free_class(sp->u.arr);
      break;
    }

    case T_MAPPING: {
      i = (sp - 1)->u.map != sp->u.map;
      free_mapping((sp--)->u.map);
      free_mapping(sp->u.map);
      break;
    }

    case T_STRING: {
      if (SVALUE_STRLEN_DIFFERS(sp - 1, sp)) {
        i = 1;
      } else {
        i = !!strcmp((sp - 1)->u.string, sp->u.string);
      }
      free_string_svalue(sp--);
      free_string_svalue(sp);
      break;
    }

    case T_OBJECT: {
      i = (sp - 1)->u.ob != sp->u.ob;
      free_object(&(sp--)->u.ob, "f_ne: 1");
      free_object(&sp->u.ob, "f_ne: 2");
      break;
    }

    case T_FUNCTION: {
      i = (sp - 1)->u.fp != sp->u.fp;
      free_funp((sp--)->u.fp);
      free_funp(sp->u.fp);
      break;
    }

    case T_BUFFER: {
      i = (sp - 1)->u.buf != sp->u.buf;
      free_buffer((sp--)->u.buf);
      free_buffer(sp->u.buf);
      break;
    }

    default:
      pop_stack();
      free_svalue(sp, "f_ne");
      i = 1;
  }
  sp->type = T_NUMBER;
  sp->subtype = 0;
  sp->u.number = i;
}

void f_or() {
  if (sp->type == T_ARRAY && (sp - 1)->type == T_ARRAY) {
    sp--;
    sp->u.arr = union_array(sp->u.arr, (sp + 1)->u.arr);
    return;
  }
  CHECK_TYPES((sp - 1), T_NUMBER, 1, F_OR);
  CHECK_TYPES(sp, T_NUMBER, 2, F_OR);
  sp--;
  sp->u.number |= (sp + 1)->u.number;
}

void f_or_eq() {
  svalue_t *argp;

  argp = (sp--)->u.lvalue;
  if (argp->type == T_ARRAY && sp->type == T_ARRAY) {
    argp->u.arr = sp->u.arr = union_array(argp->u.arr, sp->u.arr);
    sp->u.arr->ref++; /* because we put it in two places */
    return;
  }

  if (argp->type != T_NUMBER) {
    error("Bad left type to |=\n");
  }
  if (sp->type != T_NUMBER) {
    error("Bad right type to |=\n");
  }
  sp->u.number = argp->u.number |= sp->u.number;
  sp->subtype = 0;
}

void f_parse_command() {
  svalue_t *arg;
  svalue_t *fp;
  int i;
  int num_arg;

  /*
   * get number of lvalue args
   */
  num_arg = EXTRACT_UCHAR(pc);
  pc++;

  /*
   * type checking on first three required parameters to parse_command()
   */
  arg = sp - 2;
  CHECK_TYPES(&arg[0], T_STRING, 1, F_PARSE_COMMAND);
  CHECK_TYPES(&arg[1], T_OBJECT | T_ARRAY, 2, F_PARSE_COMMAND);
  CHECK_TYPES(&arg[2], T_STRING, 3, F_PARSE_COMMAND);

  /*
   * allocate stack frame for rvalues and return value (number of matches);
   * perform some stack manipulation;
   */
  fp = sp;
  CHECK_STACK_OVERFLOW(num_arg + 1);
  sp += num_arg + 1;
  arg = sp;
  *(arg--) = *(fp--); /* move pattern to top of stack */
  *(arg--) = *(fp--); /* move source object or array to just below
                           the pattern */
  *(arg) = *(fp);     /* move source string just below the object */
  fp->type = T_NUMBER;

  /*
   * prep area for rvalues
   */
  for (i = 1; i <= num_arg; i++) {
    fp[i].type = T_INVALID;
  }

  /*
   * do it...
   */
  i = parse(arg[0].u.string, &arg[1], arg[2].u.string, &fp[1], num_arg);

  /*
   * remove mandatory parameters
   */
  pop_3_elems();

  /*
   * save return value on stack
   */
  fp->u.number = i;
  fp->subtype = 0;
}

void f_range(int code) {
  if ((sp - 2)->type != T_NUMBER) {
    error("Start of range [ .. ] interval must be a number.\n");
  }
  if ((sp - 1)->type != T_NUMBER) {
    error("End of range [ .. ] interval must be a number.\n");
  }

  switch (sp->type) {
    case T_STRING: {
      int32_t from, to;
      int32_t const len = SVALUE_STRLEN(sp);

      EGCSmartIterator iter(sp->u.string, len);
      if (!iter.ok()) {
        error("Invalid UTF-8 string: f_range");
      }

      to = (sp - 1)->u.number;
      from = (sp - 2)->u.number;

      if (CONFIG_INT(__RC_OLD_RANGE_BEHAVIOR__)) {
        if (to < 0 && !(code & 0x01)) {
          code |= 0x01;
          to = to * -1;
        }
        if (from < 0 && !(code & 0x10)) {
          code |= 0x10;
          from = from * -1;
        }
      }

      // Notes:
      // ranges with indexes which are out of bounds do not give an error.
      // Instead if a maximal subrange can be found within the range requested
      // that lies within the bounds of the indexed value, it will be used.
      if (code & 0x01) {
        if (to <= 0) {
          to = len;
        } else {
          to = iter.post_index_to_offset(-1 * to);
        }
      } else {
        if (to < 0) {
          to = -1;
        } else {
          auto pos = iter.post_index_to_offset(to);
          if (pos > 0) {
            to = pos;
          } else if (pos == -1 && to >= iter.count()) {
            to = len;
          } else {
            to = -1;
          }
        }
      }

      if (code & 0x10) {
        if (from <= 0) {
          from = len;
        } else {
          from = iter.index_to_offset(-1 * from);
          if (from < 0) from = 0;
        }
      } else {
        if (from < 0) {
          from = 0;
        } else {
          from = iter.index_to_offset(from);
        }
      }

      if ((from < 0 || to < 0) /* invalid range */
          || (to < from)       /* empty range */
      ) {
        pop_3_elems();
        push_constant_string("");
        return;
      }
      char *tmp = new_string(to - from, "f_range");
      memcpy(tmp, iter.data() + from, to - from);
      tmp[to - from] = '\0';

      pop_3_elems();
      push_malloced_string(tmp);
      break;
    }
    case T_BUFFER: {
      int from, to, len;

      buffer_t *rbuf = sp->u.buf;

      len = rbuf->size;
      to = (--sp)->u.number;
      if (code & 0x01) {
        to = len - to;
      }
      if (CONFIG_INT(__RC_OLD_RANGE_BEHAVIOR__)) {
        if (to < 0) {
          to += len;
        }
      }
      from = (--sp)->u.number;
      if (code & 0x10) {
        from = len - from;
      }
      if (CONFIG_INT(__RC_OLD_RANGE_BEHAVIOR__)) {
        if (from < 0) {
          if ((from += len) < 0) {
            from = 0;
          }
        }
      } else {
        if (from < 0) {
          from = 0;
        }
      }
      if (to < from || from >= len) {
        free_buffer(rbuf);
        put_buffer(null_buffer());
        return;
      }
      if (to >= len) {
        to = len - 1;
      }
      {
        buffer_t *nbuf = allocate_buffer(to - from + 1);
        memcpy(nbuf->item, rbuf->item + from, to - from + 1);
        free_buffer(rbuf);
        put_buffer(nbuf);
      }
      break;
    }

    case T_ARRAY: {
      int from, to;

      array_t *v = sp->u.arr;
      to = (--sp)->u.number;
      if (code & 0x01) {
        to = v->size - to;
      }
      from = (--sp)->u.number;
      if (code & 0x10) {
        from = v->size - from;
      }
      put_array(slice_array(v, from, to));
      break;
    }

    default:
      error("Cannot index type '%s' using [ .. ] operator.\n", type_name(sp->type));
  }
}

void f_extract_range(int code) {
  if ((sp - 1)->type != T_NUMBER) {
    error("Start of range [ .. ] interval must be a number.\n");
  }

  switch (sp->type) {
    case T_STRING: {
      int32_t from;

      EGCSmartIterator iter(sp->u.string, SVALUE_STRLEN(sp));
      if (!iter.ok()) {
        error("Invalid UTF-8 String: f_extract_range.");
      }
      from = (--sp)->u.number;
      if (CONFIG_INT(__RC_OLD_RANGE_BEHAVIOR__)) {
        if (!code && from < 0) {
          code = 1;
          from = -1 * from;
        }
      }

      int offset;
      if (code) {
        offset = from <= 0 ? -1 : iter.index_to_offset(-1 * from);
      } else {
        offset = from < 0 ? 0 : iter.index_to_offset(from);
      }
      if (code && from > 0 && offset < 0) {
        if (from > (int32_t)iter.count()) {
          offset = 0;
        }
      }
      if (offset < 0) {
        sp->type = T_STRING;
        sp->subtype = STRING_CONSTANT;
        sp->u.string = "";
      } else {
        put_malloced_string(string_copy(iter.data() + offset, "f_extract_range"));
      }
      free_string_svalue(sp + 1);
      break;
    }
    case T_BUFFER: {
      int32_t from;
      size_t len;

      buffer_t *rbuf = sp->u.buf;
      buffer_t *nbuf;

      len = rbuf->size;
      from = (--sp)->u.number;
      if (code) {
        from = len - from;
      }
      if (CONFIG_INT(__RC_OLD_RANGE_BEHAVIOR__)) {
        if (from < 0) {
          if ((from += len) < 0) {
            from = 0;
          }
        }
      } else {
        if (from < 0) {
          from = 0;
        }
      }
      if (from > len) {
        from = len;
      }
      nbuf = allocate_buffer(len - from);
      memcpy(nbuf->item, rbuf->item + from, len - from);
      free_buffer(rbuf);
      put_buffer(nbuf);
      break;
    }

    case T_ARRAY: {
      size_t from;

      array_t *v = sp->u.arr;
      from = (--sp)->u.number;
      if (code) {
        from = v->size - from;
      }
      put_array(slice_array(v, from, v->size - 1));
      break;
    }

    default:
      error("Bad argument to [ .. ] range operator.\n");
  }
}

void f_rsh() {
  CHECK_TYPES((sp - 1), T_NUMBER, 1, F_RSH);
  CHECK_TYPES(sp, T_NUMBER, 2, F_RSH);
  sp--;
  sp->u.number >>= (sp + 1)->u.number;
}

void f_rsh_eq() {
  svalue_t *argp;

  if ((argp = sp->u.lvalue)->type != T_NUMBER) {
    error("Bad left type to >>=\n");
  }
  if ((--sp)->type != T_NUMBER) {
    error("Bad right type to >>=\n");
  }
  sp->u.number = argp->u.number >>= sp->u.number;
  sp->subtype = 0;
}

void f_sub_eq() {
  svalue_t *argp = (sp--)->u.lvalue;

  switch (argp->type | sp->type) {
    case T_NUMBER: {
      sp->u.number = argp->u.number -= sp->u.number;
      sp->subtype = 0;
      break;
    }

    case T_REAL: {
      sp->u.real = argp->u.real -= sp->u.real;
      break;
    }

    case T_NUMBER | T_REAL: {
      if (sp->type == T_NUMBER) {
        sp->type = T_REAL;
        sp->u.real = argp->u.real -= sp->u.number;
      } else {
        sp->u.real = argp->u.number -= sp->u.real;
      }
      break;
    }

    case T_ARRAY: {
      sp->u.arr = argp->u.arr = subtract_array(argp->u.arr, sp->u.arr);
      sp->u.arr->ref++;
      break;
    }

    case T_LVALUE_BYTE | T_NUMBER: {
      char c;

      c = *global_lvalue_byte.u.lvalue_byte - sp->u.number;

      if (global_lvalue_byte.subtype == 0 && c == '\0') {
        error("Strings cannot contain 0 bytes.\n");
      }
      *global_lvalue_byte.u.lvalue_byte = c;
      break;
    }

    default: {
      if (!(sp->type & (T_NUMBER | T_REAL | T_ARRAY))) {
        error("Bad right type to -=\n");
      } else if (!(argp->type & (T_NUMBER | T_REAL | T_ARRAY))) {
        error("Bad left type to -=\n");
      } else {
        error("Arguments to -= do not match in type.\n");
      }
    }
  }
}

/*
 * Structure of F_SWITCH:
 *   table type (1 byte)
 *   address of table (1 short)
 *   address of break (1 short)
 *   address of default (1 short)
 *     then all the switch code
 *   switch table (varies)
 *
 * Table type is either
 *   0xfe  - integer labels, direct lookup.
 *           Table is followed by 1 int that is minimum key value.
 *           Each table entry is a short address to jump to.
 *   0xfN  - integer labels.  N is size as a power of 2.
 *           Each table entry is 1 LPC_INT (key) followed by 1 short (address).
 *   0xNf  - string labels.  Otherwise same as for integer labels.
 *
 * For normal string or integer tables, if the address is 0 or 1,
 * the key is the lower end of a range, and the upper end is in
 * the next entry.  If it's a 0, the second address indicates a
 * direct lookup table (currently this case is never generated by
 * the compiler).  If it's a 1, the second address is used for
 * all keys in the range (corresponds to 'case x..y:' labels).
 *
 * Binary search is used on the normal tables.
 */

#define SWITCH_CASE_SIZE (sizeof(LPC_INT) + sizeof(short))

/* offsets from 'pc' */
enum { SW_TYPE = 0, SW_TABLE = 1, SW_ENDTAB = 3, SW_DEFAULT = 5 };

/* offsets used for range (L_ for lower member, U_ for upper member) */
enum { L_LOWER = 0 };
#define L_TYPE (sizeof(LPC_INT))
#define L_UPPER (SWITCH_CASE_SIZE)
#define L_ADDR (SWITCH_CASE_SIZE + sizeof(LPC_INT))
#define U_LOWER (-SWITCH_CASE_SIZE)
#define U_TYPE (-SWITCH_CASE_SIZE + sizeof(LPC_INT))
enum { U_UPPER = 0 };
#define U_ADDR (sizeof(LPC_INT))

// FIXME: The variable naming scheme is horrible, need to
// read again and rename what i, d, s, r to something meaningful.
void f_switch() {
  unsigned short offset, end_off;
  LPC_INT i, d, s, r;
  char *l, *end_tab;
  static unsigned short const off_tab[] = {
      0 * SWITCH_CASE_SIZE,    1 * SWITCH_CASE_SIZE,    3 * SWITCH_CASE_SIZE,
      7 * SWITCH_CASE_SIZE,    15 * SWITCH_CASE_SIZE,   31 * SWITCH_CASE_SIZE,
      63 * SWITCH_CASE_SIZE,   127 * SWITCH_CASE_SIZE,  255 * SWITCH_CASE_SIZE,
      511 * SWITCH_CASE_SIZE,  1023 * SWITCH_CASE_SIZE, 2047 * SWITCH_CASE_SIZE,
      4095 * SWITCH_CASE_SIZE,
  };

  COPY_SHORT(&offset, pc + SW_TABLE);
  COPY_SHORT(&end_off, pc + SW_ENDTAB);

  if ((i = EXTRACT_UCHAR(pc) >> 4) != 0xf) {
    /* String table, find correct key */
    if (sp->type == T_NUMBER && !sp->u.number) {
      /* special case: 0 as a string */
      s = 0;
      sp--;
    } else if (sp->type == T_STRING) {
      if (sp->subtype == STRING_SHARED) {
        s = ((POINTER_INT)sp->u.string);
        free_string(sp->u.string);
        sp--;
      } else {
        s = ((POINTER_INT)findstring(sp->u.string));
        free_string_svalue(sp--);
      }
      if (s == 0) {
        /*
         * Take default case now - else we could be get confused with
         * ZERO_AS_STR_CASE_LABEL.
         */
        COPY_SHORT(&offset, pc + SW_DEFAULT);
        pc += offset;
        return;
      }
    } else {
      bad_argument(sp, T_STRING, 1, F_SWITCH);
    }
  } else { /* Integer table, check type */
    CHECK_TYPES(sp, T_NUMBER, 1, F_SWITCH);
    s = (sp--)->u.number;
    i = pc[0] & 0xf;
  }
  end_tab = pc + end_off;
  /*
   * i is the table size as a power of 2.  Tells us where to start
   * searching.  i==14 is a special case.
   */

  if (i >= 13) {
    if (i == 0xe) {
      char *zz = end_tab - sizeof(LPC_INT);
      /* fastest switch format : lookup table */
      l = pc + offset;
      COPY_INT(&d, zz);
      /* d is minimum value - see if in range or not */
      s -= d;
      if (s >= 0 && s < (zz - l) / sizeof(short)) {
        COPY_SHORT(&offset, l + s * sizeof(short));
        if (offset) {
          pc += offset;
          return;
        }
      }
      /* default */
      COPY_SHORT(&offset, pc + SW_DEFAULT);
      pc += offset;
      return;
    }
    fatal("unsupported switch table format.\n");
  }
  /*
   * l - current entry we are looking at.
   * d - size to add/subtract from l each iteration.
   * s - key we're looking for
   * r - key l is pointing at
   */
  l = pc + offset + off_tab[i];
  d = (off_tab[i] + SWITCH_CASE_SIZE) >> 1;
  if (d < SWITCH_CASE_SIZE) {
    d = 0;
  }
  for (;;) {
    COPY_INT(&r, l);
    if (s < r) {
      if (d < SWITCH_CASE_SIZE) {
        /* test if entry is part of a range */
        /* Don't worry about reading from F_BREAK (byte before table) */
        COPY_SHORT(&offset, l + U_TYPE);
        if (offset <= 1) {
          COPY_INT(&r, l + U_LOWER);
          if (s >= r) {
            /* s is in the range */
            COPY_SHORT(&offset, l + U_ADDR);
            if (!offset) {
              /* range with lookup table */
              l = pc + offset + (s - r) * sizeof(short);
              COPY_SHORT(&offset, l);
            } /* else normal range and offset is correct */
            break;
          }
        }
        /* key not found, use default address */
        COPY_SHORT(&offset, pc + SW_DEFAULT);
        break;
      } /* d >= SWITCH_CASE_SIZE */
      l -= d;
      d >>= 1;

    } else if (s > r) {
      if (d < SWITCH_CASE_SIZE) {
        /* test if entry is part of a range */
        COPY_SHORT(&offset, l + L_TYPE);
        if (offset <= 1) {
          COPY_INT(&r, l + L_UPPER);
          if (s <= r) {
            /* s is in the range */
            COPY_SHORT(&offset, l + L_ADDR);
            if (!offset) {
              /* range with lookup table */
              l = pc + offset + (s - r) * sizeof(short);
              COPY_SHORT(&offset, l);
            } /* else normal range and offset is correct */
            break;
          }
        }
        /* use default address */
        COPY_SHORT(&offset, pc + SW_DEFAULT);
        break;
      } /* d >= SWITCH_CASE_SIZE */
      l += d;
      /* if table isn't a power of 2 in size, fix us up */
      while (l >= end_tab) {
        d >>= 1;
        if (d < SWITCH_CASE_SIZE) {
          d = 0;
          break;
        }
        l -= d;
      }
      if (l == end_tab) {
        /* use default address */
        COPY_SHORT(&offset, pc + SW_DEFAULT);
        break;
      }
      d >>= 1;

    } else {
      /* s == r */
      COPY_SHORT(&offset, l + U_ADDR);
      /* found the key - but could be part of a range... */
      if (!l[U_TYPE] && !l[U_TYPE + 1]) {
        /* end of range with lookup table */
        COPY_INT(&r, l + U_LOWER);
        l = pc + offset + (s - r) * sizeof(short);
        COPY_SHORT(&offset, l);
      }
      if (offset <= 1) {
        COPY_SHORT(&offset, l + L_ADDR);
        if (!offset) {
          /* start of range with lookup table */
          l = pc + offset;
          COPY_SHORT(&offset, l);
        } /* else normal range, offset is correct */
      }
      break;
    }
  }
  /* now do jump */
  pc += offset;
}

void f_xor() {
  CHECK_TYPES((sp - 1), T_NUMBER, 1, F_XOR);
  CHECK_TYPES(sp, T_NUMBER, 2, F_XOR);
  sp--;
  sp->u.number ^= (sp + 1)->u.number;
}

void f_xor_eq() {
  svalue_t *argp;

  if ((argp = sp->u.lvalue)->type != T_NUMBER) {
    error("Bad left type to ^=\n");
  }
  if ((--sp)->type != T_NUMBER) {
    error("Bad right type to ^=\n");
  }
  sp->u.number = argp->u.number ^= sp->u.number;
}

void f_function_constructor() {
  funptr_t *fp = nullptr;
  int kind;
  unsigned short index;

  kind = EXTRACT_UCHAR(pc++);

  switch (kind) {
    case FP_EFUN:
      LOAD_SHORT(index, pc);
      fp = make_efun_funp(index, sp);
      pop_stack();
      break;
    case FP_LOCAL:
      LOAD_SHORT(index, pc);
      fp = make_lfun_funp(index, sp);
      pop_stack();
      break;
    case FP_SIMUL:
      LOAD_SHORT(index, pc);
      fp = make_simul_funp(index, sp);
      pop_stack();
      break;
    case FP_FUNCTIONAL:
    case FP_FUNCTIONAL | FP_NOT_BINDABLE: {
      int num_arg;

      num_arg = EXTRACT_UCHAR(pc++); /* number of arguments */
      LOAD_SHORT(index, pc);         /* length of functional */
      fp = make_functional_funp(num_arg, 0, index, sp, kind & FP_NOT_BINDABLE);
      pop_stack();
      break;
    }
    case FP_ANONYMOUS:
    case FP_ANONYMOUS | FP_NOT_BINDABLE: {
      int num_arg, locals;

      num_arg = EXTRACT_UCHAR(pc++);
      locals = EXTRACT_UCHAR(pc++);
      LOAD_SHORT(index, pc); /* length */
      fp = make_functional_funp(num_arg, locals, index, nullptr, kind & FP_NOT_BINDABLE);
      break;
    }
    default:
      fatal("Tried to make unknown type of function pointer.\n");
  }
  push_refed_funp(fp);
}

void f__evaluate() {
  svalue_t *v;
  svalue_t *arg = sp - st_num_arg + 1;

  if (arg->type != T_FUNCTION) {
    pop_n_elems(st_num_arg - 1);
    return;
  }
  if (current_object->flags & O_DESTRUCTED) {
    pop_n_elems(st_num_arg);
    push_undefined();
    return;
  }
  v = call_function_pointer(arg->u.fp, st_num_arg - 1);
  assign_svalue(sp, v);
}

void f_sscanf() {
  svalue_t *fp;
  int i;
  int num_arg;

  /*
   * get number of lvalue args
   */
  num_arg = EXTRACT_UCHAR(pc);
  pc++;

  /*
   * allocate stack frame for rvalues and return value (number of matches);
   * perform some stack manipulation; note: source and template strings are
   * already on the stack by this time
   */
  fp = sp;
  CHECK_STACK_OVERFLOW(num_arg + 1);
  sp += num_arg + 1;
  *sp = *(fp--);       /* move format description to top of stack */
  *(sp - 1) = *(fp);   /* move source string just below the format
                        * desc. */
  fp->type = T_NUMBER; /* this svalue isn't invalidated below, and
                        * if we don't change it to something safe,
                        * it will get freed twice if an error occurs */
  /*
   * prep area for rvalues
   */
  for (i = 1; i <= num_arg; i++) {
    fp[i].type = T_INVALID;
  }

  /*
   * do it...
   */
  i = inter_sscanf(sp - 2, sp - 1, sp, num_arg);

  /*
   * remove source & template strings from top of stack
   */
  pop_2_elems();

  /*
   * save number of matches on stack
   */
  fp->u.number = i;
  fp->subtype = 0;
}
