/* 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 "XSParseInfix.h"
#include "perl-backcompat.c.inc"
#include "perl-additions.c.inc"
#include "newSVop.c.inc"
struct XPIFPHookdata {
/* Phase callbacks */
CV *permitcv;
CV *new_opcv;
SV *hookdata;
};
static bool cb_permit(pTHX_ void *hookdata)
{
struct XPIFPHookdata *data = hookdata;
dSP;
ENTER;
SAVETMPS;
PUSHMARK(SP);
if(data->hookdata)
XPUSHs(sv_mortalcopy(data->hookdata));
else
XPUSHs(&PL_sv_undef);
PUTBACK;
call_sv((SV *)data->permitcv, G_SCALAR);
SPAGAIN;
bool ret = SvTRUEx(POPs);
FREETMPS;
LEAVE;
return ret;
}
static OP *cb_new_op(pTHX_ U32 flags, OP *lhs, OP *rhs, SV **parsedata, void *hookdata)
{
struct XPIFPHookdata *data = hookdata;
dSP;
ENTER;
SAVETMPS;
PUSHMARK(SP);
EXTEND(SP, 5);
mPUSHu(flags);
PUSHs(sv_2mortal(newSVop(lhs)));
PUSHs(sv_2mortal(newSVop(rhs)));
PUSHs(&PL_sv_undef); /* parsedata; ignore for now */
if(data->hookdata)
PUSHs(sv_mortalcopy(data->hookdata));
else
PUSHs(&PL_sv_undef);
PUTBACK;
call_sv((SV *)data->new_opcv, G_SCALAR);
SPAGAIN;
OP *ret = SvOPo(POPs);
PUTBACK;
FREETMPS;
LEAVE;
return ret;
}
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("XS::Parse::Infix::FromPerl", TRUE);
export = get_av("XS::Parse::Infix::FromPerl::EXPORT_OK", TRUE);
DO_CONSTANT(XPI_CLS_NONE);
DO_CONSTANT(XPI_CLS_PREDICATE);
DO_CONSTANT(XPI_CLS_RELATION);
DO_CONSTANT(XPI_CLS_EQUALITY);
DO_CONSTANT(XPI_CLS_SMARTMATCH);
DO_CONSTANT(XPI_CLS_MATCHRE);
DO_CONSTANT(XPI_CLS_ISA);
DO_CONSTANT(XPI_CLS_MATCH_MISC);
DO_CONSTANT(XPI_CLS_ORDERING);
DO_CONSTANT(XPI_CLS_LOW_MISC);
DO_CONSTANT(XPI_CLS_LOGICAL_OR_LOW_MISC);
DO_CONSTANT(XPI_CLS_LOGICAL_AND_LOW_MISC);
DO_CONSTANT(XPI_CLS_ASSIGN_MISC);
DO_CONSTANT(XPI_CLS_LOGICAL_OR_MISC);
DO_CONSTANT(XPI_CLS_LOGICAL_AND_MISC);
DO_CONSTANT(XPI_CLS_ADD_MISC);
DO_CONSTANT(XPI_CLS_MUL_MISC);
DO_CONSTANT(XPI_CLS_POW_MISC);
DO_CONSTANT(XPI_CLS_HIGH_MISC);
}
MODULE = XS::Parse::Infix::FromPerl PACKAGE = XS::Parse::Infix::FromPerl
void
register_xs_parse_infix(const char *name, ...)
CODE:
dKWARG(1);
struct XSParseInfixHooks hooks = {0};
struct XPIFPHookdata data = {0};
SV *wrapper_func_namesv = NULL;
SV *permit_hintkeysv = NULL;
static const char *args[] = {
"flags",
"lhs_flags",
"rhs_flags",
"cls",
"wrapper_func_name",
"permit_hintkey",
"permit",
"new_op",
/* TODO: parse? */
"hookdata",
};
while(KWARG_NEXT(args))
switch(kwarg) {
case 0: /* flags */
case 1: /* lhs_flags */
case 2: /* rhs_flags */
croak("TODO: flags not currently supported");
case 3: /* cls */
hooks.cls = SvUV(kwval);
break;
case 4: /* wrapper_func_name */
wrapper_func_namesv = kwval;
break;
case 5: /* permit_hintkey */
permit_hintkeysv = kwval;
break;
case 6: /* permit */
if(!SvROK(kwval) || SvTYPE(SvRV(kwval)) != SVt_PVCV)
croak("Expected 'permit' to be a CODE ref");
data.permitcv = (CV *)SvREFCNT_inc((SV *)CV_FROM_REF(kwval));
break;
case 7: /* new_op */
if(!SvROK(kwval) || SvTYPE(SvRV(kwval)) != SVt_PVCV)
croak("Expected 'new_op' to be a CODE ref");
data.new_opcv = (CV *)SvREFCNT_inc((SV *)CV_FROM_REF(kwval));
break;
case 8: /* hookdata */
data.hookdata = newSVsv(kwval);
break;
}
if(!permit_hintkeysv && !data.permitcv)
croak("Require at least one of 'permit_hintkey' or 'permit'");
struct XSParseInfixHooks *hooksptr;
Newx(hooksptr, 1, struct XSParseInfixHooks);
*hooksptr = hooks;
if(wrapper_func_namesv)
hooksptr->wrapper_func_name = savepv(SvPV_nolen(wrapper_func_namesv));
if(permit_hintkeysv)
hooksptr->permit_hintkey = savepv(SvPV_nolen(permit_hintkeysv));
if(data.permitcv)
hooksptr->permit = &cb_permit;
if(data.new_opcv)
hooksptr->new_op = &cb_new_op;
struct XPIFPHookdata *dataptr;
Newx(dataptr, 1, struct XPIFPHookdata);
*dataptr = data;
register_xs_parse_infix(savepv(name), hooksptr, dataptr);
BOOT:
boot_xs_parse_infix(0);
S_setup_constants(aTHX);