/* 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, 2022 -- leonerd@leonerd.org.uk
*/
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "XSParseKeyword.h"
#include "perl-backcompat.c.inc"
enum {
OPpDUP_MOVEMARK = (1<<0),
};
static XOP xop_dup;
static OP *pp_dup(pTHX)
{
dSP;
SV *sv = TOPs;
if(PL_op->op_flags & OPf_REF)
XPUSHs(sv);
else
XPUSHs(sv_mortalcopy(sv));
if(PL_op->op_private & OPpDUP_MOVEMARK)
(*PL_markstack_ptr)++;
RETURN;
}
#define newDUPOP(flags) S_newDUPOP(aTHX_ flags)
static OP *S_newDUPOP(pTHX_ U32 flags)
{
OP *o = newUNOP(OP_CUSTOM, flags, NULL);
o->op_ppaddr = &pp_dup;
return o;
}
static bool arg_is_acceptable(OP *argop)
{
switch(argop->op_type) {
case OP_PADSV:
case OP_RV2SV:
case OP_HELEM:
case OP_AELEM:
return TRUE;
}
return FALSE;
}
static OP *build_inplace_coreop(pTHX_ OP *op)
{
/* Turn EXPR... -> OP into EXPR... -> DUP -> OP -> SASSIGN */
/* The tree shape of this will be horrible */
OP *expr = cUNOPx(op)->op_first;
if(!arg_is_acceptable(expr))
croak("Cannot use %s as an argument to an inplace operator", PL_op_name[expr->op_type]);
/* Thread the DUP op in without it appearing structurally */
OP *dupop = newDUPOP(OPf_REF);
dupop->op_next = expr->op_next;
expr->op_next = dupop;
/* This really weird OP_SASSIGN is a binop with only one child. Don't worry.
* At runtime it will still see two SVs because of the DUP; but they'll be
* in the wrong order so we'll have to swap them */
OP *assignop = newBINOP(OP_SASSIGN, (OPpASSIGN_BACKWARDS << 8), op, newOP(OP_NULL, 0));
assignop->op_next = op->op_next;
op->op_next = assignop;
return assignop;
}
static OP *build_inplace_entersub(pTHX_ OP *op)
{
OP *args = cUNOPx(op)->op_first;
if(!args->op_type && args->op_targ == OP_LIST)
args = cLISTOPx(args)->op_first;
assert(args->op_type == OP_PUSHMARK);
OP *arg = OpSIBLING(args);
/* If this op has a single argument then OpSIBLING of arg will be set,
* but OpSIBLING of that will be NULL
*/
if(!OpSIBLING(arg))
croak("Cannot use a function call with no arguments as an inplace operator");
if(OpSIBLING(OpSIBLING(arg)))
croak("Cannot use a function call with more than one argument as an inplace operator");
if(!arg_is_acceptable(arg))
croak("Cannot use %s as an argument to an inplace operator", PL_op_name[arg->op_type]);
OP *start = LINKLIST(op);
op->op_next = start;
/* Thread the DUP op in without it appearing structurally */
OP *dupop = newDUPOP(OPf_REF | (OPpDUP_MOVEMARK << 8));
dupop->op_next = arg->op_next;
arg->op_next = dupop;
/* This really weird OP_SASSIGN is a binop with only one child. Don't worry.
* At runtime it will still see two SVs because of the DUP; but they'll be
* in the wrong order so we'll have to swap them */
OP *assignop = newBINOP(OP_SASSIGN, (OPpASSIGN_BACKWARDS << 8), op, newOP(OP_NULL, 0));
assignop->op_next = op->op_next;
op->op_next = assignop;
return assignop;
}
static int build_inplace(pTHX_ OP **out, XSParseKeywordPiece *arg0, void *hookdata)
{
OP *op = arg0->op;
OPCODE optype = op->op_type;
#if 0
warn("Initial optree:\n");
op_dump(op);
#endif
/* Any retscalar + unop is fine */
if((PL_opargs[optype] & OA_RETSCALAR) &&
((PL_opargs[optype] & OA_CLASS_MASK) == OA_UNOP))
*out = build_inplace_coreop(aTHX_ op);
/* Any retscalar baseop_or_unop is fine provided it has a kid */
else if((PL_opargs[optype] & OA_RETSCALAR) &&
((PL_opargs[optype] & OA_CLASS_MASK) == OA_BASEOP_OR_UNOP)) {
if(!(op->op_flags & OPf_KIDS))
croak("Cannot use %s as an inplace operator without an expression", PL_op_name[optype]);
*out = build_inplace_coreop(aTHX_ op);
}
/* We'll possibly allow entersub but only of a single argument */
else if(optype == OP_ENTERSUB)
*out = build_inplace_entersub(aTHX_ op);
else
croak("Cannot use %s as an inplace operator", PL_op_name[optype]);
#if 0
warn("Final optree\n");
op_dump(*out);
#endif
return KEYWORD_PLUGIN_EXPR;
}
static const struct XSParseKeywordHooks hooks_inplace = {
.permit_hintkey = "Syntax::Keyword::Inplace/inplace",
.piece1 = XPK_TERMEXPR,
.build1 = &build_inplace,
};
MODULE = Syntax::Keyword::Inplace PACKAGE = Syntax::Keyword::Inplace
BOOT:
boot_xs_parse_keyword(0.13);
XopENTRY_set(&xop_dup, xop_name, "dup");
XopENTRY_set(&xop_dup, xop_desc, "duplicate");
XopENTRY_set(&xop_dup, xop_class, OA_UNOP);
Perl_custom_op_register(aTHX_ &pp_dup, &xop_dup);
register_xs_parse_keyword("inplace", &hooks_inplace, NULL);