#ifndef __XS_PARSE_INFIX_H__
#define __XS_PARSE_INFIX_H__

#define XSPARSEINFIX_ABI_VERSION 2

/* Infix operator classifications */
/* No built-in operators use the _MISC categories, but they are provided for
 * custom infix operators to use so they are still found by selections */
enum XSParseInfixClassification {
  XPI_CLS_NONE = 0,
  XPI_CLS_PREDICATE,   /* any boolean-returning operator */
  XPI_CLS_RELATION,    /*  ... any predicate that is typewise symmetric */
  XPI_CLS_EQUALITY,    /*      ... any relation that is true for (x == x) and false otherwise */
  XPI_CLS_SMARTMATCH,  /*  ... the predicate smartmatch (~~) */
  XPI_CLS_MATCHRE,     /*  ... the predicate regexp match (=~) */
  XPI_CLS_ISA,         /*  ... the predicate instance of (isa) */
  XPI_CLS_MATCH_MISC,  /*  ... any other match-like predicate */
  XPI_CLS_ORDERING,    /* cmp or <=> */

  /* Since the _MISC categories never turn up in selections, put them at high
   * index so as to leave space for more */
  XPI_CLS_LOW_MISC = 0x80,  /* an operator at low precedence */
  XPI_CLS_LOGICAL_OR_LOW_MISC,
  XPI_CLS_LOGICAL_AND_LOW_MISC,
  XPI_CLS_ASSIGN_MISC,
  XPI_CLS_LOGICAL_OR_MISC,
  XPI_CLS_LOGICAL_AND_MISC,
  XPI_CLS_ADD_MISC,         /* an operator at addition-like precedence */
  XPI_CLS_MUL_MISC,         /* an operator at multiplication-like precedence */
  XPI_CLS_POW_MISC,         /* an operator at power exponentiation-like precedence */
  XPI_CLS_HIGH_MISC,        /* an operator at high precedence */
};

enum XSParseInfixSelection {
  XPI_SELECT_ANY,
  XPI_SELECT_PREDICATE, /* any predicate */
  XPI_SELECT_RELATION,  /* any relation */
  XPI_SELECT_EQUALITY,  /* any equality */
  XPI_SELECT_ORDERING,  /* any ordering */

  XPI_SELECT_MATCH_NOSMART, /* any equality or other match operator, including smartmatch */
  XPI_SELECT_MATCH_SMART,   /* any equality or other match operator, not including smartmatch */
};

/* flags */
enum {
  XPI_FLAG_LISTASSOC = (1<<0),
};

/* lhs_flags, rhs_flags */
enum {
  XPI_OPERAND_TERM_LIST = 6, /* term in list context */
  XPI_OPERAND_LIST      = 7, /* list in list context */

  /* Other bitflags */
  XPI_OPERAND_ONLY_LOOK = (1<<3),
};

struct XSParseInfixHooks {
  U16 flags;
  U8 lhs_flags, rhs_flags;
  enum XSParseInfixClassification cls;

  const char *wrapper_func_name;

  /* These two hooks are ANDed together; both must pass, if present */
  const char *permit_hintkey;
  bool (*permit) (pTHX_ void *hookdata);

  /* These hooks are alternatives; the first one defined is used */
  OP *(*new_op)(pTHX_ U32 flags, OP *lhs, OP *rhs, SV **parsedata, void *hookdata);
  OP *(*ppaddr)(pTHX); /* A pp func used directly in newBINOP_custom() */

  /* optional */
  void (*parse)(pTHX_ U32 flags, SV **parsedata, void *hookdata);
};

struct XSParseInfixInfo {
  const char *opname;
  OPCODE opcode;

  const struct XSParseInfixHooks *hooks;
  void *hookdata;

  enum XSParseInfixClassification cls;
};

static bool (*parse_infix_func)(pTHX_ enum XSParseInfixSelection select, struct XSParseInfixInfo **infop);
#define parse_infix(select, infop) S_parse_infix(aTHX_ select, infop)
static bool S_parse_infix(pTHX_ enum XSParseInfixSelection select, struct XSParseInfixInfo **infop)
{
  if(!parse_infix_func)
    croak("Must call boot_xs_parse_infix() first");

  struct XSParseInfixInfo *infocopy;

  return (*parse_infix_func)(aTHX_ select, infop);
}

static OP *(*xs_parse_infix_new_op_func)(pTHX_ const struct XSParseInfixInfo *info, U32 flags, OP *lhs, OP *rhs);
#define xs_parse_infix_new_op(info, flags, lhs, rhs)  S_xs_parse_infix_new_op(aTHX_ info, flags, lhs, rhs)
static OP *S_xs_parse_infix_new_op(pTHX_ const struct XSParseInfixInfo *info, U32 flags, OP *lhs, OP *rhs)
{
  if(!xs_parse_infix_new_op_func)
    croak("Must call boot_xs_parse_infix() first");

  return (*xs_parse_infix_new_op_func)(aTHX_ info, flags, lhs, rhs);
}

static void (*register_xs_parse_infix_func)(pTHX_ const char *kw, const struct XSParseInfixHooks *hooks, void *hookdata);
#define register_xs_parse_infix(opname, hooks, hookdata)  S_register_xs_parse_infix(aTHX_ opname, hooks, hookdata)
static void S_register_xs_parse_infix(pTHX_ const char *opname, const struct XSParseInfixHooks *hooks, void *hookdata)
{
  if(!register_xs_parse_infix_func)
    croak("Must call boot_xs_parse_infix() first");

  (*register_xs_parse_infix_func)(aTHX_ opname, hooks, hookdata);
}

#define boot_xs_parse_infix(ver) S_boot_xs_parse_infix(aTHX_ ver)
static void S_boot_xs_parse_infix(pTHX_ double ver) {
  SV **svp;
  SV *versv = ver ? newSVnv(ver) : NULL;

  load_module(PERL_LOADMOD_NOIMPORT, newSVpvs("XS::Parse::Infix"), versv, NULL);

  svp = hv_fetchs(PL_modglobal, "XS::Parse::Infix/ABIVERSION_MIN", 0);
  if(!svp)
    croak("XS::Parse::Infix ABI minimum version missing");
  int abi_ver = SvIV(*svp);
  if(abi_ver > XSPARSEINFIX_ABI_VERSION)
    croak("XS::Parse::Infix ABI version mismatch - library supports >= %d, compiled for %d",
        abi_ver, XSPARSEINFIX_ABI_VERSION);

  svp = hv_fetchs(PL_modglobal, "XS::Parse::Infix/ABIVERSION_MAX", 0);
  abi_ver = SvIV(*svp);
  if(abi_ver < XSPARSEINFIX_ABI_VERSION)
    croak("XS::Parse::Infix ABI version mismatch - library supports <= %d, compiled for %d",
        abi_ver, XSPARSEINFIX_ABI_VERSION);

  parse_infix_func = INT2PTR(bool (*)(pTHX_ enum XSParseInfixSelection, struct XSParseInfixInfo **),
      SvUV(*hv_fetchs(PL_modglobal, "XS::Parse::Infix/parse()@2", 0)));
  xs_parse_infix_new_op_func = INT2PTR(OP *(*)(pTHX_ const struct XSParseInfixInfo *, U32, OP *, OP *),
      SvUV(*hv_fetchs(PL_modglobal, "XS::Parse::Infix/new_op()@0", 0)));
  register_xs_parse_infix_func = INT2PTR(void (*)(pTHX_ const char *, const struct XSParseInfixHooks *, void *),
      SvUV(*hv_fetchs(PL_modglobal, "XS::Parse::Infix/register()@2", 0)));
}

#endif