rewrite to generate actual ops, not source code
Lukas Mai [Thu, 21 Jun 2012 16:20:39 +0000 (18:20 +0200)]
Parameters.xs
padop_on_crack.c.inc [new file with mode: 0644]
t/02-compiles.t
t/03-compiles.t
t/precedence.t
t/regress.t [new file with mode: 0644]
toke_on_crack.c.inc

index 87b1e38..47ad69d 100644 (file)
@@ -144,37 +144,116 @@ static int kw_flags(pTHX_ const char *kw_ptr, STRLEN kw_len, Spec *spec) {
 #include "toke_on_crack.c.inc"
 
 
+static void free_ptr_op(void *vp) {
+       OP **pp = vp;
+       op_free(*pp);
+       Safefree(pp);
+}
+
+#define sv_eq_pvs(SV, S) sv_eq_pvn(SV, "" S "", sizeof (S) - 1)
+
+static int sv_eq_pvn(SV *sv, const char *p, STRLEN n) {
+       STRLEN sv_len;
+       const char *sv_p = SvPV(sv, sv_len);
+       return
+               sv_len == n &&
+               memcmp(sv_p, p, n) == 0
+       ;
+}
+
+
+#include "padop_on_crack.c.inc"
+
+
+#if 0
+static PADOFFSET pad_add_my_sv(SV *name) {
+       PADOFFSET offset;
+       SV *namesv, *myvar;
+       char *p;
+       STRLEN len;
+
+       p = SvPV(name, len);
+       myvar = *av_fetch(PL_comppad, AvFILLp(PL_comppad) + 1, 1);
+       offset = AvFILLp(PL_comppad);
+       SvPADMY_on(myvar);
+       if (*p == '@') {
+               SvUPGRADE(myvar, SVt_PVAV);
+       } else if (*p == '%') {
+               SvUPGRADE(myvar, SVt_PVHV);
+       }
+       PL_curpad = AvARRAY(PL_comppad);
+       namesv = newSV_type(SVt_PVMG);
+       sv_setpvn(namesv, p, len);
+       COP_SEQ_RANGE_LOW_set(namesv, PL_cop_seqmax);
+       COP_SEQ_RANGE_HIGH_set(namesv, PERL_PADSEQ_INTRO);
+       PL_cop_seqmax++;
+       av_store(PL_comppad_name, offset, namesv);
+       return offset;
+}
+#endif
+
+enum {
+       MY_ATTR_LVALUE = 0x01,
+       MY_ATTR_METHOD = 0x02,
+       MY_ATTR_SPECIAL = 0x04
+};
+
 static int parse_fun(pTHX_ OP **pop, const char *keyword_ptr, STRLEN keyword_len, const Spec *spec) {
-       SV *gen, *declarator, *params, *sv;
-       int saw_name, saw_colon;
+       SV *declarator;
+       I32 floor_ix;
+       SV *saw_name;
+       AV *params;
+       SV *proto;
+       OP **attrs_sentinel, *body;
+       unsigned builtin_attrs;
+       int saw_colon;
        STRLEN len;
        char *s;
        I32 c;
 
-       gen = sv_2mortal(newSVpvs("sub"));
        declarator = sv_2mortal(newSVpvn(keyword_ptr, keyword_len));
-       params = sv_2mortal(newSVpvs(""));
 
        lex_read_space(0);
 
+       builtin_attrs = 0;
+
        /* function name */
-       saw_name = 0;
+       saw_name = NULL;
        s = PL_parser->bufptr;
        if (spec->name != FLAG_NAME_PROHIBITED && (len = S_scan_word(aTHX_ s, TRUE))) {
-               sv_catpvs(gen, " ");
-               sv_catpvn(gen, s, len);
+               saw_name = sv_2mortal(newSVpvn_flags(s, len, PARSING_UTF ? SVf_UTF8 : 0));
+
+               if (PL_parser->expect != XSTATE) {
+                       /* bail out early so we don't predeclare $saw_name */
+                       croak("In %"SVf": I was expecting a function body, not \"%"SVf"\"", SVfARG(declarator), SVfARG(saw_name));
+               }
+
                sv_catpvs(declarator, " ");
-               sv_catpvn(declarator, s, len);
+               sv_catsv(declarator, saw_name);
+
+               if (
+                       sv_eq_pvs(saw_name, "BEGIN") ||
+                       sv_eq_pvs(saw_name, "END") ||
+                       sv_eq_pvs(saw_name, "INIT") ||
+                       sv_eq_pvs(saw_name, "CHECK") ||
+                       sv_eq_pvs(saw_name, "UNITCHECK")
+               ) {
+                       builtin_attrs |= MY_ATTR_SPECIAL;
+               }
+
                lex_read_to(s + len);
                lex_read_space(0);
-               saw_name = 1;
        } else if (spec->name == FLAG_NAME_REQUIRED) {
                croak("I was expecting a function name, not \"%.*s\"", (int)(PL_parser->bufend - s), s);
        } else {
                sv_catpvs(declarator, " (anon)");
        }
 
+       floor_ix = start_subparse(FALSE, saw_name ? 0 : CVf_ANON);
+       SAVEFREESV(PL_compcv);
+
        /* parameters */
+       params = NULL;
        c = lex_peek_unichar(0);
        if (c == '(') {
                SV *saw_slurpy = NULL;
@@ -182,10 +261,14 @@ static int parse_fun(pTHX_ OP **pop, const char *keyword_ptr, STRLEN keyword_len
                lex_read_unichar(0);
                lex_read_space(0);
 
+               params = newAV();
+               sv_2mortal((SV *)params);
+
                for (;;) {
                        c = lex_peek_unichar(0);
                        if (c == '$' || c == '@' || c == '%') {
-                               sv_catpvf(params, "%c", (int)c);
+                               SV *param;
+
                                lex_read_unichar(0);
                                lex_read_space(0);
 
@@ -193,14 +276,14 @@ static int parse_fun(pTHX_ OP **pop, const char *keyword_ptr, STRLEN keyword_len
                                if (!(len = S_scan_word(aTHX_ s, FALSE))) {
                                        croak("In %"SVf": missing identifier", SVfARG(declarator));
                                }
+                               param = sv_2mortal(newSVpvf("%c%.*s", (int)c, (int)len, s));
                                if (saw_slurpy) {
-                                       croak("In %"SVf": I was expecting \")\" after \"%"SVf"\", not \"%c%.*s\"", SVfARG(declarator), SVfARG(saw_slurpy), (int)c, (int)len, s);
+                                       croak("In %"SVf": I was expecting \")\" after \"%"SVf"\", not \"%"SVf"\"", SVfARG(declarator), SVfARG(saw_slurpy), SVfARG(param));
                                }
                                if (c != '$') {
-                                       saw_slurpy = sv_2mortal(newSVpvf("%c%.*s", (int)c, (int)len, s));
+                                       saw_slurpy = param;
                                }
-                               sv_catpvn(params, s, len);
-                               sv_catpvs(params, ",");
+                               av_push(params, SvREFCNT_inc_simple_NN(param));
                                lex_read_to(s + len);
                                lex_read_space(0);
 
@@ -226,6 +309,7 @@ static int parse_fun(pTHX_ OP **pop, const char *keyword_ptr, STRLEN keyword_len
        }
 
        /* prototype */
+       proto = NULL;
        saw_colon = 0;
        c = lex_peek_unichar(0);
        if (c == ':') {
@@ -234,102 +318,195 @@ static int parse_fun(pTHX_ OP **pop, const char *keyword_ptr, STRLEN keyword_len
 
                c = lex_peek_unichar(0);
                if (c != '(') {
-                       saw_colon = 1;
+                       lex_stuff_pvs(":", 0);
+                       c = ':';
                } else {
-                       sv = sv_2mortal(newSVpvs(""));
-                       if (!S_scan_str(aTHX_ sv, TRUE, TRUE)) {
+                       proto = sv_2mortal(newSVpvs(""));
+                       if (!S_scan_str(aTHX_ proto, FALSE, FALSE)) {
                                croak("In %"SVf": prototype not terminated", SVfARG(declarator));
                        }
-                       sv_catsv(gen, sv);
+                       S_check_prototype(declarator, proto);
                        lex_read_space(0);
+                       c = lex_peek_unichar(0);
                }
        }
 
+       /* surprise predeclaration! */
        if (saw_name) {
-               len = SvCUR(gen);
-               s = SvGROW(gen, (len + 1) * 2);
-               sv_catpvs(gen, ";");
-               sv_catpvn(gen, s, len);
+               /* 'sub NAME (PROTO);' to make name/proto known to perl before it
+                  starts parsing the body */
+               SvREFCNT_inc_simple_void(PL_compcv);
+
+               newATTRSUB(
+                       floor_ix,
+                       newSVOP(OP_CONST, 0, SvREFCNT_inc_simple_NN(saw_name)),
+                       proto ? newSVOP(OP_CONST, 0, SvREFCNT_inc_simple_NN(proto)) : NULL,
+                       NULL,
+                       NULL
+               );
+
+               floor_ix = start_subparse(FALSE, 0);
+               SAVEFREESV(PL_compcv);
        }
 
+
        /* attributes */
-       if (SvTRUE(spec->attrs)) {
-               sv_catsv(gen, spec->attrs);
-       }
+       Newx(attrs_sentinel, 1, OP *);
+       *attrs_sentinel = NULL;
+       SAVEDESTRUCTOR(free_ptr_op, attrs_sentinel);
+
+       if (c == ':' || c == '{') {
+
+               /* kludge default attributes in */
+               if (SvTRUE(spec->attrs) && SvPV_nolen(spec->attrs)[0] == ':') {
+                       lex_stuff_sv(spec->attrs, 0);
+                       c = ':';
+               }
 
-       if (!saw_colon) {
-               c = lex_peek_unichar(0);
                if (c == ':') {
-                       saw_colon = 1;
                        lex_read_unichar(0);
                        lex_read_space(0);
-               }
-       }
-       if (saw_colon) {
-               for (;;) {
-                       s = PL_parser->bufptr;
-                       if (!(len = S_scan_word(aTHX_ s, FALSE))) {
-                               break;
-                       }
-                       sv_catpvs(gen, ":");
-                       sv_catpvn(gen, s, len);
-                       lex_read_to(s + len);
-                       lex_read_space(0);
                        c = lex_peek_unichar(0);
-                       if (c == '(') {
-                               sv = sv_2mortal(newSVpvs(""));
-                               if (!S_scan_str(aTHX_ sv, TRUE, TRUE)) {
-                                       croak("In %"SVf": unterminated attribute parameter in attribute list", SVfARG(declarator));
+
+                       for (;;) {
+                               SV *attr;
+
+                               s = PL_parser->bufptr;
+                               if (!(len = S_scan_word(aTHX_ s, FALSE))) {
+                                       break;
                                }
-                               sv_catsv(gen, sv);
+
+                               attr = sv_2mortal(newSVpvn_flags(s, len, PARSING_UTF ? SVf_UTF8 : 0));
+
+                               lex_read_to(s + len);
                                lex_read_space(0);
                                c = lex_peek_unichar(0);
-                       }
-                       if (c == ':') {
-                               lex_read_unichar(0);
-                               lex_read_space(0);
+
+                               if (c != '(') {
+                                       if (sv_eq_pvs(attr, "lvalue")) {
+                                               builtin_attrs |= MY_ATTR_LVALUE;
+                                               attr = NULL;
+                                       } else if (sv_eq_pvs(attr, "method")) {
+                                               builtin_attrs |= MY_ATTR_METHOD;
+                                               attr = NULL;
+                                       }
+                               } else {
+                                       SV *sv = sv_2mortal(newSVpvs(""));
+                                       if (!S_scan_str(aTHX_ sv, TRUE, TRUE)) {
+                                               croak("In %"SVf": unterminated attribute parameter in attribute list", SVfARG(declarator));
+                                       }
+                                       sv_catsv(attr, sv);
+
+                                       lex_read_space(0);
+                                       c = lex_peek_unichar(0);
+                               }
+
+                               if (attr) {
+                                       *attrs_sentinel = op_append_elem(OP_LIST, *attrs_sentinel, newSVOP(OP_CONST, 0, SvREFCNT_inc_simple_NN(attr)));
+                               }
+
+                               if (c == ':') {
+                                       lex_read_unichar(0);
+                                       lex_read_space(0);
+                                       c = lex_peek_unichar(0);
+                               }
                        }
                }
        }
 
        /* body */
-       c = lex_peek_unichar(0);
        if (c != '{') {
                croak("In %"SVf": I was expecting a function body, not \"%c\"", SVfARG(declarator), (int)c);
        }
-       lex_read_unichar(0);
-       sv_catpvs(gen, "{");
-       if (SvTRUE(spec->shift)) {
-               sv_catpvs(gen, "my");
-               sv_catsv(gen, spec->shift);
-               sv_catpvs(gen, "=shift;");
+
+       if (builtin_attrs & MY_ATTR_LVALUE) {
+               CvLVALUE_on(PL_compcv);
        }
-       if (SvCUR(params)) {
-               sv_catpvs(gen, "my(");
-               sv_catsv(gen, params);
-               sv_catpvs(gen, ")=@_;");
+       if (builtin_attrs & MY_ATTR_METHOD) {
+               CvMETHOD_on(PL_compcv);
+       }
+       if (builtin_attrs & MY_ATTR_SPECIAL) {
+               CvSPECIAL_on(PL_compcv);
        }
 
-       /* named sub */
-       if (saw_name) {
-               /* fprintf(stderr, "! [%.*s]\n", (int)(PL_bufend - PL_bufptr), PL_bufptr); */
-               lex_stuff_sv(gen, SvUTF8(gen));
-               *pop = parse_barestmt(0);
-               return KEYWORD_PLUGIN_STMT;
+       /* munge */
+       {
+               /* create outer block: '{' */
+               const int save_ix = S_block_start(TRUE);
+               OP *init = NULL;
+
+               /* my $self = shift; */
+               if (SvTRUE(spec->shift)) {
+                       OP *const var = newOP(OP_PADSV, OPf_WANT_SCALAR | (OPpLVAL_INTRO << 8));
+                       var->op_targ = pad_add_name_sv(spec->shift, 0, NULL, NULL);
+
+                       init = newASSIGNOP(OPf_STACKED, var, 0, newOP(OP_SHIFT, 0));
+                       init = newSTATEOP(0, NULL, init);
+               }
+
+               /* my (PARAMS) = @_; */
+               if (params && av_len(params) > -1) {
+                       SV *param;
+                       OP *init_param, *left, *right;
+
+                       left = NULL;
+                       while ((param = av_shift(params)) != &PL_sv_undef) {
+                               OP *const var = newOP(OP_PADSV, OPf_WANT_LIST | (OPpLVAL_INTRO << 8));
+                               var->op_targ = pad_add_name_sv(param, 0, NULL, NULL);
+                               SvREFCNT_dec(param);
+                               left = op_append_elem(OP_LIST, left, var);
+                       }
+
+                       left->op_flags |= OPf_PARENS;
+                       right = newAVREF(newGVOP(OP_GV, 0, PL_defgv));
+                       init_param = newASSIGNOP(OPf_STACKED, left, 0, right);
+                       init_param = newSTATEOP(0, NULL, init_param);
+
+                       init = op_append_list(OP_LINESEQ, init, init_param);
+               }
+
+               /* add '();' to make function return nothing by default */
+               /* (otherwise the invisible parameter initialization can "leak" into
+                  the return value: fun ($x) {}->("asdf", 0) == 2) */
+               if (init) {
+                       init = op_append_list(OP_LINESEQ, init, newSTATEOP(0, NULL, newOP(OP_STUB, OPf_PARENS)));
+               }
+
+               /* finally let perl parse the actual subroutine body */
+               body = parse_block(0);
+
+               body = op_append_list(OP_LINESEQ, init, body);
+
+               /* close outer block: '}' */
+               S_block_end(save_ix, body);
        }
 
-       /* anon sub */
-       sv_catpvs(gen, "BEGIN{" MY_PKG "::_fini}");
-       /* fprintf(stderr, "!> [%.*s]\n", (int)(PL_bufend - PL_bufptr), PL_bufptr); */
-       lex_stuff_sv(gen, SvUTF8(gen));
-       *pop = parse_arithexpr(0);
-       s = PL_parser->bufptr;
-       if (*s != '}') {
-               croak("%s: internal error: expected '}', found '%c'", MY_PKG, *s);
+       /* it's go time. */
+       {
+               OP *const attrs = *attrs_sentinel;
+               *attrs_sentinel = NULL;
+               SvREFCNT_inc_simple_void(PL_compcv);
+
+               if (!saw_name) {
+                       *pop = newANONATTRSUB(
+                               floor_ix,
+                               proto ? newSVOP(OP_CONST, 0, SvREFCNT_inc_simple_NN(proto)) : NULL,
+                               attrs,
+                               body
+                       );
+                       return KEYWORD_PLUGIN_EXPR;
+               }
+
+               newATTRSUB(
+                       floor_ix,
+                       newSVOP(OP_CONST, 0, SvREFCNT_inc_simple_NN(saw_name)),
+                       proto ? newSVOP(OP_CONST, 0, SvREFCNT_inc_simple_NN(proto)) : NULL,
+                       attrs,
+                       body
+               );
+               *pop = NULL;
+               return KEYWORD_PLUGIN_STMT;
        }
-       lex_unstuff(s + 1);
-       /* fprintf(stderr, "!< [%.*s]\n", (int)(PL_bufend - PL_bufptr), PL_bufptr); */
-       return KEYWORD_PLUGIN_EXPR;
 }
 
 static int my_keyword_plugin(pTHX_ char *keyword_ptr, STRLEN keyword_len, OP **op_ptr) {
@@ -349,27 +526,6 @@ static int my_keyword_plugin(pTHX_ char *keyword_ptr, STRLEN keyword_len, OP **o
        return ret;
 }
 
-static int magic_free(pTHX_ SV *sv, MAGIC *mg) {
-       lex_stuff_pvn("}", 1, 0);
-       /* fprintf(stderr, "!~ [%.*s]\n", (int)(PL_bufend - PL_bufptr), PL_bufptr); */
-       return 0;
-}
-
-static int magic_nop(pTHX_ SV *sv, MAGIC *mg) {
-       return 0;
-}
-
-static MGVTBL my_vtbl = {
-       0,           /* get   */
-       0,           /* set   */
-       0,           /* len   */
-       0,           /* clear */
-       magic_free,  /* free  */
-       0,           /* copy  */
-       0,           /* dup   */
-       magic_nop    /* local */
-};
-
 WARNINGS_RESET
 
 MODULE = Function::Parameters   PACKAGE = Function::Parameters
@@ -390,8 +546,3 @@ WARNINGS_ENABLE {
        next_keyword_plugin = PL_keyword_plugin;
        PL_keyword_plugin = my_keyword_plugin;
 } WARNINGS_RESET
-
-void
-_fini()
-       CODE:
-       sv_magicext((SV *)GvHV(PL_hintgv), NULL, PERL_MAGIC_ext, &my_vtbl, NULL, 0);
diff --git a/padop_on_crack.c.inc b/padop_on_crack.c.inc
new file mode 100644 (file)
index 0000000..f40ded3
--- /dev/null
@@ -0,0 +1,627 @@
+/*
+ * This code was copied from perl/pad.c and perl/op.c and subsequently
+ * butchered by Lukas Mai (2012).
+ */
+/* vi: set ft=c inde=: */
+
+#define COP_SEQ_RANGE_LOW_set(SV, VAL) \
+       do { ((XPVNV*)SvANY(SV))->xnv_u.xpad_cop_seq.xlow = (VAL); } while (0)
+#define COP_SEQ_RANGE_HIGH_set(SV, VAL) \
+       do { ((XPVNV*)SvANY(SV))->xnv_u.xpad_cop_seq.xhigh = (VAL); } while (0)
+
+static void S_pad_block_start(pTHX_ int full) {
+       dVAR;
+       ASSERT_CURPAD_ACTIVE("pad_block_start");
+       SAVEI32(PL_comppad_name_floor);
+       PL_comppad_name_floor = AvFILLp(PL_comppad_name);
+       if (full)
+               PL_comppad_name_fill = PL_comppad_name_floor;
+       if (PL_comppad_name_floor < 0)
+               PL_comppad_name_floor = 0;
+       SAVEI32(PL_min_intro_pending);
+       SAVEI32(PL_max_intro_pending);
+       PL_min_intro_pending = 0;
+       SAVEI32(PL_comppad_name_fill);
+       SAVEI32(PL_padix_floor);
+       PL_padix_floor = PL_padix;
+       PL_pad_reset_pending = FALSE;
+}
+
+static int S_block_start(pTHX_ int full) {
+       dVAR;
+       const int retval = PL_savestack_ix;
+
+       S_pad_block_start(full);
+       SAVEHINTS();
+       PL_hints &= ~HINT_BLOCK_SCOPE;
+       SAVECOMPILEWARNINGS();
+       PL_compiling.cop_warnings = DUP_WARNINGS(PL_compiling.cop_warnings);
+
+       CALL_BLOCK_HOOKS(bhk_start, full);
+
+       return retval;
+}
+
+/* Check for in place reverse and sort assignments like "@a = reverse @a"
+   and modify the optree to make them work inplace */
+
+static void S_inplace_aassign(pTHX_ OP *o) {
+       OP *modop, *modop_pushmark;
+       OP *oright;
+       OP *oleft, *oleft_pushmark;
+
+       assert((o->op_flags & OPf_WANT) == OPf_WANT_VOID);
+
+       assert(cUNOPo->op_first->op_type == OP_NULL);
+       modop_pushmark = cUNOPx(cUNOPo->op_first)->op_first;
+       assert(modop_pushmark->op_type == OP_PUSHMARK);
+       modop = modop_pushmark->op_sibling;
+
+       if (modop->op_type != OP_SORT && modop->op_type != OP_REVERSE)
+               return;
+
+       /* no other operation except sort/reverse */
+       if (modop->op_sibling)
+               return;
+
+       assert(cUNOPx(modop)->op_first->op_type == OP_PUSHMARK);
+       if (!(oright = cUNOPx(modop)->op_first->op_sibling)) return;
+
+       if (modop->op_flags & OPf_STACKED) {
+               /* skip sort subroutine/block */
+               assert(oright->op_type == OP_NULL);
+               oright = oright->op_sibling;
+       }
+
+       assert(cUNOPo->op_first->op_sibling->op_type == OP_NULL);
+       oleft_pushmark = cUNOPx(cUNOPo->op_first->op_sibling)->op_first;
+       assert(oleft_pushmark->op_type == OP_PUSHMARK);
+       oleft = oleft_pushmark->op_sibling;
+
+       /* Check the lhs is an array */
+       if (!oleft ||
+               (oleft->op_type != OP_RV2AV && oleft->op_type != OP_PADAV)
+               || oleft->op_sibling
+               || (oleft->op_private & OPpLVAL_INTRO)
+       )
+               return;
+
+       /* Only one thing on the rhs */
+       if (oright->op_sibling)
+               return;
+
+       /* check the array is the same on both sides */
+       if (oleft->op_type == OP_RV2AV) {
+               if (oright->op_type != OP_RV2AV
+                       || !cUNOPx(oright)->op_first
+                       || cUNOPx(oright)->op_first->op_type != OP_GV
+                       || cUNOPx(oleft )->op_first->op_type != OP_GV
+                       || cGVOPx_gv(cUNOPx(oleft)->op_first) !=
+                       cGVOPx_gv(cUNOPx(oright)->op_first)
+               )
+                       return;
+       }
+       else if (oright->op_type != OP_PADAV
+                        || oright->op_targ != oleft->op_targ
+       )
+               return;
+
+       /* This actually is an inplace assignment */
+
+       modop->op_private |= OPpSORT_INPLACE;
+
+       /* transfer MODishness etc from LHS arg to RHS arg */
+       oright->op_flags = oleft->op_flags;
+
+       /* remove the aassign op and the lhs */
+       op_null(o);
+       op_null(oleft_pushmark);
+       if (oleft->op_type == OP_RV2AV && cUNOPx(oleft)->op_first)
+               op_null(cUNOPx(oleft)->op_first);
+       op_null(oleft);
+}
+
+static OP *S_scalarvoid(pTHX_ OP *);
+
+static OP *S_scalar(pTHX_ OP *o) {
+       dVAR;
+       OP *kid;
+
+       /* assumes no premature commitment */
+       if (!o || (PL_parser && PL_parser->error_count)
+               || (o->op_flags & OPf_WANT)
+               || o->op_type == OP_RETURN)
+       {
+               return o;
+       }
+
+       o->op_flags = (o->op_flags & ~OPf_WANT) | OPf_WANT_SCALAR;
+
+       switch (o->op_type) {
+               case OP_REPEAT:
+                       S_scalar(cBINOPo->op_first);
+                       break;
+               case OP_OR:
+               case OP_AND:
+               case OP_COND_EXPR:
+                       for (kid = cUNOPo->op_first->op_sibling; kid; kid = kid->op_sibling)
+                               S_scalar(kid);
+                       break;
+                       /* FALL THROUGH */
+               case OP_SPLIT:
+               case OP_MATCH:
+               case OP_QR:
+               case OP_SUBST:
+               case OP_NULL:
+               default:
+                       if (o->op_flags & OPf_KIDS) {
+                               for (kid = cUNOPo->op_first; kid; kid = kid->op_sibling)
+                                       S_scalar(kid);
+                       }
+                       break;
+               case OP_LEAVE:
+               case OP_LEAVETRY:
+                       kid = cLISTOPo->op_first;
+                       S_scalar(kid);
+                       kid = kid->op_sibling;
+do_kids:
+                       while (kid) {
+                               OP *sib = kid->op_sibling;
+                               if (sib && kid->op_type != OP_LEAVEWHEN)
+                                       S_scalarvoid(kid);
+                               else
+                                       S_scalar(kid);
+                               kid = sib;
+                       }
+                       PL_curcop = &PL_compiling;
+                       break;
+               case OP_SCOPE:
+               case OP_LINESEQ:
+               case OP_LIST:
+                       kid = cLISTOPo->op_first;
+                       goto do_kids;
+               case OP_SORT:
+                       Perl_ck_warner(aTHX_ packWARN(WARN_VOID), "Useless use of sort in scalar context");
+                       break;
+       }
+       return o;
+}
+
+static OP *S_scalarkids(pTHX_ OP *o) {
+    if (o && o->op_flags & OPf_KIDS) {
+        OP *kid;
+       for (kid = cLISTOPo->op_first; kid; kid = kid->op_sibling)
+           S_scalar(kid);
+    }
+    return o;
+}
+
+static OP *S_scalarvoid(pTHX_ OP *o) {
+       dVAR;
+       OP *kid;
+       const char *useless = NULL;
+       U32 useless_is_utf8 = 0;
+       SV *sv;
+       U8 want;
+
+       PERL_ARGS_ASSERT_SCALARVOID;
+
+       if (
+               o->op_type == OP_NEXTSTATE ||
+               o->op_type == OP_DBSTATE || (
+                       o->op_type == OP_NULL && (
+                               o->op_targ == OP_NEXTSTATE ||
+                               o->op_targ == OP_DBSTATE
+                       )
+               )
+       ) {
+               PL_curcop = (COP*)o;            /* for warning below */
+       }
+
+       /* assumes no premature commitment */
+       want = o->op_flags & OPf_WANT;
+       if (
+               (want && want != OPf_WANT_SCALAR) ||
+               (PL_parser && PL_parser->error_count) ||
+               o->op_type == OP_RETURN ||
+               o->op_type == OP_REQUIRE ||
+               o->op_type == OP_LEAVEWHEN
+       ) {
+               return o;
+       }
+
+       if (
+               (o->op_private & OPpTARGET_MY) &&
+               (PL_opargs[o->op_type] & OA_TARGLEX)
+               /* OPp share the meaning */
+       ) {
+               return S_scalar(o);                     /* As if inside SASSIGN */
+       }
+
+       o->op_flags = (o->op_flags & ~OPf_WANT) | OPf_WANT_VOID;
+
+       switch (o->op_type) {
+               default:
+                       if (!(PL_opargs[o->op_type] & OA_FOLDCONST))
+                               break;
+                       /* FALL THROUGH */
+               case OP_REPEAT:
+                       if (o->op_flags & OPf_STACKED)
+                               break;
+                       goto func_ops;
+               case OP_SUBSTR:
+                       if (o->op_private == 4)
+                               break;
+                       /* FALL THROUGH */
+               case OP_GVSV:
+               case OP_WANTARRAY:
+               case OP_GV:
+               case OP_SMARTMATCH:
+               case OP_PADSV:
+               case OP_PADAV:
+               case OP_PADHV:
+               case OP_PADANY:
+               case OP_AV2ARYLEN:
+               case OP_REF:
+               case OP_REFGEN:
+               case OP_SREFGEN:
+               case OP_DEFINED:
+               case OP_HEX:
+               case OP_OCT:
+               case OP_LENGTH:
+               case OP_VEC:
+               case OP_INDEX:
+               case OP_RINDEX:
+               case OP_SPRINTF:
+               case OP_AELEM:
+               case OP_AELEMFAST:
+               case OP_AELEMFAST_LEX:
+               case OP_ASLICE:
+               case OP_HELEM:
+               case OP_HSLICE:
+               case OP_UNPACK:
+               case OP_PACK:
+               case OP_JOIN:
+               case OP_LSLICE:
+               case OP_ANONLIST:
+               case OP_ANONHASH:
+               case OP_SORT:
+               case OP_REVERSE:
+               case OP_RANGE:
+               case OP_FLIP:
+               case OP_FLOP:
+               case OP_CALLER:
+               case OP_FILENO:
+               case OP_EOF:
+               case OP_TELL:
+               case OP_GETSOCKNAME:
+               case OP_GETPEERNAME:
+               case OP_READLINK:
+               case OP_TELLDIR:
+               case OP_GETPPID:
+               case OP_GETPGRP:
+               case OP_GETPRIORITY:
+               case OP_TIME:
+               case OP_TMS:
+               case OP_LOCALTIME:
+               case OP_GMTIME:
+               case OP_GHBYNAME:
+               case OP_GHBYADDR:
+               case OP_GHOSTENT:
+               case OP_GNBYNAME:
+               case OP_GNBYADDR:
+               case OP_GNETENT:
+               case OP_GPBYNAME:
+               case OP_GPBYNUMBER:
+               case OP_GPROTOENT:
+               case OP_GSBYNAME:
+               case OP_GSBYPORT:
+               case OP_GSERVENT:
+               case OP_GPWNAM:
+               case OP_GPWUID:
+               case OP_GGRNAM:
+               case OP_GGRGID:
+               case OP_GETLOGIN:
+               case OP_PROTOTYPE:
+               case OP_RUNCV:
+func_ops:
+                       if (!(o->op_private & (OPpLVAL_INTRO|OPpOUR_INTRO)))
+                               /* Otherwise it's "Useless use of grep iterator" */
+                               useless = OP_DESC(o);
+                       break;
+
+               case OP_SPLIT:
+                       kid = cLISTOPo->op_first;
+                       if (kid && kid->op_type == OP_PUSHRE
+#ifdef USE_ITHREADS
+                               && !((PMOP*)kid)->op_pmreplrootu.op_pmtargetoff)
+#else
+                               && !((PMOP*)kid)->op_pmreplrootu.op_pmtargetgv)
+#endif
+                                       useless = OP_DESC(o);
+                       break;
+
+               case OP_NOT:
+                       kid = cUNOPo->op_first;
+                       if (kid->op_type != OP_MATCH && kid->op_type != OP_SUBST &&
+                               kid->op_type != OP_TRANS && kid->op_type != OP_TRANSR) {
+                               goto func_ops;
+                       }
+                       useless = "negative pattern binding (!~)";
+                       break;
+
+               case OP_SUBST:
+                       if (cPMOPo->op_pmflags & PMf_NONDESTRUCT)
+                               useless = "non-destructive substitution (s///r)";
+                       break;
+
+               case OP_TRANSR:
+                       useless = "non-destructive transliteration (tr///r)";
+                       break;
+
+               case OP_RV2GV:
+               case OP_RV2SV:
+               case OP_RV2AV:
+               case OP_RV2HV:
+                       if (!(o->op_private & (OPpLVAL_INTRO|OPpOUR_INTRO)) &&
+                               (!o->op_sibling || o->op_sibling->op_type != OP_READLINE))
+                               useless = "a variable";
+                       break;
+
+               case OP_CONST:
+                       sv = cSVOPo_sv;
+                       if (cSVOPo->op_private & OPpCONST_STRICT) {
+                               //no_bareword_allowed(o);
+                               *((int *)NULL) += 1;
+                       } else {
+                               if (ckWARN(WARN_VOID)) {
+                                       /* don't warn on optimised away booleans, eg 
+                                        * use constant Foo, 5; Foo || print; */
+                                       if (cSVOPo->op_private & OPpCONST_SHORTCIRCUIT)
+                                               useless = NULL;
+                                       /* the constants 0 and 1 are permitted as they are
+                                          conventionally used as dummies in constructs like
+                                          1 while some_condition_with_side_effects;  */
+                                       else if (SvNIOK(sv) && (SvNV(sv) == 0.0 || SvNV(sv) == 1.0))
+                                               useless = NULL;
+                                       else if (SvPOK(sv)) {
+                                               /* perl4's way of mixing documentation and code
+                                                  (before the invention of POD) was based on a
+                                                  trick to mix nroff and perl code. The trick was
+                                                  built upon these three nroff macros being used in
+                                                  void context. The pink camel has the details in
+                                                  the script wrapman near page 319. */
+                                               const char * const maybe_macro = SvPVX_const(sv);
+                                               if (strnEQ(maybe_macro, "di", 2) ||
+                                                       strnEQ(maybe_macro, "ds", 2) ||
+                                                       strnEQ(maybe_macro, "ig", 2))
+                                                       useless = NULL;
+                                               else {
+                                                       SV * const dsv = newSVpvs("");
+                                                       SV* msv = sv_2mortal(Perl_newSVpvf(aTHX_
+                                                                                                                          "a constant (%s)",
+                                                                                                                          pv_pretty(dsv, maybe_macro, SvCUR(sv), 32, NULL, NULL,
+                                                                                                                                                PERL_PV_PRETTY_DUMP | PERL_PV_ESCAPE_NOCLEAR | PERL_PV_ESCAPE_UNI_DETECT )));
+                                                       SvREFCNT_dec(dsv);
+                                                       useless = SvPV_nolen(msv);
+                                                       useless_is_utf8 = SvUTF8(msv);
+                                               }
+                                       }
+                                       else if (SvOK(sv)) {
+                                               SV* msv = sv_2mortal(Perl_newSVpvf(aTHX_
+                                                                                                                  "a constant (%"SVf")", sv));
+                                               useless = SvPV_nolen(msv);
+                                       }
+                                       else
+                                               useless = "a constant (undef)";
+                               }
+                       }
+                       op_null(o);             /* don't execute or even remember it */
+                       break;
+
+               case OP_POSTINC:
+                       o->op_type = OP_PREINC;         /* pre-increment is faster */
+                       o->op_ppaddr = PL_ppaddr[OP_PREINC];
+                       break;
+
+               case OP_POSTDEC:
+                       o->op_type = OP_PREDEC;         /* pre-decrement is faster */
+                       o->op_ppaddr = PL_ppaddr[OP_PREDEC];
+                       break;
+
+               case OP_I_POSTINC:
+                       o->op_type = OP_I_PREINC;       /* pre-increment is faster */
+                       o->op_ppaddr = PL_ppaddr[OP_I_PREINC];
+                       break;
+
+               case OP_I_POSTDEC:
+                       o->op_type = OP_I_PREDEC;       /* pre-decrement is faster */
+                       o->op_ppaddr = PL_ppaddr[OP_I_PREDEC];
+                       break;
+
+               case OP_SASSIGN: {
+                       OP *rv2gv;
+                       UNOP *refgen, *rv2cv;
+                       LISTOP *exlist;
+
+                       if ((o->op_private & ~OPpASSIGN_BACKWARDS) != 2)
+                               break;
+
+                       rv2gv = ((BINOP *)o)->op_last;
+                       if (!rv2gv || rv2gv->op_type != OP_RV2GV)
+                               break;
+
+                       refgen = (UNOP *)((BINOP *)o)->op_first;
+
+                       if (!refgen || refgen->op_type != OP_REFGEN)
+                               break;
+
+                       exlist = (LISTOP *)refgen->op_first;
+                       if (!exlist || exlist->op_type != OP_NULL
+                               || exlist->op_targ != OP_LIST)
+                               break;
+
+                       if (exlist->op_first->op_type != OP_PUSHMARK)
+                               break;
+
+                       rv2cv = (UNOP*)exlist->op_last;
+
+                       if (rv2cv->op_type != OP_RV2CV)
+                               break;
+
+                       assert ((rv2gv->op_private & OPpDONT_INIT_GV) == 0);
+                       assert ((o->op_private & OPpASSIGN_CV_TO_GV) == 0);
+                       assert ((rv2cv->op_private & OPpMAY_RETURN_CONSTANT) == 0);
+
+                       o->op_private |= OPpASSIGN_CV_TO_GV;
+                       rv2gv->op_private |= OPpDONT_INIT_GV;
+                       rv2cv->op_private |= OPpMAY_RETURN_CONSTANT;
+
+                       break;
+               }
+
+               case OP_AASSIGN: {
+                       S_inplace_aassign(o);
+                       break;
+               }
+
+               case OP_OR:
+               case OP_AND:
+                       kid = cLOGOPo->op_first;
+                       if (kid->op_type == OP_NOT
+                               && (kid->op_flags & OPf_KIDS)
+                               && !PL_madskills) {
+                               if (o->op_type == OP_AND) {
+                                       o->op_type = OP_OR;
+                                       o->op_ppaddr = PL_ppaddr[OP_OR];
+                               } else {
+                                       o->op_type = OP_AND;
+                                       o->op_ppaddr = PL_ppaddr[OP_AND];
+                               }
+                               op_null(kid);
+                       }
+
+               case OP_DOR:
+               case OP_COND_EXPR:
+               case OP_ENTERGIVEN:
+               case OP_ENTERWHEN:
+                       for (kid = cUNOPo->op_first->op_sibling; kid; kid = kid->op_sibling)
+                               S_scalarvoid(kid);
+                       break;
+
+               case OP_NULL:
+                       if (o->op_flags & OPf_STACKED)
+                               break;
+                       /* FALL THROUGH */
+               case OP_NEXTSTATE:
+               case OP_DBSTATE:
+               case OP_ENTERTRY:
+               case OP_ENTER:
+                       if (!(o->op_flags & OPf_KIDS))
+                               break;
+                       /* FALL THROUGH */
+               case OP_SCOPE:
+               case OP_LEAVE:
+               case OP_LEAVETRY:
+               case OP_LEAVELOOP:
+               case OP_LINESEQ:
+               case OP_LIST:
+               case OP_LEAVEGIVEN:
+               case OP_LEAVEWHEN:
+                       for (kid = cLISTOPo->op_first; kid; kid = kid->op_sibling)
+                               S_scalarvoid(kid);
+                       break;
+               case OP_ENTEREVAL:
+                       S_scalarkids(o);
+                       break;
+               case OP_SCALAR:
+                       return S_scalar(o);
+       }
+       if (useless)
+               Perl_ck_warner(aTHX_ packWARN(WARN_VOID), "Useless use of %"SVf" in void context",
+                                          newSVpvn_flags(useless, strlen(useless),
+                                                                         SVs_TEMP | ( useless_is_utf8 ? SVf_UTF8 : 0 )));
+       return o;
+}
+
+static OP *S_scalarseq(pTHX_ OP *o) {
+       dVAR;
+       if (o) {
+               const OPCODE type = o->op_type;
+
+               if (type == OP_LINESEQ || type == OP_SCOPE ||
+                   type == OP_LEAVE || type == OP_LEAVETRY)
+               {
+                       OP *kid;
+                       for (kid = cLISTOPo->op_first; kid; kid = kid->op_sibling) {
+                               if (kid->op_sibling) {
+                                       S_scalarvoid(kid);
+                               }
+                       }
+                       PL_curcop = &PL_compiling;
+               }
+               o->op_flags &= ~OPf_PARENS;
+               if (PL_hints & HINT_BLOCK_SCOPE)
+                       o->op_flags |= OPf_PARENS;
+       }
+       else
+               o = newOP(OP_STUB, 0);
+       return o;
+}
+
+static void S_pad_leavemy(pTHX) {
+       dVAR;
+       I32 off;
+       SV * const * const svp = AvARRAY(PL_comppad_name);
+
+       PL_pad_reset_pending = FALSE;
+
+       ASSERT_CURPAD_ACTIVE("pad_leavemy");
+       if (PL_min_intro_pending && PL_comppad_name_fill < PL_min_intro_pending) {
+               for (off = PL_max_intro_pending; off >= PL_min_intro_pending; off--) {
+                       const SV * const sv = svp[off];
+                       if (sv && sv != &PL_sv_undef && !SvFAKE(sv))
+                               Perl_ck_warner_d(aTHX_ packWARN(WARN_INTERNAL),
+                                                                "%"SVf" never introduced",
+                                                                SVfARG(sv));
+               }
+       }
+       /* "Deintroduce" my variables that are leaving with this scope. */
+       for (off = AvFILLp(PL_comppad_name); off > PL_comppad_name_fill; off--) {
+               const SV * const sv = svp[off];
+               if (sv && sv != &PL_sv_undef && !SvFAKE(sv)
+                       && COP_SEQ_RANGE_HIGH(sv) == PERL_PADSEQ_INTRO)
+               {
+                       COP_SEQ_RANGE_HIGH_set(sv, PL_cop_seqmax);
+                       DEBUG_Xv(PerlIO_printf(Perl_debug_log,
+                                                                  "Pad leavemy: %ld \"%s\", (%lu,%lu)\n",
+                                                                  (long)off, SvPVX_const(sv),
+                                                                  (unsigned long)COP_SEQ_RANGE_LOW(sv),
+                                                                  (unsigned long)COP_SEQ_RANGE_HIGH(sv))
+                       );
+               }
+       }
+       PL_cop_seqmax++;
+       if (PL_cop_seqmax == PERL_PADSEQ_INTRO) /* not a legal value */
+               PL_cop_seqmax++;
+       DEBUG_Xv(PerlIO_printf(Perl_debug_log,
+                                                  "Pad leavemy: seq = %ld\n", (long)PL_cop_seqmax));
+}
+
+static OP *S_block_end(pTHX_ I32 floor, OP *seq) {
+       dVAR;
+       const int needblockscope = PL_hints & HINT_BLOCK_SCOPE;
+       OP *retval = S_scalarseq(seq);
+
+       CALL_BLOCK_HOOKS(bhk_pre_end, &retval);
+
+       LEAVE_SCOPE(floor);
+       CopHINTS_set(&PL_compiling, PL_hints);
+       if (needblockscope)
+               PL_hints |= HINT_BLOCK_SCOPE; /* propagate out */
+       S_pad_leavemy();
+
+       CALL_BLOCK_HOOKS(bhk_post_end, &retval);
+
+       return retval;
+}
index 4128807..1e50754 100644 (file)
@@ -27,7 +27,7 @@ method##
         #
  ) ##AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
  { ##
-         ##
+       $self ##
  } ##
 
 method add($y) {
index 277b3bd..7c10546 100644 (file)
@@ -27,7 +27,7 @@ clathod##
         #
  ) ##AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 
  { ##
-         ##
+         $class##
  } ##
 
 clathod add($y) {
index c988bab..01f088d 100644 (file)
@@ -19,10 +19,10 @@ is quantum / 2 #/
 , 0xf00d / 2, "basic sanity 4 - () proto";
 
 is eval('my $x = fun forbidden {}'), undef, "statements aren't expressions";
-like $@, qr/syntax error/;
+like $@, qr/expect.*function body/;
 
 is eval('my $x = { fun forbidden {} }'), undef, "statements aren't expressions 2 - electric boogaloo";
-like $@, qr/syntax error/;
+like $@, qr/expect.*function body/;
 
 is fun { join '.', five, four }->(), '5.4', "can immedicall anon subs";
 
diff --git a/t/regress.t b/t/regress.t
new file mode 100644 (file)
index 0000000..2c4bbfb
--- /dev/null
@@ -0,0 +1,44 @@
+#!perl
+
+use Test::More tests => 21;
+
+use warnings FATAL => 'all';
+use strict;
+
+use Function::Parameters;
+
+fun mk_counter($i) {
+       fun () { $i++ }
+}
+
+method nop() {}
+fun fnop($x, $y, $z) {
+}
+
+is_deeply [nop], [];
+is_deeply [main->nop], [];
+is_deeply [nop 1], [];
+is scalar(nop), undef;
+is scalar(nop 2), undef;
+
+is_deeply [fnop], [];
+is_deeply [fnop 3, 4], [];
+is scalar(fnop), undef;
+is scalar(fnop 5, 6), undef;
+
+my $f = mk_counter 0;
+my $g = mk_counter 10;
+my $h = mk_counter 50;
+
+is $f->(), 0;
+is $g->(), 10;
+is $h->(), 50;
+is $f->(), 1;
+is $g->(), 11;
+is $h->(), 51;
+is $f->(), 2;
+is $f->(), 3;
+is $f->(), 4;
+is $g->(), 12;
+is $h->(), 52;
+is $g->(), 13;
index bd609eb..fca95b5 100644 (file)
 #define CLINE (PL_copline = (CopLINE(PL_curcop) < PL_copline ? CopLINE(PL_curcop) : PL_copline))
 
 #ifdef USE_UTF8_SCRIPTS
-#   define UTF (!IN_BYTES)
+#   define PARSING_UTF (!IN_BYTES)
 #else
-#   define UTF ((PL_linestr && DO_UTF8(PL_linestr)) || (PL_hints & HINT_UTF8))
+#   define PARSING_UTF ((PL_linestr && DO_UTF8(PL_linestr)) || (PL_hints & HINT_UTF8))
 #endif
 
 static STRLEN S_scan_word(pTHX_ const char *start, int allow_package) {
        const char *s = start;
        for (;;) {
-               if (isALNUM(*s) || (!UTF && isALNUMC_L1(*s))) {  /* UTF handled below */
+               if (isALNUM(*s) || (!PARSING_UTF && isALNUMC_L1(*s))) {  /* UTF handled below */
                        s++;
-               } else if (allow_package && s > start && *s == '\'' && isIDFIRST_lazy_if(s+1, UTF)) {
+               } else if (allow_package && s > start && *s == '\'' && isIDFIRST_lazy_if(s+1, PARSING_UTF)) {
                        s++;
-               } else if (allow_package && s[0] == ':' && s[1] == ':' && isIDFIRST_lazy_if(s+2, UTF)) {
+               } else if (allow_package && s[0] == ':' && s[1] == ':' && isIDFIRST_lazy_if(s+2, PARSING_UTF)) {
                        s += 2;
-               } else if (UTF && UTF8_IS_START(*s) && isALNUM_utf8((U8*)s)) {
+               } else if (PARSING_UTF && UTF8_IS_START(*s) && isALNUM_utf8((U8*)s)) {
                        do {
                                s += UTF8SKIP(s);
                        } while (UTF8_IS_CONTINUED(*s) && is_utf8_mark((U8*)s));
@@ -63,7 +63,7 @@ static char *S_scan_str(pTHX_ SV *sv, int keep_quoted, int keep_delims) {
 
        /* after skipping whitespace, the next character is the terminator */
        term = *s;
-       if (!UTF) {
+       if (!PARSING_UTF) {
                termcode = termstr[0] = term;
                termlen = 1;
        }
@@ -99,7 +99,7 @@ static char *S_scan_str(pTHX_ SV *sv, int keep_quoted, int keep_delims) {
                sv_catpvn(sv, s, termlen);
        s += termlen;
        for (;;) {
-               if (PL_encoding && !UTF) {
+               if (PL_encoding && !PARSING_UTF) {
                        bool cont = TRUE;
 
                        while (cont) {
@@ -205,7 +205,7 @@ static char *S_scan_str(pTHX_ SV *sv, int keep_quoted, int keep_delims) {
                                        if (s+termlen <= PL_bufend && memEQ(s, (char*)termstr, termlen))
                                                break;
                                }
-                               else if (!has_utf8 && !UTF8_IS_INVARIANT((U8)*s) && UTF)
+                               else if (!has_utf8 && !UTF8_IS_INVARIANT((U8)*s) && PARSING_UTF)
                                        has_utf8 = TRUE;
                                *to = *s;
                        }
@@ -239,7 +239,7 @@ static char *S_scan_str(pTHX_ SV *sv, int keep_quoted, int keep_delims) {
                                        break;
                                else if (*s == PL_multi_open)
                                        brackets++;
-                               else if (!has_utf8 && !UTF8_IS_INVARIANT((U8)*s) && UTF)
+                               else if (!has_utf8 && !UTF8_IS_INVARIANT((U8)*s) && PARSING_UTF)
                                        has_utf8 = TRUE;
                                *to = *s;
                        }
@@ -286,7 +286,7 @@ static char *S_scan_str(pTHX_ SV *sv, int keep_quoted, int keep_delims) {
 
        /* at this point, we have successfully read the delimited string */
 
-       if (!PL_encoding || UTF) {
+       if (!PL_encoding || PARSING_UTF) {
                if (keep_delims)
                        sv_catpvn(sv, s, termlen);
                s += termlen;
@@ -305,4 +305,84 @@ static char *S_scan_str(pTHX_ SV *sv, int keep_quoted, int keep_delims) {
        PL_bufptr = s;
        return s;
 }
+
+static void S_check_prototype(const SV *declarator, SV *proto) {
+       bool bad_proto = FALSE;
+       bool in_brackets = FALSE;
+       char greedy_proto = ' ';
+       bool proto_after_greedy_proto = FALSE;
+       bool must_be_last = FALSE;
+       bool underscore = FALSE;
+       bool seen_underscore = FALSE;
+       const bool warnillegalproto = ckWARN(WARN_ILLEGALPROTO);
+       char *d, *p;
+       STRLEN tmp, tmplen;
+
+       /* strip spaces and check for bad characters */
+       d = SvPV(proto, tmplen);
+       tmp = 0;
+       for (p = d; tmplen; tmplen--, ++p) {
+               if (!isSPACE(*p)) {
+                       d[tmp++] = *p;
+
+                       if (warnillegalproto) {
+                               if (must_be_last) {
+                                       proto_after_greedy_proto = TRUE;
+                               }
+                               if (!strchr("$@%*;[]&\\_+", *p) || *p == '\0') {
+                                       bad_proto = TRUE;
+                               } else {
+                                       if (underscore) {
+                                               if (!strchr(";@%", *p)) {
+                                                       bad_proto = TRUE;
+                                               }
+                                               underscore = FALSE;
+                                       }
+                                       if (*p == '[') {
+                                               in_brackets = TRUE;
+                                       } else if (*p == ']') {
+                                               in_brackets = FALSE;
+                                       } else if (
+                                               (*p == '@' || *p == '%') &&
+                                               (tmp < 2 || d[tmp - 2] != '\\') &&
+                                               !in_brackets
+                                       ) {
+                                               must_be_last = TRUE;
+                                               greedy_proto = *p;
+                                       } else if (*p == '_') {
+                                               underscore = seen_underscore = TRUE;
+                                       }
+                               }
+                       }
+               }
+       }
+       d[tmp] = '\0';
+       SvCUR_set(proto, tmp);
+       if (proto_after_greedy_proto) {
+               Perl_warner(aTHX_ packWARN(WARN_ILLEGALPROTO),
+                       "In %"SVf": prototype after '%c': %s",
+                        SVfARG(declarator), greedy_proto, d
+               );
+       }
+       if (bad_proto) {
+               SV *dsv = newSVpvs_flags("", SVs_TEMP);
+               Perl_warner(aTHX_ packWARN(WARN_ILLEGALPROTO),
+                       "In %"SVf": illegal character %sin prototype: %s",
+                       SVfARG(declarator),
+                       seen_underscore ? "after '_' " : "",
+                       SvUTF8(proto)
+                               ? sv_uni_display(dsv,
+                                       newSVpvn_flags(d, tmp, SVs_TEMP | SVf_UTF8),
+                                       tmp,
+                                       UNI_DISPLAY_ISPRINT
+                               )
+                               : pv_pretty(dsv, d, tmp, 60, NULL, NULL,
+                                       PERL_PV_ESCAPE_NONASCII
+                               )
+               );
+       }
+       SvCUR_set(proto, tmp);
+}
+
+#undef CLINE
 /* ^^^^^^^^^^^^^^^^^^^^^ I HAVE NO IDEA WHAT I'M DOING ^^^^^^^^^^^^^^^^^^^^ */