/*  You may distribute under the terms of either the GNU General Public License
 *  or the Artistic License (the same terms as Perl itself)
 *
 *  (C) Paul Evans, 2023 -- leonerd@leonerd.org.uk
 */
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "perl-backcompat.c.inc"
#include "perl-additions.c.inc"

#include "newSVop.c.inc"

#define ENTER_and_setup_pad(name)  S_ENTER_and_setup_pad(aTHX_ name)
static void S_ENTER_and_setup_pad(pTHX_ const char *name)
{
  if(!PL_compcv)
    croak("Cannot call %s while not compiling a subroutine", name);

  ENTER;

  PAD_SET_CUR(CvPADLIST(PL_compcv), 1);

  SAVESPTR(PL_comppad_name);
  PL_comppad_name = PadlistNAMES(CvPADLIST(PL_compcv));
}

static void S_setup_constants(pTHX)
{
  HV *stash;
  AV *export;

#define DO_CONSTANT(c) \
  newCONSTSUB(stash, #c, newSViv(c)); \
  av_push(export, newSVpv(#c, 0))

  stash = gv_stashpvs("Optree::Generate", TRUE);
  export = get_av("Optree::Generate::EXPORT_OK", TRUE);

  DO_CONSTANT(G_SCALAR);
  DO_CONSTANT(G_LIST);
  DO_CONSTANT(G_VOID);

  DO_CONSTANT(OPf_WANT);
  DO_CONSTANT(OPf_WANT_VOID);
  DO_CONSTANT(OPf_WANT_SCALAR);
  DO_CONSTANT(OPf_WANT_LIST);
  DO_CONSTANT(OPf_KIDS);
  DO_CONSTANT(OPf_PARENS);
  DO_CONSTANT(OPf_REF);
  DO_CONSTANT(OPf_MOD);
  DO_CONSTANT(OPf_STACKED);
  DO_CONSTANT(OPf_SPECIAL);
}

MODULE = Optree::Generate    PACKAGE = Optree::Generate

I32 opcode(const char *opname)
  CODE:
    for(RETVAL = 0; RETVAL < OP_max; RETVAL++)
      if(strEQ(opname, PL_op_name[RETVAL]))
        goto found;
    croak("Unrecognised opcode(\"%s\")", opname);
found:
    ;
  OUTPUT:
    RETVAL

SV *
op_contextualize(SV *o, I32 context)
  CODE:
    ENTER_and_setup_pad("op_contextualize");
    RETVAL = newSVop(op_contextualize(SvOPo(o), context));
    LEAVE;
  OUTPUT:
    RETVAL

SV *
op_scope(SV *o)
  CODE:
    ENTER_and_setup_pad("op_scope");
    RETVAL = newSVop(op_scope(SvOPo(o)));
    LEAVE;
  OUTPUT:
    RETVAL

SV *
newOP(I32 type, I32 flags)
  CODE:
    ENTER_and_setup_pad("newOP");
    RETVAL = newSVop(newOP(type, flags));
    LEAVE;
  OUTPUT:
    RETVAL

SV *
newASSIGNOP(I32 flags, SV *left, I32 optype, SV *right)
  CODE:
    ENTER_and_setup_pad("newASSIGNOP");
    RETVAL = newSVop(newASSIGNOP(flags, SvOPo(left), optype, SvOPo(right)));
    LEAVE;
  OUTPUT:
    RETVAL

SV *
newBINOP(I32 type, I32 flags, SV *first, SV *last)
  CODE:
    ENTER_and_setup_pad("newBINOP");
    RETVAL = newSVop(newBINOP(type, flags, SvOPo(first), SvOPo(last)));
    LEAVE;
  OUTPUT:
    RETVAL

SV *
newCONDOP(I32 flags, SV *first, SV *trueop, SV *falseop)
  CODE:
    ENTER_and_setup_pad("newCONDOP");
    RETVAL = newSVop(newCONDOP(flags, SvOPo(first), SvOPo(trueop), SvOPo(falseop)));
    LEAVE;
  OUTPUT:
    RETVAL

SV *
newFOROP(I32 flags, SV *sv, SV *expr, SV *block, SV *cont)
  CODE:
    ENTER_and_setup_pad("newFOROP");
    RETVAL = newSVop(newFOROP(flags, maySvOPo(sv), SvOPo(expr), SvOPo(block), maySvOPo(cont)));
    LEAVE;
  OUTPUT:
    RETVAL

SV *
newGVOP(I32 type, I32 flags, SV *gv)
  CODE:
    if(!SvROK(gv) || SvTYPE(SvRV(gv)) != SVt_PVGV)
      croak("Expected a GLOB ref to newGVOP");
    ENTER_and_setup_pad("newGVOP");
    RETVAL = newSVop(newGVOP(type, flags, (GV *)SvRV(gv)));
    LEAVE;
  OUTPUT:
    RETVAL

SV *
newLISTOP(I32 type, I32 flags, ...)
  CODE:
    ENTER_and_setup_pad("newLISTOP");
    /* Can't use newLISTOPn() here because of a variable number of kid ops */
    OP *o = newLISTOP(OP_LIST, 0, NULL, NULL);
    for(U32 i = 2; i < items; i++)
      o = op_append_elem(OP_LIST, o, SvOPo(ST(i)));
    if(type != OP_LIST)
      o = op_convert_list(type, flags, o);
    RETVAL = newSVop(o);
    LEAVE;
  OUTPUT:
    RETVAL

SV *
newLOGOP(I32 type, I32 flags, SV *first, SV *other)
  CODE:
    ENTER_and_setup_pad("newLOGOP");
    RETVAL = newSVop(newLOGOP(type, flags, SvOPo(first), SvOPo(other)));
    LEAVE;
  OUTPUT:
    RETVAL

SV *
newPADxVOP(I32 type, I32 flags, U32 padoffset)
  CODE:
    ENTER_and_setup_pad("newPADxVOP");
    RETVAL = newSVop(newPADxVOP(type, flags, padoffset));
    LEAVE;
  OUTPUT:
    RETVAL

SV *
newSVOP(I32 type, I32 flags, SV *sv)
  CODE:
    ENTER_and_setup_pad("newSVOP");
    RETVAL = newSVop(newSVOP(type, flags, newSVsv(sv)));
    LEAVE;
  OUTPUT:
    RETVAL

SV *
newUNOP(I32 type, I32 flags, SV *first)
  CODE:
    ENTER_and_setup_pad("newUNOP");
    RETVAL = newSVop(newUNOP(type, flags, SvOPo(first)));
    LEAVE;
  OUTPUT:
    RETVAL

BOOT:
  S_setup_constants(aTHX);