+STATIC OP *
+THX_ck_entersub_args_lists(pTHX_ OP *entersubop, GV *namegv, SV *ckobj)
+{
+ return ck_entersub_args_list(entersubop);
+}
+
+STATIC OP *
+THX_ck_entersub_args_scalars(pTHX_ OP *entersubop, GV *namegv, SV *ckobj)
+{
+ OP *aop = cUNOPx(entersubop)->op_first;
+ if (!aop->op_sibling)
+ aop = cUNOPx(aop)->op_first;
+ for (aop = aop->op_sibling; aop->op_sibling; aop = aop->op_sibling) {
+ op_contextualize(aop, G_SCALAR);
+ }
+ return entersubop;
+}
+
+STATIC OP *
+THX_ck_entersub_multi_sum(pTHX_ OP *entersubop, GV *namegv, SV *ckobj)
+{
+ OP *sumop = NULL;
+ OP *pushop = cUNOPx(entersubop)->op_first;
+ if (!pushop->op_sibling)
+ pushop = cUNOPx(pushop)->op_first;
+ while (1) {
+ OP *aop = pushop->op_sibling;
+ if (!aop->op_sibling)
+ break;
+ pushop->op_sibling = aop->op_sibling;
+ aop->op_sibling = NULL;
+ op_contextualize(aop, G_SCALAR);
+ if (sumop) {
+ sumop = newBINOP(OP_ADD, 0, sumop, aop);
+ } else {
+ sumop = aop;
+ }
+ }
+ if (!sumop)
+ sumop = newSVOP(OP_CONST, 0, newSViv(0));
+ op_free(entersubop);
+ return sumop;
+}
+
+STATIC void test_op_list_describe_part(SV *res, OP *o);
+STATIC void
+test_op_list_describe_part(SV *res, OP *o)
+{
+ sv_catpv(res, PL_op_name[o->op_type]);
+ switch (o->op_type) {
+ case OP_CONST: {
+ sv_catpvf(res, "(%d)", (int)SvIV(cSVOPx(o)->op_sv));
+ } break;
+ }
+ if (o->op_flags & OPf_KIDS) {
+ OP *k;
+ sv_catpvs(res, "[");
+ for (k = cUNOPx(o)->op_first; k; k = k->op_sibling)
+ test_op_list_describe_part(res, k);
+ sv_catpvs(res, "]");
+ } else {
+ sv_catpvs(res, ".");
+ }
+}
+
+STATIC char *
+test_op_list_describe(OP *o)
+{
+ SV *res = sv_2mortal(newSVpvs(""));
+ if (o)
+ test_op_list_describe_part(res, o);
+ return SvPVX(res);
+}
+
+/* the real new*OP functions have a tendancy to call fold_constants, and
+ * other such unhelpful things, so we need our own versions for testing */
+
+#define mkUNOP(t, f) THX_mkUNOP(aTHX_ (t), (f))
+static OP *
+THX_mkUNOP(pTHX_ U32 type, OP *first)
+{
+ UNOP *unop;
+ NewOp(1103, unop, 1, UNOP);
+ unop->op_type = (OPCODE)type;
+ unop->op_first = first;
+ unop->op_flags = OPf_KIDS;
+ return (OP *)unop;
+}
+
+#define mkBINOP(t, f, l) THX_mkBINOP(aTHX_ (t), (f), (l))
+static OP *
+THX_mkBINOP(pTHX_ U32 type, OP *first, OP *last)
+{
+ BINOP *binop;
+ NewOp(1103, binop, 1, BINOP);
+ binop->op_type = (OPCODE)type;
+ binop->op_first = first;
+ binop->op_flags = OPf_KIDS;
+ binop->op_last = last;
+ first->op_sibling = last;
+ return (OP *)binop;
+}
+
+#define mkLISTOP(t, f, s, l) THX_mkLISTOP(aTHX_ (t), (f), (s), (l))
+static OP *
+THX_mkLISTOP(pTHX_ U32 type, OP *first, OP *sib, OP *last)
+{
+ LISTOP *listop;
+ NewOp(1103, listop, 1, LISTOP);
+ listop->op_type = (OPCODE)type;
+ listop->op_flags = OPf_KIDS;
+ listop->op_first = first;
+ first->op_sibling = sib;
+ sib->op_sibling = last;
+ listop->op_last = last;
+ return (OP *)listop;
+}
+
+static char *
+test_op_linklist_describe(OP *start)
+{
+ SV *rv = sv_2mortal(newSVpvs(""));
+ OP *o;
+ o = start = LINKLIST(start);
+ do {
+ sv_catpvs(rv, ".");
+ sv_catpv(rv, OP_NAME(o));
+ if (o->op_type == OP_CONST)
+ sv_catsv(rv, cSVOPo->op_sv);
+ o = o->op_next;
+ } while (o && o != start);
+ return SvPVX(rv);
+}
+
+/** RPN keyword parser **/
+
+#define sv_is_glob(sv) (SvTYPE(sv) == SVt_PVGV)
+#define sv_is_regexp(sv) (SvTYPE(sv) == SVt_REGEXP)
+#define sv_is_string(sv) \
+ (!sv_is_glob(sv) && !sv_is_regexp(sv) && \
+ (SvFLAGS(sv) & (SVf_IOK|SVf_NOK|SVf_POK|SVp_IOK|SVp_NOK|SVp_POK)))
+
+static SV *hintkey_rpn_sv, *hintkey_calcrpn_sv, *hintkey_stufftest_sv;
+static SV *hintkey_swaptwostmts_sv, *hintkey_looprest_sv;
+static SV *hintkey_scopelessblock_sv;
+static int (*next_keyword_plugin)(pTHX_ char *, STRLEN, OP **);
+
+/* low-level parser helpers */
+
+#define PL_bufptr (PL_parser->bufptr)
+#define PL_bufend (PL_parser->bufend)
+
+/* RPN parser */
+
+#define parse_var() THX_parse_var(aTHX)
+static OP *THX_parse_var(pTHX)
+{
+ char *s = PL_bufptr;
+ char *start = s;
+ PADOFFSET varpos;
+ OP *padop;
+ if(*s != '$') croak("RPN syntax error");
+ while(1) {
+ char c = *++s;
+ if(!isALNUM(c)) break;
+ }
+ if(s-start < 2) croak("RPN syntax error");
+ lex_read_to(s);
+ {
+ /* because pad_findmy() doesn't really use length yet */
+ SV *namesv = sv_2mortal(newSVpvn(start, s-start));
+ varpos = pad_findmy(SvPVX(namesv), s-start, 0);
+ }
+ if(varpos == NOT_IN_PAD || PAD_COMPNAME_FLAGS_isOUR(varpos))
+ croak("RPN only supports \"my\" variables");
+ padop = newOP(OP_PADSV, 0);
+ padop->op_targ = varpos;
+ return padop;
+}
+
+#define push_rpn_item(o) \
+ (tmpop = (o), tmpop->op_sibling = stack, stack = tmpop)
+#define pop_rpn_item() \
+ (!stack ? (croak("RPN stack underflow"), (OP*)NULL) : \
+ (tmpop = stack, stack = stack->op_sibling, \
+ tmpop->op_sibling = NULL, tmpop))
+
+#define parse_rpn_expr() THX_parse_rpn_expr(aTHX)
+static OP *THX_parse_rpn_expr(pTHX)
+{
+ OP *stack = NULL, *tmpop;
+ while(1) {
+ I32 c;
+ lex_read_space(0);
+ c = lex_peek_unichar(0);
+ switch(c) {
+ case /*(*/')': case /*{*/'}': {
+ OP *result = pop_rpn_item();
+ if(stack) croak("RPN expression must return a single value");
+ return result;
+ } break;
+ case '0': case '1': case '2': case '3': case '4':
+ case '5': case '6': case '7': case '8': case '9': {
+ UV val = 0;
+ do {
+ lex_read_unichar(0);
+ val = 10*val + (c - '0');
+ c = lex_peek_unichar(0);
+ } while(c >= '0' && c <= '9');
+ push_rpn_item(newSVOP(OP_CONST, 0, newSVuv(val)));
+ } break;
+ case '$': {
+ push_rpn_item(parse_var());
+ } break;
+ case '+': {
+ OP *b = pop_rpn_item();
+ OP *a = pop_rpn_item();
+ lex_read_unichar(0);
+ push_rpn_item(newBINOP(OP_I_ADD, 0, a, b));
+ } break;
+ case '-': {
+ OP *b = pop_rpn_item();
+ OP *a = pop_rpn_item();
+ lex_read_unichar(0);
+ push_rpn_item(newBINOP(OP_I_SUBTRACT, 0, a, b));
+ } break;
+ case '*': {
+ OP *b = pop_rpn_item();
+ OP *a = pop_rpn_item();
+ lex_read_unichar(0);
+ push_rpn_item(newBINOP(OP_I_MULTIPLY, 0, a, b));
+ } break;
+ case '/': {
+ OP *b = pop_rpn_item();
+ OP *a = pop_rpn_item();
+ lex_read_unichar(0);
+ push_rpn_item(newBINOP(OP_I_DIVIDE, 0, a, b));
+ } break;
+ case '%': {
+ OP *b = pop_rpn_item();
+ OP *a = pop_rpn_item();
+ lex_read_unichar(0);
+ push_rpn_item(newBINOP(OP_I_MODULO, 0, a, b));
+ } break;
+ default: {
+ croak("RPN syntax error");
+ } break;
+ }
+ }
+}
+
+#define parse_keyword_rpn() THX_parse_keyword_rpn(aTHX)
+static OP *THX_parse_keyword_rpn(pTHX)
+{
+ OP *op;
+ lex_read_space(0);
+ if(lex_peek_unichar(0) != '('/*)*/)
+ croak("RPN expression must be parenthesised");
+ lex_read_unichar(0);
+ op = parse_rpn_expr();
+ if(lex_peek_unichar(0) != /*(*/')')
+ croak("RPN expression must be parenthesised");
+ lex_read_unichar(0);
+ return op;
+}
+
+#define parse_keyword_calcrpn() THX_parse_keyword_calcrpn(aTHX)
+static OP *THX_parse_keyword_calcrpn(pTHX)
+{
+ OP *varop, *exprop;
+ lex_read_space(0);
+ varop = parse_var();
+ lex_read_space(0);
+ if(lex_peek_unichar(0) != '{'/*}*/)
+ croak("RPN expression must be braced");
+ lex_read_unichar(0);
+ exprop = parse_rpn_expr();
+ if(lex_peek_unichar(0) != /*{*/'}')
+ croak("RPN expression must be braced");
+ lex_read_unichar(0);
+ return newASSIGNOP(OPf_STACKED, varop, 0, exprop);
+}
+
+#define parse_keyword_stufftest() THX_parse_keyword_stufftest(aTHX)
+static OP *THX_parse_keyword_stufftest(pTHX)
+{
+ I32 c;
+ bool do_stuff;
+ lex_read_space(0);
+ do_stuff = lex_peek_unichar(0) == '+';
+ if(do_stuff) {
+ lex_read_unichar(0);
+ lex_read_space(0);
+ }
+ c = lex_peek_unichar(0);
+ if(c == ';') {
+ lex_read_unichar(0);
+ } else if(c != /*{*/'}') {
+ croak("syntax error");
+ }
+ if(do_stuff) lex_stuff_pvs(" ", 0);
+ return newOP(OP_NULL, 0);
+}
+
+#define parse_keyword_swaptwostmts() THX_parse_keyword_swaptwostmts(aTHX)
+static OP *THX_parse_keyword_swaptwostmts(pTHX)
+{
+ OP *a, *b;
+ a = parse_fullstmt(0);
+ b = parse_fullstmt(0);
+ if(a && b)
+ PL_hints |= HINT_BLOCK_SCOPE;
+ return op_append_list(OP_LINESEQ, b, a);
+}
+
+#define parse_keyword_looprest() THX_parse_keyword_looprest(aTHX)
+static OP *THX_parse_keyword_looprest(pTHX)
+{
+ I32 condline;
+ OP *body;
+ condline = CopLINE(PL_curcop);
+ body = parse_stmtseq(0);
+ return newWHILEOP(0, 1, NULL, condline, newSVOP(OP_CONST, 0, &PL_sv_yes),
+ body, NULL, 1);
+}
+
+#define parse_keyword_scopelessblock() THX_parse_keyword_scopelessblock(aTHX)
+static OP *THX_parse_keyword_scopelessblock(pTHX)
+{
+ I32 c;
+ OP *body;
+ lex_read_space(0);
+ if(lex_peek_unichar(0) != '{'/*}*/) croak("syntax error");
+ lex_read_unichar(0);
+ body = parse_stmtseq(0);
+ c = lex_peek_unichar(0);
+ if(c != /*{*/'}' && c != /*[*/']' && c != /*(*/')') croak("syntax error");
+ lex_read_unichar(0);
+ return body;
+}
+
+/* plugin glue */
+
+#define keyword_active(hintkey_sv) THX_keyword_active(aTHX_ hintkey_sv)
+static int THX_keyword_active(pTHX_ SV *hintkey_sv)
+{
+ HE *he;
+ if(!GvHV(PL_hintgv)) return 0;
+ he = hv_fetch_ent(GvHV(PL_hintgv), hintkey_sv, 0,
+ SvSHARED_HASH(hintkey_sv));
+ return he && SvTRUE(HeVAL(he));
+}
+
+static int my_keyword_plugin(pTHX_
+ char *keyword_ptr, STRLEN keyword_len, OP **op_ptr)
+{
+ if(keyword_len == 3 && strnEQ(keyword_ptr, "rpn", 3) &&
+ keyword_active(hintkey_rpn_sv)) {
+ *op_ptr = parse_keyword_rpn();
+ return KEYWORD_PLUGIN_EXPR;
+ } else if(keyword_len == 7 && strnEQ(keyword_ptr, "calcrpn", 7) &&
+ keyword_active(hintkey_calcrpn_sv)) {
+ *op_ptr = parse_keyword_calcrpn();
+ return KEYWORD_PLUGIN_STMT;
+ } else if(keyword_len == 9 && strnEQ(keyword_ptr, "stufftest", 9) &&
+ keyword_active(hintkey_stufftest_sv)) {
+ *op_ptr = parse_keyword_stufftest();
+ return KEYWORD_PLUGIN_STMT;
+ } else if(keyword_len == 12 &&
+ strnEQ(keyword_ptr, "swaptwostmts", 12) &&
+ keyword_active(hintkey_swaptwostmts_sv)) {
+ *op_ptr = parse_keyword_swaptwostmts();
+ return KEYWORD_PLUGIN_STMT;
+ } else if(keyword_len == 8 && strnEQ(keyword_ptr, "looprest", 8) &&
+ keyword_active(hintkey_looprest_sv)) {
+ *op_ptr = parse_keyword_looprest();
+ return KEYWORD_PLUGIN_STMT;
+ } else if(keyword_len == 14 && strnEQ(keyword_ptr, "scopelessblock", 14) &&
+ keyword_active(hintkey_scopelessblock_sv)) {
+ *op_ptr = parse_keyword_scopelessblock();
+ return KEYWORD_PLUGIN_STMT;
+ } else {
+ return next_keyword_plugin(aTHX_ keyword_ptr, keyword_len, op_ptr);
+ }
+}
+
+XS(XS_XS__APItest__XSUB_XS_VERSION_undef);
+XS(XS_XS__APItest__XSUB_XS_VERSION_empty);
+XS(XS_XS__APItest__XSUB_XS_APIVERSION_invalid);
+